{"schema":"libjg2-1",
"vpath":"/git/",
"avatar":"/git/avatar/",
"alang":"",
"gen_ut":1756842600,
"reponame":"libwebsockets",
"desc":"libwebsockets lightweight C networking library",
"owner": { "name": "Andy Green", "email": "andy@warmcat.com", "md5": "c50933ca2aa61e0fe2c43d46bb6b59cb" },"url":"https://libwebsockets.org/repo/libwebsockets",
"f":3,
"items": [
{"schema":"libjg2-1",
"cid":"2bb092b56d0df0e07a0b62b96f94e08e",
"commit": {"type":"commit",
"time": 1583324269,
"time_ofs": 0,
"oid_tree": { "oid": "8f90534b18e350595871f44e33a9304528ed3966", "alias": []},
"oid":{ "oid": "28ce32af64d8c32f51463aa6723a43fe4985d613", "alias": []},
"msg": "client: secure streams",
"sig_commit": { "git_time": { "time": 1583324269, "offset": 0 }, "name": "Andy Green", "email": "andy@warmcat.com", "md5": "c50933ca2aa61e0fe2c43d46bb6b59cb" },
"sig_author": { "git_time": { "time": 1582979844, "offset": 0 }, "name": "Andy Green", "email": "andy@warmcat.com", "md5": "c50933ca2aa61e0fe2c43d46bb6b59cb" }},
"body": "client: secure streams\n\nSecure Streams is an optional layer on top of lws that separates policy\nlike endpoint selection and tls cert validation into a device JSON\npolicy document.\n\nCode that wants to open a client connection just specifies a streamtype name,\nand no longer deals with details like the endpoint, the protocol (!) or anything\nelse other than payloads and optionally generic metadata; the JSON policy\ncontains all the details for each streamtype. h1, h2, ws and mqtt client\nconnections are supported.\n\nLogical secure streams outlive any particular connection and supports \u0022nailed-up\u0022\nconnectivity regardless of underlying connection stability."
,
"diff": "diff --git a/.travis.yml b/.travis.yml\nindex 22419b2..b0b9f59 100644\n--- a/.travis.yml\n+++ b/.travis.yml\n@@ -11,6 +11,8 @@ env:\n - LWS_METHOD\u003dlwsws2 CMAKE_ARGS\u003d\u0022-DLWS_WITH_LWSWS\u003dON -DLWS_WITHOUT_EXTENSIONS\u003d0 -DLWS_WITH_HTTP2\u003d1 -DLWS_WITH_ACME\u003d1 -DLWS_WITH_MINIMAL_EXAMPLES\u003d1 -DCMAKE_BUILD_TYPE\u003dDEBUG -DLWS_WITH_LWS_DSH\u003d1\u0022\n - LWS_METHOD\u003ddefault CMAKE_ARGS\u003d\u0022-DLWS_WITH_MINIMAL_EXAMPLES\u003d1\u0022\n - LWS_METHOD\u003dmbedtls CMAKE_ARGS\u003d\u0022-DLWS_WITH_MBEDTLS\u003d1 -DLWS_WITH_HTTP2\u003d1 -DLWS_WITH_LWSWS\u003d1 -DLWS_WITH_MINIMAL_EXAMPLES\u003d1 -DLWS_WITH_JOSE\u003d1 -DCMAKE_BUILD_TYPE\u003dDEBUG\u0022\n+ - LWS_METHOD\u003dss CMAKE_ARGS\u003d\u0022-DLWS_WITH_SECURE_STREAMS\u003d1 -DLWS_WITH_MINIMAL_EXAMPLES\u003d1\u0022\n+ - LWS_METHOD\u003dss+mbedtls CMAKE_ARGS\u003d\u0022-DLWS_WITH_MBEDTLS\u003d1 -DLWS_WITH_SECURE_STREAMS\u003d1 -DLWS_WITH_MINIMAL_EXAMPLES\u003d1\u0022\n - LWS_METHOD\u003dnoserver CMAKE_ARGS\u003d\u0022-DLWS_WITHOUT_SERVER\u003dON -DLWS_WITH_MINIMAL_EXAMPLES\u003d1\u0022\n - LWS_METHOD\u003dnoclient CMAKE_ARGS\u003d\u0022-DLWS_WITHOUT_CLIENT\u003dON -DLWS_WITH_MINIMAL_EXAMPLES\u003d1\u0022\n - LWS_METHOD\u003dnoext CMAKE_ARGS\u003d\u0022-DLWS_WITHOUT_EXTENSIONS\u003dON -DLWS_WITH_MINIMAL_EXAMPLES\u003d1\u0022\ndiff --git a/CMakeLists.txt b/CMakeLists.txt\nindex 1600341..e4bf58f 100644\n--- a/CMakeLists.txt\n+++ b/CMakeLists.txt\n@@ -61,6 +61,13 @@ option(LWS_WITH_HTTP_BASIC_AUTH \u0022Support Basic Auth\u0022 ON)\n option(LWS_WITH_HTTP_UNCOMMON_HEADERS \u0022Include less common http header support\u0022 ON)\n \n #\n+# Secure Streams\n+#\n+option(LWS_WITH_SECURE_STREAMS \u0022Secure Streams protocol-agnostic API\u0022 OFF)\n+option(LWS_WITH_SECURE_STREAMS_PROXY_API \u0022Secure Streams support to work across processes\u0022 OFF)\n+option(LWS_WITH_SECURE_STREAMS_SYS_AUTH_API_AMAZON_COM \u0022Auth support for api.amazon.com\u0022 OFF)\n+\n+#\n # TLS library options... all except mbedTLS are basically OpenSSL variants.\n #\n option(LWS_WITH_SSL \u0022Include SSL support (defaults to OpenSSL or similar, mbedTLS if LWS_WITH_MBEDTLS is set)\u0022 ON)\n@@ -245,6 +252,11 @@ if(LWS_WITH_DISTRO_RECOMMENDED)\n \tset(LWS_ROLE_MQTT 1)\n endif()\n \n+if (LWS_WITH_SECURE_STREAMS_PROXY_API)\n+\tset(LWS_WITH_LWS_DSH 1)\n+\tset(LWS_WITH_UNIX_SOCK 1)\n+endif()\n+\n if (NOT LWS_WITH_NETWORK)\n \tset(LWS_ROLE_MQTT 0)\n \tset(LWS_ROLE_H1 0)\n@@ -394,6 +406,10 @@ endif()\n \n include_directories(include plugins lib/core lib/core-net lib/event-libs include/abstract lib/tls lib/roles lib/event-libs/libuv lib/event-libs/poll lib/event-libs/libevent lib/event-libs/glib lib/event-libs/libev lib/jose/jwe lib/jose/jws lib/jose lib/misc lib/roles/http lib/roles/http/compression lib/roles/h1 lib/roles/h2 lib/roles/ws lib/roles/cgi lib/roles/dbus lib/roles/raw-proxy lib/abstract lib/system/async-dns lib/roles/mqtt)\n \n+if (LWS_WITH_SECURE_STREAMS)\n+\tset(LWS_WITH_SECURE_STREAMS_SYS_AUTH_API_AMAZON_COM 1)\n+endif()\n+\n if (LWS_PLAT_FREERTOS)\n \tinclude_directories(lib/plat/freertos lib/plat/freertos/esp32)\n else()\n@@ -752,14 +768,6 @@ if (LWS_WITHOUT_DAEMONIZE OR WIN32)\n \tset(LWS_NO_DAEMONIZE 1)\n endif()\n \n-if (LWS_WITHOUT_SERVER)\n-\tset(LWS_WITHOUT_SERVER 1)\n-endif()\n-\n-if (LWS_WITHOUT_CLIENT)\n-\tset(LWS_WITHOUT_CLIENT 1)\n-endif()\n-\n if (LWS_WITH_LIBEV)\n \tset(LWS_WITH_LIBEV 1)\n endif()\n@@ -790,11 +798,11 @@ endif()\n \n set(LWS_WITH_CLIENT 1)\n if (LWS_WITHOUT_CLIENT)\n-\tset(LWS_WITH_CLIENT 0)\n+\tset(LWS_WITH_CLIENT)\n endif()\n set(LWS_WITH_SERVER 1)\n if (LWS_WITHOUT_SERVER)\n-\tset(LWS_WITH_SERVER 0)\n+\tset(LWS_WITH_SERVER)\n endif()\n \n # using any abstract protocol enables LWS_WITH_ABSTRACT\n@@ -1165,6 +1173,53 @@ if (LWS_WITH_NETWORK)\n \t\tendif()\n \tendif()\n \n+\tif (LWS_WITH_SECURE_STREAMS)\n+\t\tlist(APPEND SOURCES\n+\t\t\tlib/secure-streams/secure-streams.c\n+\t\t\tlib/secure-streams/policy.c\n+\t\t\tlib/secure-streams/system/fetch-policy/fetch-policy.c\n+\t\t)\n+\t\tif (LWS_ROLE_H1)\n+\t\t\tlist(APPEND SOURCES\n+\t\t\t\tlib/secure-streams/protocols/ss-h1.c\n+\t\t\t)\n+\t\tendif()\n+\t\tif (LWS_ROLE_H2)\n+\t\t\tlist(APPEND SOURCES\n+\t\t\t\tlib/secure-streams/protocols/ss-h2.c\n+\t\t\t)\n+\t\tendif()\n+\t\tif (LWS_ROLE_WS)\n+\t\t\tlist(APPEND SOURCES\n+\t\t\t\tlib/secure-streams/protocols/ss-ws.c\n+\t\t\t)\n+\t\tendif()\n+\t\tif (LWS_ROLE_MQTT)\n+\t\t\tlist(APPEND SOURCES\n+\t\t\t\tlib/secure-streams/protocols/ss-mqtt.c\n+\t\t\t)\n+\t\tendif()\n+\n+\t\tif (LWS_WITH_SECURE_STREAMS_PROXY_API)\n+\t\t\tlist(APPEND SOURCES\n+\t\t\t\tlib/secure-streams/secure-streams-serialize.c\n+\t\t\t\tlib/secure-streams/secure-streams-client.c\n+\t\t\t)\n+\t\tendif()\n+\n+\t\tif (LWS_WITH_SECURE_STREAMS_PROXY_API)\n+\t\t\tlist(APPEND SOURCES\n+\t\t\t\tlib/secure-streams/secure-streams-process.c\n+\t\t\t)\n+\t\tendif()\n+\n+\t\tif (LWS_WITH_SECURE_STREAMS_SYS_AUTH_API_AMAZON_COM)\n+\t\t\tlist(APPEND SOURCES\n+\t\t\t\tlib/secure-streams/system/auth-api.amazon.com/auth.c\n+\t\t\t)\n+\t\tendif()\n+\tendif()\n+\n \tif (LWS_WITH_STATS)\n \t\tlist(APPEND SOURCES\n \t\t\tlib/core-net/stats.c\n@@ -1300,7 +1355,11 @@ endif()\n \n if (NOT LWS_WITHOUT_SERVER)\n \tlist(APPEND SOURCES\n-\t\tlib/core-net/server.c\n+\t\tlib/core-net/server.c)\n+endif()\n+\n+if (NOT LWS_WITHOUT_SERVER OR LWS_WITH_SECURE_STREAMS_PROCESS_API)\n+\tlist(APPEND SOURCES\n \t\tlib/roles/listen/ops-listen.c)\n endif()\n \n@@ -1684,7 +1743,7 @@ if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX OR COMPILER_IS_CLANG)\n \tendif()\n \n if (UNIX AND NOT LWS_PLAT_FREERTOS)\n-\t set(CMAKE_C_FLAGS \u0022-Wall -Wsign-compare -Wuninitialized -Werror ${VISIBILITY_FLAG} -Wundef ${GCOV_FLAGS} ${CMAKE_C_FLAGS} ${ASAN_FLAGS}\u0022 )\n+\t set(CMAKE_C_FLAGS \u0022-Wall -Wsign-compare -Wstrict-aliasing -Wuninitialized -Werror ${VISIBILITY_FLAG} -Wundef ${GCOV_FLAGS} ${CMAKE_C_FLAGS} ${ASAN_FLAGS}\u0022 )\n else()\n \t set(CMAKE_C_FLAGS \u0022-Wall -Wsign-compare -Wuninitialized -Werror ${VISIBILITY_FLAG} ${GCOV_FLAGS} ${CMAKE_C_FLAGS}\u0022 )\n endif()\n@@ -2666,6 +2725,59 @@ if (LWS_WITH_LWSWS)\n \t\t)\n endif (LWS_WITH_LWSWS)\n \n+# secure streams plugins\n+\n+if (LWS_WITH_SECURE_STREAMS)\n+\t#\n+\t# Helper function for adding a secure stream plugin\n+\t#\n+\tmacro(create_ss_plugin NAME S2 S3 S4 S5 S6)\n+\n+\t\tset(SSP_SRCS)\n+\t\tset(SSP_PUBLIC_HDR)\n+\t\tset(SSP_HDR)\n+\n+\t\tif (\u0022${S2}\u0022 STREQUAL \u0022\u0022)\n+\t\telse()\n+\t\t\tlist(APPEND SSP_SRCS\n+\t\t\t lib/secure-streams/plugins/${NAME}/${S2})\n+\t\tendif()\n+\t\tif (\u0022${S3}\u0022 STREQUAL \u0022\u0022)\n+\t\telse()\n+\t\t\tlist(APPEND SSP_SRCS\n+\t\t\t lib/secure-streams/plugins/${NAME}/${S3})\n+\t\tendif()\n+\t\tif (\u0022${S4}\u0022 STREQUAL \u0022\u0022)\n+\t\telse()\n+\t\t\tlist(APPEND SSP_SRCS\n+\t\t\t lib/secure-streams/plugins/${NAME}/${S4})\n+\t\tendif()\n+\t\tif (\u0022${S5}\u0022 STREQUAL \u0022\u0022)\n+\t\telse()\n+\t\t\tlist(APPEND SSP_SRCS\n+\t\t\t lib/secure-streams/plugins/${NAME}/${S5})\n+\t\tendif()\n+\t\tif (\u0022${S6}\u0022 STREQUAL \u0022\u0022)\n+\t\telse()\n+\t\t\tlist(APPEND SSP_SRCS\n+\t\t\t lib/secure-streams/plugins/${NAME}/${S6})\n+\t\tendif()\n+\n+\t\tsource_group(\u0022Headers Private\u0022 FILES ${SSP_HDR})\n+\t\tsource_group(\u0022Sources\u0022\t\tFILES ${SSP_SRCS})\n+\n+\t\tadd_library( ${NAME} STATIC\n+\t\t\t ${SSP_HDR} ${SSP_PUBLIC_HDR} ${SSP_SRCS} )\n+\n+ add_dependencies(${NAME} websockets_shared)\n+\t\tlist(APPEND SS_PLUGINS_LIST ${NAME})\n+\tendmacro()\n+\n+\tinclude_directories(lib/secure-streams)\n+\n+\tcreate_ss_plugin(ssp-h1url \u0022h1url.c\u0022 \u0022\u0022 \u0022\u0022 \u0022\u0022 \u0022\u0022)\n+endif()\n+\n if (UNIX)\n \n # Generate and install pkgconfig.\ndiff --git a/README.md b/README.md\nindex 76d8bde..528d5cc 100644\n--- a/README.md\n+++ b/README.md\n@@ -16,6 +16,26 @@ various scenarios, CC0-licensed (public domain) for cut-and-paste, allow you to \n News\n ----\n \n+## Introducing Secure Streams client support\n+\n+Secure Streams is an optional layer above lws (`-DLWS_WITH_SECURE_STREAMS\u003d1`) that\n+separates connectivity policy into a JSON document, which can be part of the\n+firmware or fetched at boot time.\n+\n+Code no longer deals with details like endpoint specification or tls cert stack used\n+to validate the remote server, it's all specified in JSON, eg, see\n+[this example](https://warmcat.com/policy/minimal-proxy.json). Even the protocol to use to talk to the\n+server, between h1, h2, ws or MQTT, is specified in the policy JSON and the code\n+itself just deals with payloads and optionally metadata, making it possible to\n+switch endpoints, update certs and even switch communication protocols by just\n+editing the JSON policy and leaving the code alone.\n+\n+Logical Secure Stream connections outlive any underlying lws connection, and support\n+\u0022nailed-up\u0022 connection reacquisition and exponential backoff management.\n+\n+See [./lib/secure-streams/README.md](https://libwebsockets.org/git/libwebsockets/tree/lib/secure-streams/README.md) and the related minimal examples\n+for more details.\n+\n ## mqtt client support\n \n If you enable `-DLWS_ROLE_MQTT\u003d1`, lws can now support QoS0 and QoS1 MQTT client\ndiff --git a/cmake/lws_config.h.in b/cmake/lws_config.h.in\nindex ba397cb..56691a3 100644\n--- a/cmake/lws_config.h.in\n+++ b/cmake/lws_config.h.in\n@@ -147,6 +147,9 @@\n #cmakedefine LWS_WITH_POLARSSL\n #cmakedefine LWS_WITH_POLL\n #cmakedefine LWS_WITH_RANGES\n+#cmakedefine LWS_WITH_SECURE_STREAMS\n+#cmakedefine LWS_WITH_SECURE_STREAMS_SYS_AUTH_API_AMAZON_COM\n+#cmakedefine LWS_WITH_SECURE_STREAMS_PROXY_API\n #cmakedefine LWS_WITH_SELFTESTS\n #cmakedefine LWS_WITH_SEQUENCER\n #cmakedefine LWS_WITH_SERVER_STATUS\ndiff --git a/doc-assets/ss-explain.png b/doc-assets/ss-explain.png\nnew file mode 100644\nindex 0000000..14bd7e7\nBinary files /dev/null and b/doc-assets/ss-explain.png differ\ndiff --git a/include/libwebsockets.h b/include/libwebsockets.h\nindex 6e7fb77..8b26586 100644\n--- a/include/libwebsockets.h\n+++ b/include/libwebsockets.h\n@@ -576,6 +576,9 @@ struct lws;\n #include \u003clibwebsockets/lws-fts.h\u003e\n #include \u003clibwebsockets/lws-diskcache.h\u003e\n #include \u003clibwebsockets/lws-sequencer.h\u003e\n+#include \u003clibwebsockets/lws-secure-streams.h\u003e\n+#include \u003clibwebsockets/lws-secure-streams-policy.h\u003e\n+#include \u003clibwebsockets/lws-secure-streams-client.h\u003e\n \n #if !defined(LWS_PLAT_FREERTOS)\n #include \u003clibwebsockets/abstract/abstract.h\u003e\ndiff --git a/include/libwebsockets/lws-context-vhost.h b/include/libwebsockets/lws-context-vhost.h\nindex c379b0f..33143b9 100644\n--- a/include/libwebsockets/lws-context-vhost.h\n+++ b/include/libwebsockets/lws-context-vhost.h\n@@ -227,6 +227,8 @@\n #define lws_check_opt(c, f) ((((uint64_t)c) \u0026 ((uint64_t)f)) \u003d\u003d ((uint64_t)f))\n \n struct lws_plat_file_ops;\n+struct lws_ss_policy;\n+struct lws_ss_plugin;\n \n typedef int (*lws_context_ready_cb_t)(struct lws_context *context);\n \n@@ -699,7 +701,7 @@ struct lws_context_creation_info {\n \t/**\u003c VHOST: optional retry and idle policy to apply to this vhost.\n \t * Currently only the idle parts are applied to the connections.\n \t */\n-\tlws_state_notify_link_t **register_notifier_list;\n+\tlws_state_notify_link_t * const *register_notifier_list;\n \t/**\u003c CONTEXT: NULL, or pointer to an array of notifiers that should\n \t * be registered during context creation, so they can see state change\n \t * events from very early on. The array should end with a NULL. */\n@@ -709,6 +711,26 @@ struct lws_context_creation_info {\n \tuint8_t udp_loss_sim_rx_pc;\n \t/**\u003c CONTEXT: percentage of udp reads we actually received\n \t * to make disappear, in order to simulate and test udp retry flow */\n+#if defined(LWS_WITH_SECURE_STREAMS)\n+\tconst char *pss_policies_json; /**\u003c CONTEXT: point to a string\n+\t * containing a JSON description of the secure streams policies. Set\n+\t * to NULL if not using Secure Streams. */\n+\tconst struct lws_ss_plugin **pss_plugins; /**\u003c CONTEXT: point to an array\n+\t * of pointers to plugin structs here, terminated with a NULL ptr.\n+\t * Set to NULL if not using Secure Streams. */\n+\tconst char *ss_proxy_bind; /**\u003c CONTEXT: NULL, or: ss_proxy_port \u003d\u003d 0:\n+\t * point to a string giving the Unix Domain Socket address to use (start\n+\t * with @ for abstract namespace), ss_proxy_port nonzero: set the\n+\t * network interface address (not name, it's ambiguous for ipv4/6) to\n+\t * bind the tcp connection to the proxy to */\n+\tconst char *ss_proxy_address; /**\u003c CONTEXT: NULL, or if ss_proxy_port\n+\t * nonzero: the tcp address of the ss proxy to connect to */\n+\tuint16_t ss_proxy_port; /* 0 \u003d if connecting to ss proxy, do it via a\n+\t * Unix Domain Socket, \u0022+@proxy.ss.lws\u0022 if ss_proxy_bind is NULL else\n+\t * the socket path given in ss_proxy_bind (start it with a + or +@);\n+\t * nonzero means connect via a tcp socket to the tcp address in\n+\t * ss_proxy_bind and the given port */\n+#endif\n \n \t/* Add new things just above here ---^\n \t * This is part of the ABI, don't needlessly break compatibility\n@@ -718,7 +740,7 @@ struct lws_context_creation_info {\n \t * was not built against the newer headers.\n \t */\n \n-\tvoid *_unused[4]; /**\u003c dummy */\n+\tvoid *_unused[2]; /**\u003c dummy */\n };\n \n /**\ndiff --git a/include/libwebsockets/lws-secure-streams-client.h b/include/libwebsockets/lws-secure-streams-client.h\nnew file mode 100644\nindex 0000000..b724415\n--- /dev/null\n+++ b/include/libwebsockets/lws-secure-streams-client.h\n@@ -0,0 +1,172 @@\n+/*\n+ * libwebsockets - small server side websockets and web server implementation\n+ *\n+ * Copyright (C) 2019 - 2020 Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * Permission is hereby granted, free of charge, to any person obtaining a copy\n+ * of this software and associated documentation files (the \u0022Software\u0022), to\n+ * deal in the Software without restriction, including without limitation the\n+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n+ * sell copies of the Software, and to permit persons to whom the Software is\n+ * furnished to do so, subject to the following conditions:\n+ *\n+ * The above copyright notice and this permission notice shall be included in\n+ * all copies or substantial portions of the Software.\n+ *\n+ * THE SOFTWARE IS PROVIDED \u0022AS IS\u0022, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n+ * IN THE SOFTWARE.\n+ *\n+ * This is the headers for secure stream api variants that deal with clients in\n+ * different threads or even different processes.\n+ *\n+ * lws_ss_ when client is directly using the event loop\n+ * lws_sstc_ when client is in a different thread to the event loop\n+ * lws_sspc_ when client is in a different process to the event loop\n+ *\n+ * The client api is almost the same except the slightly diffent names.\n+ */\n+\n+/*\n+ * lws_sspc_ apis... different process\n+ */\n+\n+/*\n+ * Helper translation so user code written to lws_ss_ can be built for\n+ * lws_sspc_ in one step by #define LWS_SS_USE_SSPC before including\n+ */\n+\n+#if defined(LWS_SS_USE_SSPC)\n+#define lws_ss_handle lws_sspc_handle\n+#define lws_ss_create lws_sspc_create\n+#define lws_ss_destroy lws_sspc_destroy\n+#define lws_ss_request_tx lws_sspc_request_tx\n+#define lws_ss_client_connect lws_sspc_client_connect\n+#define lws_ss_get_sequencer lws_sspc_get_sequencer\n+#define lws_ss_proxy_create lws_sspc_proxy_create\n+#define lws_ss_get_context lws_sspc_get_context\n+#define lws_ss_rideshare lws_sspc_rideshare\n+#define lws_ss_set_metadata lws_sspc_set_metadata\n+#define lws_ss_add_peer_tx_credit lws_sspc_add_peer_tx_credit\n+#define lws_ss_get_est_peer_tx_credit lws_sspc_get_est_peer_tx_credit\n+#endif\n+\n+\n+struct lws_sspc_handle;\n+\n+LWS_VISIBLE LWS_EXTERN int\n+lws_sspc_create(struct lws_context *context, int tsi, const lws_ss_info_t *ssi,\n+\t void *opaque_user_data, struct lws_sspc_handle **ppss,\n+\t struct lws_sequencer *seq_owner, const char **ppayload_fmt);\n+\n+/**\n+ * lws_sspc_destroy() - Destroy secure stream\n+ *\n+ * \u005cparam ppss: pointer to lws_ss_t pointer to be destroyed\n+ *\n+ * Destroys the lws_ss_t pointed to by *ppss, and sets *ppss to NULL.\n+ */\n+LWS_VISIBLE LWS_EXTERN void\n+lws_sspc_destroy(struct lws_sspc_handle **ppss);\n+\n+/**\n+ * lws_sspc_request_tx() - Schedule stream for tx\n+ *\n+ * \u005cparam pss: pointer to lws_ss_t representing stream that wants to transmit\n+ *\n+ * Schedules a write on the stream represented by \u005cp pss. When it's possible to\n+ * write on this stream, the *tx callback will occur with an empty buffer for\n+ * the stream owner to fill in.\n+ */\n+LWS_VISIBLE LWS_EXTERN void\n+lws_sspc_request_tx(struct lws_sspc_handle *pss);\n+\n+/**\n+ * lws_sspc_client_connect() - Attempt the client connect\n+ *\n+ * \u005cparam h: secure streams handle\n+ *\n+ * Starts the connection process for the secure stream. Returns 0 if OK or\n+ * nonzero if we have already failed.\n+ */\n+LWS_VISIBLE LWS_EXTERN int\n+lws_sspc_client_connect(struct lws_sspc_handle *h);\n+\n+/**\n+ * lws_sspc_get_sequencer() - Return parent sequencer pointer if any\n+ *\n+ * \u005cparam h: secure streams handle\n+ *\n+ * Returns NULL if the secure stream is not associated with a sequencer.\n+ * Otherwise returns a pointer to the owning sequencer. You can use this to\n+ * identify which sequencer to direct messages to, from the secure stream\n+ * callback.\n+ */\n+LWS_VISIBLE LWS_EXTERN struct lws_sequencer *\n+lws_sspc_get_sequencer(struct lws_sspc_handle *h);\n+\n+/**\n+ * lws_sspc_proxy_create() - Start a unix domain socket proxy for Secure Streams\n+ *\n+ * \u005cparam context: lws_context\n+ *\n+ * Creates a vhost that listens on an abstract namespace unix domain socket at\n+ * address \u0022proxy.ss.lws\u0022. Client connections to this proxy to Secure Streams\n+ */\n+LWS_VISIBLE LWS_EXTERN int\n+lws_sspc_proxy_create(struct lws_context *context);\n+\n+/**\n+ * lws_ss_get_context() - convenience helper to recover the lws context\n+ *\n+ * \u005ch: secure streams handle\n+ *\n+ * Returns the lws context. Dispenses with the need to pass a copy of it into\n+ * your secure streams handler.\n+ */\n+\n+LWS_VISIBLE LWS_EXTERN struct lws_context *\n+lws_sspc_get_context(struct lws_sspc_handle *h);\n+\n+LWS_VISIBLE LWS_EXTERN const struct lws_protocols lws_sspc_protocols[];\n+\n+LWS_VISIBLE LWS_EXTERN const char *\n+lws_sspc_rideshare(struct lws_sspc_handle *h);\n+\n+\n+/**\n+ * lws_sspc_set_metadata() - allow user to bind external data to defined ss metadata\n+ *\n+ * \u005ch: secure streams handle\n+ * \u005cname: metadata name from the policy\n+ * \u005cvalue: pointer to user-managed data to bind to name\n+ * \u005clen: length of the user-managed data in value\n+ *\n+ * Binds user-managed data to the named metadata item from the ss policy.\n+ * If present, the metadata item is handled in a protocol-specific way using\n+ * the associated policy information. For example, in the policy\n+ *\n+ * \t\u0022\u005c\u0022metadata\u005c\u0022:\u0022\t\t\u0022[\u0022\n+ *\t\t\u0022{\u005c\u0022uptag\u005c\u0022:\u0022 \u0022\u005c\u0022X-Upload-Tag:\u005c\u0022},\u0022\n+ *\t\t\u0022{\u005c\u0022ctype\u005c\u0022:\u0022 \u0022\u005c\u0022Content-Type:\u005c\u0022},\u0022\n+ *\t\t\u0022{\u005c\u0022xctype\u005c\u0022:\u0022 \u0022\u005c\u0022X-Content-Type:\u005c\u0022}\u0022\n+ *\t\u0022],\u0022\n+ *\n+ * when the policy is using h1 is interpreted to add h1 headers of the given\n+ * name with the value of the metadata on the left.\n+ *\n+ * Return 0 if OK.\n+ */\n+LWS_VISIBLE LWS_EXTERN int\n+lws_sspc_set_metadata(struct lws_sspc_handle *h, const char *name,\n+\t\t void *value, size_t len);\n+\n+LWS_VISIBLE LWS_EXTERN int\n+lws_sspc_add_peer_tx_credit(struct lws_sspc_handle *h, int32_t add);\n+\n+LWS_VISIBLE LWS_EXTERN int\n+lws_sspc_get_est_peer_tx_credit(struct lws_sspc_handle *h);\ndiff --git a/include/libwebsockets/lws-secure-streams-policy.h b/include/libwebsockets/lws-secure-streams-policy.h\nnew file mode 100644\nindex 0000000..967ef8d\n--- /dev/null\n+++ b/include/libwebsockets/lws-secure-streams-policy.h\n@@ -0,0 +1,237 @@\n+/*\n+ * libwebsockets - small server side websockets and web server implementation\n+ *\n+ * Copyright (C) 2019 - 2020 Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * Permission is hereby granted, free of charge, to any person obtaining a copy\n+ * of this software and associated documentation files (the \u0022Software\u0022), to\n+ * deal in the Software without restriction, including without limitation the\n+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n+ * sell copies of the Software, and to permit persons to whom the Software is\n+ * furnished to do so, subject to the following conditions:\n+ *\n+ * The above copyright notice and this permission notice shall be included in\n+ * all copies or substantial portions of the Software.\n+ *\n+ * THE SOFTWARE IS PROVIDED \u0022AS IS\u0022, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n+ * IN THE SOFTWARE.\n+ *\n+ * included from libwebsockets.h\n+ */\n+\n+typedef int (*plugin_auth_status_cb)(struct lws_ss_handle *ss, int status);\n+\n+/**\n+ * lws_ss_plugin_auth_t - api for an auth plugin\n+ *\n+ * Auth plugins create and sequence authenticated connections that can carry one\n+ * or more streams to an endpoint. That may involve other connections to other\n+ * places to eg, gather authenticated tokens and then make the real connection\n+ * using the tokens.\n+ *\n+ * The secure stream object contains members to record which auth plugin the\n+ * stream is bound to and an over-allocation of the secure stream object to\n+ * contain the plugin auth private data.\n+ *\n+ * The auth plugin controls the state of the stream connection via the status\n+ * callback, and handles retries.\n+ *\n+ * Network connections may require one kind of auth sequencing, and streams\n+ * inside those connections another kind of auth sequencing depending on their\n+ * role. So the secure stream object allows defining plugins for both kinds.\n+ *\n+ * Streams may disappear at any time and require reauth to bring a new one up.\n+ * The auth plugin sequencer will connect / reconnect either on demand, or from\n+ * the start and after any connectivity loss if any stream using the connection\n+ * has the LWSSSPOLF_NAILED_UP flag.\n+ */\n+\n+typedef struct lws_ss_plugin {\n+\tstruct lws_ss_plugin\t*next;\n+\tconst char\t\t*name;\t/**\u003c auth plugin name */\n+\tsize_t\t\t\talloc;\t/**\u003c size of private allocation */\n+\n+\tint\t\t\t(*create)(struct lws_ss_handle *ss, void *info,\n+\t\t\t\t\t plugin_auth_status_cb status);\n+\t\t\t\t/**\u003c called when the auth plugin is instantiated\n+\t\t\t\t and bound to the secure stream. status is\n+\t\t\t\t called back with advisory information about\n+\t\t\t\t the authenticated stream state as it\n+\t\t\t\t proceeds */\n+\tint\t\t\t(*destroy)(struct lws_ss_handle *ss);\n+\t\t\t\t/**\u003c called when the related secure stream is\n+\t\t\t\t being destroyed, and anything the auth\n+\t\t\t\t plugin is doing should also be destroyed */\n+\tint\t\t\t(*munge)(struct lws_ss_handle *ss, char *path,\n+\t\t\t\t\t size_t path_len);\n+\t\t\t\t/**\u003c if the plugin needs to munge transactions\n+\t\t\t\t that have metadata outside the payload (eg,\n+\t\t\t\t add http headers) this callback will give\n+\t\t\t\t it the opportunity to do so */\n+} lws_ss_plugin_t;\n+\n+\n+typedef struct lws_ss_x509 {\n+\tstruct lws_ss_x509\t*next;\n+\tconst char\t\t*vhost_name; /**\u003c vhost name using cert ctx */\n+\tconst uint8_t\t\t*ca_der;\t/**\u003c DER x.509 cert */\n+\tsize_t\t\t\tca_der_len;\t/**\u003c length of DER cert */\n+} lws_ss_x509_t;\n+\n+enum {\n+\tLWSSSPOLF_OPPORTUNISTIC\t\t\t\t\t\u003d (1 \u003c\u003c 0),\n+\t/**\u003c the connection doesn't exist unless client asks to write */\n+\tLWSSSPOLF_NAILED_UP\t\t\t\t\t\u003d (1 \u003c\u003c 1),\n+\t/**\u003c the connection tries to be connected the whole life of the ss */\n+\tLWSSSPOLF_URGENT_TX\t\t\t\t\t\u003d (1 \u003c\u003c 2),\n+\t/**\u003c this connection carries critical tx data */\n+\tLWSSSPOLF_URGENT_RX\t\t\t\t\t\u003d (1 \u003c\u003c 3),\n+\t/**\u003c this connection carries critical rx data */\n+\tLWSSSPOLF_TLS\t\t\t\t\t\t\u003d (1 \u003c\u003c 4),\n+\t/**\u003c stream must be connected via a tls tunnel */\n+\tLWSSSPOLF_LONG_POLL\t\t\t\t\t\u003d (1 \u003c\u003c 5),\n+\t/**\u003c stream used to receive async rx at arbitrary intervals */\n+\tLWSSSPOLF_AUTH_BEARER\t\t\t\t\t\u003d (1 \u003c\u003c 6),\n+\t/**\u003c for http, use lws_system auth token 0 in authentication: bearer */\n+\tLWSSSPOLF_HTTP_NO_CONTENT_LENGTH\t\t\t\u003d (1 \u003c\u003c 7),\n+\t/**\u003c don't add any content length even if we have it */\n+\tLWSSSPOLF_QUIRK_NGHTTP2_END_STREAM\t\t\t\u003d (1 \u003c\u003c 8),\n+\t/**\u003c set the client flag LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM */\n+\tLWSSSPOLF_H2_QUIRK_OVERFLOWS_TXCR\t\t\t\u003d (1 \u003c\u003c 9),\n+\t/**\u003c set the client flag LCCSCF_H2_QUIRK_OVERFLOWS_TXCR */\n+\tLWSSSPOLF_H2_QUIRK_UNCLEAN_HPACK_STATE\t\t\t\u003d (1 \u003c\u003c 10),\n+\t/**\u003c HPACK decoder state does not end cleanly */\n+\tLWSSSPOLF_HTTP_MULTIPART\t\t\t\t\u003d (1 \u003c\u003c 11),\n+\t/**\u003c indicates stream goes out as specifically a multipart mime POST\n+\t * section... if the tx has LWSSS_FLAG_COALESCE_CONTINUES flag then more\n+\t * multipart sections are expected. Without it, the multipart wrapper\n+\t * is closed and the http transaction issue completed when this message\n+\t * finishes. */\n+\tLWSSSPOLF_HTTP_X_WWW_FORM_URLENCODED\t\t\t\u003d (1 \u003c\u003c 12),\n+\t/**\u003c set up lws_system client cert */\n+\tLWSSSPOLF_LOCAL_SINK\t\t\t\t\t\u003d (1 \u003c\u003c 13),\n+\t/**\u003c expected to bind to a local sink only */\n+};\n+\n+typedef struct lws_ss_trust_store {\n+\tstruct lws_ss_trust_store\t*next;\n+\tconst char\t\t\t*name;\n+\n+\tlws_ss_x509_t\t\t\t*ssx509[8];\n+\tint\t\t\t\tcount;\n+} lws_ss_trust_store_t;\n+\n+enum {\n+\tLWSSSP_H1,\n+\tLWSSSP_H2,\n+\tLWSSSP_WS,\n+\n+\n+\tLWSSS_HBI_AUTH \u003d 0,\n+\tLWSSS_HBI_DSN,\n+\tLWSSS_HBI_FWV,\n+\tLWSSS_HBI_TYPE,\n+\n+\t_LWSSS_HBI_COUNT /* always last */\n+};\n+\n+typedef struct lws_ss_metadata {\n+\tstruct lws_ss_metadata\t*next;\n+\tconst char\t\t*name;\n+\tvoid\t\t\t*value;\n+\tsize_t\t\t\tlength;\n+\n+\tuint8_t\t\t\tvalue_on_lws_heap; /* proxy does this */\n+} lws_ss_metadata_t;\n+\n+\n+/**\n+ * lws_ss_policy_t: policy database entry for a stream type\n+ *\n+ * Decides the system policy for how to implement connections of name\n+ * .streamtype.\n+ *\n+ * Streams may need one kind of auth sequencing for the network connection and\n+ * another kind of auth sequencing for the streams that are carried inside it,\n+ * this is the purpose of .nauth and .sauth. Both are optional and may be NULL.\n+ *\n+ * An array of these is set at context creation time, ending with one with a\n+ * NULL streamtype.\n+ */\n+typedef struct lws_ss_policy {\n+\tstruct lws_ss_policy\t*next;\n+\tconst char\t\t*streamtype; /**\u003c stream type lhs to match on */\n+\n+\tconst char\t\t*endpoint; /**\u003c DNS address to connect to */\n+\tconst char\t\t*rideshare_streamtype; /**\u003c optional transport\n+\t\t\t\t\t* on another, preexisting stream of this\n+\t\t\t\t\t* streamtype name */\n+\tconst char\t\t*payload_fmt;\n+\tconst char\t\t*socks5_proxy;\n+\tlws_ss_metadata_t\t*metadata; /* linked-list of metadata */\n+\n+\t/* protocol-specific connection policy details */\n+\n+\tunion {\n+\n+\t\t/* details for http-related protocols... */\n+\n+\t\tstruct {\n+\n+\t\t\t/* common to all http-related protocols */\n+\n+\t\t\tconst char\t*method;\n+\t\t\tconst char\t*url;\n+\n+\t\t\tconst char\t*multipart_name;\n+\t\t\tconst char\t*multipart_filename;\n+\t\t\tconst char\t*multipart_content_type;\n+\n+\t\t\tconst char\t*blob_header[_LWSSS_HBI_COUNT];\n+\t\t\tconst char\t*auth_preamble;\n+\n+\t\t\tunion {\n+//\t\t\t\tstruct { /* LWSSSP_H1 */\n+//\t\t\t\t} h1;\n+//\t\t\t\tstruct { /* LWSSSP_H2 */\n+//\t\t\t\t} h2;\n+\t\t\t\tstruct { /* LWSSSP_WS */\n+\t\t\t\t\tconst char\t*subprotocol;\n+\t\t\t\t\tuint8_t\t\tbinary;\n+\t\t\t\t\t/* false \u003d TEXT, true \u003d BINARY */\n+\t\t\t\t} ws;\n+\t\t\t} u;\n+\t\t} http;\n+\n+\t\tstruct {\n+\t\t\tconst char\t*topic;\t /* stream sends on this topic */\n+\t\t\tconst char\t*subscribe; /* stream subscribes to this topic */\n+\t\t\tuint8_t\t\tqos;\n+\t\t} mqtt;\n+\n+\t\t/* details for non-http related protocols... */\n+\t} u;\n+\n+\tconst\n+\tstruct lws_ss_plugin\t*plugins[2]; /**\u003c NULL or auth plugin */\n+\tconst void\t\t*plugins_info[2]; /**\u003c plugin-specific data */\n+\n+\tconst lws_ss_trust_store_t *trust_store; /**\u003c CA certs needed for conn\n+\t validation, only set between policy parsing and vhost creation */\n+\n+\tconst lws_retry_bo_t\t*retry_bo; /**\u003c retry policy to use */\n+\n+\tuint32_t\t\tflags;\t /**\u003c stream attribute flags */\n+\n+\tuint16_t\t\tport;\t /**\u003c endpoint port */\n+\n+\tuint8_t\t\t\tmetadata_count; /**\u003c metadata count */\n+\tuint8_t\t\t\tprotocol; /**\u003c protocol index */\n+\tuint8_t\t\t\tclient_cert; /**\u003c which client cert to apply\n+\t\t\t\t\t\t 0 \u003d none, 1+ \u003d cc 0+ */\n+} lws_ss_policy_t;\ndiff --git a/include/libwebsockets/lws-secure-streams.h b/include/libwebsockets/lws-secure-streams.h\nnew file mode 100644\nindex 0000000..afd8bee\n--- /dev/null\n+++ b/include/libwebsockets/lws-secure-streams.h\n@@ -0,0 +1,492 @@\n+/*\n+ * libwebsockets - small server side websockets and web server implementation\n+ *\n+ * Copyright (C) 2019 - 2020 Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * Permission is hereby granted, free of charge, to any person obtaining a copy\n+ * of this software and associated documentation files (the \u0022Software\u0022), to\n+ * deal in the Software without restriction, including without limitation the\n+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n+ * sell copies of the Software, and to permit persons to whom the Software is\n+ * furnished to do so, subject to the following conditions:\n+ *\n+ * The above copyright notice and this permission notice shall be included in\n+ * all copies or substantial portions of the Software.\n+ *\n+ * THE SOFTWARE IS PROVIDED \u0022AS IS\u0022, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n+ * IN THE SOFTWARE.\n+ *\n+ * included from libwebsockets.h\n+ *\n+ *\n+ * Secure Streams is a *payload-only* client communication channel where all the\n+ * details about the connection are held in a systemwide policy database and\n+ * are keyed by the streamtype field... the user of the communication channel\n+ * does not know or manage the choice of endpoint, tls CA, or even wire\n+ * protocol. The advantage is he then does not have any dependency on any of\n+ * those and they can be changed just by changing the policy database without\n+ * touching the code using the stream.\n+ *\n+ * There are two ways secure streams interfaces to user code:\n+ *\n+ * 1) [Linux / RTOS] the natural, smallest interface is to call back to user\n+ * code that only operates directly from the lws event loop thread context\n+ * (direct callbacks from lws_ss_t)\n+ *\n+ * lws_thread( [user code] ---- lws )\n+ *\n+ * 2) [Linux] where the user code is in a different process and communicates\n+ * asynchronously via a proxy socket\n+ *\n+ * user_process{ [user code] | shim | socket-}------ lws_process{ lws }\n+ *\n+ * In the second, IPC, case, all packets are prepended by one or more bytes\n+ * indicating the packet type and serializing any associated data, known as\n+ * Serialized Secure Streams or SSS.\n+ *\n+ * Serialized Secure Streams\n+ * -------------------------\n+ *\n+ * On the transport, adjacent packets may be coalesced, that is, the original\n+ * packet sizes are lost and two or more packets are combined. For that reason\n+ * the serialization format always contains a 1-byte type and then a 2-byte\n+ * frame length.\n+ *\n+ * Client to proxy\n+ *\n+ * - Proxied connection setup\n+ *\n+ * - 0: LWSSS_SER_TXPRE_STREAMTYPE\n+ * - 1: 2-byte MSB-first rest-of-frame length\n+ * - 3: 4 byte MSB-first initial tx credit\n+ * - 7: the streamtype name with no NUL\n+ *\n+ * - Proxied tx\n+ *\n+ * - 0: LWSSS_SER_TXPRE_TX_PAYLOAD\n+ * - 1: 2 byte MSB-first rest-of-frame length\n+ * - 3: 4-byte MSB-first flags\n+ * - 7: 4-byte MSB-first us between client requested write and wrote to proxy\n+ * - 11: 8-byte MSB-first us resolution unix time client wrote to proxy\n+ * - 17: payload\n+ *\n+ * - Proxied secure stream destroy\n+ *\n+ * - 0: LWSSS_SER_TXPRE_DESTROYING\n+ * - 1: 00, 00\n+ *\n+ * - Proxied metadata - sent when one metadata item set clientside\n+ *\n+ * - 0: LWSSS_SER_TXPRE_METADATA\n+ * - 1: 2-byte MSB-first rest-of-frame length\n+ * - 2: 1-byte metadata name length\n+ * - 3: metadata name\n+ * - ...: metadata value (for rest of packet)\n+ *\n+ * Proxy to client\n+ *\n+ * - Proxied connection setup result\n+ *\n+ * - 0: LWSSS_SER_RXPRE_CREATE_RESULT\n+ * - 1: 2 byte MSB-first rest-of-frame length (usually 00, 03)\n+ * - 3: 1 byte result, 0 \u003d success. On failure, proxy will close connection.\n+ * - 4: 2 byte MSB-first initial tx credit\n+ * - 6: if present, comma-sep list of rideshare types from policy\n+ *\n+ * - Proxied rx\n+ *\n+ * - 0: LWSSS_SER_RXPRE_RX_PAYLOAD\n+ * - 1: 2 byte MSB-first rest-of-frame length\n+ * - 3: 4-byte MSB-first flags\n+ * - 7: 4-byte MSB-first us between inbound read and wrote to client\n+ * - 11: 8-byte MSB-first us resolution unix time proxy wrote to client\n+ * - 17: (rideshare name len + rideshare name if flags \u0026 LWSSS_FLAG_RIDESHARE)\n+ * payload\n+ *\n+ * - Proxied tx credit\n+ *\n+ * - 0: LWSSS_SER_RXPRE_TXCR_UPDATE\n+ * - 1: 00, 04\n+ * - 3: 4-byte MSB-first addition tx credit bytes\n+ *\n+ * - Proxied state\n+ *\n+ * - 0: LWSSS_SER_RXPRE_CONNSTATE\n+ * - 1: 00, 05\n+ * - 3: 1 byte state index\n+ * - 7: 4-byte MSB-first ordinal\n+ *\n+ *\n+ * Proxied tx may be read by the proxy but rejected due to lack of buffer space\n+ * at the proxy. For that reason, tx must be held at the sender until it has\n+ * been acknowledged or denied.\n+ *\n+ * Sinks\n+ * -----\n+ *\n+ * Sinks are logical \u0022servers\u0022, you can register as a sink for a particular\n+ * streamtype by using the lws_ss_create() api with ssi-\u003eregister_sink set to 1.\n+ *\n+ * For directly fulfilled Secure Streams, new streams of that streamtype bind\n+ * to the rx, tx and state handlers given when it was registered.\n+ *\n+ * - When new streams are created the registered sink handler for (*state) is\n+ * called with event LWSSSCS_SINK_JOIN and the new client stream handle in\n+ * the h_src parameter.\n+ *\n+ * - When the client stream sends something to the sink, it calls the sink's\n+ * (*rx) with the client stream's\n+ */\n+\n+#define LWS_SS_MTU 1540\n+\n+struct lws_ss_handle;\n+typedef uint32_t lws_ss_tx_ordinal_t;\n+\n+/*\n+ * connection state events\n+ */\n+typedef enum {\n+\tLWSSSCS_CREATING,\n+\tLWSSSCS_DISCONNECTED,\n+\tLWSSSCS_UNREACHABLE,\n+\tLWSSSCS_AUTH_FAILED,\n+\tLWSSSCS_CONNECTED,\n+\tLWSSSCS_CONNECTING,\n+\tLWSSSCS_DESTROYING,\n+\tLWSSSCS_POLL,\n+\tLWSSSCS_ALL_RETRIES_FAILED,\t/* all retries in bo policy failed */\n+\tLWSSSCS_QOS_ACK_REMOTE,\t\t/* remote peer received and acked tx */\n+\tLWSSSCS_QOS_NACK_REMOTE,\n+\tLWSSSCS_QOS_ACK_LOCAL,\t\t/* local proxy accepted our tx */\n+\tLWSSSCS_QOS_NACK_LOCAL,\t\t/* local proxy refused our tx */\n+\n+\tLWSSSCS_SINK_JOIN,\t\t/* sinks get this when a new source\n+\t\t\t\t\t * stream joins the sink */\n+\tLWSSSCS_SINK_PART,\t\t/* sinks get this when a new source\n+\t\t\t\t\t * stream leaves the sink */\n+} lws_ss_constate_t;\n+\n+enum {\n+\tLWSSS_FLAG_SOM\t\t\t\t\t\t\u003d (1 \u003c\u003c 0),\n+\t/* payload contains the start of new message */\n+\tLWSSS_FLAG_EOM\t\t\t\t\t\t\u003d (1 \u003c\u003c 1),\n+\t/* payload contains the end of message */\n+\tLWSSS_FLAG_POLL\t\t\t\t\t\t\u003d (1 \u003c\u003c 2),\n+\t/* Not a real transmit... poll for rx if protocol needs it */\n+\tLWSSS_FLAG_RELATED_START\t\t\t\t\u003d (1 \u003c\u003c 3),\n+\t/* Appears in a zero-length message indicating a message group of zero\n+\t * or more messages is now starting. */\n+\tLWSSS_FLAG_RELATED_END\t\t\t\t\t\u003d (1 \u003c\u003c 4),\n+\t/* Appears in a zero-length message indicating a message group of zero\n+\t * or more messages has now finished. */\n+\tLWSSS_FLAG_RIDESHARE\t\t\t\t\t\u003d (1 \u003c\u003c 5),\n+\t/* Serialized payload starts with non-default rideshare name length and\n+\t * name string without NUL, then payload */\n+\n+\t/*\n+\t * In the case the secure stream is proxied across a process or thread\n+\t * boundary, eg by proxying through a socket for IPC, metadata must be\n+\t * carried in-band. A byte is prepended to each rx payload to\n+\t * differentiate what it is.\n+\t *\n+\t * Secure streams where the user is called back directly does not need\n+\t * any of this and only pure payloads are passed.\n+\t *\n+\t * rx (received by client) prepends for proxied connections\n+\t */\n+\n+\tLWSSS_SER_RXPRE_RX_PAYLOAD\t\t\t\t\u003d 0x55,\n+\tLWSSS_SER_RXPRE_CREATE_RESULT,\n+\tLWSSS_SER_RXPRE_CONNSTATE,\n+\tLWSSS_SER_RXPRE_TXCR_UPDATE,\n+\tLWSSS_SER_RXPRE_TLSNEG_ENCLAVE_SIGN,\n+\n+\t/* tx (send by client) prepends for proxied connections */\n+\n+\tLWSSS_SER_TXPRE_STREAMTYPE\t\t\t\t\u003d 0xaa,\n+\tLWSSS_SER_TXPRE_ONWARD_CONNECT,\n+\tLWSSS_SER_TXPRE_DESTROYING,\n+\tLWSSS_SER_TXPRE_TX_PAYLOAD,\n+\tLWSSS_SER_TXPRE_METADATA,\n+\tLWSSS_SER_TXPRE_TXCR_UPDATE,\n+\tLWSSS_SER_TXPRE_TLSNEG_ENCLAVE_SIGNED,\n+};\n+\n+typedef enum {\n+\tLPCS_WAIT_INITIAL_TX \u003d 1, /* after connect, must send streamtype */\n+\tLPCS_REPORTING_FAIL, /* stream creation failed, wait to to tell */\n+\tLPCS_REPORTING_OK, /* stream creation succeeded, wait to to tell */\n+\tLPCS_OPERATIONAL, /* ready for payloads */\n+\tLPCS_DESTROYED,\n+\n+\tLPCS_SENDING_INITIAL_TX \u003d 1, /* after connect, must send streamtype */\n+\tLPCS_WAITING_CREATE_RESULT, /* wait to hear if proxy ss create OK */\n+\tLPCS_LOCAL_CONNECTED,\t /* we are in touch with the proxy */\n+\tLPCS_ONWARD_CONNECT,\t /* request onward ss connection */\n+\n+} lws_ss_conn_states_t;\n+\n+/**\n+ * lws_ss_info_t: information about stream to be created\n+ *\n+ * Prepare this struct with information about what the stream type is and how\n+ * the stream should interface with your code, and pass it to lws_ss_create()\n+ * to create the requested stream.\n+ */\n+\n+typedef struct lws_ss_info {\n+\tconst char *streamtype; /**\u003c type of stream we want to create */\n+\tsize_t\t user_alloc; /**\u003c size of user allocation */\n+\tsize_t\t handle_offset; /**\u003c offset of handle stg in user_alloc type,\n+\t\t\t\t set to offsetof(mytype, my_handle_member) */\n+\tsize_t\t opaque_user_data_offset;\n+\t/**\u003c offset of opaque user data ptr in user_alloc type, set to\n+\t offsetof(mytype, opaque_ud_member) */\n+\n+\tint\t (*rx)(void *userobj, const uint8_t *buf, size_t len,\n+\t\t\t int flags);\n+\t/**\u003c callback with rx payload for this stream */\n+\tint\t (*tx)(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf,\n+\t\t\t size_t *len, int *flags);\n+\t/**\u003c callback to send payload on this stream... 0 \u003d send as set in\n+\t * len and flags, 1 \u003d do not send anything (ie, not even 0 len frame) */\n+\tint\t (*state)(void *userobj, void *h_src /* ss handle type */,\n+\t\t\t lws_ss_constate_t state, lws_ss_tx_ordinal_t ack);\n+\t/**\u003c advisory cb about state of stream and QoS status if applicable...\n+\t * h_src is only used with sinks and LWSSSCS_SINK_JOIN/_PART events.\n+\t * Return nonzero to indicate you want to destroy the stream. */\n+\tint\t manual_initial_tx_credit;\n+\t/**\u003c 0 \u003d manage any tx credit automatically, nonzero explicitly sets the\n+\t * peer stream to have the given amount of tx credit, if the protocol\n+\t * can support it. */\n+\tchar\t register_sink;\n+\t/**\u003c If set, we're not creating a specific stream, but registering\n+\t * ourselves as the \u0022sink\u0022 for .streamtype. It's analogous to saying\n+\t * we want to be the many-to-one \u0022server\u0022 for .streamtype; when other\n+\t * streams are created with that streamtype, they should be forwarded\n+\t * to this stream owner, where they join and part from the sink via\n+\t * (*state) LWSSSCS_SINK_JOIN / _PART events, the new client handle\n+\t * being provided in the h_src parameter.\n+\t */\n+} lws_ss_info_t;\n+\n+/**\n+ * lws_ss_create() - Create secure stream\n+ *\n+ * \u005cparam context: the lws context to create this inside\n+ * \u005cparam tsi: service thread index to create on (normally 0)\n+ * \u005cparam ssi: pointer to lws_ss_info_t filled in with info about desired stream\n+ * \u005cparam opaque_user_data: opaque data to set in the stream's user object\n+ * \u005cparam ppss: pointer to secure stream handle pointer set on exit\n+ * \u005cparam ppayload_fmt: NULL or pointer to a string ptr to take payload format\n+ *\t\t\tname from the policy\n+ *\n+ * Requests a new secure stream described by \u005cp ssi be created. If successful,\n+ * the stream is created, its state callback called with LWSSSCS_CREATING, *ppss\n+ * is set to point to the handle, and it returns 0. If it failed, it returns\n+ * nonzero.\n+ *\n+ * Along with the opaque stream object, streams overallocate\n+ *\n+ * 1) a user data struct whose size is set in ssi\n+ * 2) nauth plugin instantiation data (size set in the plugin struct)\n+ * 3) sauth plugin instantiation data (size set in the plugin struct)\n+ * 4) space for a copy of the stream type name\n+ *\n+ * The user data struct is initialized to all zeros, then the .handle_offset and\n+ * .opaque_user_data_offset fields of the ssi are used to prepare the user data\n+ * struct with the ss handle that was created, and a copy of the\n+ * opaque_user_data pointer given as an argument.\n+ *\n+ * If you want to set up the stream with specific information, point to it in\n+ * opaque_user_data and use the copy of that pointer in your user data member\n+ * for it starting from the LWSSSCS_CREATING state call.\n+ *\n+ * Since different endpoints chosen by the policy may require different payload\n+ * formats, \u005cp ppayload_fmt is set to point to the name of the needed payload\n+ * format from the policy database if non-NULL.\n+ */\n+LWS_VISIBLE LWS_EXTERN int\n+lws_ss_create(struct lws_context *context, int tsi, const lws_ss_info_t *ssi,\n+\t void *opaque_user_data, struct lws_ss_handle **ppss,\n+\t struct lws_sequencer *seq_owner, const char **ppayload_fmt);\n+\n+/**\n+ * lws_ss_destroy() - Destroy secure stream\n+ *\n+ * \u005cparam ppss: pointer to lws_ss_t pointer to be destroyed\n+ *\n+ * Destroys the lws_ss_t pointed to by *ppss, and sets *ppss to NULL.\n+ */\n+LWS_VISIBLE LWS_EXTERN void\n+lws_ss_destroy(struct lws_ss_handle **ppss);\n+\n+/**\n+ * lws_ss_request_tx() - Schedule stream for tx\n+ *\n+ * \u005cparam pss: pointer to lws_ss_t representing stream that wants to transmit\n+ *\n+ * Schedules a write on the stream represented by \u005cp pss. When it's possible to\n+ * write on this stream, the *tx callback will occur with an empty buffer for\n+ * the stream owner to fill in.\n+ */\n+LWS_VISIBLE LWS_EXTERN void\n+lws_ss_request_tx(struct lws_ss_handle *pss);\n+\n+/**\n+ * lws_ss_request_tx() - Schedule stream for tx\n+ *\n+ * \u005cparam pss: pointer to lws_ss_t representing stream that wants to transmit\n+ * \u005cparam len: the length of the write in bytes\n+ *\n+ * Schedules a write on the stream represented by \u005cp pss. When it's possible to\n+ * write on this stream, the *tx callback will occur with an empty buffer for\n+ * the stream owner to fill in.\n+ *\n+ * This api variant should be used when it's possible the payload will go out\n+ * over h1 with x-web-form-urlencoded or similar Content-Type.\n+ */\n+LWS_VISIBLE LWS_EXTERN void\n+lws_ss_request_tx_len(struct lws_ss_handle *pss, unsigned long len);\n+\n+\n+/**\n+ * lws_ss_client_connect() - Attempt the client connect\n+ *\n+ * \u005cparam h: secure streams handle\n+ *\n+ * Starts the connection process for the secure stream. Returns 0 if OK or\n+ * nonzero if we have already failed.\n+ */\n+LWS_VISIBLE LWS_EXTERN int\n+lws_ss_client_connect(struct lws_ss_handle *h);\n+\n+/**\n+ * lws_ss_get_sequencer() - Return parent sequencer pointer if any\n+ *\n+ * \u005cparam h: secure streams handle\n+ *\n+ * Returns NULL if the secure stream is not associated with a sequencer.\n+ * Otherwise returns a pointer to the owning sequencer. You can use this to\n+ * identify which sequencer to direct messages to, from the secure stream\n+ * callback.\n+ */\n+LWS_VISIBLE LWS_EXTERN struct lws_sequencer *\n+lws_ss_get_sequencer(struct lws_ss_handle *h);\n+\n+/**\n+ * lws_ss_proxy_create() - Start a unix domain socket proxy for Secure Streams\n+ *\n+ * \u005cparam context: lws_context\n+ * \u005cparam bind: if port is 0, unix domain path with leading @ for abstract.\n+ *\t\tif port nonzero, NULL, or network interface to bind listen to\n+ * \u005cparam port: tcp port to listen on\n+ *\n+ * Creates a vhost that listens either on an abstract namespace unix domain\n+ * socket (port \u003d 0) or a tcp listen socket (port nonzero). If bind is NULL\n+ * and port is 0, the abstract unix domain socket defaults to \u0022proxy.ss.lws\u0022.\n+ *\n+ * Client connections to this proxy to Secure Streams are fulfilled using the\n+ * policy local to the proxy and the data passed between the client and the\n+ * proxy using serialized Secure Streams protocol.\n+ */\n+LWS_VISIBLE LWS_EXTERN int\n+lws_ss_proxy_create(struct lws_context *context, const char *bind, int port);\n+\n+/**\n+ * lws_ss_state_name() - convenience helper to get a printable conn state name\n+ *\n+ * \u005cparam state: the connection state index\n+ *\n+ * Returns a printable name for the connection state index passed in.\n+ */\n+LWS_VISIBLE LWS_EXTERN const char *\n+lws_ss_state_name(int state);\n+\n+/**\n+ * lws_ss_get_context() - convenience helper to recover the lws context\n+ *\n+ * \u005cparam h: secure streams handle\n+ *\n+ * Returns the lws context. Dispenses with the need to pass a copy of it into\n+ * your secure streams handler.\n+ */\n+LWS_VISIBLE LWS_EXTERN struct lws_context *\n+lws_ss_get_context(struct lws_ss_handle *h);\n+\n+/**\n+ * lws_ss_rideshare() - find the current streamtype when types rideshare\n+ *\n+ * \u005cparam h: the stream handle\n+ *\n+ * Under some conditions, the payloads may be structured using protocol-\n+ * specific formatting, eg, http multipart mime. It's possible to map the\n+ * logical partitions in the payload to different stream types using\n+ * the policy \u0022rideshare\u0022 feature.\n+ *\n+ * This api lets the callback code find out which rideshare stream type the\n+ * current payload chunk belongs to.\n+ */\n+LWS_VISIBLE LWS_EXTERN const char *\n+lws_ss_rideshare(struct lws_ss_handle *h);\n+\n+\n+/**\n+ * lws_ss_set_metadata() - allow user to bind external data to defined ss metadata\n+ *\n+ * \u005cparam h: secure streams handle\n+ * \u005cparam name: metadata name from the policy\n+ * \u005cparam value: pointer to user-managed data to bind to name\n+ * \u005cparam len: length of the user-managed data in value\n+ *\n+ * Binds user-managed data to the named metadata item from the ss policy.\n+ * If present, the metadata item is handled in a protocol-specific way using\n+ * the associated policy information. For example, in the policy\n+ *\n+ * \t\u0022\u005c\u0022metadata\u005c\u0022:\u0022\t\t\u0022[\u0022\n+ *\t\t\u0022{\u005c\u0022uptag\u005c\u0022:\u0022 \u0022\u005c\u0022X-Upload-Tag:\u005c\u0022},\u0022\n+ *\t\t\u0022{\u005c\u0022ctype\u005c\u0022:\u0022 \u0022\u005c\u0022Content-Type:\u005c\u0022},\u0022\n+ *\t\t\u0022{\u005c\u0022xctype\u005c\u0022:\u0022 \u0022\u005c\u0022\u005c\u0022}\u0022\n+ *\t\u0022],\u0022\n+ *\n+ * when the policy is using h1 is interpreted to add h1 headers of the given\n+ * name with the value of the metadata on the left.\n+ *\n+ * Return 0 if OK or nonzero if, eg, metadata name does not exist on the\n+ * streamtype.\n+ */\n+LWS_VISIBLE LWS_EXTERN int\n+lws_ss_set_metadata(struct lws_ss_handle *h, const char *name,\n+\t\t void *value, size_t len);\n+\n+\n+/**\n+ * lws_ss_add_peer_tx_credit() - allow peer to transmit more to us\n+ *\n+ * \u005cparam h: secure streams handle\n+ * \u005cparam add: additional tx credit (signed)\n+ *\n+ * Indicate to remote peer that we can accept \u005cp add bytes more payload being\n+ * sent to us.\n+ */\n+LWS_VISIBLE LWS_EXTERN int\n+lws_ss_add_peer_tx_credit(struct lws_ss_handle *h, int32_t add);\n+\n+/**\n+ * lws_ss_get_est_peer_tx_credit() - get our current estimate of peer's tx credit\n+ *\n+ * \u005cparam h: secure streams handle\n+ *\n+ * Based on what credit we gave it, and what we have received, report our\n+ * estimate of peer's tx credit usable to transmit to us. This may be outdated\n+ * in that some or all of its credit may already have been expended by sending\n+ * stuff to us that is in flight already.\n+ */\n+LWS_VISIBLE LWS_EXTERN int\n+lws_ss_get_est_peer_tx_credit(struct lws_ss_handle *h);\ndiff --git a/include/libwebsockets/lws-sequencer.h b/include/libwebsockets/lws-sequencer.h\nindex 4b50b82..a08f613 100644\n--- a/include/libwebsockets/lws-sequencer.h\n+++ b/include/libwebsockets/lws-sequencer.h\n@@ -49,6 +49,14 @@ typedef enum {\n \tLWSSEQ_WSI_CONN_FAIL,\t/* wsi we bound to us has failed to connect */\n \tLWSSEQ_WSI_CONN_CLOSE,\t/* wsi we bound to us has closed */\n \n+\n+\tLWSSEQ_SS_STATE_BASE,\t/* secure streams owned by a sequencer provide\n+\t\t\t\t * automatic messages about state changes on\n+\t\t\t\t * the sequencer, passing the oridinal in the\n+\t\t\t\t * event argument field. The message index is\n+\t\t\t\t * LWSSEQ_SS_STATE_BASE + the enum from\n+\t\t\t\t * lws_ss_constate_t */\n+\n \tLWSSEQ_USER_BASE \u003d 100\t/* define your events from here */\n } lws_seq_events_t;\n \ndiff --git a/lib/core-net/close.c b/lib/core-net/close.c\nindex 6312254..062f410 100644\n--- a/lib/core-net/close.c\n+++ b/lib/core-net/close.c\n@@ -234,8 +234,9 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason,\n \t\t const char *caller)\n {\n \tstruct lws_context_per_thread *pt;\n-\tstruct lws *wsi1, *wsi2;\n+\tconst struct lws_protocols *pro;\n \tstruct lws_context *context;\n+\tstruct lws *wsi1, *wsi2;\n \tint n, ccb;\n \n \tlwsl_info(\u0022%s: %p: caller: %s\u005cn\u0022, __func__, wsi, caller);\n@@ -585,8 +586,15 @@ just_kill_connection:\n \t\t */\n \t\tccb \u003d 1;\n \n+\tpro \u003d wsi-\u003eprotocol;\n+\n+#if defined(LWS_WITH_CLIENT)\n+\tif (!ccb \u0026\u0026 (lwsi_state_PRE_CLOSE(wsi) \u0026 LWSIFS_NOT_EST) \u0026\u0026\n+\t\t\tlwsi_role_client(wsi)) {\n+\t\tlws_inform_client_conn_fail(wsi, \u0022Closed before conn\u0022, 18);\n+\t}\n+#endif\n \tif (ccb) {\n-\t\tconst struct lws_protocols *pro \u003d wsi-\u003eprotocol;\n \n \t\tif (!wsi-\u003eprotocol \u0026\u0026 wsi-\u003evhost \u0026\u0026 wsi-\u003evhost-\u003eprotocols)\n \t\t\tpro \u003d \u0026wsi-\u003evhost-\u003eprotocols[0];\ndiff --git a/lib/core-net/connect.c b/lib/core-net/connect.c\nindex 450c853..1b6d82f 100644\n--- a/lib/core-net/connect.c\n+++ b/lib/core-net/connect.c\n@@ -313,8 +313,14 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i)\n \t\t\t\t\t i-\u003euri_replace_to);\n #endif\n \n-\tif (i-\u003emethod \u0026\u0026 (!strcmp(i-\u003emethod, \u0022RAW\u0022) ||\n-\t\t\t !strcmp(i-\u003emethod, \u0022MQTT\u0022))) {\n+\tif (i-\u003emethod \u0026\u0026 (!strcmp(i-\u003emethod, \u0022RAW\u0022) // ||\n+//\t\t\t !strcmp(i-\u003emethod, \u0022MQTT\u0022)\n+\t)) {\n+\n+\t\t/*\n+\t\t * Not for MQTT here, since we don't know if we will\n+\t\t * pipeline it or not...\n+\t\t */\n \n #if defined(LWS_WITH_TLS)\n \ndiff --git a/lib/core-net/network.c b/lib/core-net/network.c\nindex 8c89544..99e5619 100644\n--- a/lib/core-net/network.c\n+++ b/lib/core-net/network.c\n@@ -213,7 +213,7 @@ lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port,\n \tmemset(\u0026sin, 0, sizeof(sin));\n \n #if defined(LWS_WITH_UNIX_SOCK)\n-\tif (LWS_UNIX_SOCK_ENABLED(vhost)) {\n+\tif (!port \u0026\u0026 LWS_UNIX_SOCK_ENABLED(vhost)) {\n \t\tv \u003d (struct sockaddr *)\u0026serv_unix;\n \t\tn \u003d sizeof(struct sockaddr_un);\n \t\tmemset(\u0026serv_unix, 0, sizeof(serv_unix));\n@@ -313,7 +313,7 @@ lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port,\n \t}\n \n #if defined(LWS_WITH_UNIX_SOCK)\n-\tif (LWS_UNIX_SOCK_ENABLED(vhost)) {\n+\tif (!port \u0026\u0026 LWS_UNIX_SOCK_ENABLED(vhost)) {\n \t\tuid_t uid \u003d vhost-\u003econtext-\u003euid;\n \t\tgid_t gid \u003d vhost-\u003econtext-\u003egid;\n \ndiff --git a/lib/core-net/output.c b/lib/core-net/output.c\nindex fcb6a5a..83b8f9a 100644\n--- a/lib/core-net/output.c\n+++ b/lib/core-net/output.c\n@@ -264,10 +264,10 @@ lws_write(struct lws *wsi, unsigned char *buf, size_t len,\n \t\twsi-\u003edetlat.acc_size \u003d m;\n \t\twsi-\u003edetlat.type \u003d LDLT_WRITE;\n \t\tif (wsi-\u003edetlat.earliest_write_req_pre_write)\n-\t\t\twsi-\u003edetlat.latencies[LAT_DUR_PROXY_RX_TO_ONWARD_TX] \u003d\n+\t\t\twsi-\u003edetlat.latencies[LAT_DUR_PROXY_PROXY_REQ_TO_WRITE] \u003d\n \t\t\t\t\tus - wsi-\u003edetlat.earliest_write_req_pre_write;\n \t\telse\n-\t\t\twsi-\u003edetlat.latencies[LAT_DUR_PROXY_RX_TO_ONWARD_TX] \u003d 0;\n+\t\t\twsi-\u003edetlat.latencies[LAT_DUR_PROXY_PROXY_REQ_TO_WRITE] \u003d 0;\n \t\twsi-\u003edetlat.latencies[LAT_DUR_USERCB] \u003d lws_now_usecs() - us;\n \t\tlws_det_lat_cb(wsi-\u003econtext, \u0026wsi-\u003edetlat);\n \ndiff --git a/lib/core-net/private-lib-core-net.h b/lib/core-net/private-lib-core-net.h\nindex 1507a27..f6f0d97 100644\n--- a/lib/core-net/private-lib-core-net.h\n+++ b/lib/core-net/private-lib-core-net.h\n@@ -359,6 +359,15 @@ struct lws_context_per_thread {\n \tstruct lws_dll2_owner seq_owner;\t /* list of lws_sequencer-s */\n \tlws_dll2_owner_t attach_owner;\t/* pending lws_attach */\n \n+#if defined(LWS_WITH_SECURE_STREAMS)\n+\tlws_dll2_owner_t ss_owner;\n+#endif\n+#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API) || \u005c\n+ defined(LWS_WITH_SECURE_STREAMS_THREAD_API)\n+\tlws_dll2_owner_t ss_dsh_owner;\n+\tlws_dll2_owner_t ss_client_owner;\n+#endif\n+\n \tstruct lws_dll2_owner pt_sul_owner;\n \n #if defined (LWS_WITH_SEQUENCER)\n@@ -588,9 +597,10 @@ struct lws_vhost {\n \tint log_fd;\n #endif\n \n-\tunsigned int allocated_vhost_protocols:1;\n-\tunsigned int created_vhost_protocols:1;\n-\tunsigned int being_destroyed:1;\n+\tuint8_t allocated_vhost_protocols:1;\n+\tuint8_t created_vhost_protocols:1;\n+\tuint8_t being_destroyed:1;\n+\tuint8_t from_ss_policy:1;\n \n \tunsigned char default_protocol_index;\n \tunsigned char raw_protocol_index;\n@@ -1150,6 +1160,9 @@ lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len);\n lws_usec_t\n __lws_seq_timeout_check(struct lws_context_per_thread *pt, lws_usec_t usnow);\n \n+lws_usec_t\n+__lws_ss_timeout_check(struct lws_context_per_thread *pt, lws_usec_t usnow);\n+\n struct lws * LWS_WARN_UNUSED_RESULT\n lws_client_connect_2_dnsreq(struct lws *wsi);\n \ndiff --git a/lib/core-net/socks5-client.c b/lib/core-net/socks5-client.c\nindex 7d04d4d..0e2c514 100644\n--- a/lib/core-net/socks5-client.c\n+++ b/lib/core-net/socks5-client.c\n@@ -86,7 +86,7 @@ lws_set_socks(struct lws_vhost *vhost, const char *socks)\n \t\t}\n \t}\n \n-\tlwsl_notice(\u0022%s: Connections via Socks5 %s:%u\u005cn\u0022, __func__,\n+\tlwsl_debug(\u0022%s: Connections via Socks5 %s:%u\u005cn\u0022, __func__,\n \t\t vhost-\u003esocks_proxy_address, vhost-\u003esocks_proxy_port);\n \n \treturn 0;\ndiff --git a/lib/core-net/vhost.c b/lib/core-net/vhost.c\nindex eab28d0..dd9e526 100644\n--- a/lib/core-net/vhost.c\n+++ b/lib/core-net/vhost.c\n@@ -71,6 +71,24 @@ const struct lws_protocols *available_abstract_protocols[] \u003d {\n };\n #endif\n \n+#if defined(LWS_WITH_SECURE_STREAMS)\n+const struct lws_protocols *available_secstream_protocols[] \u003d {\n+#if defined(LWS_ROLE_H1)\n+\t\u0026protocol_secstream_h1,\n+#endif\n+#if defined(LWS_ROLE_H2)\n+\t\u0026protocol_secstream_h2,\n+#endif\n+#if defined(LWS_ROLE_WS)\n+\t\u0026protocol_secstream_ws,\n+#endif\n+#if defined(LWS_ROLE_MQTT)\n+\t\u0026protocol_secstream_mqtt,\n+#endif\n+\tNULL\n+};\n+#endif\n+\n static const char * const mount_protocols[] \u003d {\n \t\u0022http://\u0022,\n \t\u0022https://\u0022,\n@@ -456,7 +474,7 @@ lws_create_vhost(struct lws_context *context,\n \tstruct lws_plugin *plugin \u003d context-\u003eplugin_list;\n #endif\n \tstruct lws_protocols *lwsp;\n-\tint m, f \u003d !info-\u003epvo, fx \u003d 0, abs_pcol_count \u003d 0;\n+\tint m, f \u003d !info-\u003epvo, fx \u003d 0, abs_pcol_count \u003d 0, sec_pcol_count \u003d 0;\n \tchar buf[96];\n #if ((defined(LWS_CLIENT_HTTP_PROXYING) \u0026\u0026 defined(LWS_WITH_CLIENT)) \u005c\n \t\t|| defined(LWS_WITH_SOCKS5)) \u0026\u0026 defined(LWS_HAVE_GETENV)\n@@ -581,6 +599,9 @@ lws_create_vhost(struct lws_context *context,\n #if defined(LWS_WITH_ABSTRACT)\n \tabs_pcol_count \u003d (int)LWS_ARRAY_SIZE(available_abstract_protocols) - 1;\n #endif\n+#if defined(LWS_WITH_SECURE_STREAMS)\n+\tsec_pcol_count \u003d (int)LWS_ARRAY_SIZE(available_secstream_protocols) - 1;\n+#endif\n \n \t/*\n \t * give the vhost a unified list of protocols including:\n@@ -592,7 +613,7 @@ lws_create_vhost(struct lws_context *context,\n \t */\n \tlwsp \u003d lws_zalloc(sizeof(struct lws_protocols) *\n \t\t\t\t(vh-\u003ecount_protocols +\n-\t\t\t\t abs_pcol_count +\n+\t\t\t\t abs_pcol_count + sec_pcol_count +\n \t\t\t\t context-\u003eplugin_protocol_count +\n \t\t\t\t fx + 1),\n \t\t\t \u0022vhost-specific plugin table\u0022);\n@@ -633,6 +654,14 @@ lws_create_vhost(struct lws_context *context,\n \t}\n #endif\n \n+#if defined(LWS_WITH_SECURE_STREAMS)\n+\tfor (n \u003d 0; n \u003c sec_pcol_count; n++) {\n+\t\tmemcpy(\u0026lwsp[m++], available_secstream_protocols[n],\n+\t\t sizeof(*lwsp));\n+\t\tvh-\u003ecount_protocols++;\n+\t}\n+#endif\n+\n \t/*\n \t * 3: For compatibility, all protocols enabled on vhost if only\n \t * the default vhost exists. Otherwise only vhosts who ask\n@@ -1287,6 +1316,8 @@ lws_vhost_destroy(struct lws_vhost *vh)\n \n \tlws_vhost_destroy1(vh);\n \n+\tlwsl_debug(\u0022%s: count_bound_wsi %d\u005cn\u0022, __func__, vh-\u003ecount_bound_wsi);\n+\n \tif (!vh-\u003ecount_bound_wsi) {\n \t\t/*\n \t\t * After listen handoff, there are already no wsi bound to this\n@@ -1406,6 +1437,7 @@ lws_vhost_active_conns(struct lws *wsi, struct lws **nwsi, const char *adsin)\n \t\treturn ACTIVE_CONNS_QUEUED;\n \t}\n \n+#if defined(LWS_ROLE_H2) || defined(LWS_ROLE_MQTT)\n \tif (wsi-\u003emux.parent_wsi) {\n \t\t/*\n \t\t * We already decided...\n@@ -1415,6 +1447,7 @@ lws_vhost_active_conns(struct lws *wsi, struct lws **nwsi, const char *adsin)\n \n \t\treturn ACTIVE_CONNS_MUXED;\n \t}\n+#endif\n \n \tlws_vhost_lock(wsi-\u003evhost); /* ----------------------------------- { */\n \n@@ -1448,7 +1481,7 @@ lws_vhost_active_conns(struct lws *wsi, struct lws **nwsi, const char *adsin)\n \t\t\t * connection that it doesn't support pipelining...\n \t\t\t */\n \t\t\tif (w-\u003ekeepalive_rejected) {\n-\t\t\t\tlwsl_info(\u0022defeating pipelining due to no \u0022\n+\t\t\t\tlwsl_notice(\u0022defeating pipelining due to no \u0022\n \t\t\t\t\t \u0022keepalive on server\u005cn\u0022);\n \t\t\t\tgoto solo;\n \t\t\t}\ndiff --git a/lib/core-net/wsi-timeout.c b/lib/core-net/wsi-timeout.c\nindex cfbb7d2..843869f 100644\n--- a/lib/core-net/wsi-timeout.c\n+++ b/lib/core-net/wsi-timeout.c\n@@ -294,7 +294,7 @@ lws_validity_cb(lws_sorted_usec_list_t *sul)\n \n \t/* schedule a protocol-dependent ping */\n \n-\tlwsl_info(\u0022%s: wsi %p: scheduling validity check\u005cn\u0022, __func__, wsi);\n+\tlwsl_notice(\u0022%s: wsi %p: scheduling validity check\u005cn\u0022, __func__, wsi);\n \n \tif (wsi-\u003erole_ops \u0026\u0026 wsi-\u003erole_ops-\u003eissue_keepalive)\n \t\twsi-\u003erole_ops-\u003eissue_keepalive(wsi, 0);\ndiff --git a/lib/core/context.c b/lib/core/context.c\nindex 9ad6939..8b07594 100644\n--- a/lib/core/context.c\n+++ b/lib/core/context.c\n@@ -114,7 +114,7 @@ lws_state_notify_protocol_init(struct lws_state_manager *mgr,\n \t\tlws_system_do_attach(\u0026context-\u003ept[n]);\n \n #if defined(LWS_WITH_SYS_DHCP_CLIENT)\n-\tif (current \u003d\u003d LWS_SYSTATE_DHCP) {\n+\tif (target \u003d\u003d LWS_SYSTATE_DHCP) {\n \t\t/*\n \t\t * Don't let it past here until at least one iface has been\n \t\t * configured for operation with DHCP\n@@ -125,6 +125,40 @@ lws_state_notify_protocol_init(struct lws_state_manager *mgr,\n \t}\n #endif\n \n+#if defined(LWS_WITH_SECURE_STREAMS_SYS_AUTH_API_AMAZON_COM)\n+\t/*\n+\t * Skip this if we are running something without the policy for it\n+\t */\n+\tif (target \u003d\u003d LWS_SYSTATE_AUTH1 \u0026\u0026\n+\t context-\u003epss_policies \u0026\u0026\n+\t !lws_system_blob_get_size(lws_system_get_blob(context,\n+\t\t\t\t\t\t LWS_SYSBLOB_TYPE_AUTH,\n+\t\t\t\t\t\t 0))) {\n+\t\tlwsl_info(\u0022%s: AUTH1 state triggering api.amazon.com auth\u005cn\u0022, __func__);\n+\t\t/*\n+\t\t * Start trying to acquire it if it's not already in progress\n+\t\t * returns nonzero if we determine it's not needed\n+\t\t */\n+\t\tif (!lws_ss_sys_auth_api_amazon_com(context))\n+\t\t\treturn 1;\n+\t}\n+#endif\n+\n+#if defined(LWS_WITH_SECURE_STREAMS)\n+\t/*\n+\t * Skip this if we are running something without the policy for it\n+\t */\n+\tif (target \u003d\u003d LWS_SYSTATE_POLICY_VALID \u0026\u0026\n+\t context-\u003epss_policies \u0026\u0026 !context-\u003epolicy_updated) {\n+\t\t/*\n+\t\t * Start trying to acquire it if it's not already in progress\n+\t\t * returns nonzero if we determine it's not needed\n+\t\t */\n+\t\tif (!lws_ss_sys_fetch_policy(context))\n+\t\t\treturn 1;\n+\t}\n+#endif\n+\n \t/* protocol part */\n \n \tif (context-\u003eprotocol_init_done)\n@@ -245,6 +279,15 @@ lws_create_context(const struct lws_context_creation_info *info)\n \t\t\t __func__, context-\u003eudp_loss_sim_tx_pc,\n \t\t\t context-\u003eudp_loss_sim_rx_pc);\n \n+#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API)\n+\tcontext-\u003ess_proxy_bind \u003d info-\u003ess_proxy_bind;\n+\tcontext-\u003ess_proxy_port \u003d info-\u003ess_proxy_port;\n+\tcontext-\u003ess_proxy_address \u003d info-\u003ess_proxy_address;\n+\tlwsl_notice(\u0022%s: using ss proxy bind '%s', port %d, ads '%s'\u005cn\u0022,\n+\t\t\t__func__, context-\u003ess_proxy_bind, context-\u003ess_proxy_port,\n+\t\t\tcontext-\u003ess_proxy_address);\n+#endif\n+\n #if defined(LWS_WITH_NETWORK)\n \tcontext-\u003ecount_threads \u003d count_threads;\n #if defined(LWS_WITH_DETAILED_LATENCY)\n@@ -258,6 +301,11 @@ lws_create_context(const struct lws_context_creation_info *info)\n #endif\n #endif\n \n+#if defined(LWS_WITH_SECURE_STREAMS)\n+\tcontext-\u003epss_policies_json \u003d info-\u003epss_policies_json;\n+\tcontext-\u003epss_plugins \u003d info-\u003epss_plugins;\n+#endif\n+\n \t/* if he gave us names, set the uid / gid */\n \tif (lws_plat_drop_app_privileges(context, 0))\n \t\tgoto bail;\n@@ -634,7 +682,6 @@ lws_create_context(const struct lws_context_creation_info *info)\n \n \tcontext-\u003euser_space \u003d info-\u003euser;\n \n-\n #if defined(LWS_WITH_SERVER)\n \tstrcpy(context-\u003ecanonical_hostname, \u0022unknown\u0022);\n #if defined(LWS_WITH_NETWORK)\n@@ -755,6 +802,33 @@ lws_create_context(const struct lws_context_creation_info *info)\n \t\t\tgoto fail_clean_pipes;\n \t\t}\n \n+#if defined(LWS_WITH_SECURE_STREAMS)\n+\n+\tif (context-\u003epss_policies_json) {\n+\t\t/*\n+\t\t * You must create your context with the explicit vhosts flag\n+\t\t * in order to use secure streams\n+\t\t */\n+\t\tassert(lws_check_opt(info-\u003eoptions,\n+\t\t LWS_SERVER_OPTION_EXPLICIT_VHOSTS));\n+\n+\t\tif (lws_ss_policy_parse_begin(context))\n+\t\t\tgoto bail;\n+\n+\t\tn \u003d lws_ss_policy_parse(context,\n+\t\t\t\t\t(uint8_t *)context-\u003epss_policies_json,\n+\t\t\t\t\tstrlen(context-\u003epss_policies_json));\n+\t\tif (n !\u003d LEJP_CONTINUE \u0026\u0026 n \u003c 0)\n+\t\t\tgoto bail;\n+\n+\t\tif (lws_ss_policy_set(context, \u0022hardcoded\u0022)) {\n+\t\t\tlwsl_err(\u0022%s: policy set failed\u005cn\u0022, __func__);\n+\t\t\tgoto bail;\n+\t\t}\n+\t} else\n+\t\tlws_create_vhost(context, info);\n+#endif\n+\n \tlws_context_init_extensions(info, context);\n \n \tlwsl_info(\u0022 mem: per-conn: %5lu bytes + protocol rx buf\u005cn\u0022,\n@@ -859,9 +933,10 @@ static void\n lws_context_destroy3(struct lws_context *context)\n {\n \tstruct lws_context **pcontext_finalize \u003d context-\u003epcontext_finalize;\n-#if defined(LWS_WITH_NETWORK)\n \tint n;\n \n+#if defined(LWS_WITH_NETWORK)\n+\n \tlwsl_debug(\u0022%s\u005cn\u0022, __func__);\n \n \tfor (n \u003d 0; n \u003c context-\u003ecount_threads; n++) {\n@@ -905,6 +980,10 @@ lws_context_destroy3(struct lws_context *context)\n \t\tcompatible_close(context-\u003elatencies_fd);\n #endif\n \n+\tfor (n \u003d 0; n \u003c LWS_SYSBLOB_TYPE_COUNT; n++)\n+\t\tlws_system_blob_destroy(\n+\t\t\t\tlws_system_get_blob(context, n, 0));\n+\n \tlws_free(context);\n \tlwsl_info(\u0022%s: ctx %p freed\u005cn\u0022, __func__, context);\n \n@@ -921,6 +1000,7 @@ lws_context_destroy2(struct lws_context *context)\n {\n #if defined(LWS_WITH_NETWORK)\n \tstruct lws_vhost *vh \u003d NULL, *vh1;\n+\tint n;\n #endif\n #if defined(LWS_WITH_PEER_LIMITS)\n \tuint32_t nu;\n@@ -932,6 +1012,48 @@ lws_context_destroy2(struct lws_context *context)\n \n \tcontext-\u003ebeing_destroyed2 \u003d 1;\n #if defined(LWS_WITH_NETWORK)\n+\n+\t/*\n+\t * We're going to trash things like vhost-protocols\n+\t * So we need to finish dealing with wsi close that\n+\t * might make callbacks first\n+\t */\n+\tfor (n \u003d 0; n \u003c context-\u003ecount_threads; n++) {\n+\t\tstruct lws_context_per_thread *pt \u003d \u0026context-\u003ept[n];\n+\n+\t\t(void)pt;\n+\n+#if defined(LWS_WITH_SECURE_STREAMS)\n+\t\tlws_dll2_foreach_safe(\u0026pt-\u003ess_owner, NULL, lws_ss_destroy_dll);\n+\t\tif (context-\u003eac_policy)\n+\t\t\tlwsac_free(\u0026context-\u003eac_policy);\n+#endif\n+\n+#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API)\n+\t\tlws_dll2_foreach_safe(\u0026pt-\u003ess_client_owner, NULL, lws_sspc_destroy_dll);\n+#endif\n+\n+#if defined(LWS_WITH_SEQUENCER)\n+\t\tlws_seq_destroy_all_on_pt(pt);\n+#endif\n+\t\tLWS_FOR_EVERY_AVAILABLE_ROLE_START(ar) {\n+\t\t\tif (ar-\u003ept_init_destroy)\n+\t\t\t\tar-\u003ept_init_destroy(context, NULL, pt, 1);\n+\t\t} LWS_FOR_EVERY_AVAILABLE_ROLE_END;\n+\n+#if defined(LWS_WITH_CGI)\n+\t\trole_ops_cgi.pt_init_destroy(context, NULL, pt, 1);\n+#endif\n+\n+\t\tif (context-\u003eevent_loop_ops-\u003edestroy_pt)\n+\t\t\tcontext-\u003eevent_loop_ops-\u003edestroy_pt(context, n);\n+\n+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)\n+\t\twhile (pt-\u003ehttp.ah_list)\n+\t\t\t_lws_destroy_ah(pt, pt-\u003ehttp.ah_list);\n+#endif\n+\t}\n+\n \t/*\n \t * free all the per-vhost allocations\n \t */\n@@ -982,6 +1104,7 @@ lws_context_destroy2(struct lws_context *context)\n \tlws_check_deferred_free(context, 0, 1);\n #endif\n \n+\n #if LWS_MAX_SMP \u003e 1\n \tlws_mutex_refcount_destroy(\u0026context-\u003emr);\n #endif\n@@ -1185,3 +1308,13 @@ out:\n \tcontext-\u003einside_context_destroy \u003d 0;\n #endif\n }\n+\n+struct lws_context *\n+lws_system_context_from_system_mgr(lws_state_manager_t *mgr)\n+{\n+#if defined(LWS_WITH_NETWORK)\n+\treturn lws_container_of(mgr, struct lws_context, mgr_system);\n+#else\n+\treturn NULL;\n+#endif\n+}\ndiff --git a/lib/core/private-lib-core.h b/lib/core/private-lib-core.h\nindex 9a639b0..f134383 100644\n--- a/lib/core/private-lib-core.h\n+++ b/lib/core/private-lib-core.h\n@@ -197,6 +197,9 @@ struct lws;\n #if defined(LWS_WITH_NETWORK)\n #include \u0022private-lib-event-libs.h\u0022\n \n+#if defined(LWS_WITH_SECURE_STREAMS)\n+#include \u0022private-lib-secure-streams.h\u0022\n+#endif\n \n struct lws_io_watcher {\n #ifdef LWS_WITH_LIBEV\n@@ -357,6 +360,14 @@ struct lws_context {\n \tlws_async_dns_t\t\tasync_dns;\n #endif\n \n+#if defined(LWS_WITH_SECURE_STREAMS_SYS_AUTH_API_AMAZON_COM)\n+\tvoid\t\t\t\t*pol_args;\n+\tstruct lws_ss_handle\t\t*hss_auth;\n+\tstruct lws_ss_handle\t\t*hss_fetch_policy;\n+\tlws_sorted_usec_list_t\t\tsul_api_amazon_com;\n+\tlws_sorted_usec_list_t\t\tsul_api_amazon_com_kick;\n+#endif\n+\n \tlws_state_manager_t\t\tmgr_system;\n \tlws_state_notify_link_t\t\tprotocols_notify;\n #if defined (LWS_WITH_SYS_DHCP_CLIENT)\n@@ -397,6 +408,11 @@ struct lws_context {\n #endif\n #endif /* NETWORK */\n \n+#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API)\n+\tconst char\t*ss_proxy_bind;\n+\tconst char\t*ss_proxy_address;\n+#endif\n+\n #if defined(LWS_WITH_FILE_OPS)\n \tconst struct lws_plat_file_ops *fops;\n #endif\n@@ -425,6 +441,14 @@ struct lws_context {\n #endif\n \n \tconst lws_system_ops_t *system_ops;\n+\n+#if defined(LWS_WITH_SECURE_STREAMS)\n+\tconst char *pss_policies_json;\n+\tconst lws_ss_policy_t *pss_policies;\n+\tconst lws_ss_plugin_t **pss_plugins;\n+\tstruct lwsac *ac_policy;\n+#endif\n+\n \tvoid *external_baggage_free_on_destroy;\n \tconst struct lws_token_limits *token_limits;\n \tvoid *user_space;\n@@ -487,6 +511,7 @@ struct lws_context {\n \tunsigned int done_protocol_destroy_cb:1;\n \tunsigned int finalize_destroy_after_internal_loops_stopped:1;\n \tunsigned int max_fds_unrelated_to_ulimit:1;\n+\tunsigned int policy_updated:1;\n \n \tshort count_threads;\n \tshort plugin_protocol_count;\n@@ -494,6 +519,9 @@ struct lws_context {\n \tshort server_string_len;\n \tunsigned short ws_ping_pong_interval;\n \tunsigned short deprecation_pending_listen_close_count;\n+#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API)\n+\tuint16_t\tss_proxy_port;\n+#endif\n \n \tuint8_t max_fi;\n \tuint8_t udp_loss_sim_tx_pc;\ndiff --git a/lib/misc/lwsac/lwsac.c b/lib/misc/lwsac/lwsac.c\nindex 3ca48a7..596b082 100644\n--- a/lib/misc/lwsac/lwsac.c\n+++ b/lib/misc/lwsac/lwsac.c\n@@ -146,6 +146,7 @@ _lwsac_use(struct lwsac **head, size_t ensure, size_t chunk_size, char backfill)\n \tif (al \u003e\u003d alloc - hp)\n \t\talloc \u003d al + hp;\n \n+\tlwsl_debug(\u0022%s: alloc %d for %d\u005cn\u0022, __func__, (int)alloc, (int)ensure);\n \tbf \u003d malloc(alloc);\n \tif (!bf) {\n \t\tlwsl_err(\u0022%s: OOM trying to alloc %llud\u005cn\u0022, __func__,\ndiff --git a/lib/roles/h2/http2.c b/lib/roles/h2/http2.c\nindex 1c33385..d08084a 100644\n--- a/lib/roles/h2/http2.c\n+++ b/lib/roles/h2/http2.c\n@@ -2611,7 +2611,7 @@ lws_h2_client_stream_long_poll_rxonly(struct lws *wsi)\n \twsi-\u003eh2.long_poll \u003d 1;\n \twsi-\u003eh2.send_END_STREAM \u003d 1;\n \n-\tlws_header_table_detach(wsi, 0);\n+\t// lws_header_table_detach(wsi, 0);\n \n \tlws_callback_on_writable(wsi);\n \ndiff --git a/lib/roles/http/client/client-handshake.c b/lib/roles/http/client/client-handshake.c\nindex 6d70902..e81a4fc 100644\n--- a/lib/roles/http/client/client-handshake.c\n+++ b/lib/roles/http/client/client-handshake.c\n@@ -200,6 +200,31 @@ send_hs:\n \t\telse {\n \t\t\t/* for a method \u003d \u0022RAW\u0022 connection, this makes us\n \t\t\t * established */\n+\n+#if defined(LWS_WITH_TLS)\n+\t\t\tif (wsi-\u003etls.use_ssl \u0026 LCCSCF_USE_SSL) {\n+\n+\t\t\t\t/* we can retry this... just cook the SSL BIO the first time */\n+\n+\t\t\t\tif (lws_ssl_client_bio_create(wsi) \u003c 0) {\n+\t\t\t\t\tlwsl_err(\u0022%s: bio_create failed\u005cn\u0022, __func__);\n+\t\t\t\t\tgoto failed;\n+\t\t\t\t}\n+\n+\t//#if !defined(LWS_WITH_SYS_ASYNC_DNS)\n+\t\t\t\tif (wsi-\u003etls.use_ssl \u0026 LCCSCF_USE_SSL) {\n+\t\t\t\t\tn \u003d lws_ssl_client_connect1(wsi);\n+\t\t\t\t\tif (!n)\n+\t\t\t\t\t\treturn wsi;\n+\t\t\t\t\tif (n \u003c 0) {\n+\t\t\t\t\t\tlwsl_err(\u0022%s: lws_ssl_client_connect1 failed\u005cn\u0022, __func__);\n+\t\t\t\t\t\tgoto failed;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t//#endif\n+\t\t\t}\n+#endif\n+\n #if 0\n #if defined(LWS_WITH_SYS_ASYNC_DNS)\n \t\t\tif (wsi-\u003etls.use_ssl \u0026 LCCSCF_USE_SSL) {\ndiff --git a/lib/roles/http/parsers.c b/lib/roles/http/parsers.c\nindex 5afe0ac..7abedb6 100644\n--- a/lib/roles/http/parsers.c\n+++ b/lib/roles/http/parsers.c\n@@ -286,10 +286,10 @@ reset:\n \n \tlws_pt_unlock(pt);\n \n+#if defined(LWS_WITH_CLIENT)\n #if defined(LWS_ROLE_MQTT)\n connect_via_info2:\n #endif\n-#if defined(LWS_WITH_CLIENT)\n \tif (lwsi_role_client(wsi) \u0026\u0026 lwsi_state(wsi) \u003d\u003d LRS_UNCONNECTED)\n \t\tif (!lws_http_client_connect_via_info2(wsi))\n \t\t\t/* our client connect has failed, the wsi\n@@ -349,6 +349,7 @@ int __lws_header_table_detach(struct lws *wsi, int autoservice)\n \t\tlws_peer_track_ah_detach(context, wsi-\u003epeer);\n #endif\n \tah-\u003ewsi \u003d NULL; /* no owner */\n+\twsi-\u003ehttp.ah \u003d NULL;\n \n \tpwsi \u003d \u0026pt-\u003ehttp.ah_wait_list;\n \ndiff --git a/lib/roles/http/server/server.c b/lib/roles/http/server/server.c\nindex f9cae32..eb45bb4 100644\n--- a/lib/roles/http/server/server.c\n+++ b/lib/roles/http/server/server.c\n@@ -1900,8 +1900,8 @@ lws_http_to_fallback(struct lws *wsi, unsigned char *obuf, size_t olen)\n \tlws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);\n \n \tn \u003d LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED;\n-\tif (wsi-\u003erole_ops-\u003eadoption_cb[lwsi_role_server(wsi)])\n-\t\tn \u003d wsi-\u003erole_ops-\u003eadoption_cb[lwsi_role_server(wsi)];\n+\tif (wsi-\u003erole_ops-\u003eadoption_cb[0])\n+\t\tn \u003d wsi-\u003erole_ops-\u003eadoption_cb[0];\n \n \tipbuf[0] \u003d '\u005c0';\n #if !defined(LWS_PLAT_OPTEE)\ndiff --git a/lib/roles/mqtt/mqtt.c b/lib/roles/mqtt/mqtt.c\nindex 808ca37..bb07feb 100644\n--- a/lib/roles/mqtt/mqtt.c\n+++ b/lib/roles/mqtt/mqtt.c\n@@ -1705,6 +1705,8 @@ lws_mqtt_client_send_publish(struct lws *wsi, lws_mqtt_publish_param_t *pub,\n \n do_write:\n \n+\t// lwsl_hexdump_err(start, lws_ptr_diff(p, start));\n+\n \tif (lws_write(nwsi, start, lws_ptr_diff(p, start), LWS_WRITE_BINARY) !\u003d\n \t\t\tlws_ptr_diff(p, start)) {\n \t\tlwsl_err(\u0022%s: write failed\u005cn\u0022, __func__);\ndiff --git a/lib/secure-streams/README.md b/lib/secure-streams/README.md\nnew file mode 100644\nindex 0000000..baf7c37\n--- /dev/null\n+++ b/lib/secure-streams/README.md\n@@ -0,0 +1,398 @@\n+# Secure Streams\n+\n+Secure Streams is a client api that strictly separates payload from any metadata.\n+That includes the endpoint address for the connection, the tls CA and even the\n+protocol used to connect to the endpoint.\n+\n+The user api just receives and transmits payload, and receives advisory connection\n+state information.\n+\n+The details about how the connections for different types of secure stream should\n+be made are held in JSON \u0022policy database\u0022 initially passed in to the context\n+creation, but able to be updated from a remote copy.\n+\n+\n+\n+# JSON Policy Database\n+\n+Example JSON policy... formatting is shown for clarity but whitespace can be\n+omitted in the actual policy.\n+\n+Ordering is not critical in itself, but forward references are not allowed,\n+things must be defined before they are allowed to be referenced later in the\n+JSON.\n+\n+\n+```\n+{\n+\t\u0022release\u0022: \u002201234567\u0022,\n+\t\u0022product\u0022: \u0022myproduct\u0022,\n+\t\u0022schema-version\u0022: 1,\n+\t\u0022retry\u0022: [{\n+\t\t\u0022default\u0022: {\n+\t\t\t\u0022backoff\u0022: [1000, 2000, 3000, 5000, 10000],\n+\t\t\t\u0022conceal\u0022: 5,\n+\t\t\t\u0022jitterpc\u0022: 20\n+\t\t}\n+\t}],\n+\t\u0022certs\u0022: [{\n+\t\t\u0022isrg_root_x1\u0022: \u0022MIIFazCCA1OgAw...AnX5iItreGCc\u003d\u0022\n+\t}, {\n+\t\t\u0022LEX3_isrg_root_x1\u0022: \u0022MIIFjTCCA3WgAwIB...WEsikxqEt\u0022\n+\t}],\n+\t\u0022trust_stores\u0022: [{\n+\t\t\u0022le_via_isrg\u0022: [\u0022isrg_root_x1\u0022, \u0022LEX3_isrg_root_x1\u0022]\n+\t}],\n+\t\u0022s\u0022: [{\n+\t\t\u0022mintest\u0022: {\n+\t\t\t\u0022endpoint\u0022: \u0022warmcat.com\u0022,\n+\t\t\t\u0022port\u0022: 4443,\n+\t\t\t\u0022protocol\u0022: \u0022h1get\u0022,\n+\t\t\t\u0022aux\u0022: \u0022index.html\u0022,\n+\t\t\t\u0022plugins\u0022: [],\n+\t\t\t\u0022tls\u0022: true,\n+\t\t\t\u0022opportunistic\u0022: true,\n+\t\t\t\u0022retry\u0022: \u0022default\u0022,\n+\t\t\t\u0022tls_trust_store\u0022: \u0022le_via_isrg\u0022\n+\t\t}\n+\t}]\n+}\n+```\n+\n+### `Release`\n+\n+Identifies the policy version\n+\n+### `Product`\n+\n+Identifies the product the policy should apply to\n+\n+### `Schema-version`\n+\n+The minimum version of the policy parser required to parse this policy\n+\n+### `via-socks5`\n+\n+Optional redirect for Secure Streams client traffic through a socks5\n+proxy given in the format `address:port`, eg, `127.0.0.1:12345`.\n+\n+### `retry`\n+\n+A list of backoff schemes referred to in the policy\n+\n+### `backoff`\n+\n+An array of ms delays for each retry in turn\n+\n+### `conceal`\n+\n+The number of retries to conceal from higher layers before giving errors. If\n+this is larger than the number of times in the backoff array, then the last time\n+is used for the extra delays\n+\n+### `jitterpc`\n+\n+Percentage of the delay times mentioned in the backoff array that may be\n+randomly added to the figure from the array. For example with an array entry of\n+1000ms, and jitterpc of 20%, actual delays will be chosen randomly from 1000ms\n+through 1200ms. This is to stop retry storms triggered by a single event like\n+an outage becoming synchronized into a DoS.\n+\n+### `certs`\n+\n+Certificates needed for validation should be listed here each with a name. The\n+format is base64 DER, which is the same as the part of PEM that is inside the\n+start and end lines.\n+\n+### `trust_stores`\n+\n+Chains of certificates given in the `certs` section may be named and described\n+inside the `trust_stores` section. Each entry in `trust_stores` is created as\n+a vhost + tls context with the given name. Stream types can later be associated\n+with one of these to enforce validity checking of the remote server.\n+\n+Entries should be named using \u0022name\u0022 and the stack array defined using \u0022stack\u0022\n+\n+### `s`\n+\n+These are an array of policies for the supported stream type names.\n+\n+### `endpoint`\n+\n+The DNS address the secure stream should connect to\n+\n+### `port`\n+\n+The port number as an integer on the endpoint to connect to\n+\n+### `protocol`\n+\n+The wire protocol to connect to the endpoint with. Currently supported\n+streamtypes are\n+\n+|Wire protocol|Description|\n+|---|---|\n+|h1|http/1|\n+|h2|http/2|\n+|ws|http/1 Websockets|\n+|mqtt|mqtt 3.1.1|\n+\n+### `plugins`\n+\n+Array of plugin names to apply to the stream, if any\n+\n+### `tls`\n+\n+Set to `true` to enforce the stream travelling in a tls tunnel\n+\n+### `client cert`\n+\n+Set if the stream needs to authenticate itself using a tls client certificate.\n+Set to the certificate index counting from 0+. The certificates are managed\n+using lws_sytstem blobs.\n+\n+### `opportunistic`\n+\n+Set to `true` if the connection may be left dropped except when in use\n+\n+### `nailed_up`\n+\n+Set to `true` to have lws retry if the connection carrying this stream should\n+ever drop.\n+\n+### `retry`\n+\n+The name of the policy described in the `retry` section to apply to this\n+connection for retry + backoff\n+\n+### `tls_trust_store`\n+\n+The name of the trust store described in the `trust_stores` section to apply\n+to validate the remote server cert.\n+\n+## http transport\n+\n+### `http_method`\n+\n+HTTP method to use with http-related protocols, like GET or POST.\n+Not required for ws.\n+\n+### `http_url`\n+\n+Url path to use with http-related protocols\n+\n+The URL path can include metatadata like this\n+\n+\u0022/mypath?whatever\u003d${metadataname}\u0022\n+\n+${metadataname} will be replaced by the current value of the\n+same metadata name. The metadata names must be listed in the\n+\u0022metadata\u0022: [ ] section.\n+\n+### `http_auth_header`\n+\n+The name of the header that takes the auth token, with a trailing ':', eg\n+\n+```\n+ \u0022http_auth_header\u0022: \u0022authorization:\u0022\n+```\n+\n+### `http_dsn_header`\n+\n+The name of the header that takes the dsn token, with a trailing ':', eg\n+\n+```\n+ \u0022http_dsn_header\u0022: \u0022x-dsn:\u0022\n+```\n+\n+### `http_fwv_header`\n+\n+The name of the header that takes the firmware version token, with a trailing ':', eg\n+\n+```\n+ \u0022http_fwv_header\u0022: \u0022x-fw-version:\u0022\n+```\n+\n+### `http_devtype_header`\n+\n+The name of the header that takes the device type token, with a trailing ':', eg\n+\n+```\n+ \u0022http_devtype_header\u0022: \u0022x-device-type:\u0022\n+```\n+\n+### `http_auth_preamble`\n+\n+An optional string that precedes the auth token, eg\n+\n+```\n+ \u0022http_auth_preamble\u0022: \u0022bearer \u0022\n+```\n+\n+### `auth_hexify`\n+\n+Convert the auth token to hex ('A' -\u003e \u002241\u0022) before transporting. Not necessary if the\n+auth token is already in printable string format suitable for transport. Needed if the\n+auth token is a chunk of 8-bit binary.\n+\n+### `nghttp2_quirk_end_stream`\n+\n+Set this to `true` if the peer server has the quirk it won't send a response until we have\n+sent an `END_STREAM`, even though we have sent headers with `END_HEADERS`.\n+\n+### `h2q_oflow_txcr`\n+\n+Set this to `true` if the peer server has the quirk it sends an maximum initial tx credit\n+of 0x7fffffff and then later increments it illegally.\n+\n+### `http_multipart_name`\n+\n+Indicates this stream goes out using multipart mime, and provides the name part of the\n+multipart header\n+\n+### `http_multipart_filename`\n+\n+Indicates this stream goes out using multipart mime, and provides the filename part of the\n+multipart header\n+\n+### `http_multipart_content_type`\n+\n+The `content-type` to mark up the multipart mime section with if present\n+\n+### `http_www_form_urlencoded`\n+\n+Indicate the data is sent in `x-www-form-urlencoded` form\n+\n+### `rideshare`\n+\n+For special cases where one logically separate stream travels with another when using this\n+protocol. Eg, a single multipart mime transaction carries content from two or more streams.\n+\n+## ws transport\n+\n+### `ws_subprotocol`\n+\n+Name of the ws subprotocol to use.\n+\n+### `ws_binary`\n+\n+Use if the ws messages are binary\n+\n+## MQTT transport\n+\n+### `mqtt_topic`\n+\n+Set the topic this streamtype uses for writes\n+\n+### `mqtt_subscribe`\n+\n+Set the topic this streamtype subscribes to\n+\n+### `mqtt qos`\n+\n+Set the QOS level for this streamtype\n+\n+## Loading and using updated remote policy\n+\n+If the default, hardcoded policy includes a streamtype `fetch_policy`,\n+during startup when lws_system reaches the POLICY state, lws will use\n+a Secure Stream of type `fetch_policy` to download, parse and update\n+the policy to use it.\n+\n+The secure-streams-proxy minimal example shows how this is done and\n+fetches its real policy from warmcat.com at startup using the built-in\n+one.\n+\n+## Stream serialization and proxying\n+\n+By default Secure Streams expects to make the outgoing connection described in\n+the policy in the same process / thread, this suits the case where all the\n+participating clients are in the same statically-linked image.\n+\n+In this case the `lws_ss_` apis are fulfilled locally by secure-streams.c and\n+policy.c for policy lookups.\n+\n+However it also supports serialization, where the SS api can be streamed over\n+another transport such as a Unix Domain Socket connection. This suits the case\n+where the clients are actually in different processes in, eg, Linux or Android.\n+\n+In those cases, you run a proxy process (minimal-secure-streams-proxy) that\n+listens on a Unix Domain Socket and is connected to by one or more other\n+processes that pass their SS API activity to the proxy for fulfilment (or\n+onward proxying).\n+\n+In this case the proxy uses secure-streams.c and policy.c as before to fulfil\n+the inbound proxy streams, but uses secure-streams-serialize.c to serialize and\n+deserialize the proxied SS API activity. The proxy clients define\n+LWS_SS_USE_SSPC either very early in their sources before the includes, or on\n+the compiler commandline... this causes the lws_ss_ apis to be replaced at\n+preprocessor time with lws_sspc_ equivalents. These serialize the api action\n+and pass it to the proxy over a Unix Domain Socket for fulfilment, the results\n+and state changes etc are streamed over the Unix Domain Socket and presented to\n+the application exactly the same as if it was being fulfilled locally.\n+\n+To demonstrate this, some minimal examples, eg, minimal-secure-streams and\n+mimimal-secure-streams-avs build themselves both ways, once with direct SS API\n+fulfilment and once with Unix Domain Socket proxying and -client appended on the\n+executable name. To test the -client variants, run minimal-secure-streams-proxy\n+on the same machine.\n+\n+## Complicated scenarios with secure streams proxy\n+\n+As mentioned above, Secure Streams has two modes, by default the application\n+directly parses the policy and makes the outgoing connections itself.\n+However when configured at cmake with\n+\n+```\n+-DLWS_WITH_SOCKS\u003d1 -DLWS_WITH_SECURE_STREAMS\u003d1 -DLWS_WITH_SECURE_STREAMS_PROXY_API\u003d1 -DLWS_WITH_MINIMAL_EXAMPLES\u003d1\n+```\n+\n+and define `LWS_SS_USE_SSPC` when building the application, applications forward\n+their network requests to a local or remote SS proxy for fulfilment... and only\n+the SS proxy has the system policy. By default, the SS proxy is on the local\n+machine and is connected to via a Unix Domain Socket, but tcp links are also\n+possible. (Note the proxied traffic is not encrypyed by default.)\n+\n+Using the configuration above, the example SS applications are built two ways,\n+once for direct connection fulfilment (eg, `./bin/lws-minimal-secure-streams`),\n+and once with `LWS_SS_USE_SSPC` also defined so it connects via an SS proxy,\n+(eg, `./bin/lws-minimal-secure-streams-client`).\n+\n+## Testing an example scenario with SS Proxy and socks5 proxy\n+\n+```\n+ [ SS application ] --- tcp --- [ socks 5 proxy ] --- tcp --- [ SS proxy ] --- internet\n+```\n+\n+In this scenario, everything is on localhost, the socks5 proxy listens on :1337 and\n+the SS proxy listens on :1234. The SS application connects to the socks5\n+proxy to get to the SS proxy, which then goes out to the internet\n+\n+### 1 Start the SS proxy\n+\n+Tell it to listen on lo interface on port 1234 \n+\n+```\n+$ ./bin/lws-minimal-secure-streams-proxy -p 1234 -i lo\n+```\n+\n+### 2 Start the SOCKS5 proxy\n+\n+```\n+$ ssh -D 1337 -N -v localhost\n+```\n+\n+The -v makes connections to the proxy visible in the terminal for testing\n+\n+### 3 Run the SS application\n+\n+The application is told to make all connections via the socks5 proxy at\n+127.0.0.1:1337, and to fulfil its SS connections via an SS proxy, binding\n+connections to 127.0.0.1 (ipv4 lo interface, -1), to 127.0.0.1:1234 (-a/-p).\n+\n+```\n+socks_proxy\u003d127.0.0.1:1337 ./bin/lws-minimal-secure-streams-client -p 1234 -i 127.0.0.1 -a 127.0.0.1\n+```\n+\n+You can confirm this goes through the ssh socks5 proxy to get to the SS proxy\n+and fulfil the connection.\ndiff --git a/lib/secure-streams/plugins/ssp-h1url/h1url.c b/lib/secure-streams/plugins/ssp-h1url/h1url.c\nnew file mode 100644\nindex 0000000..2e47c71\n--- /dev/null\n+++ b/lib/secure-streams/plugins/ssp-h1url/h1url.c\n@@ -0,0 +1,40 @@\n+/*\n+ * ssp-h1url plugin\n+ *\n+ * Written in 2010-2020 by Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * This file is made available under the Creative Commons CC0 1.0\n+ * Universal Public Domain Dedication.\n+ *\n+ * CC0 so it can be used as a template for your own secure streams plugins\n+ * licensed how you like.\n+ */\n+\n+#include \u003clibwebsockets.h\u003e\n+\n+static int\n+ssp_h1url_create(struct lws_ss_handle *ss, void *info, plugin_auth_status_cb status)\n+{\n+\treturn 0;\n+}\n+\n+static int\n+ssp_h1url_destroy(struct lws_ss_handle *ss)\n+{\n+\treturn 0;\n+}\n+\n+static int\n+ssp_h1url_munge(struct lws_ss_handle *ss, char *path, size_t path_len)\n+{\n+\treturn 0;\n+}\n+\n+/* this is the only exported symbol */\n+const lws_ss_plugin_t ssp_h1url \u003d {\n+\t.name\t\t\t\u003d \u0022h1url\u0022,\n+\t.alloc\t\t\t\u003d 0,\n+\t.create\t\t\t\u003d ssp_h1url_create,\n+\t.destroy\t\t\u003d ssp_h1url_destroy,\n+\t.munge\t\t\t\u003d ssp_h1url_munge\n+};\ndiff --git a/lib/secure-streams/policy.c b/lib/secure-streams/policy.c\nnew file mode 100644\nindex 0000000..0ef8f1d\n--- /dev/null\n+++ b/lib/secure-streams/policy.c\n@@ -0,0 +1,942 @@\n+/*\n+ * libwebsockets - small server side websockets and web server implementation\n+ *\n+ * Copyright (C) 2019 - 2020 Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * Permission is hereby granted, free of charge, to any person obtaining a copy\n+ * of this software and associated documentation files (the \u0022Software\u0022), to\n+ * deal in the Software without restriction, including without limitation the\n+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n+ * sell copies of the Software, and to permit persons to whom the Software is\n+ * furnished to do so, subject to the following conditions:\n+ *\n+ * The above copyright notice and this permission notice shall be included in\n+ * all copies or substantial portions of the Software.\n+ *\n+ * THE SOFTWARE IS PROVIDED \u0022AS IS\u0022, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n+ * IN THE SOFTWARE.\n+ */\n+\n+#include \u003cprivate-lib-core.h\u003e\n+\n+typedef struct backoffs {\n+\tstruct backoffs *next;\n+\tconst char *name;\n+\tlws_retry_bo_t r;\n+} backoff_t;\n+\n+static const char * const lejp_tokens_policy[] \u003d {\n+\t\u0022release\u0022,\n+\t\u0022product\u0022,\n+\t\u0022schema-version\u0022,\n+\t\u0022via-socks5\u0022,\n+\t\u0022retry[].*.backoff\u0022,\n+\t\u0022retry[].*.conceal\u0022,\n+\t\u0022retry[].*.jitterpc\u0022,\n+\t\u0022retry[].*.svalidping\u0022,\n+\t\u0022retry[].*.svalidhup\u0022,\n+\t\u0022retry[].*\u0022,\n+\t\u0022certs[].*\u0022,\n+\t\u0022trust_stores[].name\u0022,\n+\t\u0022trust_stores[].stack\u0022,\n+\t\u0022s[].*.endpoint\u0022,\n+\t\u0022s[].*.via-socks5\u0022,\n+\t\u0022s[].*.protocol\u0022,\n+\t\u0022s[].*.port\u0022,\n+\t\u0022s[].*.plugins\u0022,\n+\t\u0022s[].*.tls\u0022,\n+\t\u0022s[].*.client_cert\u0022,\n+\t\u0022s[].*.opportunistic\u0022,\n+\t\u0022s[].*.nailed_up\u0022,\n+\t\u0022s[].*.urgent_tx\u0022,\n+\t\u0022s[].*.urgent_rx\u0022,\n+\t\u0022s[].*.long_poll\u0022,\n+\t\u0022s[].*.retry\u0022,\n+\t\u0022s[].*.tls_trust_store\u0022,\n+\t\u0022s[].*.metadata\u0022,\n+\t\u0022s[].*.metadata[].*\u0022,\n+\n+\t\u0022s[].*.http_auth_header\u0022,\n+\t\u0022s[].*.http_dsn_header\u0022,\n+\t\u0022s[].*.http_fwv_header\u0022,\n+\t\u0022s[].*.http_devtype_header\u0022,\n+\n+\t\u0022s[].*.http_auth_preamble\u0022,\n+\n+\t\u0022s[].*.http_no_content_length\u0022,\n+\t\u0022s[].*.rideshare\u0022,\t/* streamtype name this rides shotgun with */\n+\t\u0022s[].*.payload_fmt\u0022,\n+\t\u0022s[].*.http_method\u0022,\n+\t\u0022s[].*.http_url\u0022,\n+\t\u0022s[].*.nghttp2_quirk_end_stream\u0022,\n+\t\u0022s[].*.h2q_oflow_txcr\u0022,\n+\t\u0022s[].*.http_multipart_name\u0022,\n+\t\u0022s[].*.http_multipart_filename\u0022,\n+\t\u0022s[].*.http_mime_content_type\u0022,\n+\t\u0022s[].*.http_www_form_urlencoded\u0022,\n+\t\u0022s[].*.ws_subprotocol\u0022,\n+\t\u0022s[].*.ws_binary\u0022,\n+\t\u0022s[].*.local_sink\u0022,\n+\t\u0022s[].*.mqtt_topic\u0022,\n+\t\u0022s[].*.mqtt_subscribe\u0022,\n+\t\u0022s[].*.mqtt_qos\u0022,\n+\t\u0022s[].*\u0022,\n+};\n+\n+typedef enum {\n+\tLSSPPT_RELEASE,\n+\tLSSPPT_PRODUCT,\n+\tLSSPPT_SCHEMA_VERSION,\n+\tLSSPPT_VIA_SOCKS5,\n+\tLSSPPT_BACKOFF,\n+\tLSSPPT_CONCEAL,\n+\tLSSPPT_JITTERPC,\n+\tLSSPPT_VALIDPING_S,\n+\tLSSPPT_VALIDHUP_S,\n+\tLSSPPT_RETRY,\n+\tLSSPPT_CERTS,\n+\tLSSPPT_TRUST_STORES_NAME,\n+\tLSSPPT_TRUST_STORES_STACK,\n+\tLSSPPT_ENDPOINT,\n+\tLSSPPT_VH_VIA_SOCKS5,\n+\tLSSPPT_PROTOCOL,\n+\tLSSPPT_PORT,\n+\tLSSPPT_PLUGINS,\n+\tLSSPPT_TLS,\n+\tLSSPPT_TLS_CLIENT_CERT,\n+\tLSSPPT_OPPORTUNISTIC,\n+\tLSSPPT_NAILED_UP,\n+\tLSSPPT_URGENT_TX,\n+\tLSSPPT_URGENT_RX,\n+\tLSSPPT_LONG_POLL,\n+\tLSSPPT_RETRYPTR,\n+\tLSSPPT_TRUST,\n+\tLSSPPT_METADATA,\n+\tLSSPPT_METADATA_ITEM,\n+\n+\tLSSPPT_HTTP_AUTH_HEADER,\n+\tLSSPPT_HTTP_DSN_HEADER,\n+\tLSSPPT_HTTP_FWV_HEADER,\n+\tLSSPPT_HTTP_TYPE_HEADER,\n+\n+\tLSSPPT_HTTP_AUTH_PREAMBLE,\n+\tLSSPPT_HTTP_NO_CONTENT_LENGTH,\n+\tLSSPPT_RIDESHARE,\n+\tLSSPPT_PAYLOAD_FORMAT,\n+\tLSSPPT_HTTP_METHOD,\n+\tLSSPPT_HTTP_URL,\n+\tLSSPPT_NGHTTP2_QUIRK_END_STREAM,\n+\tLSSPPT_H2_QUIRK_OVERFLOWS_TXCR,\n+\tLSSPPT_HTTP_MULTIPART_NAME,\n+\tLSSPPT_HTTP_MULTIPART_FILENAME,\n+\tLSSPPT_HTTP_MULTIPART_CONTENT_TYPE,\n+\tLSSPPT_HTTP_WWW_FORM_URLENCODED,\n+\tLSSPPT_WS_SUBPROTOCOL,\n+\tLSSPPT_WS_BINARY,\n+\tLSSPPT_LOCAL_SINK,\n+\tLSSPPT_MQTT_TOPIC,\n+\tLSSPPT_MQTT_SUBSCRIBE,\n+\tLSSPPT_MQTT_QOS,\n+\tLSSPPT_STREAMTYPES\n+} policy_token_t;\n+\n+union u {\n+\tbackoff_t *b;\n+\tlws_ss_x509_t *x;\n+\tlws_ss_trust_store_t *t;\n+\tlws_ss_policy_t *p;\n+};\n+\n+enum {\n+\tLTY_BACKOFF,\n+\tLTY_X509,\n+\tLTY_TRUSTSTORE,\n+\tLTY_POLICY,\n+\n+\t_LTY_COUNT /* always last */\n+};\n+\n+struct policy_cb_args {\n+\tstruct lejp_ctx jctx;\n+\tstruct lws_context *context;\n+\tstruct lwsac *ac;\n+\n+\tconst char *socks5_proxy;\n+\n+\tstruct lws_b64state b64;\n+\n+\tunion u heads[_LTY_COUNT];\n+\tunion u curr[_LTY_COUNT];\n+\n+\tuint8_t *p;\n+\n+\tint count;\n+};\n+\n+#define POL_AC_INITIAL\t2048\n+#define POL_AC_GRAIN\t800\n+#define MAX_CERT_TEMP\t2048 /* used to discover actual cert size for realloc */\n+\n+static uint8_t sizes[] \u003d {\n+\tsizeof(backoff_t),\n+\tsizeof(lws_ss_x509_t),\n+\tsizeof(lws_ss_trust_store_t),\n+\tsizeof(lws_ss_policy_t),\n+};\n+\n+static const char *protonames[] \u003d {\n+\t\u0022h1\u0022,\t\t/* LWSSSP_H1 */\n+\t\u0022h2\u0022,\t\t/* LWSSSP_H2 */\n+\t\u0022ws\u0022,\t\t/* LWSSSP_WS */\n+\t\u0022mqtt\u0022,\t\t/* LWSSSP_MQTT */\n+};\n+\n+lws_ss_metadata_t *\n+lws_ss_policy_metadata(const lws_ss_policy_t *p, const char *name)\n+{\n+\tlws_ss_metadata_t *pmd \u003d p-\u003emetadata;\n+\n+\twhile (pmd) {\n+\t\tif (pmd-\u003ename \u0026\u0026 !strcmp(name, pmd-\u003ename))\n+\t\t\treturn pmd;\n+\t\tpmd \u003d pmd-\u003enext;\n+\t}\n+\n+\treturn NULL;\n+}\n+\n+lws_ss_metadata_t *\n+lws_ss_policy_metadata_index(const lws_ss_policy_t *p, size_t index)\n+{\n+\tlws_ss_metadata_t *pmd \u003d p-\u003emetadata;\n+\n+\twhile (pmd) {\n+\t\tif (pmd-\u003elength \u003d\u003d index)\n+\t\t\treturn pmd;\n+\t\tpmd \u003d pmd-\u003enext;\n+\t}\n+\n+\treturn NULL;\n+}\n+\n+int\n+lws_ss_set_metadata(struct lws_ss_handle *h, const char *name,\n+\t\t void *value, size_t len)\n+{\n+\tlws_ss_metadata_t *omd \u003d lws_ss_policy_metadata(h-\u003epolicy, name);\n+\n+\tif (!omd) {\n+\t\tlwsl_err(\u0022%s: unknown metadata %s\u005cn\u0022, __func__, name);\n+\t\treturn 1;\n+\t}\n+\n+\th-\u003emetadata[omd-\u003elength].name \u003d name;\n+\th-\u003emetadata[omd-\u003elength].value \u003d value;\n+\th-\u003emetadata[omd-\u003elength].length \u003d len;\n+\n+\treturn 0;\n+}\n+\n+lws_ss_metadata_t *\n+lws_ss_get_handle_metadata(struct lws_ss_handle *h, const char *name)\n+{\n+\tlws_ss_metadata_t *omd \u003d lws_ss_policy_metadata(h-\u003epolicy, name);\n+\n+\tif (!omd)\n+\t\treturn NULL;\n+\n+\treturn \u0026h-\u003emetadata[omd-\u003elength];\n+}\n+\n+static signed char\n+lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason)\n+{\n+\tstruct policy_cb_args *a \u003d (struct policy_cb_args *)ctx-\u003euser;\n+\tconst lws_ss_plugin_t **pin;\n+\tchar **pp, dotstar[32], *q;\n+\tlws_ss_trust_store_t *ts;\n+\tlws_ss_metadata_t *pmd;\n+\tlws_retry_bo_t *b;\n+\tsize_t inl, outl;\n+\tlws_ss_x509_t *x;\n+\tuint8_t *extant;\n+\tbackoff_t *bot;\n+\tint n \u003d -1;\n+\n+\tlwsl_debug(\u0022%s: %d %d %s\u005cn\u0022, __func__, reason, ctx-\u003epath_match - 1,\n+\t\t ctx-\u003epath);\n+\n+\tswitch (ctx-\u003epath_match - 1) {\n+\tcase LSSPPT_RETRY:\n+\t\tn \u003d LTY_BACKOFF;\n+\t\tbreak;\n+\tcase LSSPPT_CERTS:\n+\t\tn \u003d LTY_X509;\n+\t\tbreak;\n+\tcase LSSPPT_TRUST_STORES_NAME:\n+\tcase LSSPPT_TRUST_STORES_STACK:\n+\t\tn \u003d LTY_TRUSTSTORE;\n+\t\tbreak;\n+\tcase LSSPPT_STREAMTYPES:\n+\t\tn \u003d LTY_POLICY;\n+\t\tbreak;\n+\t}\n+\n+\tif (reason \u003d\u003d LEJPCB_ARRAY_START \u0026\u0026\n+\t (ctx-\u003epath_match - 1 \u003d\u003d LSSPPT_PLUGINS ||\n+\t ctx-\u003epath_match - 1 \u003d\u003d LSSPPT_METADATA))\n+\t\ta-\u003ecount \u003d 0;\n+\n+\tif (reason \u003d\u003d LEJPCB_ARRAY_END \u0026\u0026\n+\t ctx-\u003epath_match - 1 \u003d\u003d LSSPPT_TRUST_STORES_STACK \u0026\u0026 !a-\u003ecount) {\n+\t\tlwsl_err(\u0022%s: at least one cert required in trust store\u005cn\u0022,\n+\t\t\t\t__func__);\n+\t\tgoto oom;\n+\t}\n+\n+\tif (reason \u003d\u003d LEJPCB_OBJECT_END \u0026\u0026 a-\u003ep) {\n+\t\t/*\n+\t\t * Allocate a just-the-right-size buf for the cert DER now\n+\t\t * we decoded it into the a-\u003ep temp buffer and know the exact\n+\t\t * size\n+\t\t */\n+\t\ta-\u003ecurr[LTY_X509].x-\u003eca_der \u003d lws_malloc(a-\u003ecount, \u0022ssx509\u0022);\n+\t\tif (!a-\u003ecurr[LTY_X509].x-\u003eca_der)\n+\t\t\tgoto oom;\n+\t\tmemcpy((uint8_t *)a-\u003ecurr[LTY_X509].x-\u003eca_der, a-\u003ep, a-\u003ecount);\n+\t\ta-\u003ecurr[LTY_X509].x-\u003eca_der_len \u003d a-\u003ecount;\n+\n+\t\t/*\n+\t\t * ... and then we can free the temp buffer\n+\t\t */\n+\t\tlws_free_set_NULL(a-\u003ep);\n+\n+\t\treturn 0;\n+\t}\n+\n+\tif (reason \u003d\u003d LEJPCB_PAIR_NAME \u0026\u0026 n !\u003d -1 \u0026\u0026 n !\u003d LTY_TRUSTSTORE) {\n+\t\t/*\n+\t\t * We do the pointers always as .b, all of the participating\n+\t\t * structs begin with .next and .name\n+\t\t */\n+\t\ta-\u003ecurr[n].b \u003d lwsac_use_zero(\u0026a-\u003eac, sizes[n], POL_AC_GRAIN);\n+\t\tif (!a-\u003ecurr[n].b)\n+\t\t\tgoto oom;\n+\n+\t\tif (n \u003d\u003d LTY_X509) {\n+\t\t\ta-\u003ep \u003d lws_malloc(MAX_CERT_TEMP, \u0022cert temp\u0022);\n+\t\t\tif (!a-\u003ep)\n+\t\t\t\tgoto oom;\n+\t\t\tmemset(\u0026a-\u003eb64, 0, sizeof(a-\u003eb64));\n+\t\t}\n+\n+\t\ta-\u003ecount \u003d 0;\n+\t\ta-\u003ecurr[n].b-\u003enext \u003d a-\u003eheads[n].b;\n+\t\ta-\u003eheads[n].b \u003d a-\u003ecurr[n].b;\n+\t\tpp \u003d (char **)\u0026a-\u003ecurr[n].b-\u003ename;\n+\n+\t\tgoto string1;\n+\t}\n+\n+\tif (!(reason \u0026 LEJP_FLAG_CB_IS_VALUE) || !ctx-\u003epath_match)\n+\t\treturn 0;\n+\n+\tswitch (ctx-\u003epath_match - 1) {\n+\n+\t/* strings */\n+\n+\tcase LSSPPT_RELEASE:\n+\t\tbreak;\n+\n+\tcase LSSPPT_PRODUCT:\n+\t\tbreak;\n+\n+\tcase LSSPPT_SCHEMA_VERSION:\n+\t\tbreak;\n+\n+\tcase LSSPPT_VIA_SOCKS5:\n+\t\t/* the global / default proxy */\n+\t\tpp \u003d (char **)\u0026a-\u003esocks5_proxy;\n+\t\tgoto string2;\n+\n+\tcase LSSPPT_BACKOFF:\n+\t\tb \u003d \u0026a-\u003ecurr[LTY_BACKOFF].b-\u003er;\n+\t\tif (b-\u003eretry_ms_table_count \u003d\u003d 8) {\n+\t\t\tlwsl_err(\u0022%s: \u003e 8 backoff levels\u005cn\u0022, __func__);\n+\t\t\treturn 1;\n+\t\t}\n+\t\tif (!b-\u003eretry_ms_table_count) {\n+\t\t\tb-\u003eretry_ms_table \u003d (uint32_t *)lwsac_use_zero(\u0026a-\u003eac,\n+\t\t\t\t\t sizeof(uint32_t) * 8, POL_AC_GRAIN);\n+\t\t\tif (!b-\u003eretry_ms_table)\n+\t\t\t\tgoto oom;\n+\t\t}\n+\n+\t\t((uint32_t *)b-\u003eretry_ms_table)\n+\t\t\t\t[b-\u003eretry_ms_table_count++] \u003d atoi(ctx-\u003ebuf);\n+\t\tbreak;\n+\n+\tcase LSSPPT_CONCEAL:\n+\t\ta-\u003ecurr[LTY_BACKOFF].b-\u003er.conceal_count \u003d atoi(ctx-\u003ebuf);\n+\t\tbreak;\n+\n+\tcase LSSPPT_JITTERPC:\n+\t\ta-\u003ecurr[LTY_BACKOFF].b-\u003er.jitter_percent \u003d atoi(ctx-\u003ebuf);\n+\t\tbreak;\n+\n+\tcase LSSPPT_VALIDPING_S:\n+\t\ta-\u003ecurr[LTY_BACKOFF].b-\u003er.secs_since_valid_ping \u003d atoi(ctx-\u003ebuf);\n+\t\tbreak;\n+\n+\tcase LSSPPT_VALIDHUP_S:\n+\t\ta-\u003ecurr[LTY_BACKOFF].b-\u003er.secs_since_valid_hangup \u003d atoi(ctx-\u003ebuf);\n+\t\tbreak;\n+\n+\tcase LSSPPT_CERTS:\n+\t\tif (a-\u003ecount + ctx-\u003enpos \u003e\u003d MAX_CERT_TEMP) {\n+\t\t\tlwsl_err(\u0022%s: cert too big\u005cn\u0022, __func__);\n+\t\t\tgoto oom;\n+\t\t}\n+\t\tinl \u003d ctx-\u003enpos;\n+\t\toutl \u003d MAX_CERT_TEMP - a-\u003ecount;\n+\n+\t\tlws_b64_decode_stateful(\u0026a-\u003eb64, ctx-\u003ebuf, \u0026inl,\n+\t\t\t\t\ta-\u003ep + a-\u003ecount, \u0026outl,\n+\t\t\t\t\treason \u003d\u003d LEJPCB_VAL_STR_END);\n+\t\ta-\u003ecount +\u003d outl;\n+\t\tif (inl !\u003d ctx-\u003enpos) {\n+\t\t\tlwsl_err(\u0022%s: b64 decode fail\u005cn\u0022, __func__);\n+\t\t\tgoto oom;\n+\t\t}\n+\t\tbreak;\n+\n+\tcase LSSPPT_TRUST_STORES_NAME:\n+\t\t/*\n+\t\t * We do the pointers always as .b, all of the participating\n+\t\t * structs begin with .next and .name\n+\t\t */\n+\t\ta-\u003ecurr[LTY_TRUSTSTORE].b \u003d lwsac_use_zero(\u0026a-\u003eac,\n+\t\t\t\t\tsizes[LTY_TRUSTSTORE], POL_AC_GRAIN);\n+\t\tif (!a-\u003ecurr[LTY_TRUSTSTORE].b)\n+\t\t\tgoto oom;\n+\n+\t\ta-\u003ecount \u003d 0;\n+\t\ta-\u003ecurr[LTY_TRUSTSTORE].b-\u003enext \u003d a-\u003eheads[LTY_TRUSTSTORE].b;\n+\t\ta-\u003eheads[LTY_TRUSTSTORE].b \u003d a-\u003ecurr[LTY_TRUSTSTORE].b;\n+\t\tpp \u003d (char **)\u0026a-\u003ecurr[LTY_TRUSTSTORE].b-\u003ename;\n+\n+\t\tgoto string2;\n+\n+\tcase LSSPPT_TRUST_STORES_STACK:\n+\t\tif (a-\u003ecount \u003e\u003d (int)LWS_ARRAY_SIZE(\n+\t\t\t\t\ta-\u003ecurr[LTY_TRUSTSTORE].t-\u003essx509)) {\n+\t\t\tlwsl_err(\u0022%s: trust store too big\u005cn\u0022, __func__);\n+\t\t\tgoto oom;\n+\t\t}\n+\t\tlwsl_debug(\u0022%s: trust stores stack %.*s\u005cn\u0022, __func__,\n+\t\t\t ctx-\u003enpos, ctx-\u003ebuf);\n+\t\tx \u003d a-\u003eheads[LTY_X509].x;\n+\t\twhile (x) {\n+\t\t\tif (!strncmp(x-\u003evhost_name, ctx-\u003ebuf, ctx-\u003enpos)) {\n+\t\t\t\ta-\u003ecurr[LTY_TRUSTSTORE].t-\u003essx509[a-\u003ecount++] \u003d x;\n+\t\t\t\ta-\u003ecurr[LTY_TRUSTSTORE].t-\u003ecount++;\n+\n+\t\t\t\treturn 0;\n+\t\t\t}\n+\t\t\tx \u003d x-\u003enext;\n+\t\t}\n+\t\tlws_strnncpy(dotstar, ctx-\u003ebuf, ctx-\u003enpos, sizeof(dotstar));\n+\t\tlwsl_err(\u0022%s: unknown trust store entry %s\u005cn\u0022, __func__,\n+\t\t\t dotstar);\n+\t\tgoto oom;\n+\n+\tcase LSSPPT_ENDPOINT:\n+\t\tpp \u003d (char **)\u0026a-\u003ecurr[LTY_POLICY].p-\u003eendpoint;\n+\t\tgoto string2;\n+\n+\tcase LSSPPT_VH_VIA_SOCKS5:\n+\t\tpp \u003d (char **)\u0026a-\u003ecurr[LTY_POLICY].p-\u003esocks5_proxy;\n+\t\tgoto string2;\n+\n+\tcase LSSPPT_PORT:\n+\t\ta-\u003ecurr[LTY_POLICY].p-\u003eport \u003d atoi(ctx-\u003ebuf);\n+\t\tbreak;\n+\n+\tcase LSSPPT_HTTP_METHOD:\n+\t\tpp \u003d (char **)\u0026a-\u003ecurr[LTY_POLICY].p-\u003eu.http.method;\n+\t\tgoto string2;\n+\n+\tcase LSSPPT_HTTP_URL:\n+\t\tpp \u003d (char **)\u0026a-\u003ecurr[LTY_POLICY].p-\u003eu.http.url;\n+\t\tgoto string2;\n+\n+\tcase LSSPPT_RIDESHARE:\n+\t\tpp \u003d (char **)\u0026a-\u003ecurr[LTY_POLICY].p-\u003erideshare_streamtype;\n+\t\tgoto string2;\n+\n+\tcase LSSPPT_PAYLOAD_FORMAT:\n+\t\tpp \u003d (char **)\u0026a-\u003ecurr[LTY_POLICY].p-\u003epayload_fmt;\n+\t\tgoto string2;\n+\n+\tcase LSSPPT_PLUGINS:\n+\t\tpin \u003d a-\u003econtext-\u003epss_plugins;\n+\t\tif (a-\u003ecount \u003d\u003d\n+\t\t\t (int)LWS_ARRAY_SIZE(a-\u003ecurr[LTY_POLICY].p-\u003eplugins)) {\n+\t\t\tlwsl_err(\u0022%s: too many plugins\u005cn\u0022, __func__);\n+\n+\t\t\tgoto oom;\n+\t\t}\n+\t\tif (!pin)\n+\t\t\tbreak;\n+\t\twhile (*pin) {\n+\t\t\tif (!strncmp((*pin)-\u003ename, ctx-\u003ebuf, ctx-\u003enpos)) {\n+\t\t\t\ta-\u003ecurr[LTY_POLICY].p-\u003eplugins[a-\u003ecount++] \u003d *pin;\n+\t\t\t\treturn 0;\n+\t\t\t}\n+\t\t\tpin++;\n+\t\t}\n+\t\tlwsl_err(\u0022%s: unknown plugin\u005cn\u0022, __func__);\n+\t\tgoto oom;\n+\n+\tcase LSSPPT_TLS:\n+\t\tif (reason \u003d\u003d LEJPCB_VAL_TRUE)\n+\t\t\ta-\u003ecurr[LTY_POLICY].p-\u003eflags |\u003d LWSSSPOLF_TLS;\n+\t\tbreak;\n+\n+\tcase LSSPPT_TLS_CLIENT_CERT:\n+\t\ta-\u003ecurr[LTY_POLICY].p-\u003eclient_cert \u003d atoi(ctx-\u003ebuf) + 1;\n+\t\tbreak;\n+\n+\tcase LSSPPT_OPPORTUNISTIC:\n+\t\tif (reason \u003d\u003d LEJPCB_VAL_TRUE)\n+\t\t\ta-\u003ecurr[LTY_POLICY].p-\u003eflags |\u003d LWSSSPOLF_OPPORTUNISTIC;\n+\t\tbreak;\n+\tcase LSSPPT_NAILED_UP:\n+\t\tif (reason \u003d\u003d LEJPCB_VAL_TRUE)\n+\t\t\ta-\u003ecurr[LTY_POLICY].p-\u003eflags |\u003d LWSSSPOLF_NAILED_UP;\n+\t\tbreak;\n+\tcase LSSPPT_URGENT_TX:\n+\t\tif (reason \u003d\u003d LEJPCB_VAL_TRUE)\n+\t\t\ta-\u003ecurr[LTY_POLICY].p-\u003eflags |\u003d LWSSSPOLF_URGENT_TX;\n+\t\tbreak;\n+\tcase LSSPPT_URGENT_RX:\n+\t\tif (reason \u003d\u003d LEJPCB_VAL_TRUE)\n+\t\t\ta-\u003ecurr[LTY_POLICY].p-\u003eflags |\u003d LWSSSPOLF_URGENT_RX;\n+\t\tbreak;\n+\tcase LSSPPT_LONG_POLL:\n+\t\tif (reason \u003d\u003d LEJPCB_VAL_TRUE)\n+\t\t\ta-\u003ecurr[LTY_POLICY].p-\u003eflags |\u003d LWSSSPOLF_LONG_POLL;\n+\t\tbreak;\n+\tcase LSSPPT_HTTP_WWW_FORM_URLENCODED:\n+\t\tif (reason \u003d\u003d LEJPCB_VAL_TRUE)\n+\t\t\ta-\u003ecurr[LTY_POLICY].p-\u003eflags |\u003d\n+\t\t\t\t\tLWSSSPOLF_HTTP_X_WWW_FORM_URLENCODED;\n+\t\tbreak;\n+\n+\tcase LSSPPT_RETRYPTR:\n+\t\tbot \u003d a-\u003eheads[LTY_BACKOFF].b;\n+\t\twhile (bot) {\n+\t\t\tif (!strncmp(ctx-\u003ebuf, bot-\u003ename, ctx-\u003enpos)) {\n+\t\t\t\ta-\u003ecurr[LTY_POLICY].p-\u003eretry_bo \u003d \u0026bot-\u003er;\n+\n+\t\t\t\treturn 0;\n+\t\t\t}\n+\t\t\tbot \u003d bot-\u003enext;\n+\t\t}\n+\t\tlwsl_err(\u0022%s: unknown backoff scheme\u005cn\u0022, __func__);\n+\n+\t\treturn -1;\n+\n+\tcase LSSPPT_TRUST:\n+\t\tts \u003d a-\u003eheads[LTY_TRUSTSTORE].t;\n+\t\twhile (ts) {\n+\t\t\tif (!strncmp(ctx-\u003ebuf, ts-\u003ename, ctx-\u003enpos)) {\n+\t\t\t\ta-\u003ecurr[LTY_POLICY].p-\u003etrust_store \u003d ts;\n+\t\t\t\treturn 0;\n+\t\t\t}\n+\t\t\tts \u003d ts-\u003enext;\n+\t\t}\n+\t\tlws_strnncpy(dotstar, ctx-\u003ebuf, ctx-\u003enpos, sizeof(dotstar));\n+\t\tlwsl_err(\u0022%s: unknown trust store name %s\u005cn\u0022, __func__,\n+\t\t\t dotstar);\n+\n+\t\treturn -1;\n+\n+\tcase LSSPPT_METADATA:\n+\t\tbreak;\n+\n+\tcase LSSPPT_METADATA_ITEM:\n+\t\tpmd \u003d a-\u003ecurr[LTY_POLICY].p-\u003emetadata;\n+\t\ta-\u003ecurr[LTY_POLICY].p-\u003emetadata \u003d lwsac_use_zero(\u0026a-\u003eac,\n+\t\t\tsizeof(lws_ss_metadata_t) + ctx-\u003enpos +\n+\t\t\t(ctx-\u003epath_match_len - ctx-\u003est[ctx-\u003esp - 2].p + 1) + 2,\n+\t\t\tPOL_AC_GRAIN);\n+\t\ta-\u003ecurr[LTY_POLICY].p-\u003emetadata-\u003enext \u003d pmd;\n+\n+\t\tq \u003d (char *)a-\u003ecurr[LTY_POLICY].p-\u003emetadata +\n+\t\t\t\tsizeof(lws_ss_metadata_t);\n+\t\ta-\u003ecurr[LTY_POLICY].p-\u003emetadata-\u003ename \u003d q;\n+\t\tmemcpy(q, ctx-\u003epath + ctx-\u003est[ctx-\u003esp - 2].p + 1,\n+\t\t ctx-\u003epath_match_len - ctx-\u003est[ctx-\u003esp - 2].p);\n+\n+\t\tq +\u003d ctx-\u003epath_match_len - ctx-\u003est[ctx-\u003esp - 2].p;\n+\t\ta-\u003ecurr[LTY_POLICY].p-\u003emetadata-\u003evalue \u003d q;\n+\t\tmemcpy(q, ctx-\u003ebuf, ctx-\u003enpos);\n+\n+\t\ta-\u003ecurr[LTY_POLICY].p-\u003emetadata-\u003elength \u003d /* the index in handle-\u003emetadata */\n+\t\t\t\ta-\u003ecurr[LTY_POLICY].p-\u003emetadata_count++;\n+\t\tbreak;\n+\n+\tcase LSSPPT_HTTP_AUTH_HEADER:\n+\tcase LSSPPT_HTTP_DSN_HEADER:\n+\tcase LSSPPT_HTTP_FWV_HEADER:\n+\tcase LSSPPT_HTTP_TYPE_HEADER:\n+\t\tpp \u003d (char **)\u0026a-\u003ecurr[LTY_POLICY].p-\u003eu.http.blob_header[\n+\t\t (ctx-\u003epath_match - 1) - LSSPPT_HTTP_AUTH_HEADER];\n+\t\tgoto string2;\n+\n+\tcase LSSPPT_HTTP_AUTH_PREAMBLE:\n+\t\tpp \u003d (char **)\u0026a-\u003ecurr[LTY_POLICY].p-\u003eu.http.auth_preamble;\n+\t\tgoto string2;\n+\n+\tcase LSSPPT_HTTP_NO_CONTENT_LENGTH:\n+\t\tif (reason \u003d\u003d LEJPCB_VAL_TRUE)\n+\t\t\ta-\u003ecurr[LTY_POLICY].p-\u003eflags |\u003d\n+\t\t\t\t\tLWSSSPOLF_HTTP_NO_CONTENT_LENGTH;\n+\t\tbreak;\n+\n+\tcase LSSPPT_NGHTTP2_QUIRK_END_STREAM:\n+\t\tif (reason \u003d\u003d LEJPCB_VAL_TRUE)\n+\t\t\ta-\u003ecurr[LTY_POLICY].p-\u003eflags |\u003d\n+\t\t\t\t\tLWSSSPOLF_QUIRK_NGHTTP2_END_STREAM;\n+\t\tbreak;\n+\tcase LSSPPT_H2_QUIRK_OVERFLOWS_TXCR:\n+\t\tif (reason \u003d\u003d LEJPCB_VAL_TRUE)\n+\t\t\ta-\u003ecurr[LTY_POLICY].p-\u003eflags |\u003d\n+\t\t\t\t\tLWSSSPOLF_H2_QUIRK_OVERFLOWS_TXCR;\n+\t\tbreak;\n+\tcase LSSPPT_HTTP_MULTIPART_NAME:\n+\t\ta-\u003ecurr[LTY_POLICY].p-\u003eflags |\u003d LWSSSPOLF_HTTP_MULTIPART;\n+\t\tpp \u003d (char **)\u0026a-\u003ecurr[LTY_POLICY].p-\u003eu.http.multipart_name;\n+\t\tgoto string2;\n+\tcase LSSPPT_HTTP_MULTIPART_FILENAME:\n+\t\ta-\u003ecurr[LTY_POLICY].p-\u003eflags |\u003d LWSSSPOLF_HTTP_MULTIPART;\n+\t\tpp \u003d (char **)\u0026a-\u003ecurr[LTY_POLICY].p-\u003eu.http.multipart_filename;\n+\t\tgoto string2;\n+\tcase LSSPPT_HTTP_MULTIPART_CONTENT_TYPE:\n+\t\ta-\u003ecurr[LTY_POLICY].p-\u003eflags |\u003d LWSSSPOLF_HTTP_MULTIPART;\n+\t\tpp \u003d (char **)\u0026a-\u003ecurr[LTY_POLICY].p-\u003eu.http.multipart_content_type;\n+\t\tgoto string2;\n+\tcase LSSPPT_WS_SUBPROTOCOL:\n+\t\tpp \u003d (char **)\u0026a-\u003ecurr[LTY_POLICY].p-\u003eu.http.u.ws.subprotocol;\n+\t\tgoto string2;\n+\n+\tcase LSSPPT_WS_BINARY:\n+\t\ta-\u003ecurr[LTY_POLICY].p-\u003eu.http.u.ws.binary \u003d\n+\t\t\t\t\t\treason \u003d\u003d LEJPCB_VAL_TRUE;\n+\t\tbreak;\n+\tcase LSSPPT_LOCAL_SINK:\n+\t\tif (reason \u003d\u003d LEJPCB_VAL_TRUE)\n+\t\t\ta-\u003ecurr[LTY_POLICY].p-\u003eflags |\u003d LWSSSPOLF_LOCAL_SINK;\n+\t\tbreak;\n+\n+\tcase LSSPPT_MQTT_TOPIC:\n+\t\tpp \u003d (char **)\u0026a-\u003ecurr[LTY_POLICY].p-\u003eu.mqtt.topic;\n+\t\tgoto string2;\n+\n+\tcase LSSPPT_MQTT_SUBSCRIBE:\n+\t\tpp \u003d (char **)\u0026a-\u003ecurr[LTY_POLICY].p-\u003eu.mqtt.subscribe;\n+\t\tgoto string2;\n+\n+\tcase LSSPPT_MQTT_QOS:\n+\t\ta-\u003ecurr[LTY_POLICY].p-\u003eu.mqtt.qos \u003d atoi(ctx-\u003ebuf);\n+\t\tbreak;\n+\n+\tcase LSSPPT_PROTOCOL:\n+\t\ta-\u003ecurr[LTY_POLICY].p-\u003eprotocol \u003d 0xff;\n+\t\tfor (n \u003d 0; n \u003c (int)LWS_ARRAY_SIZE(protonames); n++)\n+\t\t\tif (strlen(protonames[n]) \u003d\u003d ctx-\u003enpos \u0026\u0026\n+\t\t\t !strncmp(ctx-\u003ebuf, protonames[n], ctx-\u003enpos))\n+\t\t\t\ta-\u003ecurr[LTY_POLICY].p-\u003eprotocol \u003d (uint8_t)n;\n+\n+\t\tif (a-\u003ecurr[LTY_POLICY].p-\u003eprotocol !\u003d 0xff)\n+\t\t\tbreak;\n+\t\tlws_strnncpy(dotstar, ctx-\u003ebuf, ctx-\u003enpos, sizeof(dotstar));\n+\t\tlwsl_err(\u0022%s: unknown protocol name %s\u005cn\u0022, __func__, dotstar);\n+\t\treturn -1;\n+\t}\n+\n+\treturn 0;\n+\n+string2:\n+\t/*\n+\t * If we can do const string folding, reuse the existing string rather\n+\t * than make a new entry\n+\t */\n+\textant \u003d lwsac_scan_extant(a-\u003eac, (uint8_t *)ctx-\u003ebuf, ctx-\u003enpos, 1);\n+\tif (extant) {\n+\t\t*pp \u003d (char *)extant;\n+\n+\t\treturn 0;\n+\t}\n+\t*pp \u003d lwsac_use_backfill(\u0026a-\u003eac, ctx-\u003enpos + 1, POL_AC_GRAIN);\n+\tif (!*pp)\n+\t\tgoto oom;\n+\tmemcpy(*pp, ctx-\u003ebuf, ctx-\u003enpos);\n+\t(*pp)[ctx-\u003enpos] \u003d '\u005c0';\n+\n+\treturn 0;\n+\n+string1:\n+\tn \u003d ctx-\u003est[ctx-\u003esp].p;\n+\t*pp \u003d lwsac_use_backfill(\u0026a-\u003eac, ctx-\u003epath_match_len + 1 - n,\n+\t\t\t\t POL_AC_GRAIN);\n+\tif (!*pp)\n+\t\tgoto oom;\n+\tmemcpy(*pp, ctx-\u003epath + n, ctx-\u003epath_match_len - n);\n+\t(*pp)[ctx-\u003epath_match_len - n] \u003d '\u005c0';\n+\n+\treturn 0;\n+\n+oom:\n+\tlwsl_err(\u0022%s: OOM\u005cn\u0022, __func__);\n+\tlws_free_set_NULL(a-\u003ep);\n+\tlwsac_free(\u0026a-\u003eac);\n+\n+\treturn -1;\n+}\n+\n+int\n+lws_ss_policy_parse_begin(struct lws_context *context)\n+{\n+\tstruct policy_cb_args *args;\n+\tchar *p;\n+\n+\targs \u003d lws_zalloc(sizeof(struct policy_cb_args), __func__);\n+\tif (!args) {\n+\t\tlwsl_err(\u0022%s: OOM\u005cn\u0022, __func__);\n+\n+\t\treturn 1;\n+\t}\n+\n+\tcontext-\u003epol_args \u003d args;\n+\targs-\u003econtext \u003d context;\n+\tp \u003d lwsac_use(\u0026args-\u003eac, 1, POL_AC_INITIAL);\n+\tif (!p) {\n+\t\tlwsl_err(\u0022%s: OOM\u005cn\u0022, __func__);\n+\t\tlws_free_set_NULL(context-\u003epol_args);\n+\n+\t\treturn -1;\n+\t}\n+\t*p \u003d 0;\n+\tlejp_construct(\u0026args-\u003ejctx, lws_ss_policy_parser_cb, args,\n+\t\t lejp_tokens_policy, LWS_ARRAY_SIZE(lejp_tokens_policy));\n+\n+\treturn 0;\n+}\n+\n+int\n+lws_ss_policy_parse_abandon(struct lws_context *context)\n+{\n+\tstruct policy_cb_args *args \u003d (struct policy_cb_args *)context-\u003epol_args;\n+\n+\tlejp_destruct(\u0026args-\u003ejctx);\n+\tlws_free_set_NULL(context-\u003epol_args);\n+\n+\treturn 0;\n+}\n+\n+int\n+lws_ss_policy_parse(struct lws_context *context, const uint8_t *buf, size_t len)\n+{\n+\tstruct policy_cb_args *args \u003d (struct policy_cb_args *)context-\u003epol_args;\n+\tint m;\n+\n+\tm \u003d (int)(signed char)lejp_parse(\u0026args-\u003ejctx, buf, len);\n+\tif (m \u003d\u003d LEJP_CONTINUE || m \u003e\u003d 0)\n+\t\treturn m;\n+\n+\tlwsl_err(\u0022%s: parse failed: %d: %s\u005cn\u0022, __func__, m,\n+\t\t lejp_error_to_string(m));\n+\tlws_ss_policy_parse_abandon(context);\n+\n+\treturn m;\n+}\n+\n+int\n+lws_ss_policy_set(struct lws_context *context, const char *name)\n+{\n+\tstruct policy_cb_args *args \u003d (struct policy_cb_args *)context-\u003epol_args;\n+\tlws_ss_trust_store_t *ts;\n+\tstruct lws_vhost *v;\n+\tlws_ss_x509_t *x;\n+\tchar buf[16];\n+\tint m, ret \u003d 0;\n+\n+\t/*\n+\t * Parsing seems to have succeeded, and we're going to use the new\n+\t * policy that's laid out in args-\u003eac\n+\t */\n+\n+\tlejp_destruct(\u0026args-\u003ejctx);\n+\n+\tif (context-\u003eac_policy) {\n+\n+\t\t/*\n+\t\t * So this is a bit fun-filled, we already had a policy in\n+\t\t * force, perhaps it was the default policy that's just good for\n+\t\t * fetching the real policy, and we're doing that now.\n+\t\t *\n+\t\t * We can destroy all the policy-related direct allocations\n+\t\t * easily because they're cleanly in a single lwsac...\n+\t\t */\n+\t\tlwsac_free(\u0026context-\u003eac_policy);\n+\n+\t\t/*\n+\t\t * ...but when we did the trust stores, we created vhosts for\n+\t\t * each. We need to destroy those now too, and recreate new\n+\t\t * ones from the new policy, perhaps with different X.509s.\n+\t\t */\n+\n+\t\tv \u003d context-\u003evhost_list;\n+\t\twhile (v) {\n+\t\t\tif (v-\u003efrom_ss_policy) {\n+\t\t\t\tstruct lws_vhost *vh \u003d v-\u003evhost_next;\n+\t\t\t\tlwsl_debug(\u0022%s: destroying vh %p\u005cn\u0022, __func__, v);\n+\t\t\t\tlws_vhost_destroy(v);\n+\t\t\t\tv \u003d vh;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tv \u003d v-\u003evhost_next;\n+\t\t}\n+\n+\t\tlws_check_deferred_free(context, 0, 1);\n+\t}\n+\n+\tcontext-\u003epss_policies \u003d args-\u003eheads[LTY_POLICY].p;\n+\tcontext-\u003eac_policy \u003d args-\u003eac;\n+\n+\tlws_humanize(buf, sizeof(buf), lwsac_total_alloc(args-\u003eac),\n+\t\t\thumanize_schema_si_bytes);\n+\tif (lwsac_total_alloc(args-\u003eac))\n+\t\tm \u003d (int)((lwsac_total_overhead(args-\u003eac) * 100) /\n+\t\t\t\tlwsac_total_alloc(args-\u003eac));\n+\telse\n+\t\tm \u003d 0;\n+\n+\tlwsl_notice(\u0022%s: %s, pad %d%c: %s\u005cn\u0022, __func__, buf, m, '%', name);\n+\n+\t/* Create vhosts for each type of trust store */\n+\n+\tts \u003d args-\u003eheads[LTY_TRUSTSTORE].t;\n+\twhile (ts) {\n+\t\tstruct lws_context_creation_info i;\n+\n+\t\tmemset(\u0026i, 0, sizeof(i));\n+\n+\t\t/*\n+\t\t * We get called from context creation... instantiates\n+\t\t * vhosts with client tls contexts set up for each unique CA.\n+\t\t *\n+\t\t * Create the vhost with the first (mandatory) entry in the\n+\t\t * trust store...\n+\t\t */\n+\n+\t\tv \u003d lws_get_vhost_by_name(context, ts-\u003ename);\n+\t\tif (!v) {\n+\t\t\tint n;\n+\n+\t\t\ti.options \u003d context-\u003eoptions;\n+\t\t\ti.vhost_name \u003d ts-\u003ename;\n+\t\t\tlwsl_debug(\u0022%s: %s\u005cn\u0022, __func__, i.vhost_name);\n+\t\t\ti.client_ssl_ca_mem \u003d ts-\u003essx509[0]-\u003eca_der;\n+\t\t\ti.client_ssl_ca_mem_len \u003d ts-\u003essx509[0]-\u003eca_der_len;\n+\t\t\ti.port \u003d CONTEXT_PORT_NO_LISTEN;\n+\t\t\tlwsl_info(\u0022%s: %s trust store initial '%s'\u005cn\u0022, __func__,\n+\t\t\t\t ts-\u003ename, ts-\u003essx509[0]-\u003evhost_name);\n+\n+\t\t\tv \u003d lws_create_vhost(context, \u0026i);\n+\t\t\tif (!v) {\n+\t\t\t\tlwsl_err(\u0022%s: failed to create vhost %s\u005cn\u0022,\n+\t\t\t\t\t __func__, ts-\u003ename);\n+\t\t\t\tret \u003d 1;\n+\t\t\t} else\n+\t\t\t\tv-\u003efrom_ss_policy \u003d 1;\n+\n+\t\t\tfor (n \u003d 1; v \u0026\u0026 n \u003c ts-\u003ecount; n++) {\n+\t\t\t\tlwsl_info(\u0022%s: add '%s' to trust store\u005cn\u0022,\n+\t\t\t\t\t __func__, ts-\u003essx509[n]-\u003evhost_name);\n+\t\t\t\tif (lws_tls_client_vhost_extra_cert_mem(v,\n+\t\t\t\t\t\tts-\u003essx509[n]-\u003eca_der,\n+\t\t\t\t\t\tts-\u003essx509[n]-\u003eca_der_len)) {\n+\t\t\t\t\tlwsl_err(\u0022%s: add extra cert failed\u005cn\u0022,\n+\t\t\t\t\t\t\t__func__);\n+\t\t\t\t\tret \u003d 1;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\n+\t\tts \u003d ts-\u003enext;\n+\t}\n+\n+#if defined(LWS_WITH_SOCKS5)\n+\n+\t/*\n+\t * ... we need to go through every vhost updating its understanding of\n+\t * which socks5 proxy to use...\n+\t */\n+\n+\tv \u003d context-\u003evhost_list;\n+\twhile (v) {\n+\t\tlws_set_socks(v, args-\u003esocks5_proxy);\n+\t\tv \u003d v-\u003evhost_next;\n+\t}\n+\tif (context-\u003evhost_system)\n+\t\tlws_set_socks(context-\u003evhost_system, args-\u003esocks5_proxy);\n+\n+\tif (args-\u003esocks5_proxy)\n+\t\tlwsl_notice(\u0022%s: global socks5 proxy: %s\u005cn\u0022, __func__,\n+\t\t\t args-\u003esocks5_proxy);\n+#endif\n+\n+\t/* now we processed the x.509 CAs, we can free all of our originals */\n+\n+\tx \u003d args-\u003eheads[LTY_X509].x;\n+\twhile (x) {\n+\t\t/*\n+\t\t * Free all the DER buffers now they have been parsed into\n+\t\t * tls library X.509 objects\n+\t\t */\n+\t\tlws_free((void *)x-\u003eca_der);\n+\t\tx-\u003eca_der \u003d NULL;\n+\t\tx \u003d x-\u003enext;\n+\t}\n+\n+\t/* and we can discard the parsing args object now, invalidating args */\n+\n+\tlws_free_set_NULL(context-\u003epol_args);\n+\n+\treturn ret;\n+}\n+\n+const lws_ss_policy_t *\n+lws_ss_policy_lookup(const struct lws_context *context, const char *streamtype)\n+{\n+\tconst lws_ss_policy_t *p \u003d context-\u003epss_policies;\n+\n+\tif (!streamtype)\n+\t\treturn NULL;\n+\n+\twhile (p) {\n+\t\tif (!strcmp(p-\u003estreamtype, streamtype))\n+\t\t\treturn p;\n+\t\tp \u003d p-\u003enext;\n+\t}\n+\n+\treturn NULL;\n+}\ndiff --git a/lib/secure-streams/private-lib-secure-streams.h b/lib/secure-streams/private-lib-secure-streams.h\nnew file mode 100644\nindex 0000000..b22211a\n--- /dev/null\n+++ b/lib/secure-streams/private-lib-secure-streams.h\n@@ -0,0 +1,347 @@\n+/*\n+ * libwebsockets - small server side websockets and web server implementation\n+ *\n+ * Copyright (C) 2019 - 2020 Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * Permission is hereby granted, free of charge, to any person obtaining a copy\n+ * of this software and associated documentation files (the \u0022Software\u0022), to\n+ * deal in the Software without restriction, including without limitation the\n+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n+ * sell copies of the Software, and to permit persons to whom the Software is\n+ * furnished to do so, subject to the following conditions:\n+ *\n+ * The above copyright notice and this permission notice shall be included in\n+ * all copies or substantial portions of the Software.\n+ *\n+ * THE SOFTWARE IS PROVIDED \u0022AS IS\u0022, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n+ * IN THE SOFTWARE.\n+ */\n+\n+/*\n+ * Secure Stream state\n+ */\n+\n+typedef enum {\n+\tSSSEQ_IDLE,\n+\tSSSEQ_TRY_CONNECT,\n+\tSSSEQ_TRY_CONNECT_NAUTH,\n+\tSSSEQ_TRY_CONNECT_SAUTH,\n+\tSSSEQ_RECONNECT_WAIT,\n+\tSSSEQ_DO_RETRY,\n+\tSSSEQ_CONNECTED,\n+} lws_ss_seq_state_t;\n+\n+\n+/**\n+ * lws_ss_handle_t: publicly-opaque secure stream object implementation\n+ */\n+\n+typedef struct lws_ss_handle {\n+\tlws_ss_info_t\t\tinfo;\t /**\u003c copy of stream creation info */\n+\tstruct lws_dll2\t\tlist;\t /**\u003c pt lists active ss */\n+\tstruct lws_dll2\t\tto_list; /**\u003c pt lists ss with pending to-s */\n+\n+\tstruct lws_dll2_owner\tsrc_list; /**\u003c sink's list of bound sources */\n+\n+\tstruct lws_context *context; /**\u003c lws context we are created on */\n+\tconst lws_ss_policy_t\t*policy; /**\u003c system policy for stream */\n+\n+\tstruct lws_sequencer\t*seq;\t /**\u003c owning sequencer if any */\n+\tstruct lws\t\t*wsi;\t /**\u003c the stream wsi if any */\n+\n+\tvoid\t\t\t*nauthi; /**\u003c the nauth plugin instance data */\n+\tvoid\t\t\t*sauthi; /**\u003c the sauth plugin instance data */\n+\n+\tlws_ss_metadata_t\t*metadata;\n+\tconst lws_ss_policy_t\t*rideshare;\n+\n+\tstruct lws_ss_handle\t*h_sink; /**\u003c sink we are bound to, or NULL */\n+\tvoid \t\t\t*sink_obj;/**\u003c sink's private object representing us */\n+\n+\tlws_sorted_usec_list_t\tsul;\n+\tlws_ss_tx_ordinal_t\ttxord;\n+\n+\t/* protocol-specific connection helpers */\n+\n+\tunion {\n+\n+\t\t/* ...for http-related protocols... */\n+\n+\t\tstruct {\n+\n+\t\t\t/* common to all http-related protocols */\n+\n+\t\t\t/* incoming multipart parsing */\n+\n+\t\t\tchar boundary[24];\t/* --boundary from headers */\n+\t\t\tuint8_t boundary_len;\t/* length of --boundary */\n+\t\t\tuint8_t boundary_seq;\t/* current match amount */\n+\t\t\tuint8_t boundary_dashes; /* check for -- after */\n+\t\t\tuint8_t boundary_post; /* swallow post CRLF */\n+\n+\t\t\tuint8_t som:1;\t/* SOM has been sent */\n+\t\t\tuint8_t any:1;\t/* any content has been sent */\n+\n+\n+\t\t\tuint8_t good_respcode:1; /* 200 type response code */\n+\n+\t\t\tunion {\n+\t\t\t\tstruct { /* LWSSSP_H1 */\n+\t\t\t\t} h1;\n+\t\t\t\tstruct { /* LWSSSP_H2 */\n+\t\t\t\t} h2;\n+\t\t\t\tstruct { /* LWSSSP_WS */\n+\t\t\t\t} ws;\n+\t\t\t} u;\n+\t\t} http;\n+\n+\t\t/* details for non-http related protocols... */\n+#if defined(LWS_ROLE_MQTT)\n+\t\tstruct {\n+\t\t\tlws_mqtt_topic_elem_t\t\ttopic_qos;\n+\t\t\tlws_mqtt_topic_elem_t\t\tsub_top;\n+\t\t\tlws_mqtt_subscribe_param_t \tsub_info;\n+\t\t} mqtt;\n+#endif\n+\t} u;\n+\n+\tunsigned long\t\twriteable_len;\n+\n+\tlws_ss_constate_t\tconnstate;/**\u003c public connection state */\n+\tlws_ss_seq_state_t\tseqstate; /**\u003c private connection state */\n+\n+\tuint16_t\t\tretry;\t /**\u003c retry / backoff tracking */\n+\tint16_t\t\t\ttemp16;\n+\n+\tuint8_t\t\t\ttsi;\t /**\u003c service thread idx, usually 0 */\n+\tuint8_t\t\t\tsubseq;\t /**\u003c emulate SOM tracking */\n+\tuint8_t\t\t\ttxn_ok;\t /**\u003c 1 \u003d transaction was OK */\n+\n+\tuint8_t\t\t\thanging_som:1;\n+\tuint8_t\t\t\tinside_msg:1;\n+\tuint8_t\t\t\tbeing_serialized:1; /* we are not the consumer */\n+} lws_ss_handle_t;\n+\n+/* connection helper that doesn't need to hang around after connection starts */\n+\n+union lws_ss_contemp {\n+#if defined(LWS_ROLE_MQTT)\n+\tlws_mqtt_client_connect_param_t ccp;\n+#endif\n+};\n+\n+/*\n+ * When allocating the opaque handle, we overallocate for:\n+ *\n+ * 1) policy-\u003enauth_plugin-\u003ealloc (.nauthi) if any\n+ * 2) policy-\u003esauth_plugin-\u003ealloc (.sauthi) if any\n+ * 3) copy of creation info stream type pointed to by info.streamtype... this\n+ * may be arbitrarily long and since it may be coming from socket ipc and be\n+ * temporary at creation time, we need a place for the copy to stay in scope\n+ * 4) copy of info-\u003estreamtype contents\n+ */\n+\n+\n+/* the user object allocation is immediately after the ss object allocation */\n+#define ss_to_userobj(ss) ((void *)\u0026(ss)[1])\n+\n+/*\n+ * serialization parser state\n+ */\n+\n+enum {\n+\tKIND_C_TO_P,\n+\tKIND_SS_TO_P,\n+};\n+\n+struct lws_ss_serialization_parser {\n+\tchar\t\t\tstreamtype[32];\n+\tchar\t\t\trideshare[32];\n+\tchar\t\t\tmetadata_name[32];\n+\n+\tuint64_t\t\tust_pwait;\n+\n+\tlws_ss_metadata_t\t*ssmd;\n+\n+\tint\t\t\tps;\n+\tint\t\t\tctr;\n+\n+\tuint32_t\t\tusd_phandling;\n+\tuint32_t\t\tflags;\n+\tint32_t\t\t\ttemp32;\n+\n+\tint32_t\t\t\ttxcr_out;\n+\tint32_t\t\t\ttxcr_in;\n+\tuint16_t\t\trem;\n+\n+\tuint8_t\t\t\ttype;\n+\tuint8_t\t\t\tfrag1;\n+\tuint8_t\t\t\tslen;\n+\tuint8_t\t\t\trsl_pos;\n+\tuint8_t\t\t\trsl_idx;\n+};\n+\n+/*\n+ * Unlike locally-fulfilled SS, SSS doesn't have to hold metadata on client side\n+ * but pass it through to the proxy. The client side doesn't know the real\n+ * metadata names that are available in the policy (since it's hardcoded in code\n+ * no point passing them back to the client from the policy). Because of that,\n+ * it doesn't know how many to allocate when we create the sspc_handle either.\n+ *\n+ * So we use a linked-list of changed-but-not-yet-proxied metadata allocated\n+ * on the heap and items removed as they are proxied out. Anything on the list\n+ * is sent to the proxy before any requested tx is handled.\n+ *\n+ * This is also used to queue tx credit changes\n+ */\n+\n+typedef struct lws_sspc_metadata {\n+\tlws_dll2_t\tlist;\n+\tchar\t\tname[32]; /* empty string, then actually TCXR */\n+\tsize_t\t\tlen;\n+\tint\t\ttx_cr_adjust;\n+\n+\t/* the value of length .len is overallocated after this */\n+} lws_sspc_metadata_t;\n+\n+\n+typedef struct lws_sspc_handle {\n+\tchar\t\t\trideshare_list[128];\n+\tlws_ss_info_t\t\tssi;\n+\tlws_sorted_usec_list_t\tsul_retry;\n+\n+\tstruct lws_ss_serialization_parser parser;\n+\n+\tlws_dll2_owner_t\tmetadata_owner;\n+\n+\tstruct lws_dll2\t\tclient_list;\n+\tstruct lws_tx_credit\ttxc;\n+\n+\tstruct lws\t\t*cwsi;\n+\n+\tstruct lws_dsh\t\t*dsh;\n+\tstruct lws_context\t*context;\n+\n+\tlws_usec_t\t\tus_earliest_write_req;\n+\n+\tlws_ss_conn_states_t\tstate;\n+\n+\tint16_t\t\t\ttemp16;\n+\n+\tuint32_t\t\tord;\n+\n+\tuint8_t\t\t\trideshare_ofs[4];\n+\tuint8_t\t\t\tconn_req;\n+\tuint8_t\t\t\trsidx;\n+\n+\tuint8_t\t\t\tdestroying:1;\n+} lws_sspc_handle_t;\n+\n+int\n+lws_ss_deserialize_parse(struct lws_ss_serialization_parser *par,\n+\t\t\t struct lws_context *context,\n+\t\t\t struct lws_dsh *dsh, const uint8_t *cp, size_t len,\n+\t\t\t lws_ss_conn_states_t *state, void *parconn,\n+\t\t\t lws_ss_handle_t **pss, lws_ss_info_t *ssi, char client);\n+int\n+lws_ss_serialize_rx_payload(struct lws_dsh *dsh, const uint8_t *buf,\n+\t\t\t size_t len, int flags, const char *rsp);\n+int\n+lws_ss_deserialize_tx_payload(struct lws_dsh *dsh, struct lws *wsi,\n+\t\t\t lws_ss_tx_ordinal_t ord, uint8_t *buf,\n+\t\t\t size_t *len, int *flags);\n+int\n+lws_ss_serialize_state(struct lws_dsh *dsh, lws_ss_constate_t state,\n+\t\t lws_ss_tx_ordinal_t ack);\n+\n+void\n+lws_ss_serialize_state_transition(lws_ss_conn_states_t *state, int new_state);\n+\n+const lws_ss_policy_t *\n+lws_ss_policy_lookup(const struct lws_context *context, const char *streamtype);\n+\n+/* can be used as a cb from lws_dll2_foreach_safe() to destroy ss */\n+int\n+lws_ss_destroy_dll(struct lws_dll2 *d, void *user);\n+\n+int\n+lws_sspc_destroy_dll(struct lws_dll2 *d, void *user);\n+\n+\n+int\n+lws_ss_policy_parse_begin(struct lws_context *context);\n+\n+int\n+lws_ss_policy_parse(struct lws_context *context, const uint8_t *buf, size_t len);\n+\n+int\n+lws_ss_policy_set(struct lws_context *context, const char *name);\n+\n+int\n+lws_ss_policy_parse_abandon(struct lws_context *context);\n+\n+int\n+lws_ss_sys_fetch_policy(struct lws_context *context);\n+\n+int\n+lws_ss_event_helper(lws_ss_handle_t *h, lws_ss_constate_t cs);\n+\n+int\n+lws_ss_backoff(lws_ss_handle_t *h);\n+\n+int\n+lws_ss_set_timeout_us(lws_ss_handle_t *h, lws_usec_t us);\n+\n+void\n+ss_proxy_onward_txcr(void *userobj, int bump);\n+\n+int\n+lws_ss_serialize_txcr(struct lws_dsh *dsh, int txcr);\n+\n+int\n+lws_ss_sys_auth_api_amazon_com(struct lws_context *context);\n+\n+lws_ss_metadata_t *\n+lws_ss_get_handle_metadata(struct lws_ss_handle *h, const char *name);\n+lws_ss_metadata_t *\n+lws_ss_policy_metadata_index(const lws_ss_policy_t *p, size_t index);\n+\n+lws_ss_metadata_t *\n+lws_ss_policy_metadata(const lws_ss_policy_t *p, const char *name);\n+\n+int\n+lws_ss_exp_cb_metadata(void *priv, const char *name, char *out, size_t *pos,\n+\t\t\tsize_t olen, size_t *exp_ofs);\n+\n+typedef int (* const secstream_protocol_connect_munge_t)(lws_ss_handle_t *h,\n+\t\tchar *buf, size_t len, struct lws_client_connect_info *i,\n+\t\tunion lws_ss_contemp *ct);\n+\n+typedef int (* const secstream_protocol_add_txcr_t)(lws_ss_handle_t *h, int add);\n+\n+typedef int (* const secstream_protocol_get_txcr_t)(lws_ss_handle_t *h);\n+\n+struct ss_pcols {\n+\tconst char\t\t\t\t\t*name;\n+\tconst char\t\t\t\t\t*alpn;\n+\tconst char\t\t\t\t\t*protocol_name;\n+\tconst secstream_protocol_connect_munge_t\tmunge;\n+\tconst secstream_protocol_add_txcr_t\t\ttx_cr_add;\n+\tconst secstream_protocol_get_txcr_t\t\ttx_cr_est;\n+};\n+\n+extern const struct ss_pcols ss_pcol_h1;\n+extern const struct ss_pcols ss_pcol_h2;\n+extern const struct ss_pcols ss_pcol_ws;\n+extern const struct ss_pcols ss_pcol_mqtt;\n+\n+extern const struct lws_protocols protocol_secstream_h1;\n+extern const struct lws_protocols protocol_secstream_h2;\n+extern const struct lws_protocols protocol_secstream_ws;\n+extern const struct lws_protocols protocol_secstream_mqtt;\n+\ndiff --git a/lib/secure-streams/protocols/README.md b/lib/secure-streams/protocols/README.md\nnew file mode 100644\nindex 0000000..3c02e8b\n--- /dev/null\n+++ b/lib/secure-streams/protocols/README.md\n@@ -0,0 +1,38 @@\n+# Lws Protocol bindings for Secure Streams\n+\n+This directory contains the code wiring up normal lws protocols\n+to Secure Streams.\n+\n+## The lws_protocols callback\n+\n+This is the normal lws struct lws_protocols callback that handles events and\n+traffic on the lws protocol being supported.\n+\n+The various events and traffic are converted into calls using the Secure\n+Streams api, and Secure Streams events.\n+\n+## The connect_munge helper\n+\n+Different protocols have different semantics in the arguments to the client\n+connect function, this protocol-specific helper is called to munge the\n+connect_info struct to match the details of the protocol selected.\n+\n+The `ss-\u003epolicy-\u003eaux` string is used to hold protocol-specific information\n+passed in the from the policy, eg, the URL path or websockets subprotocol\n+name.\n+\n+## The (library-private) ss_pcols export\n+\n+Each protocol binding exports two things to other parts of lws (they\n+are not exported to user code)\n+\n+ - a struct lws_protocols, including a pointer to the callback\n+\n+ - a struct ss_pcols describing how secure_streams should use, including\n+ a pointer to the related connect_munge helper.\n+\n+In ./lib/core-net/vhost.c, enabled protocols are added to vhost protcols\n+lists so they may be used. And in ./lib/secure-streams/secure-streams.c,\n+enabled struct ss_pcols are listed and checked for matches when the user\n+creates a new Secure Stream.\n+\ndiff --git a/lib/secure-streams/protocols/ss-h1.c b/lib/secure-streams/protocols/ss-h1.c\nnew file mode 100644\nindex 0000000..c9c6185\n--- /dev/null\n+++ b/lib/secure-streams/protocols/ss-h1.c\n@@ -0,0 +1,571 @@\n+/*\n+ * libwebsockets - small server side websockets and web server implementation\n+ *\n+ * Copyright (C) 2019 - 2020 Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * Permission is hereby granted, free of charge, to any person obtaining a copy\n+ * of this software and associated documentation files (the \u0022Software\u0022), to\n+ * deal in the Software without restriction, including without limitation the\n+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n+ * sell copies of the Software, and to permit persons to whom the Software is\n+ * furnished to do so, subject to the following conditions:\n+ *\n+ * The above copyright notice and this permission notice shall be included in\n+ * all copies or substantial portions of the Software.\n+ *\n+ * THE SOFTWARE IS PROVIDED \u0022AS IS\u0022, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n+ * IN THE SOFTWARE.\n+ *\n+ * This is the glue that wires up h1 to Secure Streams.\n+ */\n+\n+#include \u003cprivate-lib-core.h\u003e\n+\n+#if !defined(LWS_PLAT_FREERTOS) || defined(LWS_ROLE_H2)\n+static int\n+ss_http_multipart_parser(lws_ss_handle_t *h, void *in, size_t len)\n+{\n+\tuint8_t *q \u003d (uint8_t *)in;\n+\tint pending_issue \u003d 0, n \u003d 0;\n+\n+\t/* let's stick it in the boundary state machine first */\n+\twhile (n \u003c (int)len) {\n+\t\tif (h-\u003eu.http.boundary_seq !\u003d h-\u003eu.http.boundary_len) {\n+\t\t\tif (q[n] \u003d\u003d h-\u003eu.http.boundary[h-\u003eu.http.boundary_seq])\n+\t\t\t\th-\u003eu.http.boundary_seq++;\n+\t\t\telse {\n+\t\t\t\th-\u003eu.http.boundary_seq \u003d 0;\n+\t\t\t\th-\u003eu.http.boundary_dashes \u003d 0;\n+\t\t\t\th-\u003eu.http.boundary_post \u003d 0;\n+\t\t\t}\n+\t\t\tgoto around;\n+\t\t}\n+\n+\t\t/*\n+\t\t * We already matched the boundary string, now we're\n+\t\t * looking if there's a -- afterwards\n+\t\t */\n+\t\tif (h-\u003eu.http.boundary_dashes \u003c 2) {\n+\t\t\tif (q[n] \u003d\u003d '-') {\n+\t\t\t\th-\u003eu.http.boundary_dashes++;\n+\t\t\t\tgoto around;\n+\t\t\t}\n+\t\t\t/* there was no final -- ... */\n+\t\t}\n+\n+\t\tif (h-\u003eu.http.boundary_dashes \u003d\u003d 2) {\n+\t\t\t/*\n+\t\t\t * It's an EOM boundary: issue pending + multipart EOP\n+\t\t\t */\n+\t\t\tlwsl_debug(\u0022%s: seen EOP, n %d pi %d\u005cn\u0022,\n+\t\t\t\t __func__, n, pending_issue);\n+\t\t\t/*\n+\t\t\t * It's possible we already started the decode before\n+\t\t\t * the end of the last packet. Then there is no\n+\t\t\t * remainder to send.\n+\t\t\t */\n+\t\t\tif (n \u003e\u003d pending_issue + h-\u003eu.http.boundary_len +\n+\t\t\t (h-\u003eu.http.any ? 2 : 0) + 1)\n+\t\t\t\th-\u003einfo.rx(ss_to_userobj(h),\n+\t\t\t\t\t \u0026q[pending_issue],\n+\t\t\t\t\t n - pending_issue -\n+\t\t\t\t\t h-\u003eu.http.boundary_len - 1 -\n+\t\t\t\t\t (h-\u003eu.http.any ? 2 : 0) /* crlf */,\n+\t\t\t\t (!h-\u003eu.http.som ? LWSSS_FLAG_SOM : 0) |\n+\t\t\t\t LWSSS_FLAG_EOM | LWSSS_FLAG_RELATED_END);\n+\n+\t\t\t/*\n+\t\t\t * Peer may not END_STREAM us\n+\t\t\t */\n+\t\t\treturn 0;\n+\t\t\t//return -1;\n+\t\t}\n+\n+\t\t/* how about --boundaryCRLF */\n+\n+\t\tif (h-\u003eu.http.boundary_post \u003c 2) {\n+\t\t\tif ((!h-\u003eu.http.boundary_post \u0026\u0026 q[n] \u003d\u003d '\u005cx0d') ||\n+\t\t\t (h-\u003eu.http.boundary_post \u0026\u0026 q[n] \u003d\u003d '\u005cx0a')) {\n+\t\t\t\th-\u003eu.http.boundary_post++;\n+\t\t\t\tgoto around;\n+\t\t\t}\n+\t\t\t/* there was no final CRLF ... it's wrong */\n+\n+\t\t\treturn -1;\n+\t\t}\n+\t\tif (h-\u003eu.http.boundary_post !\u003d 2)\n+\t\t\tgoto around;\n+\n+\t\t/*\n+\t\t * We have a starting \u0022--boundaryCRLF\u0022 or intermediate\n+\t\t * \u0022CRLF--boundaryCRLF\u0022 boundary\n+\t\t */\n+\t\tlwsl_debug(\u0022%s: b_post \u003d 2 (pi %d)\u005cn\u0022, __func__, pending_issue);\n+\t\th-\u003eu.http.boundary_seq \u003d 0;\n+\t\th-\u003eu.http.boundary_post \u003d 0;\n+\n+\t\tif (n \u003e\u003d pending_issue \u0026\u0026 (h-\u003eu.http.any || !h-\u003eu.http.som)) {\n+\t\t\t/* Intermediate... do the EOM */\n+\t\t\tlwsl_debug(\u0022%s: seen interm EOP n %d pi %d\u005cn\u0022, __func__,\n+\t\t\t\t n, pending_issue);\n+\t\t\t/*\n+\t\t\t * It's possible we already started the decode before\n+\t\t\t * the end of the last packet. Then there is no\n+\t\t\t * remainder to send.\n+\t\t\t */\n+\t\t\tif (n \u003e\u003d pending_issue + h-\u003eu.http.boundary_len +\n+\t\t\t (h-\u003eu.http.any ? 2 : 0))\n+\t\t\t\th-\u003einfo.rx(ss_to_userobj(h), \u0026q[pending_issue],\n+\t\t\t\t\t n - pending_issue -\n+\t\t\t\t\t h-\u003eu.http.boundary_len -\n+\t\t\t\t\t (h-\u003eu.http.any ? 2 /* crlf */ : 0),\n+\t\t\t\t\t (!h-\u003eu.http.som ? LWSSS_FLAG_SOM : 0) |\n+\t\t\t\t\t LWSSS_FLAG_EOM);\n+\t\t}\n+\n+\t\t/* Next message starts after this boundary */\n+\n+\t\tpending_issue \u003d n;\n+\t\th-\u003eu.http.som \u003d 0;\n+\n+around:\n+\t\tn++;\n+\t}\n+\n+\tif (pending_issue !\u003d n) {\n+\t\th-\u003einfo.rx(ss_to_userobj(h), \u0026q[pending_issue], n - pending_issue,\n+\t\t\t (!h-\u003eu.http.som ? LWSSS_FLAG_SOM : 0));\n+\t\th-\u003eu.http.any \u003d 1;\n+\t\th-\u003eu.http.som \u003d 1;\n+\t}\n+\n+\treturn 0;\n+}\n+#endif\n+\n+static const uint8_t blob_idx[] \u003d {\n+\tLWS_SYSBLOB_TYPE_AUTH,\n+\tLWS_SYSBLOB_TYPE_DEVICE_SERIAL,\n+\tLWS_SYSBLOB_TYPE_DEVICE_FW_VERSION,\n+\tLWS_SYSBLOB_TYPE_DEVICE_TYPE,\n+};\n+\n+int\n+secstream_h1(struct lws *wsi, enum lws_callback_reasons reason, void *user,\n+\t void *in, size_t len)\n+{\n+\tlws_ss_handle_t *h \u003d (lws_ss_handle_t *)lws_get_opaque_user_data(wsi);\n+\tuint8_t buf[LWS_PRE + 1520], *p \u003d \u0026buf[LWS_PRE],\n+\t\t*end \u003d \u0026buf[sizeof(buf) - 1];\n+\tint f \u003d 0, m, status;\n+\tsize_t buflen;\n+\n+\tswitch (reason) {\n+\n+\t/* because we are protocols[0] ... */\n+\tcase LWS_CALLBACK_CLIENT_CONNECTION_ERROR:\n+\t\tassert(h);\n+\t\tassert(h-\u003epolicy);\n+\t\tlwsl_info(\u0022%s: h: %p, %s CLIENT_CONNECTION_ERROR: %s\u005cn\u0022, __func__,\n+\t\t\t h, h-\u003epolicy-\u003estreamtype, in ? (char *)in : \u0022(null)\u0022);\n+\t\tlws_ss_event_helper(h, LWSSSCS_UNREACHABLE);\n+\t\th-\u003ewsi \u003d NULL;\n+\t\tlws_ss_backoff(h);\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_CLOSED_CLIENT_HTTP:\n+\t\tif (!h)\n+\t\t\tbreak;\n+\t\tlwsl_info(\u0022%s: h: %p, %s LWS_CALLBACK_CLOSED_CLIENT_HTTP\u005cn\u0022,\n+\t\t\t __func__, h,\n+\t\t\t h-\u003epolicy ? h-\u003epolicy-\u003estreamtype : \u0022no policy\u0022);\n+\t\th-\u003ewsi \u003d NULL;\n+\t\t//bad \u003d status !\u003d 200;\n+\t\t//lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */\n+\t\tif (h-\u003epolicy \u0026\u0026 !(h-\u003epolicy-\u003eflags \u0026 LWSSSPOLF_OPPORTUNISTIC) \u0026\u0026\n+\t\t !h-\u003etxn_ok \u0026\u0026 !wsi-\u003econtext-\u003ebeing_destroyed)\n+\t\t\tlws_ss_backoff(h);\n+\t\tif (lws_ss_event_helper(h, LWSSSCS_DISCONNECTED))\n+\t\t\tlws_ss_destroy(\u0026h);\n+\t\tbreak;\n+\n+\n+\tcase LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:\n+\t\tstatus \u003d lws_http_client_http_response(wsi);\n+\t\tlwsl_info(\u0022%s: LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: %d\u005cn\u0022, __func__, status);\n+\t//\tif (!status)\n+\t\t\t/* it's just telling use we connected / joined the nwsi */\n+\t//\t\tbreak;\n+\t\th-\u003eu.http.good_respcode \u003d (status \u003e\u003d 200 \u0026\u0026 status \u003c 300);\n+\t\t// lwsl_err(\u0022%s: good resp %d %d\u005cn\u0022, __func__, status, h-\u003eu.http.good_respcode);\n+\n+\t\tif (h-\u003eu.http.good_respcode)\n+\t\t\tlwsl_info(\u0022%s: Connected streamtype %s, %d\u005cn\u0022, __func__,\n+\t\t\t\t h-\u003epolicy-\u003estreamtype, status);\n+\t\telse\n+\t\t\tlwsl_warn(\u0022%s: Connected streamtype %s, BAD %d\u005cn\u0022, __func__,\n+\t\t\t\t h-\u003epolicy-\u003estreamtype, status);\n+\n+\t\th-\u003ehanging_som \u003d 0;\n+\n+\t\th-\u003eretry \u003d 0;\n+\t\th-\u003eseqstate \u003d SSSEQ_CONNECTED;\n+\t\tlws_ss_set_timeout_us(h, LWS_SET_TIMER_USEC_CANCEL);\n+\t\tlws_ss_event_helper(h, LWSSSCS_CONNECTED);\n+\n+\t\t/*\n+\t\t * Since it's an http transaction we initiated... this is\n+\t\t * proof of connection validity\n+\t\t */\n+\t\tlws_validity_confirmed(wsi);\n+\n+#if !defined(LWS_PLAT_FREERTOS) || defined(LWS_ROLE_H2)\n+\n+\t\tif (lws_hdr_copy(wsi, (char *)buf, sizeof(buf),\n+\t\t\t\t WSI_TOKEN_HTTP_CONTENT_TYPE) \u003e 0 \u0026\u0026\n+\t\t/* multipart/form-data;\n+\t\t * boundary\u003d----WebKitFormBoundarycc7YgAPEIHvgE9Bf */\n+\n+\t\t (!strncmp((char *)buf, \u0022multipart/form-data\u0022, 19) ||\n+\t\t !strncmp((char *)buf, \u0022multipart/related\u0022, 17))) {\n+\t\t\tstruct lws_tokenize ts;\n+\t\t\tlws_tokenize_elem e;\n+\n+\t\t\t// puts((const char *)buf);\n+\n+\t\t\tmemset(\u0026ts, 0, sizeof(ts));\n+\t\t\tts.start \u003d (char *)buf;\n+\t\t\tts.len \u003d strlen(ts.start);\n+\t\t\tts.flags \u003d LWS_TOKENIZE_F_RFC7230_DELIMS |\n+\t\t\t\t\tLWS_TOKENIZE_F_SLASH_NONTERM |\n+\t\t\t\t\tLWS_TOKENIZE_F_MINUS_NONTERM;\n+\n+\t\t\th-\u003eu.http.boundary[0] \u003d '\u005c0';\n+\t\t\tdo {\n+\t\t\t\te \u003d lws_tokenize(\u0026ts);\n+\t\t\t\tif (e \u003d\u003d LWS_TOKZE_TOKEN_NAME_EQUALS \u0026\u0026\n+\t\t\t\t !strncmp(ts.token, \u0022boundary\u0022, 8) \u0026\u0026\n+\t\t\t\t ts.token_len \u003d\u003d 8) {\n+\t\t\t\t\te \u003d lws_tokenize(\u0026ts);\n+\t\t\t\t\tif (e !\u003d LWS_TOKZE_TOKEN)\n+\t\t\t\t\t\tgoto malformed;\n+\t\t\t\t\th-\u003eu.http.boundary[0] \u003d '\u005cx0d';\n+\t\t\t\t\th-\u003eu.http.boundary[1] \u003d '\u005cx0a';\n+\t\t\t\t\th-\u003eu.http.boundary[2] \u003d '-';\n+\t\t\t\t\th-\u003eu.http.boundary[3] \u003d '-';\n+\t\t\t\t\tlws_strnncpy(h-\u003eu.http.boundary + 4,\n+\t\t\t\t\t\t ts.token, ts.token_len,\n+\t\t\t\t\t\t sizeof(h-\u003eu.http.boundary) - 4);\n+\t\t\t\t\th-\u003eu.http.boundary_len \u003d ts.token_len + 4;\n+\t\t\t\t\th-\u003eu.http.boundary_seq \u003d 2;\n+\t\t\t\t\th-\u003eu.http.boundary_dashes \u003d 0;\n+\t\t\t\t}\n+\t\t\t} while (e \u003e 0);\n+\t\t\tlwsl_info(\u0022%s: multipart boundary '%s' len %d\u005cn\u0022, __func__,\n+\t\t\t\t\th-\u003eu.http.boundary, h-\u003eu.http.boundary_len);\n+\n+\t\t\t/* inform the ss that a related message group begins */\n+\n+\t\t\tif (h-\u003eu.http.boundary[0])\n+\t\t\t\th-\u003einfo.rx(ss_to_userobj(h), NULL, 0,\n+\t\t\t\t\t LWSSS_FLAG_RELATED_START);\n+\n+\t\t\t// lws_header_table_detach(wsi, 0);\n+\t\t}\n+\t\tbreak;\n+malformed:\n+\t\tlwsl_notice(\u0022%s: malformed multipart header\u005cn\u0022, __func__);\n+\t\treturn -1;\n+#else\n+\t\tbreak;\n+#endif\n+\n+\tcase LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:\n+\t\tif (h-\u003ewriteable_len)\n+\t\t\twsi-\u003ehttp.writeable_len \u003d h-\u003ewriteable_len;\n+\n+\t\t{\n+\t\t\tuint8_t **p \u003d (uint8_t **)in, *end \u003d (*p) + len,\n+\t\t\t\t*oin \u003d *(uint8_t **)in;\n+\n+\t\t/*\n+\t\t * blob-based headers\n+\t\t */\n+\n+\t\tfor (m \u003d 0; m \u003c _LWSSS_HBI_COUNT; m++) {\n+\t\t\tint o \u003d 0, n;\n+\n+\t\t\tif (!h-\u003epolicy-\u003eu.http.blob_header[m])\n+\t\t\t\tcontinue;\n+\n+\t\t\tif (m \u003d\u003d LWSSS_HBI_AUTH \u0026\u0026\n+\t\t\t h-\u003epolicy-\u003eu.http.auth_preamble)\n+\t\t\t\to \u003d lws_snprintf((char *)buf, sizeof(buf), \u0022%s\u0022,\n+\t\t\t\t\th-\u003epolicy-\u003eu.http.auth_preamble);\n+\n+\t\t\tif (o \u003e (int)sizeof(buf) - 2)\n+\t\t\t\treturn -1;\n+\n+\t\t\tbuflen \u003d sizeof(buf) - o - 2;\n+\t\t\tn \u003d lws_system_blob_get(\n+\t\t\t\tlws_system_get_blob(wsi-\u003econtext, blob_idx[m], 0),\n+\t\t\t\t\t\t buf + o, \u0026buflen, 0);\n+\t\t\tif (n \u003c 0)\n+\t\t\t\treturn -1;\n+\n+\t\t\tbuf[o + buflen] \u003d '\u005c0';\n+\t\t\tlwsl_debug(\u0022%s: adding blob %d: %s\u005cn\u0022, __func__, m, buf);\n+\n+\t\t\tif (lws_add_http_header_by_name(wsi,\n+\t\t\t\t\t (uint8_t *)h-\u003epolicy-\u003eu.http.blob_header[m],\n+\t\t\t\t\t buf, buflen + o, p, end))\n+\t\t\t\treturn -1;\n+\t\t}\n+\n+\t\t/*\n+\t\t * metadata-based headers\n+\t\t */\n+\n+\t\tfor (m \u003d 0; m \u003c h-\u003epolicy-\u003emetadata_count; m++) {\n+\t\t\tlws_ss_metadata_t *polmd;\n+\n+\t\t\t/* has to have a header string listed */\n+\t\t\tif (!h-\u003emetadata[m].value)\n+\t\t\t\tcontinue;\n+\n+\t\t\tpolmd \u003d lws_ss_policy_metadata_index(h-\u003epolicy, m);\n+\n+\t\t\tassert(polmd);\n+\t\t\t/* has to have a value */\n+\t\t\tif (polmd-\u003evalue \u0026\u0026 ((uint8_t *)polmd-\u003evalue)[0]) {\n+\t\t\t\tif (lws_add_http_header_by_name(wsi,\n+\t\t\t\t\t\tpolmd-\u003evalue,\n+\t\t\t\t\t\th-\u003emetadata[m].value,\n+\t\t\t\t\t\th-\u003emetadata[m].length, p, end))\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t}\n+\n+\t\t/*\n+\t\t * Content-length on POST if we have the length information\n+\t\t */\n+\n+\t\tif (!strcmp(h-\u003epolicy-\u003eu.http.method, \u0022POST\u0022) \u0026\u0026\n+\t\t wsi-\u003ehttp.writeable_len) {\n+\t\t\tif (!(h-\u003epolicy-\u003eflags \u0026\n+\t\t\t\tLWSSSPOLF_HTTP_NO_CONTENT_LENGTH)) {\n+\t\t\t\tint n \u003d lws_snprintf((char *)buf, 20, \u0022%u\u0022,\n+\t\t\t\t\t(unsigned int)wsi-\u003ehttp.writeable_len);\n+\t\t\t\tif (lws_add_http_header_by_token(wsi,\n+\t\t\t\t\t\tWSI_TOKEN_HTTP_CONTENT_LENGTH,\n+\t\t\t\t\t\tbuf, n, p, end))\n+\t\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t\tlws_client_http_body_pending(wsi, 1);\n+\t\t}\n+\n+\t\t(void)oin;\n+\t\t// if (*p !\u003d oin)\n+\t\t//\tlwsl_hexdump_notice(oin, lws_ptr_diff(*p, oin));\n+\n+\t\t}\n+\n+\t\tbreak;\n+\n+\t/* chunks of chunked content, with header removed */\n+\tcase LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:\n+\t\tlwsl_debug(\u0022%s: RECEIVE_CLIENT_HTTP_READ: read %d\u005cn\u0022,\n+\t\t\t\t__func__, (int)len);\n+\t\tif (!h)\n+\t\t\treturn 0;\n+\n+#if !defined(LWS_PLAT_FREERTOS) || defined(LWS_ROLE_H2)\n+\t\tif (h-\u003eu.http.boundary[0])\n+\t\t\treturn ss_http_multipart_parser(h, in, len);\n+#endif\n+\n+\t\tif (!h-\u003esubseq) {\n+\t\t\tf |\u003d LWSSS_FLAG_SOM;\n+\t\t\th-\u003ehanging_som \u003d 1;\n+\t\t\th-\u003esubseq \u003d 1;\n+\t\t}\n+\n+\t//\tlwsl_notice(\u0022%s: HTTP_READ: client side sent len %d fl 0x%x\u005cn\u0022,\n+\t//\t\t __func__, (int)len, (int)f);\n+\n+\t\th-\u003einfo.rx(ss_to_userobj(h), (const uint8_t *)in, len, f);\n+\n+\t\treturn 0; /* don't passthru */\n+\n+\t/* uninterpreted http content */\n+\tcase LWS_CALLBACK_RECEIVE_CLIENT_HTTP:\n+\t\t{\n+\t\t\tchar *px \u003d (char *)buf + LWS_PRE; /* guarantees LWS_PRE */\n+\t\t\tint lenx \u003d sizeof(buf) - LWS_PRE;\n+\n+\t\t\tif (lws_http_client_read(wsi, \u0026px, \u0026lenx) \u003c 0)\n+\t\t\t\treturn -1;\n+\t\t}\n+\t\tlws_set_timeout(wsi, 99, 30);\n+\n+\t\treturn 0; /* don't passthru */\n+\n+\tcase LWS_CALLBACK_COMPLETED_CLIENT_HTTP:\n+\t\tlwsl_debug(\u0022%s: LWS_CALLBACK_COMPLETED_CLIENT_HTTP\u005cn\u0022, __func__);\n+\t\tif (h-\u003ehanging_som)\n+\t\t\th-\u003einfo.rx(ss_to_userobj(h), NULL, 0, LWSSS_FLAG_EOM);\n+\n+\t\twsi-\u003ehttp.writeable_len \u003d h-\u003ewriteable_len \u003d 0;\n+\n+\t\tif (h-\u003eu.http.good_respcode)\n+\t\t\tlws_ss_event_helper(h, LWSSSCS_QOS_ACK_REMOTE);\n+\t\telse\n+\t\t\tlws_ss_event_helper(h, LWSSSCS_QOS_NACK_REMOTE);\n+\n+\t\th-\u003ewsi \u003d NULL;\n+\t\th-\u003etxn_ok \u003d 1;\n+\t\t//bad \u003d status !\u003d 200;\n+\t\tlws_cancel_service(lws_get_context(wsi)); /* abort poll wait */\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_CLIENT_HTTP_WRITEABLE:\n+\t\tlwsl_info(\u0022%s: LWS_CALLBACK_CLIENT_HTTP_WRITEABLE\u005cn\u0022, __func__);\n+\t\tif (!h)\n+\t\t\treturn 0;\n+\n+\t\tif (!h-\u003erideshare)\n+\t\t\th-\u003erideshare \u003d h-\u003epolicy;\n+\n+#if !defined(LWS_PLAT_FREERTOS) || defined(LWS_ROLE_H2)\n+\t\tif (!h-\u003einside_msg \u0026\u0026 h-\u003erideshare-\u003eu.http.multipart_name)\n+\t\t\tlws_client_http_multipart(wsi,\n+\t\t\t\th-\u003erideshare-\u003eu.http.multipart_name,\n+\t\t\t\th-\u003erideshare-\u003eu.http.multipart_filename,\n+\t\t\t\th-\u003erideshare-\u003eu.http.multipart_content_type,\n+\t\t\t\t(char **)\u0026p, (char *)end);\n+\n+\t\tbuflen \u003d lws_ptr_diff(end, p);\n+\t\tif (h-\u003epolicy-\u003eu.http.multipart_name)\n+\t\t\tbuflen -\u003d 24; /* allow space for end of multipart */\n+\n+#endif\n+\n+\t\tif (h-\u003einfo.tx(ss_to_userobj(h), h-\u003etxord++, p, \u0026buflen, \u0026f)) {\n+\t\t\t/* don't want to send anything */\n+\t\t\tlwsl_debug(\u0022%s: dont want to write\u005cn\u0022, __func__);\n+\t\t\treturn 0;\n+\t\t}\n+\n+\t\tlwsl_info(\u0022%s: WRITEABLE: user tx says len %d fl 0x%x\u005cn\u0022,\n+\t\t\t __func__, (int)buflen, (int)f);\n+\n+\t\tp +\u003d buflen;\n+\n+\t\tif (f \u0026 LWSSS_FLAG_EOM) {\n+#if !defined(LWS_PLAT_FREERTOS) || defined(LWS_ROLE_H2)\n+\t\t\t/* end of rideshares */\n+\t\t\tif (!h-\u003erideshare-\u003erideshare_streamtype) {\n+\t\t\t\tlws_client_http_body_pending(wsi, 0);\n+\t\t\t\tif (h-\u003erideshare-\u003eu.http.multipart_name)\n+\t\t\t\t\tlws_client_http_multipart(wsi, NULL, NULL, NULL,\n+\t\t\t\t\t\t(char **)\u0026p, (char *)end);\n+\t\t\t} else {\n+#endif\n+\t\t\t\th-\u003erideshare \u003d lws_ss_policy_lookup(wsi-\u003econtext,\n+\t\t\t\t\t\th-\u003erideshare-\u003erideshare_streamtype);\n+\t\t\t\tlws_callback_on_writable(wsi);\n+#if !defined(LWS_PLAT_FREERTOS) || defined(LWS_ROLE_H2)\n+\t\t\t}\n+#endif\n+\n+\t\t\th-\u003einside_msg \u003d 0;\n+\t\t} else {\n+\t\t\t/* otherwise we can spin with zero length writes */\n+\t\t\tif (!f \u0026\u0026 !lws_ptr_diff(p, buf + LWS_PRE))\n+\t\t\t\tbreak;\n+\t\t\th-\u003einside_msg \u003d 1;\n+\t\t\tlws_callback_on_writable(wsi);\n+\t\t}\n+\n+\t\tlwsl_info(\u0022%s: lws_write %d %d\u005cn\u0022, __func__,\n+\t\t\t lws_ptr_diff(p, buf + LWS_PRE), f);\n+\n+\t\tif (lws_write(wsi, buf + LWS_PRE, lws_ptr_diff(p, buf + LWS_PRE),\n+\t\t\t LWS_WRITE_HTTP) !\u003d (int)lws_ptr_diff(p, buf + LWS_PRE)) {\n+\t\t\tlwsl_err(\u0022%s: write failed\u005cn\u0022, __func__);\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tlws_set_timeout(wsi, 0, 0);\n+\t\tbreak;\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn lws_callback_http_dummy(wsi, reason, user, in, len);\n+}\n+\n+const struct lws_protocols protocol_secstream_h1 \u003d {\n+\t\u0022lws-secstream-h1\u0022,\n+\tsecstream_h1,\n+\t0,\n+\t0,\n+};\n+\n+/*\n+ * Munge connect info according to protocol-specific considerations... this\n+ * usually means interpreting aux in a protocol-specific way and using the\n+ * pieces at connection setup time, eg, http url pieces.\n+ *\n+ * len bytes of buf can be used for things with scope until after the actual\n+ * connect.\n+ */\n+\n+static int\n+secstream_connect_munge_h1(lws_ss_handle_t *h, char *buf, size_t len,\n+\t\t\t struct lws_client_connect_info *i,\n+\t\t\t union lws_ss_contemp *ct)\n+{\n+\tsize_t used_in, used_out;\n+\tlws_strexp_t exp;\n+\n+\tif (!h-\u003epolicy-\u003eu.http.url)\n+\t\treturn 0;\n+\n+#if !defined(LWS_PLAT_FREERTOS) || defined(LWS_ROLE_H2)\n+\tif (h-\u003epolicy-\u003eflags \u0026 LWSSSPOLF_HTTP_MULTIPART)\n+\t\ti-\u003essl_connection |\u003d LCCSCF_HTTP_MULTIPART_MIME;\n+\n+\tif (h-\u003epolicy-\u003eflags \u0026 LWSSSPOLF_HTTP_X_WWW_FORM_URLENCODED)\n+\t\ti-\u003essl_connection |\u003d LCCSCF_HTTP_X_WWW_FORM_URLENCODED;\n+#endif\n+\n+\t/* protocol aux is the path part */\n+\n+\ti-\u003epath \u003d buf;\n+\tbuf[0] \u003d '/';\n+\n+\tlws_strexp_init(\u0026exp, (void *)h, lws_ss_exp_cb_metadata, buf + 1, len - 1);\n+\n+\tif (lws_strexp_expand(\u0026exp, h-\u003epolicy-\u003eu.http.url,\n+\t\t\t strlen(h-\u003epolicy-\u003eu.http.url),\n+\t\t\t \u0026used_in, \u0026used_out) !\u003d LSTRX_DONE)\n+\t\treturn 1;\n+\n+\treturn 0;\n+}\n+\n+\n+const struct ss_pcols ss_pcol_h1 \u003d {\n+\t\u0022h1\u0022,\n+\t\u0022http/1.1\u0022,\n+\t\u0022lws-secstream-h1\u0022,\n+\tsecstream_connect_munge_h1,\n+\tNULL\n+};\ndiff --git a/lib/secure-streams/protocols/ss-h2.c b/lib/secure-streams/protocols/ss-h2.c\nnew file mode 100644\nindex 0000000..941183c\n--- /dev/null\n+++ b/lib/secure-streams/protocols/ss-h2.c\n@@ -0,0 +1,183 @@\n+/*\n+ * libwebsockets - small server side websockets and web server implementation\n+ *\n+ * Copyright (C) 2019 - 2020 Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * Permission is hereby granted, free of charge, to any person obtaining a copy\n+ * of this software and associated documentation files (the \u0022Software\u0022), to\n+ * deal in the Software without restriction, including without limitation the\n+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n+ * sell copies of the Software, and to permit persons to whom the Software is\n+ * furnished to do so, subject to the following conditions:\n+ *\n+ * The above copyright notice and this permission notice shall be included in\n+ * all copies or substantial portions of the Software.\n+ *\n+ * THE SOFTWARE IS PROVIDED \u0022AS IS\u0022, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n+ * IN THE SOFTWARE.\n+ */\n+\n+#include \u003cprivate-lib-core.h\u003e\n+\n+extern int\n+secstream_h1(struct lws *wsi, enum lws_callback_reasons reason, void *user,\n+\t void *in, size_t len);\n+\n+static int\n+secstream_h2(struct lws *wsi, enum lws_callback_reasons reason, void *user,\n+\t void *in, size_t len)\n+{\n+\tlws_ss_handle_t *h \u003d (lws_ss_handle_t *)lws_get_opaque_user_data(wsi);\n+\tint n;\n+\n+\tswitch (reason) {\n+\n+\tcase LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:\n+\n+#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API)\n+\t\tif (h-\u003ebeing_serialized) {\n+\t\t\t/*\n+\t\t\t * We are the proxy-side SS for a remote client... we\n+\t\t\t * need to inform the client about the initial tx credit\n+\t\t\t * to write to it that the remote h2 server set up\n+\t\t\t */\n+\t\t\tlwsl_info(\u0022%s: reporting initial tx cr from server %d\u005cn\u0022,\n+\t\t\t\t __func__, wsi-\u003etxc.tx_cr);\n+\t\t\tss_proxy_onward_txcr((void *)\u0026h[1], wsi-\u003etxc.tx_cr);\n+\t\t}\n+#endif\n+\n+\t\tn \u003d secstream_h1(wsi, reason, user, in, len);\n+\n+\t\tif (!n \u0026\u0026 (h-\u003epolicy-\u003eflags \u0026 LWSSSPOLF_LONG_POLL)) {\n+\t\t\tlwsl_notice(\u0022%s: h2 client %p entering LONG_POLL\u005cn\u0022,\n+\t\t\t\t\t__func__, wsi);\n+\t\t\tlws_h2_client_stream_long_poll_rxonly(wsi);\n+\t\t}\n+\t\treturn n;\n+\n+\tcase LWS_CALLBACK_COMPLETED_CLIENT_HTTP:\n+\t\t// lwsl_err(\u0022%s: h2 COMPLETED_CLIENT_HTTP\u005cn\u0022, __func__);\n+\t\th-\u003einfo.rx(ss_to_userobj(h), NULL, 0, LWSSS_FLAG_EOM);\n+\t\th-\u003ewsi \u003d NULL;\n+\t\th-\u003etxn_ok \u003d 1;\n+\t\t//bad \u003d status !\u003d 200;\n+\t\tlws_cancel_service(lws_get_context(wsi)); /* abort poll wait */\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_WSI_TX_CREDIT_GET:\n+\t\t/*\n+\t\t * The peer has sent us additional tx credit...\n+\t\t */\n+\t\tlwsl_info(\u0022%s: LWS_CALLBACK_WSI_TX_CREDIT_GET: %d\u005cn\u0022,\n+\t\t\t __func__, (int32_t)len);\n+\n+#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API)\n+\t\tif (h-\u003ebeing_serialized)\n+\t\t\t/* we are the proxy-side SS for a remote client */\n+\t\t\tss_proxy_onward_txcr((void *)\u0026h[1], (int)len);\n+#endif\n+\t\tbreak;\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn secstream_h1(wsi, reason, user, in, len);\n+}\n+\n+const struct lws_protocols protocol_secstream_h2 \u003d {\n+\t\u0022lws-secstream-h2\u0022,\n+\tsecstream_h2,\n+\t0,\n+\t0,\n+};\n+\n+/*\n+ * Munge connect info according to protocol-specific considerations... this\n+ * usually means interpreting aux in a protocol-specific way and using the\n+ * pieces at connection setup time, eg, http url pieces.\n+ *\n+ * len bytes of buf can be used for things with scope until after the actual\n+ * connect.\n+ */\n+\n+int\n+secstream_connect_munge_h2(lws_ss_handle_t *h, char *buf, size_t len,\n+\t\t\t struct lws_client_connect_info *i,\n+\t\t\t union lws_ss_contemp *ct)\n+{\n+\tif (h-\u003epolicy-\u003eflags \u0026 LWSSSPOLF_QUIRK_NGHTTP2_END_STREAM)\n+\t\ti-\u003essl_connection |\u003d LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM;\n+\n+\tif (h-\u003epolicy-\u003eflags \u0026 LWSSSPOLF_H2_QUIRK_OVERFLOWS_TXCR)\n+\t\ti-\u003essl_connection |\u003d LCCSCF_H2_QUIRK_OVERFLOWS_TXCR;\n+\n+\tif (h-\u003epolicy-\u003eflags \u0026 LWSSSPOLF_HTTP_MULTIPART)\n+\t\ti-\u003essl_connection |\u003d LCCSCF_HTTP_MULTIPART_MIME;\n+\n+\tif (h-\u003epolicy-\u003eflags \u0026 LWSSSPOLF_HTTP_X_WWW_FORM_URLENCODED)\n+\t\ti-\u003essl_connection |\u003d LCCSCF_HTTP_X_WWW_FORM_URLENCODED;\n+\n+\ti-\u003essl_connection |\u003d LCCSCF_PIPELINE;\n+\n+\ti-\u003ealpn \u003d \u0022h2\u0022;\n+\n+\t/* initial peer tx credit */\n+\n+\tif (h-\u003einfo.manual_initial_tx_credit) {\n+\t\ti-\u003essl_connection |\u003d LCCSCF_H2_MANUAL_RXFLOW;\n+\t\ti-\u003emanual_initial_tx_credit \u003d h-\u003einfo.manual_initial_tx_credit;\n+\t\tlwsl_info(\u0022%s: initial txcr %d\u005cn\u0022, __func__,\n+\t\t\t\ti-\u003emanual_initial_tx_credit);\n+\t}\n+\n+\tif (!h-\u003epolicy-\u003eu.http.url)\n+\t\treturn 0;\n+\n+\t/* protocol aux is the path part */\n+\n+\ti-\u003epath \u003d buf;\n+\tlws_snprintf(buf, len, \u0022/%s\u0022, h-\u003epolicy-\u003eu.http.url);\n+\n+\treturn 0;\n+}\n+\n+static int\n+secstream_tx_credit_add_h2(lws_ss_handle_t *h, int add)\n+{\n+\tlwsl_info(\u0022%s: h %p: add %d\u005cn\u0022, __func__, h, add);\n+\tif (h-\u003ewsi)\n+\t\treturn lws_h2_update_peer_txcredit(h-\u003ewsi, LWS_H2_STREAM_SID, add);\n+\n+\treturn 0;\n+}\n+\n+static int\n+secstream_tx_credit_est_h2(lws_ss_handle_t *h)\n+{\n+\tif (h-\u003ewsi) {\n+\t\tlwsl_info(\u0022%s: h %p: est %d\u005cn\u0022, __func__, h,\n+\t\t\t\tlws_h2_get_peer_txcredit_estimate(h-\u003ewsi));\n+\n+\t\treturn lws_h2_get_peer_txcredit_estimate(h-\u003ewsi);\n+\t}\n+\n+\tlwsl_info(\u0022%s: h %p: Unknown (0)\u005cn\u0022, __func__, h);\n+\n+\treturn 0;\n+}\n+\n+const struct ss_pcols ss_pcol_h2 \u003d {\n+\t\u0022h2\u0022,\n+\tNULL,\n+\t\u0022lws-secstream-h2\u0022,\n+\tsecstream_connect_munge_h2,\n+\tsecstream_tx_credit_add_h2,\n+\tsecstream_tx_credit_est_h2\n+};\ndiff --git a/lib/secure-streams/protocols/ss-mqtt.c b/lib/secure-streams/protocols/ss-mqtt.c\nnew file mode 100644\nindex 0000000..2d53c66\n--- /dev/null\n+++ b/lib/secure-streams/protocols/ss-mqtt.c\n@@ -0,0 +1,248 @@\n+/*\n+ * libwebsockets - small server side websockets and web server implementation\n+ *\n+ * Copyright (C) 2019 - 2020 Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * Permission is hereby granted, free of charge, to any person obtaining a copy\n+ * of this software and associated documentation files (the \u0022Software\u0022), to\n+ * deal in the Software without restriction, including without limitation the\n+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n+ * sell copies of the Software, and to permit persons to whom the Software is\n+ * furnished to do so, subject to the following conditions:\n+ *\n+ * The above copyright notice and this permission notice shall be included in\n+ * all copies or substantial portions of the Software.\n+ *\n+ * THE SOFTWARE IS PROVIDED \u0022AS IS\u0022, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n+ * IN THE SOFTWARE.\n+ */\n+\n+#include \u003cprivate-lib-core.h\u003e\n+\n+static int\n+secstream_mqtt(struct lws *wsi, enum lws_callback_reasons reason, void *user,\n+\t void *in, size_t len)\n+{\n+\tlws_ss_handle_t *h \u003d (lws_ss_handle_t *)lws_get_opaque_user_data(wsi);\n+\tlws_mqtt_publish_param_t mqpp, *pmqpp;\n+\tuint8_t buf[LWS_PRE + 1400];\n+\tsize_t buflen;\n+\tint f \u003d 0;\n+\n+\tswitch (reason) {\n+\n+\t/* because we are protocols[0] ... */\n+\tcase LWS_CALLBACK_CLIENT_CONNECTION_ERROR:\n+\t\tlwsl_info(\u0022%s: CLIENT_CONNECTION_ERROR: %s\u005cn\u0022, __func__,\n+\t\t\t in ? (char *)in : \u0022(null)\u0022);\n+\t\tif (!h)\n+\t\t\tbreak;\n+\t\tlws_ss_event_helper(h, LWSSSCS_UNREACHABLE);\n+\t\th-\u003ewsi \u003d NULL;\n+\t\tlws_ss_backoff(h);\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_MQTT_CLIENT_CLOSED:\n+\t\tif (!h)\n+\t\t\tbreak;\n+\t\tf \u003d lws_ss_event_helper(h, LWSSSCS_DISCONNECTED);\n+\t\tif (h-\u003ewsi)\n+\t\t\tlws_set_opaque_user_data(h-\u003ewsi, NULL);\n+\t\th-\u003ewsi \u003d NULL;\n+\t\tif (f) {\n+\t\t\tlws_ss_destroy(\u0026h);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (h-\u003epolicy \u0026\u0026 !(h-\u003epolicy-\u003eflags \u0026 LWSSSPOLF_OPPORTUNISTIC) \u0026\u0026\n+\t\t !h-\u003etxn_ok \u0026\u0026 !wsi-\u003econtext-\u003ebeing_destroyed)\n+\t\t\tlws_ss_backoff(h);\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_MQTT_CLIENT_ESTABLISHED:\n+\t\t/*\n+\t\t * Make sure the handle wsi points to the stream wsi not the\n+\t\t * original nwsi, in the case it was migrated\n+\t\t */\n+\t\th-\u003ewsi \u003d wsi;\n+\t\th-\u003eretry \u003d 0;\n+\t\th-\u003eseqstate \u003d SSSEQ_CONNECTED;\n+\t\tlws_ss_set_timeout_us(h, LWS_SET_TIMER_USEC_CANCEL);\n+\t\tlws_ss_event_helper(h, LWSSSCS_CONNECTED);\n+\t\tif (h-\u003epolicy-\u003eu.mqtt.topic)\n+\t\t\tlws_callback_on_writable(wsi);\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_MQTT_CLIENT_RX:\n+\t\t// lwsl_user(\u0022LWS_CALLBACK_CLIENT_RECEIVE: read %d\u005cn\u0022, (int)len);\n+\t\tif (!h)\n+\t\t\treturn 0;\n+\n+\t\tpmqpp \u003d (lws_mqtt_publish_param_t *)in;\n+\n+\t\tf \u003d 0;\n+\t\tif (!pmqpp-\u003epayload_pos)\n+\t\t\tf |\u003d LWSSS_FLAG_SOM;\n+\t\tif (pmqpp-\u003epayload_pos + len \u003d\u003d pmqpp-\u003epayload_len)\n+\t\t\tf |\u003d LWSSS_FLAG_EOM;\n+\n+\t\th-\u003esubseq \u003d 1;\n+\n+\t\th-\u003einfo.rx(ss_to_userobj(h), (const uint8_t *)pmqpp-\u003epayload,\n+\t\t\t len, f);\n+\n+\t\treturn 0; /* don't passthru */\n+\n+\tcase LWS_CALLBACK_MQTT_SUBSCRIBED:\n+\t\twsi-\u003emqtt-\u003edone_subscribe \u003d 1;\n+\t\tlws_callback_on_writable(wsi);\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_MQTT_ACK:\n+\t\tlws_ss_event_helper(h, LWSSSCS_QOS_ACK_REMOTE);\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_MQTT_CLIENT_WRITEABLE:\n+\t\tif (!h)\n+\t\t\treturn 0;\n+\t\tlwsl_notice(\u0022%s: ss %p: WRITEABLE\u005cn\u0022, __func__, h);\n+\n+\t\tif (h-\u003eseqstate !\u003d SSSEQ_CONNECTED) {\n+\t\t\tlwsl_warn(\u0022%s: seqstate %d\u005cn\u0022, __func__, h-\u003eseqstate);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (h-\u003epolicy-\u003eu.mqtt.subscribe \u0026\u0026 !wsi-\u003emqtt-\u003edone_subscribe) {\n+\n+\t\t\t/*\n+\t\t\t * The policy says to subscribe to something, and we\n+\t\t\t * haven't done it yet\n+\t\t\t */\n+\n+\t\t\tlwsl_warn(\u0022%s: subscribing %s\u005cn\u0022, __func__, h-\u003epolicy-\u003eu.mqtt.subscribe);\n+\n+\t\t\tmemset(\u0026h-\u003eu.mqtt.sub_top, 0, sizeof(h-\u003eu.mqtt.sub_top));\n+\t\t\th-\u003eu.mqtt.sub_top.name \u003d h-\u003epolicy-\u003eu.mqtt.subscribe;\n+\t\t\th-\u003eu.mqtt.sub_top.qos \u003d h-\u003epolicy-\u003eu.mqtt.qos;\n+\t\t\tmemset(\u0026h-\u003eu.mqtt.sub_info, 0, sizeof(h-\u003eu.mqtt.sub_info));\n+\t\t\th-\u003eu.mqtt.sub_info.num_topics \u003d 1;\n+\t\t\th-\u003eu.mqtt.sub_info.topic \u003d \u0026h-\u003eu.mqtt.sub_top;\n+\n+\t\t\tif (lws_mqtt_client_send_subcribe(wsi, \u0026h-\u003eu.mqtt.sub_info)) {\n+\t\t\t\tlwsl_notice(\u0022%s: unable to subscribe\u0022, __func__);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\n+\t\t\treturn 0;\n+\t\t}\n+\n+\n+\t\tbuflen \u003d sizeof(buf) - LWS_PRE;\n+\t\tif (h-\u003einfo.tx(ss_to_userobj(h), h-\u003etxord++, buf + LWS_PRE,\n+\t\t\t\t\u0026buflen, \u0026f))\n+\t\t\t/* don't want to send anything */\n+\t\t\treturn 0;\n+\n+\t\tmemset(\u0026mqpp, 0, sizeof(mqpp));\n+\t\tmqpp.topic \u003d (char *)h-\u003epolicy-\u003eu.mqtt.topic;\n+\t\tmqpp.topic_len \u003d strlen(mqpp.topic);\n+\t\tmqpp.packet_id \u003d h-\u003etxord - 1;\n+\t\tmqpp.payload \u003d buf + LWS_PRE;\n+\t\tif (h-\u003ewriteable_len)\n+\t\t\tmqpp.payload_len \u003d h-\u003ewriteable_len;\n+\t\telse\n+\t\t\tmqpp.payload_len \u003d buflen;\n+\n+\t\tlwsl_notice(\u0022%s: payload len %d\u005cn\u0022, __func__, (int)mqpp.payload_len);\n+\n+\t\tmqpp.qos \u003d h-\u003epolicy-\u003eu.mqtt.qos;\n+\n+\t\tif (lws_mqtt_client_send_publish(wsi, \u0026mqpp,\n+\t\t\t\t\t\t (const char *)buf + LWS_PRE, buflen,\n+\t\t\t\t\t\t f \u0026 LWSSS_FLAG_EOM)) {\n+\t\t\tlwsl_notice(\u0022%s: failed to publish\u005cn\u0022, __func__);\n+\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\treturn 0;\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn lws_callback_http_dummy(wsi, reason, user, in, len);\n+}\n+\n+const struct lws_protocols protocol_secstream_mqtt \u003d {\n+\t\u0022lws-secstream-mqtt\u0022,\n+\tsecstream_mqtt,\n+\t0,\n+\t0,\n+};\n+/*\n+ * Munge connect info according to protocol-specific considerations... this\n+ * usually means interpreting aux in a protocol-specific way and using the\n+ * pieces at connection setup time, eg, http url pieces.\n+ *\n+ * len bytes of buf can be used for things with scope until after the actual\n+ * connect.\n+ *\n+ * For ws, protocol aux is \u003curl path\u003e;\u003cws subprotocol name\u003e\n+ */\n+\n+static int\n+secstream_connect_munge_mqtt(lws_ss_handle_t *h, char *buf, size_t len,\n+\t\t\t struct lws_client_connect_info *i,\n+\t\t\t union lws_ss_contemp *ct)\n+{\n+\tmemset(\u0026ct-\u003eccp, 0, sizeof(ct-\u003eccp));\n+\n+\tct-\u003eccp.client_id\t\t\u003d \u0022lwsMqttClient\u0022;\n+\tct-\u003eccp.keep_alive\t\t\u003d 60;\n+\tct-\u003eccp.clean_start\t\t\u003d 1;\n+\tct-\u003eccp.will_param.topic\t\u003d \u0022good/bye\u0022;\n+\tct-\u003eccp.will_param.message\t\u003d \u0022sign-off\u0022;\n+\tct-\u003eccp.will_param.qos\t\t\u003d 0;\n+\tct-\u003eccp.will_param.retain\t\u003d 0;\n+\n+\tlwsl_notice(\u0022%s\u005cn\u0022, __func__);\n+\n+\th-\u003eu.mqtt.topic_qos.name \u003d h-\u003epolicy-\u003eu.mqtt.subscribe;\n+\th-\u003eu.mqtt.topic_qos.qos \u003d h-\u003epolicy-\u003eu.mqtt.qos;\n+\n+\ti-\u003emethod \u003d \u0022MQTT\u0022;\n+\ti-\u003emqtt_cp \u003d \u0026ct-\u003eccp;\n+\n+\ti-\u003ealpn \u003d \u0022x-amzn-mqtt-ca\u0022;\n+\n+\t/* share connections where possible */\n+\ti-\u003essl_connection |\u003d LCCSCF_PIPELINE;\n+\n+/*\n+\tif (!h-\u003epolicy-\u003eu.http.url)\n+\t\treturn 0;\n+\n+\t// protocol aux is the path part ; ws subprotocol name\n+\n+\ti-\u003epath \u003d NULL;\n+\tlws_snprintf(buf, len, \u0022/%s\u0022, h-\u003epolicy-\u003eu.mqtt.topic);\n+\n+//\ti-\u003eprotocol \u003d h-\u003epolicy-\u003eu.mqtt.u.ws.subprotocol;\n+\n+\tlwsl_notice(\u0022%s: url %s, ws subprotocol %s\u005cn\u0022, __func__, buf, i-\u003eprotocol);\n+*/\n+\treturn 0;\n+}\n+\n+const struct ss_pcols ss_pcol_mqtt \u003d {\n+\t\u0022MQTT\u0022,\n+\t\u0022x-amzn-mqtt-ca\u0022, //\u0022mqtt/3.1.1\u0022,\n+\t\u0022lws-secstream-mqtt\u0022,\n+\tsecstream_connect_munge_mqtt\n+};\ndiff --git a/lib/secure-streams/protocols/ss-ws.c b/lib/secure-streams/protocols/ss-ws.c\nnew file mode 100644\nindex 0000000..ac1dc32\n--- /dev/null\n+++ b/lib/secure-streams/protocols/ss-ws.c\n@@ -0,0 +1,165 @@\n+/*\n+ * libwebsockets - small server side websockets and web server implementation\n+ *\n+ * Copyright (C) 2019 - 2020 Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * Permission is hereby granted, free of charge, to any person obtaining a copy\n+ * of this software and associated documentation files (the \u0022Software\u0022), to\n+ * deal in the Software without restriction, including without limitation the\n+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n+ * sell copies of the Software, and to permit persons to whom the Software is\n+ * furnished to do so, subject to the following conditions:\n+ *\n+ * The above copyright notice and this permission notice shall be included in\n+ * all copies or substantial portions of the Software.\n+ *\n+ * THE SOFTWARE IS PROVIDED \u0022AS IS\u0022, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n+ * IN THE SOFTWARE.\n+ */\n+\n+#include \u003cprivate-lib-core.h\u003e\n+\n+static int\n+secstream_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user,\n+\t void *in, size_t len)\n+{\n+\tlws_ss_handle_t *h \u003d (lws_ss_handle_t *)lws_get_opaque_user_data(wsi);\n+\tuint8_t buf[LWS_PRE + 1400];\n+\tsize_t buflen;\n+\tint f \u003d 0, f1;\n+\n+\tswitch (reason) {\n+\n+\t/* because we are protocols[0] ... */\n+\tcase LWS_CALLBACK_CLIENT_CONNECTION_ERROR:\n+\t\tlwsl_info(\u0022%s: CLIENT_CONNECTION_ERROR: %s\u005cn\u0022, __func__,\n+\t\t\t in ? (char *)in : \u0022(null)\u0022);\n+\t\tif (!h)\n+\t\t\tbreak;\n+\t\tlws_ss_event_helper(h, LWSSSCS_UNREACHABLE);\n+\t\th-\u003ewsi \u003d NULL;\n+\t\tlws_ss_backoff(h);\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_CLIENT_CLOSED:\n+\t\tif (!h)\n+\t\t\tbreak;\n+\t\tf \u003d lws_ss_event_helper(h, LWSSSCS_DISCONNECTED);\n+\t\tif (h-\u003ewsi)\n+\t\t\tlws_set_opaque_user_data(h-\u003ewsi, NULL);\n+\t\th-\u003ewsi \u003d NULL;\n+\n+\t\tif (f) {\n+\t\t\tlws_ss_destroy(\u0026h);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (h-\u003epolicy \u0026\u0026 !(h-\u003epolicy-\u003eflags \u0026 LWSSSPOLF_OPPORTUNISTIC) \u0026\u0026\n+\t\t !h-\u003etxn_ok \u0026\u0026 !wsi-\u003econtext-\u003ebeing_destroyed)\n+\t\t\tlws_ss_backoff(h);\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_CLIENT_ESTABLISHED:\n+\t\th-\u003eretry \u003d 0;\n+\t\th-\u003eseqstate \u003d SSSEQ_CONNECTED;\n+\t\tlws_ss_set_timeout_us(h, LWS_SET_TIMER_USEC_CANCEL);\n+\t\tlws_ss_event_helper(h, LWSSSCS_CONNECTED);\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_CLIENT_RECEIVE:\n+\t\t// lwsl_user(\u0022LWS_CALLBACK_CLIENT_RECEIVE: read %d\u005cn\u0022, (int)len);\n+\t\tif (!h)\n+\t\t\treturn 0;\n+\t\tif (lws_is_first_fragment(wsi))\n+\t\t\tf |\u003d LWSSS_FLAG_SOM;\n+\t\tif (lws_is_final_fragment(wsi))\n+\t\t\tf |\u003d LWSSS_FLAG_EOM;\n+\t\t// lws_frame_is_binary(wsi);\n+\n+\t\th-\u003esubseq \u003d 1;\n+\n+\t\th-\u003einfo.rx(ss_to_userobj(h), (const uint8_t *)in, len, f);\n+\n+\t\treturn 0; /* don't passthru */\n+\n+\tcase LWS_CALLBACK_CLIENT_WRITEABLE:\n+\t\tif (!h)\n+\t\t\treturn 0;\n+\t\t// lwsl_notice(\u0022%s: ss %p: WRITEABLE\u005cn\u0022, __func__, h);\n+\n+\t\tif (h-\u003eseqstate !\u003d SSSEQ_CONNECTED) {\n+\t\t\tlwsl_warn(\u0022%s: seqstate %d\u005cn\u0022, __func__, h-\u003eseqstate);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tbuflen \u003d sizeof(buf) - LWS_PRE;\n+\t\tif (h-\u003einfo.tx(ss_to_userobj(h), h-\u003etxord++, buf + LWS_PRE,\n+\t\t\t\t\u0026buflen, \u0026f))\n+\t\t\t/* don't want to send anything */\n+\t\t\treturn 0;\n+\n+\t\tf1 \u003d lws_write_ws_flags(LWS_WRITE_BINARY,\n+\t\t\t\t\t!!(f \u0026 LWSSS_FLAG_SOM),\n+\t\t\t\t\t!!(f \u0026 LWSSS_FLAG_EOM));\n+\n+\t\tif (lws_write(wsi, buf + LWS_PRE, buflen, f1) !\u003d (int)buflen) {\n+\t\t\tlwsl_err(\u0022%s: write failed\u005cn\u0022, __func__);\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\treturn 0;\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn lws_callback_http_dummy(wsi, reason, user, in, len);\n+}\n+\n+const struct lws_protocols protocol_secstream_ws \u003d {\n+\t\u0022lws-secstream-ws\u0022,\n+\tsecstream_ws,\n+\t0,\n+\t0,\n+};\n+/*\n+ * Munge connect info according to protocol-specific considerations... this\n+ * usually means interpreting aux in a protocol-specific way and using the\n+ * pieces at connection setup time, eg, http url pieces.\n+ *\n+ * len bytes of buf can be used for things with scope until after the actual\n+ * connect.\n+ *\n+ * For ws, protocol aux is \u003curl path\u003e;\u003cws subprotocol name\u003e\n+ */\n+\n+static int\n+secstream_connect_munge_ws(lws_ss_handle_t *h, char *buf, size_t len,\n+\t\t\t struct lws_client_connect_info *i,\n+\t\t\t union lws_ss_contemp *ct)\n+{\n+\tlwsl_notice(\u0022%s\u005cn\u0022, __func__);\n+\n+\tif (!h-\u003epolicy-\u003eu.http.url)\n+\t\treturn 0;\n+\n+\t/* protocol aux is the path part ; ws subprotocol name */\n+\n+\ti-\u003epath \u003d h-\u003epolicy-\u003eu.http.url;\n+\tlws_snprintf(buf, len, \u0022/%s\u0022, h-\u003epolicy-\u003eu.http.url);\n+\n+\ti-\u003eprotocol \u003d h-\u003epolicy-\u003eu.http.u.ws.subprotocol;\n+\n+\tlwsl_notice(\u0022%s: url %s, ws subprotocol %s\u005cn\u0022, __func__, buf, i-\u003eprotocol);\n+\n+\treturn 0;\n+}\n+\n+const struct ss_pcols ss_pcol_ws \u003d {\n+\t\u0022ws\u0022, \u0022http/1.1\u0022, \u0022lws-secstream-ws\u0022, secstream_connect_munge_ws\n+};\ndiff --git a/lib/secure-streams/secure-streams-client.c b/lib/secure-streams/secure-streams-client.c\nnew file mode 100644\nindex 0000000..67d73be\n--- /dev/null\n+++ b/lib/secure-streams/secure-streams-client.c\n@@ -0,0 +1,574 @@\n+/*\n+ * lws-minimal-secure-streams-client\n+ *\n+ * Written in 2010-2020 by Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * This file is made available under the Creative Commons CC0 1.0\n+ * Universal Public Domain Dedication.\n+ *\n+ *\n+ * This client does not perform any INET networking... instead it opens a unix\n+ * domain socket on a proxy that is listening for it, and that creates the\n+ * actual secure stream connection.\n+ *\n+ * We are able to use the usual secure streams api in the client process, with\n+ * payloads and connection state information proxied over the unix domain\n+ * socket and fulfilled in the proxy process.\n+ *\n+ * The public client helper pieces are built as part of lws\n+ */\n+#include \u003cprivate-lib-core.h\u003e\n+\n+static void\n+lws_sspc_sul_retry_cb(lws_sorted_usec_list_t *sul)\n+{\n+\tlws_sspc_handle_t *h \u003d lws_container_of(sul, lws_sspc_handle_t, sul_retry);\n+\tstatic struct lws_client_connect_info i;\n+\n+\t/*\n+\t * We may have started up before the system proxy, so be prepared with\n+\t * a sul to retry at 1Hz\n+\t */\n+\n+\tmemset(\u0026i, 0, sizeof i);\n+\ti.context \u003d h-\u003econtext;\n+\tif (h-\u003econtext-\u003ess_proxy_port) { /* tcp */\n+\t\ti.address \u003d h-\u003econtext-\u003ess_proxy_address;\n+\t\ti.port \u003d h-\u003econtext-\u003ess_proxy_port;\n+\t\ti.iface \u003d h-\u003econtext-\u003ess_proxy_bind;\n+\t} else {\n+\t\tif (h-\u003econtext-\u003ess_proxy_bind)\n+\t\t\ti.address \u003d h-\u003econtext-\u003ess_proxy_bind;\n+\t\telse\n+\t\t\ti.address \u003d \u0022+@proxy.ss.lws\u0022;\n+\t}\n+\ti.host \u003d i.address;\n+\ti.origin \u003d i.address;\n+\ti.method \u003d \u0022RAW\u0022;\n+\ti.protocol \u003d lws_sspc_protocols[0].name;\n+\ti.local_protocol_name \u003d lws_sspc_protocols[0].name;\n+\ti.path \u003d \u0022\u0022;\n+\ti.pwsi \u003d \u0026h-\u003ecwsi;\n+\ti.opaque_user_data \u003d (void *)h;\n+\n+\tif (!lws_client_connect_via_info(\u0026i)) {\n+\t\tlws_sul_schedule(h-\u003econtext, 0, \u0026h-\u003esul_retry,\n+\t\t\t\t lws_sspc_sul_retry_cb, LWS_US_PER_SEC);\n+\n+\t\treturn;\n+\t}\n+}\n+\n+static int\n+lws_sspc_serialize_metadata(lws_sspc_metadata_t *md, uint8_t *p)\n+{\n+\tint n, txc;\n+\n+\tif (md-\u003ename[0] \u003d\u003d '\u005c0') {\n+\n+\t\tlwsl_info(\u0022%s: sending tx credit update %d\u005cn\u0022, __func__,\n+\t\t\t\tmd-\u003etx_cr_adjust);\n+\n+\t\tp[0] \u003d LWSSS_SER_TXPRE_TXCR_UPDATE;\n+\t\tlws_ser_wu16be(\u0026p[1], 4);\n+\t\tlws_ser_wu32be(\u0026p[3], md-\u003etx_cr_adjust);\n+\n+\t\tn \u003d 7;\n+\n+\t} else {\n+\n+\t\tlwsl_info(\u0022%s: sending metadata\u005cn\u0022, __func__);\n+\n+\t\tp[0] \u003d LWSSS_SER_TXPRE_METADATA;\n+\t\ttxc \u003d strlen(md-\u003ename);\n+\t\tn \u003d txc + 1 + md-\u003elen;\n+\t\tlws_ser_wu16be(\u0026p[1], n);\n+\t\tp[3] \u003d txc;\n+\t\tmemcpy(\u0026p[4], md-\u003ename, txc);\n+\t\tmemcpy(\u0026p[4 + txc], \u0026md[1], md-\u003elen);\n+\t\tn \u003d 4 + txc + md-\u003elen;\n+\t}\n+\n+\tlws_dll2_remove(\u0026md-\u003elist);\n+\tlws_free(md);\n+\n+\treturn n;\n+}\n+\n+static int\n+callback_sspc_client(struct lws *wsi, enum lws_callback_reasons reason,\n+\t\t void *user, void *in, size_t len)\n+{\n+\tlws_sspc_handle_t *h \u003d (lws_sspc_handle_t *)lws_get_opaque_user_data(wsi);\n+\tuint8_t s[32], pkt[LWS_PRE + 1400], *p \u003d pkt + LWS_PRE;\n+\tvoid *m \u003d (void *)((uint8_t *)\u0026h[1]);\n+\tconst uint8_t *cp;\n+\tlws_usec_t us;\n+\tint flags, n;\n+\n+\tswitch (reason) {\n+\tcase LWS_CALLBACK_PROTOCOL_INIT:\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_PROTOCOL_DESTROY:\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_CLIENT_CONNECTION_ERROR:\n+\t\tlwsl_warn(\u0022%s: CONNECTION_ERROR\u005cn\u0022, __func__);\n+\t\tlws_set_opaque_user_data(wsi, NULL);\n+\t\th-\u003ecwsi \u003d NULL;\n+\t\tlws_sul_schedule(h-\u003econtext, 0, \u0026h-\u003esul_retry,\n+\t\t\t\t lws_sspc_sul_retry_cb, LWS_US_PER_SEC);\n+\t\tbreak;\n+\n+ case LWS_CALLBACK_RAW_CONNECTED:\n+\t\tif (!h)\n+\t\t\treturn -1;\n+\t\tlwsl_info(\u0022%s: CONNECTED (%s)\u005cn\u0022, __func__, h-\u003essi.streamtype);\n+\n+\t\th-\u003estate \u003d LPCS_SENDING_INITIAL_TX;\n+\t\th-\u003edsh \u003d lws_dsh_create(NULL, (LWS_PRE + LWS_SS_MTU) * 160, 1);\n+\t\tif (!h-\u003edsh)\n+\t\t\treturn -1;\n+\n+\t\tlws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND, 3);\n+\t\tlws_callback_on_writable(wsi);\n+ break;\n+\n+\tcase LWS_CALLBACK_RAW_CLOSE:\n+\t\t/*\n+\t\t * our ss proxy Unix Domain socket has closed...\n+\t\t */\n+\t\tlwsl_notice(\u0022%s: LWS_CALLBACK_RAW_CLOSE: proxy conn down\u005cn\u0022, __func__);\n+\t\th-\u003ecwsi \u003d NULL;\n+\t\t//lws_sspc_destroy(\u0026h);\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_RAW_RX:\n+\t\tlwsl_info(\u0022%s: RAW_RX: rx %d\u005cn\u0022, __func__, (int)len);\n+\n+\t\tif (!h || !h-\u003ecwsi) {\n+\t\t\tlwsl_err(\u0022%s: rx with bad conn state\u005cn\u0022, __func__);\n+\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tif (lws_ss_deserialize_parse(\u0026h-\u003eparser, lws_get_context(wsi),\n+\t\t\t\t\t h-\u003edsh, in, len, \u0026h-\u003estate, h,\n+\t\t\t\t\t (lws_ss_handle_t **)m, \u0026h-\u003essi, 1))\n+\t\t\treturn -1;\n+\n+\t\tif (wsi \u0026\u0026 h-\u003estate \u003d\u003d LPCS_LOCAL_CONNECTED)\n+\t\t\tlws_set_timeout(wsi, 0, 0);\n+\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_RAW_WRITEABLE:\n+\n+\t\t/*\n+\t\t * We can transmit something to the proxy...\n+\t\t */\n+\n+\t\tif (!h)\n+\t\t\tbreak;\n+\n+\t\tlwsl_info(\u0022%s: WRITEABLE %p: (%s) state %d\u005cn\u0022, __func__, wsi,\n+\t\t\t\th-\u003essi.streamtype, h-\u003estate);\n+\n+\t\tn \u003d 0;\n+\t\tcp \u003d s;\n+\t\ts[1] \u003d 0;\n+\t\tswitch (h-\u003estate) {\n+\t\tcase LPCS_SENDING_INITIAL_TX:\n+\t\t\tn \u003d strlen(h-\u003essi.streamtype) + 4;\n+\n+\t\t\ts[0] \u003d LWSSS_SER_TXPRE_STREAMTYPE;\n+\t\t\tlws_ser_wu16be(\u0026s[1], n);\n+\t\t\tlws_ser_wu32be(\u0026s[3], h-\u003etxc.peer_tx_cr_est);\n+\t\t\t//h-\u003etxcr_out \u003d txc;\n+\t\t\tlws_strncpy((char *)\u0026s[7], h-\u003essi.streamtype, sizeof(s) - 7);\n+\t\t\tn +\u003d 3;\n+\t\t\th-\u003estate \u003d LPCS_WAITING_CREATE_RESULT;\n+\t\t\tbreak;\n+\n+\t\tcase LPCS_LOCAL_CONNECTED:\n+\t\t\tif (!h-\u003econn_req)\n+\t\t\t\tbreak;\n+\n+\t\t\t/*\n+\t\t\t * Do we need to prioritize sending any metadata\n+\t\t\t * changes?\n+\t\t\t */\n+\n+\t\t\tif (h-\u003emetadata_owner.count) {\n+\t\t\t\tlws_sspc_metadata_t *md \u003d lws_container_of(\n+\t\t\t\t\tlws_dll2_get_tail(\u0026h-\u003emetadata_owner),\n+\t\t\t\t\tlws_sspc_metadata_t, list);\n+\n+\t\t\t\tcp \u003d p;\n+\t\t\t\tn \u003d lws_sspc_serialize_metadata(md, p);\n+\n+\t\t\t\t/* in case anything else to write */\n+\t\t\t\tlws_callback_on_writable(h-\u003ecwsi);\n+\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\n+\t\t\th-\u003econn_req \u003d 0;\n+\t\t\ts[0] \u003d LWSSS_SER_TXPRE_ONWARD_CONNECT;\n+\t\t\ts[1] \u003d 0;\n+\t\t\ts[2] \u003d 0;\n+\t\t\tn \u003d 3;\n+\t\t\tbreak;\n+\n+\t\tcase LPCS_OPERATIONAL:\n+\n+\t\t\t/*\n+\t\t\t * Do we want to adjust the peer's ability to write\n+\t\t\t * to us?\n+\t\t\t */\n+\n+\t\t\t/*\n+\t\t\t * Do we need to prioritize sending any metadata\n+\t\t\t * changes?\n+\t\t\t */\n+\n+\t\t\tif (h-\u003emetadata_owner.count) {\n+\t\t\t\tlws_sspc_metadata_t *md \u003d lws_container_of(\n+\t\t\t\t\tlws_dll2_get_tail(\u0026h-\u003emetadata_owner),\n+\t\t\t\t\tlws_sspc_metadata_t, list);\n+\n+\t\t\t\tcp \u003d p;\n+\t\t\t\tn \u003d lws_sspc_serialize_metadata(md, p);\n+\n+\t\t\t\t/* in case anything else to write */\n+\t\t\t\tlws_callback_on_writable(h-\u003ecwsi);\n+\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\n+\t\t\t/* we can't write anything if we don't have credit */\n+\t\t\tif (h-\u003etxc.tx_cr \u003c\u003d 0) {\n+\t\t\t\tlwsl_notice(\u0022%s: WRITEABLE / OPERATIONAL:\u0022\n+\t\t\t\t\t \u0022 lack credit (%d)\u005cn\u0022, __func__,\n+\t\t\t\t\t h-\u003etxc.tx_cr);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tlen \u003d sizeof(pkt) - LWS_PRE - 19;\n+\t\t\tflags \u003d 0;\n+\t\t\tif (h-\u003essi.tx(m, h-\u003eord++, pkt + LWS_PRE + 19, \u0026len, \u0026flags))\n+\t\t\t\tbreak;\n+\n+\t\t\th-\u003etxc.tx_cr -\u003d len;\n+\n+\t\t\tcp \u003d p;\n+\t\t\tn \u003d len + 19;\n+\t\t\tus \u003d lws_now_usecs();\n+\t\t\tp[0] \u003d LWSSS_SER_TXPRE_TX_PAYLOAD;\n+\t\t\tlws_ser_wu16be(\u0026p[1], len + 19 - 3);\n+\t\t\tlws_ser_wu32be(\u0026p[3], flags);\n+\t\t\t/* time spent here waiting to send this */\n+\t\t\tlws_ser_wu32be(\u0026p[7], us - h-\u003eus_earliest_write_req);\n+\t\t\t/* ust that the client write happened */\n+\t\t\tlws_ser_wu64be(\u0026p[11], us);\n+\t\t\th-\u003eus_earliest_write_req \u003d 0;\n+\n+\t\t\tif (flags \u0026 LWSSS_FLAG_EOM)\n+\t\t\t\tif (h-\u003ersidx + 1 \u003c (int)LWS_ARRAY_SIZE(h-\u003erideshare_ofs) \u0026\u0026\n+\t\t\t\t h-\u003erideshare_ofs[h-\u003ersidx + 1])\n+\t\t\t\t\th-\u003ersidx++;\n+\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (!n)\n+\t\t\tbreak;\n+\n+\t\t// lwsl_hexdump_notice(cp, n);\n+\n+\t\tn \u003d lws_write(wsi, (uint8_t *)cp, n, LWS_WRITE_RAW);\n+\t\tif (n \u003c 0) {\n+\t\t\tlwsl_notice(\u0022%s: WRITEABLE: %d\u005cn\u0022, __func__, n);\n+\n+\t\t\tgoto hangup;\n+\t\t}\n+\t\tbreak;\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn lws_callback_http_dummy(wsi, reason, user, in, len);\n+\n+hangup:\n+\tlwsl_warn(\u0022hangup\u005cn\u0022);\n+\t/* hang up on him */\n+\treturn -1;\n+}\n+\n+const struct lws_protocols lws_sspc_protocols[] \u003d {\n+\t{\n+\t\t\u0022ssproxy-protocol\u0022,\n+\t\tcallback_sspc_client,\n+\t\t0,\n+\t\t2048, 2048, NULL, 0\n+\t},\n+\t{ NULL, NULL, 0, 0, 0, NULL, 0 }\n+};\n+\n+int\n+lws_sspc_create(struct lws_context *context, int tsi, const lws_ss_info_t *ssi,\n+\t void *opaque_user_data, lws_sspc_handle_t **ppss,\n+\t struct lws_sequencer *seq_owner, const char **ppayload_fmt)\n+{\n+\tlws_sspc_handle_t *h;\n+\tuint8_t *ua;\n+\tchar *p;\n+\n+\tlwsl_notice(\u0022%s: streamtype %s\u005cn\u0022, __func__, ssi-\u003estreamtype);\n+\n+\t/* allocate the handle (including ssi), the user alloc,\n+\t * and the streamname */\n+\n+\th \u003d malloc(sizeof(lws_sspc_handle_t) + ssi-\u003euser_alloc +\n+\t\t strlen(ssi-\u003estreamtype) + 1);\n+\tmemset(h, 0, sizeof(*h));\n+\tmemcpy(\u0026h-\u003essi, ssi, sizeof(*ssi));\n+\tua \u003d (uint8_t *)\u0026h[1];\n+\tmemset(ua, 0, ssi-\u003euser_alloc);\n+\tp \u003d (char *)ua + ssi-\u003euser_alloc;\n+\tmemcpy(p, ssi-\u003estreamtype, strlen(ssi-\u003estreamtype) + 1);\n+\th-\u003essi.streamtype \u003d (const char *)p;\n+\th-\u003econtext \u003d context;\n+\tif (!ssi-\u003emanual_initial_tx_credit)\n+\t\th-\u003etxc.peer_tx_cr_est \u003d 500000000;\n+\telse\n+\t\th-\u003etxc.peer_tx_cr_est \u003d ssi-\u003emanual_initial_tx_credit;\n+\n+\tlws_dll2_add_head(\u0026h-\u003eclient_list, \u0026context-\u003ept[tsi].ss_client_owner);\n+\n+\t/* fill in the things the real api does for the caller */\n+\n+\t*((void **)(ua + ssi-\u003eopaque_user_data_offset)) \u003d opaque_user_data;\n+\t*((void **)(ua + ssi-\u003ehandle_offset)) \u003d h;\n+\n+\tif (ppss)\n+\t\t*ppss \u003d h;\n+\n+\t/* try the actual connect */\n+\n+\tlws_sspc_sul_retry_cb(\u0026h-\u003esul_retry);\n+\n+\treturn 0;\n+}\n+\n+/* used on context destroy when iterating listed lws_ss on a pt */\n+\n+int\n+lws_sspc_destroy_dll(struct lws_dll2 *d, void *user)\n+{\n+\tlws_sspc_handle_t *h \u003d lws_container_of(d, lws_sspc_handle_t, client_list);\n+\n+\tlws_sspc_destroy(\u0026h);\n+\n+\treturn 0;\n+}\n+\n+\n+void\n+lws_sspc_destroy(lws_sspc_handle_t **ph)\n+{\n+\tlws_sspc_handle_t *h;\n+\tvoid *m;\n+\n+\tlwsl_debug(\u0022%s\u005cn\u0022, __func__);\n+\n+\tif (!*ph)\n+\t\treturn;\n+\n+\th \u003d *ph;\n+\tm \u003d (void *)((uint8_t *)\u0026h[1]);\n+\n+\tif (h-\u003edestroying)\n+\t\treturn;\n+\n+\th-\u003edestroying \u003d 1;\n+\n+\tlws_sul_schedule(h-\u003econtext, 0, \u0026h-\u003esul_retry, NULL,\n+\t\t\t LWS_SET_TIMER_USEC_CANCEL);\n+\tlws_dll2_remove(\u0026h-\u003eclient_list);\n+\n+\tif (h-\u003edsh)\n+\t\tlws_dsh_destroy(\u0026h-\u003edsh);\n+\tif (h-\u003ecwsi) {\n+\t\tstruct lws *wsi \u003d h-\u003ecwsi;\n+\t\th-\u003ecwsi \u003d NULL;\n+\t\tlws_set_timeout(wsi, 1, LWS_TO_KILL_SYNC);\n+\t}\n+\n+\t/* clean out any pending metadata changes that didn't make it */\n+\n+\tlws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,\n+\t\t\tlws_dll2_get_head(\u0026(*ph)-\u003emetadata_owner)) {\n+\t\tlws_sspc_metadata_t *md \u003d\n+\t\t\t\tlws_container_of(d, lws_sspc_metadata_t, list);\n+\n+\t\tlws_dll2_remove(\u0026md-\u003elist);\n+\t\tlws_free(md);\n+\n+\t} lws_end_foreach_dll_safe(d, d1);\n+\n+\th-\u003essi.state(m, NULL, LWSSSCS_DESTROYING, 0);\n+\t*ph \u003d NULL;\n+\tfree(h);\n+}\n+\n+void\n+lws_sspc_request_tx(lws_sspc_handle_t *h)\n+{\n+\tif (!h || !h-\u003ecwsi)\n+\t\treturn;\n+\n+\tif (!h-\u003eus_earliest_write_req)\n+\t\th-\u003eus_earliest_write_req \u003d lws_now_usecs();\n+\n+\tlws_callback_on_writable(h-\u003ecwsi);\n+}\n+\n+int\n+lws_sspc_client_connect(lws_sspc_handle_t *h)\n+{\n+\tif (!h || h-\u003estate \u003d\u003d LPCS_OPERATIONAL)\n+\t\treturn 0;\n+\n+\tassert(h-\u003estate \u003d\u003d LPCS_LOCAL_CONNECTED);\n+\th-\u003econn_req \u003d 1;\n+\tif (h-\u003ecwsi)\n+\t\tlws_callback_on_writable(h-\u003ecwsi);\n+\n+\treturn 0;\n+}\n+\n+struct lws_context *\n+lws_sspc_get_context(struct lws_sspc_handle *h)\n+{\n+\treturn h-\u003econtext;\n+}\n+\n+const char *\n+lws_sspc_rideshare(struct lws_sspc_handle *h)\n+{\n+\t/*\n+\t * ...the serialized RX rideshare name if any...\n+\t */\n+\n+\tif (h-\u003eparser.rideshare[0]) {\n+\t\tlwsl_info(\u0022%s: parser %s\u005cn\u0022, __func__, h-\u003eparser.rideshare);\n+\t\treturn h-\u003eparser.rideshare;\n+\t}\n+\n+\t/*\n+\t * The tx rideshare index\n+\t */\n+\n+\tif (h-\u003erideshare_list[0]) {\n+\t\tlwsl_info(\u0022%s: tx list %s\u005cn\u0022, __func__,\n+\t\t\t \u0026h-\u003erideshare_list[h-\u003erideshare_ofs[h-\u003ersidx]]);\n+\t\treturn \u0026h-\u003erideshare_list[h-\u003erideshare_ofs[h-\u003ersidx]];\n+\t}\n+\n+\t/*\n+\t * ... otherwise default to our stream type name\n+\t */\n+\n+\tlwsl_info(\u0022%s: def %s\u005cn\u0022, __func__, h-\u003essi.streamtype);\n+\n+\treturn h-\u003essi.streamtype;\n+}\n+\n+static int\n+_lws_sspc_set_metadata(struct lws_sspc_handle *h, const char *name,\n+\t\t void *value, size_t len, int tx_cr_adjust)\n+{\n+\tlws_sspc_metadata_t *md;\n+\n+\t/*\n+\t * Are we replacing a pending metadata of the same name? It's not\n+\t * efficient to do this but user code can do what it likes... let's\n+\t * optimize away the old one.\n+\t *\n+\t * Tx credit adjust always has name \u0022\u0022\n+\t */\n+\n+\tlws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,\n+\t\t\t\t lws_dll2_get_head(\u0026h-\u003emetadata_owner)) {\n+\t\tmd \u003d lws_container_of(d, lws_sspc_metadata_t, list);\n+\n+\t\tif (!strcmp(name, md-\u003ename)) {\n+\t\t\tlws_dll2_remove(\u0026md-\u003elist);\n+\t\t\tlws_free(md);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t} lws_end_foreach_dll_safe(d, d1);\n+\n+\t/*\n+\t * We have to stash the metadata and pass it to the proxy\n+\t */\n+\n+\tmd \u003d lws_malloc(sizeof(*md) + len, \u0022set metadata\u0022);\n+\tif (!md) {\n+\t\tlwsl_err(\u0022%s: OOM\u005cn\u0022, __func__);\n+\n+\t\treturn 1;\n+\t}\n+\n+\tmemset(md, 0, sizeof(*md));\n+\n+\tmd-\u003etx_cr_adjust \u003d tx_cr_adjust;\n+\th-\u003etxc.peer_tx_cr_est +\u003d tx_cr_adjust;\n+\n+\tlws_strncpy(md-\u003ename, name, sizeof(md-\u003ename));\n+\tmd-\u003elen \u003d len;\n+\tif (len)\n+\t\tmemcpy(\u0026md[1], value, len);\n+\n+\tlws_dll2_add_tail(\u0026md-\u003elist, \u0026h-\u003emetadata_owner);\n+\n+\tif (len) {\n+\t\tlwsl_info(\u0022%s: set metadata %s\u005cn\u0022, __func__, name);\n+\t\tlwsl_hexdump_info(value, len);\n+\t} else\n+\t\tlwsl_info(\u0022%s: serializing tx cr adj %d\u005cn\u0022, __func__,\n+\t\t\t (int)tx_cr_adjust);\n+\n+\tif (h-\u003ecwsi)\n+\t\tlws_callback_on_writable(h-\u003ecwsi);\n+\n+\treturn 0;\n+}\n+\n+int\n+lws_sspc_set_metadata(struct lws_sspc_handle *h, const char *name,\n+\t\t void *value, size_t len)\n+{\n+\treturn _lws_sspc_set_metadata(h, name, value, len, 0);\n+}\n+\n+int\n+lws_sspc_add_peer_tx_credit(struct lws_sspc_handle *h, int32_t bump)\n+{\n+\tlwsl_notice(\u0022%s: %d\u005cn\u0022, __func__, bump);\n+\treturn _lws_sspc_set_metadata(h, \u0022\u0022, NULL, 0, (int)bump);\n+}\n+\n+int\n+lws_sspc_get_est_peer_tx_credit(struct lws_sspc_handle *h)\n+{\n+\treturn h-\u003etxc.peer_tx_cr_est;\n+}\ndiff --git a/lib/secure-streams/secure-streams-process.c b/lib/secure-streams/secure-streams-process.c\nnew file mode 100644\nindex 0000000..8ee0d11\n--- /dev/null\n+++ b/lib/secure-streams/secure-streams-process.c\n@@ -0,0 +1,533 @@\n+/*\n+ * libwebsockets - small server side websockets and web server implementation\n+ *\n+ * Copyright (C) 2019 - 2020 Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * Permission is hereby granted, free of charge, to any person obtaining a copy\n+ * of this software and associated documentation files (the \u0022Software\u0022), to\n+ * deal in the Software without restriction, including without limitation the\n+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n+ * sell copies of the Software, and to permit persons to whom the Software is\n+ * furnished to do so, subject to the following conditions:\n+ *\n+ * The above copyright notice and this permission notice shall be included in\n+ * all copies or substantial portions of the Software.\n+ *\n+ * THE SOFTWARE IS PROVIDED \u0022AS IS\u0022, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n+ * IN THE SOFTWARE.\n+ *\n+ *\n+ * When the user code is in a different process, a non-tls unix domain socket\n+ * proxy is used to asynchronusly transfer buffers in each direction via the\n+ * network stack, without explicit IPC\n+ *\n+ * user_process{ [user code] | shim | socket-}------ lws_process{ lws }\n+ *\n+ * Lws exposes a listening unix domain socket in this case, the user processes\n+ * connect to it and pass just info.streamtype in an initial tx packet. All\n+ * packets are prepended by a 1-byte type field when used in this mode. See\n+ * lws-secure-streams.h for documentation and definitions.\n+ *\n+ * Proxying in either direction can face the situation it cannot send the onward\n+ * packet immediately and is subject to separating the write request from the\n+ * write action. To make the best use of memory, a single preallocated buffer\n+ * stashes pending packets in all four directions (c-\u003ep, p-\u003ec, p-\u003ess, ss-\u003ep).\n+ * This allows it to adapt to different traffic patterns without wasted areas\n+ * dedicated to traffic that isn't coming in a particular application.\n+ *\n+ * A shim is provided to monitor the process' unix domain socket and regenerate\n+ * the secure sockets api there with callbacks happening in the process thread\n+ * context.\n+ *\n+ * This file implements the listening unix domain socket proxy... this code is\n+ * only going to run on a Linux-class device with its implications about memory\n+ * availability.\n+ */\n+\n+#include \u003cprivate-lib-core.h\u003e\n+\n+/*\n+ * Because both sides of the connection share the conn, we allocate it\n+ * during accepted adoption, and both sides point to it.\n+ *\n+ * The last one of the accepted side and the onward side to close frees it.\n+ */\n+\n+struct conn {\n+\tstruct lws_ss_serialization_parser parser;\n+\n+\tlws_dsh_t\t\t*dsh;\t/* unified buffer for both sides */\n+\tstruct lws\t\t*wsi;\t/* the client side */\n+\tlws_ss_handle_t\t\t*ss;\t/* the onward, ss side */\n+\n+\tlws_ss_conn_states_t\tstate;\n+};\n+\n+struct raw_pss {\n+\tstruct conn\t\t*conn;\n+};\n+\n+/*\n+ * Proxy - onward secure-stream handler\n+ */\n+\n+typedef struct ss_proxy_onward {\n+\tlws_ss_handle_t \t*ss;\n+\tstruct conn\t\t*conn;\n+} ss_proxy_t;\n+\n+\n+/* secure streams payload interface */\n+\n+static int\n+ss_proxy_onward_rx(void *userobj, const uint8_t *buf, size_t len, int flags)\n+{\n+\tss_proxy_t *m \u003d (ss_proxy_t *)userobj;\n+\tconst char *rsp \u003d NULL;\n+\tint n;\n+\n+\t/*\n+\t * The onward secure stream connection has received something.\n+\t */\n+\n+\tif (m-\u003ess-\u003erideshare !\u003d m-\u003ess-\u003epolicy \u0026\u0026 m-\u003ess-\u003erideshare) {\n+\t\trsp \u003d m-\u003ess-\u003erideshare-\u003estreamtype;\n+\t\tflags |\u003d LWSSS_FLAG_RIDESHARE;\n+\t}\n+\n+\tn \u003d lws_ss_serialize_rx_payload(m-\u003econn-\u003edsh, buf, len, flags, rsp);\n+\tif (n)\n+\t\treturn n;\n+\n+\tif (m-\u003econn-\u003ewsi) /* if possible, request client conn write */\n+\t\tlws_callback_on_writable(m-\u003econn-\u003ewsi);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * we are transmitting buffered payload originally from the client on to the ss\n+ */\n+\n+static int\n+ss_proxy_onward_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf,\n+\t\t size_t *len, int *flags)\n+{\n+\tss_proxy_t *m \u003d (ss_proxy_t *)userobj;\n+\tvoid *p;\n+\tsize_t si;\n+\n+\tif (!m-\u003econn-\u003ess || m-\u003econn-\u003estate !\u003d LPCS_OPERATIONAL) {\n+\t\tlwsl_notice(\u0022%s: ss not ready\u005cn\u0022, __func__);\n+\t\t*len \u003d 0;\n+\n+\t\treturn 1;\n+\t}\n+\n+\t/*\n+\t * The onward secure stream says that we could send something to it\n+\t * (by putting it in buf, and setting *len and *flags)\n+\t */\n+\n+\tif (lws_ss_deserialize_tx_payload(m-\u003econn-\u003edsh, m-\u003ess-\u003ewsi,\n+\t\t\t\t\t ord, buf, len, flags))\n+\t\treturn 1;\n+\n+\tif (!lws_dsh_get_head(m-\u003econn-\u003edsh, KIND_C_TO_P, (void **)\u0026p, \u0026si))\n+\t\tlws_ss_request_tx(m-\u003econn-\u003ess);\n+\n+\tif (!*len \u0026\u0026 !*flags)\n+\t\treturn 1; /* we don't actually want to send anything */\n+\n+\tlwsl_info(\u0022%s: onward tx %d fl 0x%x\u005cn\u0022, __func__, (int)*len, *flags);\n+\n+#if 0\n+\t{\n+\t\tint ff \u003d open(\u0022/tmp/z\u0022, O_RDWR | O_CREAT | O_APPEND, 0666);\n+\t\tif (ff \u003d\u003d -1)\n+\t\t\tlwsl_err(\u0022%s: errno %d\u005cn\u0022, __func__, errno);\n+\t\twrite(ff, buf, *len);\n+\t\tclose(ff);\n+\t}\n+#endif\n+\n+\treturn 0;\n+}\n+\n+static int\n+ss_proxy_onward_state(void *userobj, void *sh,\n+\t\t lws_ss_constate_t state, lws_ss_tx_ordinal_t ack)\n+{\n+\tss_proxy_t *m \u003d (ss_proxy_t *)userobj;\n+\n+\tswitch (state) {\n+\tcase LWSSSCS_CREATING:\n+\t\tbreak;\n+\n+\tcase LWSSSCS_DESTROYING:\n+\t\tif (!m-\u003econn)\n+\t\t\tbreak;\n+\t\tif (!m-\u003econn-\u003ewsi) {\n+\t\t\t/*\n+\t\t\t * Our onward secure stream is closing and our client\n+\t\t\t * connection has already gone away... destroy the conn.\n+\t\t\t */\n+\t\t\tlwsl_info(\u0022%s: Destroying conn\u005cn\u0022, __func__);\n+\t\t\tlws_dsh_destroy(\u0026m-\u003econn-\u003edsh);\n+\t\t\tfree(m-\u003econn);\n+\t\t\tm-\u003econn \u003d NULL;\n+\t\t\treturn 0;\n+\t\t} else\n+\t\t\tlwsl_info(\u0022%s: ss DESTROYING, wsi up\u005cn\u0022, __func__);\n+\t\tbreak;\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\tif (!m-\u003econn) {\n+\t\tlwsl_warn(\u0022%s: dropping state due to conn not up\u005cn\u0022, __func__);\n+\n+\t\treturn 0;\n+\t}\n+\n+\tlws_ss_serialize_state(m-\u003econn-\u003edsh, state, ack);\n+\n+\tif (m-\u003econn-\u003ewsi) /* if possible, request client conn write */\n+\t\tlws_callback_on_writable(m-\u003econn-\u003ewsi);\n+\n+\treturn 0;\n+}\n+\n+void\n+ss_proxy_onward_txcr(void *userobj, int bump)\n+{\n+\tss_proxy_t *m \u003d (ss_proxy_t *)userobj;\n+\n+\tif (!m-\u003econn)\n+\t\treturn;\n+\n+\tlws_ss_serialize_txcr(m-\u003econn-\u003edsh, bump);\n+\n+\tif (m-\u003econn-\u003ewsi) /* if possible, request client conn write */\n+\t\tlws_callback_on_writable(m-\u003econn-\u003ewsi);\n+}\n+\n+/*\n+ * Client - Proxy connection on unix domain socket\n+ */\n+\n+static int\n+callback_ss_proxy(struct lws *wsi, enum lws_callback_reasons reason,\n+\t\t void *user, void *in, size_t len)\n+{\n+\tstruct lws_context_per_thread *pt \u003d \u0026wsi-\u003econtext-\u003ept[(int)wsi-\u003etsi];\n+\tstruct raw_pss *pss \u003d (struct raw_pss *)user;\n+\tconst lws_ss_policy_t *rsp;\n+\tstruct conn *conn \u003d NULL;\n+\tlws_ss_info_t ssi;\n+\tconst uint8_t *cp;\n+#if defined(LWS_WITH_DETAILED_LATENCY)\n+\tlws_usec_t us;\n+#endif\n+\tchar s[128];\n+\tuint8_t *p;\n+\tsize_t si;\n+\tchar pay;\n+\tint n;\n+\n+\tif (pss)\n+\t\tconn \u003d pss-\u003econn;\n+\n+\tswitch (reason) {\n+\tcase LWS_CALLBACK_PROTOCOL_INIT:\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_PROTOCOL_DESTROY:\n+\t\tbreak;\n+\n+\t/* callbacks related to raw socket descriptor \u0022accepted side\u0022 */\n+\n+ case LWS_CALLBACK_RAW_ADOPT:\n+\t\tlwsl_info(\u0022LWS_CALLBACK_RAW_ADOPT\u005cn\u0022);\n+\t\tif (!pss)\n+\t\t\treturn -1;\n+\t\tpss-\u003econn \u003d malloc(sizeof(struct conn));\n+\t\tif (!pss-\u003econn)\n+\t\t\treturn -1;\n+\t\tmemset(pss-\u003econn, 0, sizeof(*pss-\u003econn));\n+\n+\t\tpss-\u003econn-\u003edsh \u003d lws_dsh_create(\u0026pt-\u003ess_dsh_owner,\n+\t\t\t\t\t\tLWS_SS_MTU * 160, 2);\n+\t\tif (!pss-\u003econn-\u003edsh) {\n+\t\t\tfree(pss-\u003econn);\n+\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tpss-\u003econn-\u003ewsi \u003d wsi;\n+\t\tpss-\u003econn-\u003estate \u003d LPCS_WAIT_INITIAL_TX;\n+\n+\t\t/*\n+\t\t * Client is expected to follow the unix domain socket\n+\t\t * acceptance up rapidly with an initial tx containing the\n+\t\t * streamtype name. We can't create the stream until then.\n+\t\t */\n+\t\tlws_set_timeout(wsi,\n+\t\t\t\tPENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND, 3);\n+ break;\n+\n+\tcase LWS_CALLBACK_RAW_CLOSE:\n+\t\tlwsl_info(\u0022LWS_CALLBACK_RAW_CLOSE:\u005cn\u0022);\n+\n+\t\t/*\n+\t\t * the client unix domain socket connection has closed...\n+\t\t * eg, client has exited or otherwise has definitively finished\n+\t\t * with the proxying and onward connection\n+\t\t */\n+\n+\t\tif (!conn)\n+\t\t\tbreak;\n+\n+\t\tif (conn-\u003ess) {\n+\t\t\tlwsl_info(\u0022%s: destroying ss\u005cn\u0022, __func__);\n+\t\t\t/* sever relationship with ss about to be deleted */\n+\t\t\tlws_set_opaque_user_data(wsi, NULL);\n+\n+\t\t\tconn-\u003ewsi \u003d NULL;\n+\n+\n+\t\t\tlws_ss_destroy(\u0026conn-\u003ess);\n+\t\t\t/* conn may have gone */\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (conn-\u003estate \u003d\u003d LPCS_DESTROYED || !conn-\u003ess) {\n+\t\t\t/*\n+\t\t\t * There's no onward secure stream and our client\n+\t\t\t * connection is closing. Destroy the conn.\n+\t\t\t */\n+\t\t\tlws_dsh_destroy(\u0026conn-\u003edsh);\n+\t\t\tfree(conn);\n+\t\t\tpss-\u003econn \u003d NULL;\n+\t\t} else\n+\t\t\tlwsl_debug(\u0022%s: CLOSE; ss\u003d%p\u005cn\u0022, __func__, conn-\u003ess);\n+\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_RAW_RX:\n+\t\tlwsl_info(\u0022%s: RX: rx %d\u005cn\u0022, __func__, (int)len);\n+\n+\t\tif (!conn || !conn-\u003ewsi) {\n+\t\t\tlwsl_err(\u0022%s: rx with bad conn state\u005cn\u0022, __func__);\n+\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\t// lwsl_hexdump_info(in, len);\n+\n+\t\tif (conn-\u003estate \u003d\u003d LPCS_WAIT_INITIAL_TX) {\n+\t\t\tmemset(\u0026ssi, 0, sizeof(ssi));\n+\t\t\tssi.user_alloc \u003d sizeof(ss_proxy_t);\n+\t\t\tssi.handle_offset \u003d offsetof(ss_proxy_t, ss);\n+\t\t\tssi.opaque_user_data_offset \u003d\n+\t\t\t\t\toffsetof(ss_proxy_t, conn);\n+\t\t\tssi.rx \u003d ss_proxy_onward_rx;\n+\t\t\tssi.tx \u003d ss_proxy_onward_tx;\n+\t\t\tssi.state \u003d ss_proxy_onward_state;\n+\t\t}\n+\n+\t\tif (lws_ss_deserialize_parse(\u0026conn-\u003eparser,\n+\t\t\t\tlws_get_context(wsi), conn-\u003edsh, in, len,\n+\t\t\t\t\u0026conn-\u003estate, conn, \u0026conn-\u003ess, \u0026ssi, 0)) {\n+\t\t\tlwsl_err(\u0022%s: RAW_RX: deserialize_parse fail\u005cn\u0022, __func__);\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tif (conn-\u003estate \u003d\u003d LPCS_REPORTING_FAIL ||\n+\t\t conn-\u003estate \u003d\u003d LPCS_REPORTING_OK)\n+\t\t\tlws_callback_on_writable(conn-\u003ewsi);\n+\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_RAW_WRITEABLE:\n+\t\t// lwsl_notice(\u0022LWS_CALLBACK_RAW_PROXY_SRV_WRITEABLE\u005cn\u0022);\n+\n+\t\t/*\n+\t\t * We can transmit something back to the client from the dsh\n+\t\t * of stuff we received on its behalf from the ss\n+\t\t */\n+\n+\t\tif (!conn || !conn-\u003ewsi)\n+\t\t\tbreak;\n+\n+\t\tn \u003d 0;\n+\t\tpay \u003d 0;\n+\t\ts[3] \u003d 0;\n+\t\tcp \u003d (const uint8_t *)s;\n+\t\tswitch (conn-\u003estate) {\n+\t\tcase LPCS_REPORTING_FAIL:\n+\t\t\ts[3] \u003d 1;\n+\t\t\t/* fallthru */\n+\t\tcase LPCS_REPORTING_OK:\n+\t\t\ts[0] \u003d LWSSS_SER_RXPRE_CREATE_RESULT;\n+\t\t\ts[1] \u003d 0;\n+\t\t\ts[2] \u003d 1;\n+\n+\t\t\tn \u003d 4;\n+\n+\t\t\t/*\n+\t\t\t * If there's rideshare sequencing, it's added after the\n+\t\t\t * first 4 bytes or the create result, comma-separated\n+\t\t\t */\n+\n+\t\t\trsp \u003d conn-\u003ess-\u003epolicy;\n+\n+\t\t\twhile (rsp) {\n+\t\t\t\tif (n !\u003d 4 \u0026\u0026 n \u003c (int)sizeof(s) - 2)\n+\t\t\t\t\ts[n++] \u003d ',';\n+\t\t\t\tn +\u003d lws_snprintf(\u0026s[n], sizeof(s) - n,\n+\t\t\t\t\t\t\u0022%s\u0022, rsp-\u003estreamtype);\n+\t\t\t\trsp \u003d lws_ss_policy_lookup(wsi-\u003econtext,\n+\t\t\t\t\trsp-\u003erideshare_streamtype);\n+\t\t\t}\n+\t\t\ts[2] \u003d n - 3;\n+\t\t\tconn-\u003estate \u003d LPCS_OPERATIONAL;\n+\t\t\tlws_set_timeout(wsi, 0, 0);\n+\t\t\tbreak;\n+\t\tcase LPCS_OPERATIONAL:\n+\t\t\tif (lws_dsh_get_head(conn-\u003edsh, KIND_SS_TO_P,\n+\t\t\t\t\t (void **)\u0026p, \u0026si))\n+\t\t\t\tbreak;\n+\t\t\tcp \u003d p;\n+\n+#if defined(LWS_WITH_DETAILED_LATENCY)\n+\t\t\tif (cp[0] \u003d\u003d LWSSS_SER_RXPRE_RX_PAYLOAD \u0026\u0026\n+\t\t\t wsi-\u003econtext-\u003edetailed_latency_cb) {\n+\n+\t\t\t\t/*\n+\t\t\t\t * we're fulfilling rx that came in on ss\n+\t\t\t\t * by sending it back out to the client on\n+\t\t\t\t * the Unix Domain Socket\n+\t\t\t\t *\n+\t\t\t\t * + 7 u32 write will compute latency here...\n+\t\t\t\t * + 11 u32 ust we received from ss\n+\t\t\t\t *\n+\t\t\t\t * lws_write will report it and fill in\n+\t\t\t\t * LAT_DUR_PROXY_CLIENT_REQ_TO_WRITE\n+\t\t\t\t */\n+\n+\t\t\t\tus \u003d lws_now_usecs();\n+\t\t\t\tlws_ser_wu32be(\u0026p[7], us -\n+\t\t\t\t\t\t lws_ser_ru64be(\u0026p[11]));\n+\t\t\t\tlws_ser_wu64be(\u0026p[11], us);\n+\n+\t\t\t\twsi-\u003edetlat.acc_size \u003d\n+\t\t\t\t\twsi-\u003edetlat.req_size \u003d si - 19;\n+\t\t\t\t/* time proxy held it */\n+\t\t\t\twsi-\u003edetlat.latencies[\n+\t\t\t\t LAT_DUR_PROXY_RX_TO_ONWARD_TX] \u003d\n+\t\t\t\t\t\t\tlws_ser_ru32be(\u0026p[7]);\n+\t\t\t}\n+#endif\n+\n+\t\t\tpay \u003d 1;\n+\t\t\tn \u003d (int)si;\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tbreak;\n+\t\t}\n+again:\n+\t\tif (!n)\n+\t\t\tbreak;\n+\n+\t\tn \u003d lws_write(wsi, (uint8_t *)cp, n, LWS_WRITE_RAW);\n+\t\tif (n \u003c 0) {\n+\t\t\tlwsl_info(\u0022%s: WRITEABLE: %d\u005cn\u0022, __func__, n);\n+\n+\t\t\tgoto hangup;\n+\t\t}\n+\n+\t\tswitch (conn-\u003estate) {\n+\t\tcase LPCS_REPORTING_FAIL:\n+\t\t\tgoto hangup;\n+\t\tcase LPCS_OPERATIONAL:\n+\t\t\tif (pay)\n+\t\t\t\tlws_dsh_free((void **)\u0026p);\n+\t\t\tif (!lws_dsh_get_head(conn-\u003edsh, KIND_SS_TO_P,\n+\t\t\t\t\t (void **)\u0026p, \u0026si)) {\n+\t\t\t\tif (!lws_send_pipe_choked(wsi)) {\n+\t\t\t\t\tcp \u003d p;\n+\t\t\t\t\tpay \u003d 1;\n+\t\t\t\t\tn \u003d (int)si;\n+\t\t\t\t\tgoto again;\n+\t\t\t\t}\n+\t\t\t\tlws_callback_on_writable(wsi);\n+\t\t\t}\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tbreak;\n+\t\t}\n+\t\tbreak;\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn lws_callback_http_dummy(wsi, reason, user, in, len);\n+\n+hangup:\n+\t//lws_ss_destroy(\u0026conn-\u003ess);\n+\t//conn-\u003estate \u003d LPCS_DESTROYED;\n+\n+\t/* hang up on him */\n+\treturn -1;\n+}\n+\n+static const struct lws_protocols protocols[] \u003d {\n+\t{\n+\t\t\u0022ssproxy-protocol\u0022,\n+\t\tcallback_ss_proxy,\n+\t\tsizeof(struct raw_pss),\n+\t\t2048, 2048, NULL, 0\n+\t},\n+\t{ NULL, NULL, 0, 0, 0, NULL, 0 }\n+};\n+\n+/*\n+ * called from create_context()\n+ */\n+\n+int\n+lws_ss_proxy_create(struct lws_context *context, const char *bind, int port)\n+{\n+\tstruct lws_context_creation_info info;\n+\n+\tmemset(\u0026info, 0, sizeof(info));\n+\n+\tinfo.vhost_name\t\t\t\u003d \u0022ssproxy\u0022;\n+\tinfo.options \u003d LWS_SERVER_OPTION_ADOPT_APPLY_LISTEN_ACCEPT_CONFIG;\n+\tinfo.port \u003d port;\n+\tif (!port) {\n+\t\tif (!bind)\n+\t\t\tbind \u003d \u0022@proxy.ss.lws\u0022;\n+\t\tinfo.options |\u003d LWS_SERVER_OPTION_UNIX_SOCK;\n+\t}\n+\tinfo.iface\t\t\t\u003d bind;\n+\tinfo.unix_socket_perms\t\t\u003d \u0022root:root\u0022;\n+\tinfo.listen_accept_role\t\t\u003d \u0022raw-skt\u0022;\n+\tinfo.listen_accept_protocol\t\u003d \u0022ssproxy-protocol\u0022;\n+\tinfo.protocols\t\t\t\u003d protocols;\n+\n+\tif (!lws_create_vhost(context, \u0026info)) {\n+\t\tlwsl_err(\u0022%s: Failed to create ss proxy vhost\u005cn\u0022, __func__);\n+\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\ndiff --git a/lib/secure-streams/secure-streams-serialize.c b/lib/secure-streams/secure-streams-serialize.c\nnew file mode 100644\nindex 0000000..cd85f0a\n--- /dev/null\n+++ b/lib/secure-streams/secure-streams-serialize.c\n@@ -0,0 +1,926 @@\n+/*\n+ * libwebsockets - small server side websockets and web server implementation\n+ *\n+ * Copyright (C) 2019 - 2020 Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * Permission is hereby granted, free of charge, to any person obtaining a copy\n+ * of this software and associated documentation files (the \u0022Software\u0022), to\n+ * deal in the Software without restriction, including without limitation the\n+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n+ * sell copies of the Software, and to permit persons to whom the Software is\n+ * furnished to do so, subject to the following conditions:\n+ *\n+ * The above copyright notice and this permission notice shall be included in\n+ * all copies or substantial portions of the Software.\n+ *\n+ * THE SOFTWARE IS PROVIDED \u0022AS IS\u0022, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n+ * IN THE SOFTWARE.\n+ *\n+ *\n+ * In the case Secure Streams protocol needs to pass through a buffer,\n+ * or a streamed connection, the protocol metadata must be serialized. This\n+ * file provides internal apis to perform the serialization and deserialization\n+ * in and out of an lws_dsh fifo-type buffer.\n+ */\n+\n+#include \u003cprivate-lib-core.h\u003e\n+\n+typedef enum {\n+\tRPAR_TYPE,\n+\tRPAR_LEN_MSB,\n+\tRPAR_LEN_LSB,\n+\n+\tRPAR_FLAG_B3,\n+\tRPAR_FLAG_B2,\n+\tRPAR_FLAG_B1,\n+\tRPAR_FLAG_B0,\n+\n+\tRPAR_LATA3,\n+\tRPAR_LATA2,\n+\tRPAR_LATA1,\n+\tRPAR_LATA0,\n+\n+\tRPAR_LATB7,\n+\tRPAR_LATB6,\n+\tRPAR_LATB5,\n+\tRPAR_LATB4,\n+\tRPAR_LATB3,\n+\tRPAR_LATB2,\n+\tRPAR_LATB1,\n+\tRPAR_LATB0,\n+\n+\tRPAR_RIDESHARE_LEN,\n+\tRPAR_RIDESHARE,\n+\n+\tRPAR_RESULT_CREATION_RIDESHARE,\n+\n+\tRPAR_METADATA_NAMELEN,\n+\tRPAR_METADATA_NAME,\n+\tRPAR_METADATA_VALUE,\n+\n+\tRPAR_PAYLOAD,\n+\n+\tRPAR_RX_TXCR_UPDATE,\n+\n+\tRPAR_STREAMTYPE,\n+\tRPAR_INITTXC0,\n+\n+\tRPAR_TXCR0,\n+\n+\tRPAR_RESULT_CREATION,\n+\n+\tRPAR_STATEINDEX,\n+\tRPAR_ORD3,\n+\tRPAR_ORD2,\n+\tRPAR_ORD1,\n+\tRPAR_ORD0,\n+} rx_parser_t;\n+\n+#if defined(_DEBUG)\n+static const char *sn[] \u003d {\n+\t\u0022unset\u0022,\n+\n+\t\u0022LPCS_WAIT_INITIAL_TX\u0022,\n+\t\u0022LPCS_REPORTING_FAIL\u0022,\n+\t\u0022LPCS_REPORTING_OK\u0022,\n+\t\u0022LPCS_OPERATIONAL\u0022,\n+\t\u0022LPCS_DESTROYED\u0022,\n+\n+\t\u0022LPCS_SENDING_INITIAL_TX\u0022,\n+\t\u0022LPCS_WAITING_CREATE_RESULT\u0022,\n+\t\u0022LPCS_LOCAL_CONNECTED\u0022,\n+\t\u0022LPCS_ONWARD_CONNECT\u0022,\n+};\n+#endif\n+\n+void\n+lws_ss_serialize_state_transition(lws_ss_conn_states_t *state, int new_state)\n+{\n+#if defined(_DEBUG)\n+\tlwsl_info(\u0022%s: %s -\u003e %s\u005cn\u0022, __func__, sn[*state], sn[new_state]);\n+#endif\n+\t*state \u003d new_state;\n+}\n+\n+\n+/*\n+ * event loop received something and is queueing it for the foreign side of\n+ * the dsh to consume later as serialized rx\n+ */\n+\n+int\n+lws_ss_serialize_rx_payload(struct lws_dsh *dsh, const uint8_t *buf,\n+\t\t\t size_t len, int flags, const char *rsp)\n+{\n+\tlws_usec_t us \u003d lws_now_usecs();\n+\tuint8_t pre[128];\n+\tint est \u003d 19, l \u003d 0;\n+\n+\tif (flags \u0026 LWSSS_FLAG_RIDESHARE) {\n+\t\t/*\n+\t\t * We should have the rideshare name if we have been told it's\n+\t\t * on a non-default rideshare\n+\t\t */\n+\t\tassert(rsp);\n+\t\tl \u003d strlen(rsp);\n+\t\test +\u003d 1 + l;\n+\t} else\n+\t\tassert(!rsp);\n+\n+\t// lwsl_user(\u0022%s: len %d, flags: %d\u005cn\u0022, __func__, (int)len, flags);\n+\t// lwsl_hexdump_info(buf, len);\n+\n+\tpre[0] \u003d LWSSS_SER_RXPRE_RX_PAYLOAD;\n+\tlws_ser_wu16be(\u0026pre[1], len + est - 3);\n+\tlws_ser_wu32be(\u0026pre[3], flags);\n+\tlws_ser_wu32be(\u0026pre[7], 0);\t/* write will compute latency here... */\n+\tlws_ser_wu64be(\u0026pre[11], us);\t/* ... and set this to the write time */\n+\n+\t/*\n+\t * If we are on a non-default rideshare, append the non-default name to\n+\t * the headers of the payload part, 1-byte length first\n+\t */\n+\n+\tif (flags \u0026 LWSSS_FLAG_RIDESHARE) {\n+\t\tpre[19] \u003d (uint8_t)l;\n+\t\tmemcpy(\u0026pre[20], rsp, l);\n+\t}\n+\n+\tif (lws_dsh_alloc_tail(dsh, KIND_SS_TO_P, pre, est, buf, len)) {\n+\t\tlwsl_err(\u0022%s: unable to alloc in dsh 1\u005cn\u0022, __func__);\n+\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * event loop is consuming dsh-buffered, already-serialized tx from the\n+ * foreign side\n+ */\n+\n+int\n+lws_ss_deserialize_tx_payload(struct lws_dsh *dsh, struct lws *wsi,\n+\t\t\t lws_ss_tx_ordinal_t ord, uint8_t *buf,\n+\t\t\t size_t *len, int *flags)\n+{\n+\tuint8_t *p;\n+\tsize_t si;\n+\n+\tif (lws_dsh_get_head(dsh, KIND_C_TO_P, (void **)\u0026p, \u0026si)) {\n+\t\t*len \u003d 0;\n+\t\treturn 0;\n+\t}\n+\n+\t/*\n+\t * The packet in the dsh has a proxying serialization header, process\n+\t * and strip it so we just forward the payload\n+\t */\n+\n+\tif (*len \u003c\u003d si - 23 || si \u003c 23) {\n+\t\t/*\n+\t\t * What comes out of the dsh needs to fit in the tx buffer\n+\t\t */\n+\t\tlwsl_err(\u0022%s: *len \u003d %d, si \u003d %d\u005cn\u0022, __func__, (int)*len, (int)si);\n+\t\tassert(0);\n+\t\treturn 1;\n+\t}\n+\tif (p[0] !\u003d LWSSS_SER_TXPRE_TX_PAYLOAD) {\n+\t\tassert(0);\n+\t\treturn 1;\n+\t}\n+\n+\t*len \u003d lws_ser_ru16be(\u0026p[1]) - (23 - 3);\n+\tassert(*len \u003d\u003d si - 23);\n+\n+\tmemcpy(buf, p + 23, si - 23);\n+\n+\t*flags \u003d lws_ser_ru32be(\u0026p[3]);\n+\n+#if defined(LWS_WITH_DETAILED_LATENCY)\n+\tif (wsi \u0026\u0026 wsi-\u003econtext-\u003edetailed_latency_cb) {\n+\t\t/*\n+\t\t * use the proxied latency information to compute the client\n+\t\t * and our delays, and apply to wsi.\n+\t\t *\n+\t\t * + 7 u32 us held at client before written\n+\t\t * +11 u32 us taken for transit to proxy\n+\t\t * +15 u64 ustime when proxy got packet from client\n+\t\t */\n+\t\tlws_usec_t us \u003d lws_now_usecs();\n+\n+\t\twsi-\u003edetlat.acc_size \u003d wsi-\u003edetlat.req_size \u003d si - 23;\n+\t\twsi-\u003edetlat.latencies[LAT_DUR_PROXY_CLIENT_REQ_TO_WRITE] \u003d\n+\t\t\t\t\t\tlws_ser_ru32be(\u0026p[7]);\n+\t\twsi-\u003edetlat.latencies[LAT_DUR_PROXY_CLIENT_WRITE_TO_PROXY_RX] \u003d\n+\t\t\t\t\t\tlws_ser_ru32be(\u0026p[11]);\n+\t\twsi-\u003edetlat.latencies[LAT_DUR_PROXY_RX_TO_ONWARD_TX] \u003d\n+\t\t\t\t\t\tus - lws_ser_ru64be(\u0026p[15]);\n+\n+\t\twsi-\u003edetlat.latencies[LAT_DUR_USERCB] \u003d 0;\n+\t}\n+#endif\n+\n+\t// lwsl_user(\u0022%s: len %d, flags: %d\u005cn\u0022, __func__, (int)*len, *flags);\n+\t// lwsl_hexdump_info(buf, *len);\n+\n+\tlws_dsh_free((void **)\u0026p);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * event loop side is issuing state, serialize and put it in the dbuf for\n+ * the foreign side to consume later\n+ */\n+\n+int\n+lws_ss_serialize_state(struct lws_dsh *dsh, lws_ss_constate_t state,\n+\t\t lws_ss_tx_ordinal_t ack)\n+{\n+\tuint8_t pre[8];\n+\n+\tlwsl_info(\u0022%s: %s, ord 0x%x\u005cn\u0022, __func__, lws_ss_state_name(state),\n+\t\t (unsigned int)ack);\n+\n+\tpre[0] \u003d LWSSS_SER_RXPRE_CONNSTATE;\n+\tpre[1] \u003d 0;\n+\tpre[2] \u003d 5;\n+\tpre[3] \u003d (uint8_t)state;\n+\tlws_ser_wu32be(\u0026pre[4], ack);\n+\n+\tif (lws_dsh_alloc_tail(dsh, KIND_SS_TO_P, pre, 8, NULL, 0)) {\n+\t\tlwsl_err(\u0022%s: unable to alloc in dsh 2\u005cn\u0022, __func__);\n+\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * event loop side was told about remote peer tx credit window update, serialize\n+ * and put it in the dbuf for the foreign side to consume later\n+ */\n+\n+int\n+lws_ss_serialize_txcr(struct lws_dsh *dsh, int txcr)\n+{\n+\tuint8_t pre[7];\n+\n+\tlwsl_info(\u0022%s: %d\u005cn\u0022, __func__, txcr);\n+\n+\tpre[0] \u003d LWSSS_SER_RXPRE_TXCR_UPDATE;\n+\tpre[1] \u003d 0;\n+\tpre[2] \u003d 4;\n+\tlws_ser_wu32be(\u0026pre[3], txcr);\n+\n+\tif (lws_dsh_alloc_tail(dsh, KIND_SS_TO_P, pre, 7, NULL, 0)) {\n+\t\tlwsl_err(\u0022%s: unable to alloc in dsh 2\u005cn\u0022, __func__);\n+\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * event loop side is consuming serialized data from the client via dsh, parse\n+ * it using a bytewise parser for the serialization header(s)...\n+ * it's possibly coalesced\n+ */\n+\n+int\n+lws_ss_deserialize_parse(struct lws_ss_serialization_parser *par,\n+\t\t\t struct lws_context *context,\n+\t\t\t struct lws_dsh *dsh, const uint8_t *cp, size_t len,\n+\t\t\t lws_ss_conn_states_t *state, void *parconn,\n+\t\t\t lws_ss_handle_t **pss, lws_ss_info_t *ssi, char client)\n+{\n+\tlws_ss_metadata_t *pm;\n+\tlws_sspc_handle_t *h;\n+\tuint8_t pre[23];\n+\tlws_usec_t us;\n+\tuint32_t flags;\n+\tuint8_t *p;\n+\tint n;\n+\n+\twhile (len--) {\n+\t\tswitch (par-\u003eps) {\n+\t\tcase RPAR_TYPE:\n+\t\t\tpar-\u003etype \u003d *cp++;\n+\t\t\tpar-\u003eps++;\n+\t\t\tbreak;\n+\n+\t\tcase RPAR_LEN_MSB: /* this is remaining frame length */\n+\t\t\tpar-\u003erem \u003d (*cp++) \u003c\u003c 8;\n+\t\t\tpar-\u003eps++;\n+\t\t\tbreak;\n+\n+\t\tcase RPAR_LEN_LSB:\n+\t\t\tpar-\u003erem |\u003d *cp++;\n+\t\t\tswitch (par-\u003etype) {\n+\n+\t\t\t/* event loop side */\n+\n+\t\t\tcase LWSSS_SER_TXPRE_TX_PAYLOAD:\n+\t\t\t\tif (client)\n+\t\t\t\t\tgoto hangup;\n+\t\t\t\tif (*state !\u003d LPCS_OPERATIONAL)\n+\t\t\t\t\tgoto hangup;\n+\t\t\t\tpar-\u003eps \u003d RPAR_FLAG_B3;\n+\t\t\t\tbreak;\n+\n+\t\t\tcase LWSSS_SER_TXPRE_DESTROYING:\n+\t\t\t\tif (client)\n+\t\t\t\t\tgoto hangup;\n+\t\t\t\tpar-\u003eps \u003d RPAR_TYPE;\n+\t\t\t\tlwsl_notice(\u0022%s: DESTROYING\u005cn\u0022, __func__);\n+\t\t\t\tgoto hangup;\n+\n+\t\t\tcase LWSSS_SER_TXPRE_ONWARD_CONNECT:\n+\t\t\t\tif (client)\n+\t\t\t\t\tgoto hangup;\n+\t\t\t\tif (*state !\u003d LPCS_OPERATIONAL)\n+\t\t\t\t\tgoto hangup;\n+\t\t\t\tpar-\u003eps \u003d RPAR_TYPE;\n+\t\t\t\tif (*pss)\n+\t\t\t\t\tlws_ss_client_connect(*pss);\n+\t\t\t\tbreak;\n+\n+\t\t\tcase LWSSS_SER_TXPRE_STREAMTYPE:\n+\t\t\t\tif (client)\n+\t\t\t\t\tgoto hangup;\n+\t\t\t\tif (*state !\u003d LPCS_WAIT_INITIAL_TX)\n+\t\t\t\t\tgoto hangup;\n+\t\t\t\tif (par-\u003erem \u003c 4)\n+\t\t\t\t\tgoto hangup;\n+\t\t\t\tpar-\u003ectr \u003d 0;\n+\t\t\t\tpar-\u003eps \u003d RPAR_INITTXC0;\n+\t\t\t\tbreak;\n+\n+\t\t\tcase LWSSS_SER_TXPRE_METADATA:\n+\t\t\t\tif (client)\n+\t\t\t\t\tgoto hangup;\n+\t\t\t\tif (par-\u003erem \u003c 3)\n+\t\t\t\t\tgoto hangup;\n+\t\t\t\tpar-\u003ectr \u003d 0;\n+\t\t\t\tpar-\u003eps \u003d RPAR_METADATA_NAMELEN;\n+\t\t\t\tbreak;\n+\n+\t\t\tcase LWSSS_SER_TXPRE_TXCR_UPDATE:\n+\t\t\t\tpar-\u003eps \u003d RPAR_TXCR0;\n+\t\t\t\tpar-\u003ectr \u003d 0;\n+\t\t\t\tbreak;\n+\n+\t\t\t/* client side */\n+\n+\t\t\tcase LWSSS_SER_RXPRE_RX_PAYLOAD:\n+\t\t\t\tif (!client)\n+\t\t\t\t\tgoto hangup;\n+\t\t\t\tif (*state !\u003d LPCS_OPERATIONAL \u0026\u0026\n+\t\t\t\t *state !\u003d LPCS_LOCAL_CONNECTED) {\n+\t\t\t\t\tlwsl_err(\u0022rx in state %d\u005cn\u0022, *state);\n+\t\t\t\t\tgoto hangup;\n+\t\t\t\t}\n+\t\t\t\tpar-\u003erideshare[0] \u003d '\u005c0';\n+\t\t\t\tpar-\u003eps \u003d RPAR_FLAG_B3;\n+\t\t\t\tbreak;\n+\n+\t\t\tcase LWSSS_SER_RXPRE_CREATE_RESULT:\n+\t\t\t\tif (!client)\n+\t\t\t\t\tgoto hangup;\n+\t\t\t\tif (*state !\u003d LPCS_WAITING_CREATE_RESULT) {\n+\t\t\t\t\tlwsl_err(\u0022a2\u005cn\u0022);\n+\t\t\t\t\tgoto hangup;\n+\t\t\t\t}\n+\t\t\t\tif (par-\u003erem \u003c 1) {\n+\t\t\t\t\tlwsl_err(\u0022a3\u005cn\u0022);\n+\t\t\t\t\tgoto hangup;\n+\t\t\t\t}\n+\t\t\t\tpar-\u003eps \u003d RPAR_RESULT_CREATION;\n+\t\t\t\tbreak;\n+\n+\t\t\tcase LWSSS_SER_RXPRE_CONNSTATE:\n+\t\t\t\tif (!client)\n+\t\t\t\t\tgoto hangup;\n+\t\t\t\tif (*state !\u003d LPCS_LOCAL_CONNECTED \u0026\u0026\n+\t\t\t\t *state !\u003d LPCS_OPERATIONAL) {\n+\t\t\t\t\tlwsl_err(\u0022a4\u005cn\u0022);\n+\t\t\t\t\tgoto hangup;\n+\t\t\t\t}\n+\t\t\t\tif (par-\u003erem \u003c 4) {\n+\t\t\t\t\tlwsl_err(\u0022a5\u005cn\u0022);\n+\t\t\t\t\tgoto hangup;\n+\t\t\t\t}\n+\t\t\t\tpar-\u003eps \u003d RPAR_STATEINDEX;\n+\t\t\t\tbreak;\n+\n+\t\t\tcase LWSSS_SER_RXPRE_TXCR_UPDATE:\n+\t\t\t\tpar-\u003ectr \u003d 0;\n+\t\t\t\tpar-\u003eps \u003d RPAR_RX_TXCR_UPDATE;\n+\t\t\t\tbreak;\n+\n+\t\t\tdefault:\n+\t\t\t\tlwsl_notice(\u0022%s: bad type 0x%x\u005cn\u0022, __func__,\n+\t\t\t\t\t par-\u003etype);\n+\t\t\t\tgoto hangup;\n+\t\t\t}\n+\t\t\tbreak;\n+\n+\t\t\tcase RPAR_FLAG_B3:\n+\t\t\tcase RPAR_FLAG_B2:\n+\t\t\tcase RPAR_FLAG_B1:\n+\t\t\tcase RPAR_FLAG_B0:\n+\t\t\t\tpar-\u003eflags \u003c\u003c\u003d 8;\n+\t\t\t\tpar-\u003eflags |\u003d *cp++;\n+\t\t\t\tpar-\u003eps++;\n+\t\t\t\tif (!par-\u003erem--)\n+\t\t\t\t\tgoto hangup;\n+\t\t\t\tbreak;\n+\n+\t\t\tcase RPAR_LATA3:\n+\t\t\tcase RPAR_LATA2:\n+\t\t\tcase RPAR_LATA1:\n+\t\t\tcase RPAR_LATA0:\n+\t\t\t\tpar-\u003eusd_phandling \u003c\u003c\u003d 8;\n+\t\t\t\tpar-\u003eusd_phandling |\u003d *cp++;\n+\t\t\t\tpar-\u003eps++;\n+\t\t\t\tif (!par-\u003erem--)\n+\t\t\t\t\tgoto hangup;\n+\t\t\t\tbreak;\n+\n+\t\t\tcase RPAR_LATB7:\n+\t\t\tcase RPAR_LATB6:\n+\t\t\tcase RPAR_LATB5:\n+\t\t\tcase RPAR_LATB4:\n+\t\t\tcase RPAR_LATB3:\n+\t\t\tcase RPAR_LATB2:\n+\t\t\tcase RPAR_LATB1:\n+\t\t\tcase RPAR_LATB0:\n+\t\t\t\tpar-\u003eust_pwait \u003c\u003c\u003d 8;\n+\t\t\t\tpar-\u003eust_pwait |\u003d *cp++;\n+\t\t\t\tpar-\u003eps++;\n+\t\t\t\tpar-\u003efrag1 \u003d 1;\n+\t\t\t\tif (!par-\u003erem--)\n+\t\t\t\t\tgoto hangup;\n+\n+\t\t\t\tif (par-\u003eps \u003d\u003d RPAR_RIDESHARE_LEN \u0026\u0026\n+\t\t\t\t !(par-\u003eflags \u0026 LWSSS_FLAG_RIDESHARE))\n+\t\t\t\t\tpar-\u003eps \u003d RPAR_PAYLOAD;\n+\n+\t\t\t\tif (par-\u003erem)\n+\t\t\t\t\tbreak;\n+\n+\t\t\t\t/* fallthru - handle 0-length payload */\n+\n+\t\t\t\tif (!(par-\u003eflags \u0026 LWSSS_FLAG_RIDESHARE))\n+\t\t\t\t\tgoto payload_ff;\n+\t\t\t\tgoto hangup;\n+\n+\t\t\t/*\n+\t\t\t * Inbound rideshare info is provided on the RX packet\n+\t\t\t * itself\n+\t\t\t */\n+\n+\t\tcase RPAR_RIDESHARE_LEN:\n+\t\t\tpar-\u003eslen \u003d *cp++;\n+\t\t\tpar-\u003ectr \u003d 0;\n+\t\t\tpar-\u003eps++;\n+\t\t\tif (par-\u003erem-- \u003c par-\u003eslen)\n+\t\t\t\tgoto hangup;\n+\t\t\tbreak;\n+\n+\t\tcase RPAR_RIDESHARE:\n+\t\t\tpar-\u003erideshare[par-\u003ectr++] \u003d *cp++;\n+\t\t\tif (!par-\u003erem--)\n+\t\t\t\tgoto hangup;\n+\t\t\tif (par-\u003ectr !\u003d par-\u003eslen)\n+\t\t\t\tbreak;\n+\t\t\tpar-\u003eps \u003d RPAR_PAYLOAD;\n+\t\t\tif (par-\u003erem)\n+\t\t\t\tbreak;\n+\n+\t\t\t/* fallthru - handle 0-length payload */\n+\n+\t\tcase RPAR_PAYLOAD:\n+payload_ff:\n+\t\t\tn \u003d (int)len + 1;\n+\t\t\tif (n \u003e par-\u003erem)\n+\t\t\t\tn \u003d par-\u003erem;\n+\t\t\tif (n \u003e 1380)\n+\t\t\t\tn \u003d 1380;\n+\n+\t\t\t/* deal with refragmented SOM / EOM flags */\n+\n+\t\t\tflags \u003d par-\u003eflags \u0026 LWSSS_FLAG_RELATED_START;\n+\t\t\tif (par-\u003efrag1)\n+\t\t\t\tflags |\u003d par-\u003eflags \u0026\n+\t\t\t\t (LWSSS_FLAG_SOM | LWSSS_FLAG_POLL);\n+\n+\t\t\tif (par-\u003erem \u003d\u003d n)\n+\t\t\t\tflags |\u003d par-\u003eflags \u0026 (LWSSS_FLAG_EOM |\n+\t\t\t\t\t\tLWSSS_FLAG_RELATED_END);\n+\n+\t\t\tpar-\u003efrag1 \u003d 0;\n+\t\t\tus \u003d lws_now_usecs();\n+\n+\t\t\tif (!client) {\n+\t\t\t\t/*\n+\t\t\t\t * Proxy - we received some serialized tx from\n+\t\t\t\t * the client.\n+\t\t\t\t *\n+\t\t\t\t * The header for buffering private to the\n+\t\t\t\t * proxy is 23 bytes vs 19 to hold the\n+\t\t\t\t * current time when it was buffered\n+\t\t\t\t */\n+\n+\t\t\t\tlwsl_info(\u0022%s: C2P RX: len %d\u005cn\u0022, __func__, (int)n);\n+\n+\t\t\t\tp \u003d pre;\n+\t\t\t\tpre[0] \u003d LWSSS_SER_TXPRE_TX_PAYLOAD;\n+\t\t\t\tlws_ser_wu16be(\u0026p[1], n + 23 - 3);\n+\t\t\t\tlws_ser_wu32be(\u0026p[3], par-\u003eflags);\n+\t\t\t\t/* us held at client before written */\n+\t\t\t\tlws_ser_wu32be(\u0026p[7], par-\u003eusd_phandling);\n+\t\t\t\t/* us taken for transit to proxy */\n+\t\t\t\tlws_ser_wu32be(\u0026p[11], us - par-\u003eust_pwait);\n+\t\t\t\t/* time used later to find proxy hold time */\n+\t\t\t\tlws_ser_wu64be(\u0026p[15], us);\n+\n+\t\t\t\tif (lws_dsh_alloc_tail(dsh, KIND_C_TO_P, pre,\n+\t\t\t\t\t\t 23, cp, n)) {\n+\t\t\t\t\tlwsl_err(\u0022%s: unable to alloc in dsh 3\u005cn\u0022,\n+\t\t\t\t\t\t __func__);\n+\n+\t\t\t\t\treturn 1;\n+\t\t\t\t}\n+\n+\t\t\t\tlws_ss_request_tx(*pss);\n+\t\t\t} else {\n+\n+\t\t\t\t/*\n+\t\t\t\t * Client receives some RX from proxy\n+\t\t\t\t *\n+\t\t\t\t * Pass whatever payload we have to ss user\n+\t\t\t\t */\n+\n+\t\t\t\tlwsl_info(\u0022%s: P2C RX: len %d\u005cn\u0022, __func__, (int)n);\n+\n+\t\t\t\th \u003d lws_container_of(par, lws_sspc_handle_t, parser);\n+\t\t\t\th-\u003etxc.peer_tx_cr_est -\u003d n;\n+\n+\t\t\t\tssi-\u003erx((void *)pss, (uint8_t *)cp, n, flags);\n+\n+#if defined(LWS_WITH_DETAILED_LATENCY)\n+\t\t\t\tif (lws_det_lat_active(context)) {\n+\t\t\t\t\tlws_detlat_t d;\n+\n+\t\t\t\t\td.type \u003d LDLT_READ;\n+\t\t\t\t\td.acc_size \u003d d.req_size \u003d n;\n+\t\t\t\t\td.latencies[LAT_DUR_USERCB] \u003d\n+\t\t\t\t\t\t\tlws_now_usecs() - us;\n+\t\t\t\t\td.latencies[LAT_DUR_PROXY_CLIENT_REQ_TO_WRITE] \u003d\n+\t\t\t\t\t\t\tpar-\u003eusd_phandling;\n+\t\t\t\t\td.latencies[LAT_DUR_PROXY_CLIENT_WRITE_TO_PROXY_RX] \u003d\n+\t\t\t\t\t\tus - par-\u003eust_pwait;\n+\n+\t\t\t\t\tlws_det_lat_cb(context, \u0026d);\n+\t\t\t\t}\n+#endif\n+\t\t\t}\n+\n+\t\t\tif (n) {\n+\t\t\t\tcp +\u003d n;\n+\t\t\t\tpar-\u003erem -\u003d n;\n+\t\t\t\tlen \u003d (len + 1) - n;\n+\t\t\t}\n+\t\t\tif (!par-\u003erem)\n+\t\t\t\tpar-\u003eps \u003d RPAR_TYPE;\n+\t\t\tbreak;\n+\n+\t\tcase RPAR_RX_TXCR_UPDATE:\n+\t\t\tif (!--par-\u003erem \u0026\u0026 par-\u003ectr !\u003d 3)\n+\t\t\t\tgoto hangup;\n+\n+\t\t\tpar-\u003etemp32 \u003d (par-\u003etemp32 \u003c\u003c 8) | *cp++;\n+\t\t\tif (++par-\u003ectr \u003c 4)\n+\t\t\t\tbreak;\n+\n+\t\t\t/*\n+\t\t\t * Proxy is telling us remote endpoint is allowing us\n+\t\t\t * par-\u003etemp32 more bytes tx credit to write to it\n+\t\t\t */\n+\n+\t\t\th \u003d lws_container_of(par, lws_sspc_handle_t, parser);\n+\t\t\th-\u003etxc.tx_cr +\u003d par-\u003etemp32;\n+\t\t\tlwsl_info(\u0022%s: RX_PEER_TXCR: %d\u005cn\u0022, __func__, par-\u003etemp32);\n+\t\t\tlws_sspc_request_tx(h); /* in case something waiting */\n+\t\t\tpar-\u003ectr \u003d 0;\n+\t\t\tpar-\u003eps \u003d RPAR_TYPE;\n+\t\t\tbreak;\n+\n+\t\tcase RPAR_INITTXC0:\n+\t\t\tif (!--par-\u003erem)\n+\t\t\t\tgoto hangup;\n+\n+\t\t\tpar-\u003etemp32 \u003d (par-\u003etemp32 \u003c\u003c 8) | *cp++;\n+\t\t\tif (++par-\u003ectr \u003c 4)\n+\t\t\t\tbreak;\n+\n+\t\t\tpar-\u003etxcr_out \u003d par-\u003etemp32;\n+\t\t\tpar-\u003ectr \u003d 0;\n+\t\t\tpar-\u003eps \u003d RPAR_STREAMTYPE;\n+\t\t\tbreak;\n+\n+\t\t/*\n+\t\t * These are the client adjusting our / the remote peer ability\n+\t\t * to send back to him. He's sending a signed u32 BE\n+\t\t */\n+\n+\t\tcase RPAR_TXCR0:\n+\n+\t\t\tpar-\u003etemp32 \u003d (par-\u003etemp32 \u003c\u003c 8) | *cp++;\n+\t\t\tif (++par-\u003ectr \u003c 4) {\n+\t\t\t\tif (!--par-\u003erem)\n+\t\t\t\t\tgoto hangup;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tif (--par-\u003erem)\n+\t\t\t\tgoto hangup;\n+\n+\t\t\tif (!client) {\n+\t\t\t\t/*\n+\t\t\t\t * We're the proxy, being told by the client\n+\t\t\t\t * that it wants to allow more tx from the peer\n+\t\t\t\t * on the onward connection towards it.\n+\t\t\t\t */\n+#if defined(LWS_ROLE_H2) || defined(LWS_ROLE_MQTT)\n+\t\t\t\tif ((*pss)-\u003ewsi) {\n+\t\t\t\t\tlws_wsi_tx_credit((*pss)-\u003ewsi,\n+\t\t\t\t\t\t\t LWSTXCR_PEER_TO_US,\n+\t\t\t\t\t\t\t par-\u003etemp32);\n+\t\t\t\t\tlwsl_notice(\u0022%s: proxy RX_PEER_TXCR: +%d (est %d)\u005cn\u0022,\n+\t\t\t\t\t\t __func__, par-\u003etemp32,\n+\t\t\t\t\t\t (*pss)-\u003ewsi-\u003etxc.peer_tx_cr_est);\n+\t\t\t\t\tlws_ss_request_tx(*pss);\n+\t\t\t\t} else\n+#endif\n+\t\t\t\t\tlwsl_info(\u0022%s: dropping TXCR\u005cn\u0022, __func__);\n+\t\t\t} else {\n+\t\t\t\t/*\n+\t\t\t\t * We're the client, being told by the proxy\n+\t\t\t\t * about tx credit being given to us from the\n+\t\t\t\t * remote peer, allowing the client to write to\n+\t\t\t\t * it.\n+\t\t\t\t */\n+\t\t\t\th \u003d lws_container_of(par, lws_sspc_handle_t, parser);\n+\t\t\t\th-\u003etxc.tx_cr +\u003d par-\u003etemp32;\n+\t\t\t\tlwsl_info(\u0022%s: client RX_PEER_TXCR: %d\u005cn\u0022,\n+\t\t\t\t\t\t\t__func__, par-\u003etemp32);\n+\t\t\t\tlws_sspc_request_tx(h); /* in case something waiting */\n+\t\t\t}\n+\t\t\tpar-\u003eps \u003d RPAR_TYPE;\n+\t\t\tbreak;\n+\n+\t\tcase RPAR_METADATA_NAMELEN:\n+\t\t\tif (!--par-\u003erem)\n+\t\t\t\tgoto hangup;\n+\t\t\tpar-\u003eslen \u003d *cp++;\n+\t\t\tif (par-\u003eslen \u003e\u003d sizeof(par-\u003emetadata_name) - 1)\n+\t\t\t\tgoto hangup;\n+\t\t\tpar-\u003ectr \u003d 0;\n+\t\t\tpar-\u003eps++;\n+\t\t\tbreak;\n+\n+\t\tcase RPAR_METADATA_NAME:\n+\t\t\tif (!--par-\u003erem)\n+\t\t\t\tgoto hangup;\n+\t\t\tpar-\u003emetadata_name[par-\u003ectr++] \u003d *cp++;\n+\t\t\tif (par-\u003ectr !\u003d par-\u003eslen)\n+\t\t\t\tbreak;\n+\t\t\tpar-\u003eps \u003d RPAR_METADATA_VALUE;\n+\n+\t\t\t/* only non-client side can receive these */\n+\n+\t\t\t/*\n+\t\t\t * This is the policy's metadata list for the given\n+\t\t\t * name\n+\t\t\t */\n+\t\t\tpm \u003d lws_ss_policy_metadata((*pss)-\u003epolicy,\n+\t\t\t\t\t\t par-\u003emetadata_name);\n+\t\t\tif (!pm) {\n+\t\t\t\tlwsl_err(\u0022%s: metadata %s not in proxy policy\u005cn\u0022,\n+\t\t\t\t\t __func__, par-\u003emetadata_name);\n+\n+\t\t\t\tgoto hangup;\n+\t\t\t}\n+\n+\t\t\tpar-\u003essmd \u003d \u0026(*pss)-\u003emetadata[pm-\u003elength];\n+\n+\t\t\tif (par-\u003essmd-\u003evalue_on_lws_heap)\n+\t\t\t\tlws_free_set_NULL(par-\u003essmd-\u003evalue);\n+\t\t\tpar-\u003essmd-\u003evalue_on_lws_heap \u003d 0;\n+\n+\t\t\tpar-\u003essmd-\u003evalue \u003d lws_malloc(par-\u003erem + 1, \u0022metadata\u0022);\n+\t\t\tif (!par-\u003essmd-\u003evalue) {\n+\t\t\t\tlwsl_err(\u0022%s: OOM mdv\u005cn\u0022, __func__);\n+\t\t\t\tgoto hangup;\n+\t\t\t}\n+\t\t\tpar-\u003essmd-\u003elength \u003d par-\u003erem;\n+\t\t\t/* mark it as needing cleanup */\n+\t\t\tpar-\u003essmd-\u003evalue_on_lws_heap \u003d 1;\n+\t\t\tpar-\u003ectr \u003d 0;\n+\t\t\tbreak;\n+\n+\t\tcase RPAR_METADATA_VALUE:\n+\t\t\t((uint8_t *)(par-\u003essmd-\u003evalue))[par-\u003ectr++] \u003d *cp++;\n+\t\t\tif (--par-\u003erem)\n+\t\t\t\tbreak;\n+\n+\t\t\t/* we think we got all the value */\n+\t\t\tlwsl_info(\u0022%s: RPAR_METADATA_VALUE for %s (len %d)\u005cn\u0022,\n+\t\t\t\t __func__, par-\u003essmd-\u003ename,\n+\t\t\t\t (int)par-\u003essmd-\u003elength);\n+\t\t\tlwsl_hexdump_info(par-\u003essmd-\u003evalue, par-\u003essmd-\u003elength);\n+\t\t\tpar-\u003eps \u003d RPAR_TYPE;\n+\t\t\tbreak;\n+\n+\t\tcase RPAR_STREAMTYPE:\n+\t\t\tif (client)\n+\t\t\t\tgoto hangup;\n+\t\t\tif (par-\u003ectr \u003d\u003d sizeof(par-\u003estreamtype) - 1)\n+\t\t\t\tgoto hangup;\n+\n+\t\t\t/*\n+\t\t\t * We're the proxy, creating an SS on behalf of a\n+\t\t\t * client\n+\t\t\t */\n+\n+\t\t\tpar-\u003estreamtype[par-\u003ectr++] \u003d *cp++;\n+\t\t\tif (--par-\u003erem)\n+\t\t\t\tbreak;\n+\n+\t\t\tpar-\u003eps \u003d RPAR_TYPE;\n+\t\t\tpar-\u003estreamtype[par-\u003ectr] \u003d '\u005c0';\n+\t\t\tlwsl_notice(\u0022%s: creating proxied ss '%s', txcr %d\u005cn\u0022,\n+\t\t\t\t __func__, par-\u003estreamtype, par-\u003etxcr_out);\n+\n+\t\t\tssi-\u003estreamtype \u003d par-\u003estreamtype;\n+\t\t\tif (par-\u003etxcr_out)\n+\t\t\t\tssi-\u003emanual_initial_tx_credit \u003d par-\u003etxcr_out;\n+\n+\t\t\tif (lws_ss_create(context, 0, ssi, parconn, pss, NULL, NULL)) {\n+\t\t\t\t/*\n+\t\t\t\t * We're unable to create the onward secure\n+\t\t\t\t * stream he asked for... schedule a chance to\n+\t\t\t\t * inform him\n+\t\t\t\t */\n+\t\t\t\tlwsl_err(\u0022%s: create '%s' fail\u005cn\u0022,\n+\t\t\t\t\t__func__, par-\u003estreamtype);\n+\t\t\t\t*state \u003d LPCS_REPORTING_FAIL;\n+\t\t\t} else {\n+\t\t\t\tlwsl_debug(\u0022%s: create '%s' OK\u005cn\u0022,\n+\t\t\t\t\t__func__, par-\u003estreamtype);\n+\t\t\t\t*state \u003d LPCS_REPORTING_OK;\n+\t\t\t}\n+\n+\t\t\tif (*pss) {\n+\t\t\t\t(*pss)-\u003ebeing_serialized \u003d 1;\n+\t\t\t\tlwsl_notice(\u0022%s: Created SS initial credit %d\u005cn\u0022,\n+\t\t\t\t\t __func__, par-\u003etxcr_out);\n+\t\t\t\t(*pss)-\u003einfo.manual_initial_tx_credit \u003d par-\u003etxcr_out;\n+\t\t\t}\n+\n+\t\t\t/* parent needs to schedule write on client conn */\n+\t\t\tbreak;\n+\n+\t\t/* clientside states */\n+\n+\t\tcase RPAR_RESULT_CREATION:\n+\t\t\tif (*cp++) {\n+\t\t\t\tlwsl_err(\u0022%s: stream creation failed\u005cn\u0022,\n+\t\t\t\t\t __func__);\n+\t\t\t\tgoto hangup;\n+\t\t\t}\n+\n+\t\t\tlws_ss_serialize_state_transition(state,\n+\t\t\t\t\t\t\t LPCS_LOCAL_CONNECTED);\n+\t\t\th \u003d lws_container_of(par, lws_sspc_handle_t, parser);\n+\t\t\tif (h-\u003ecwsi)\n+\t\t\t\tlws_callback_on_writable(h-\u003ecwsi);\n+\n+\t\t\t/*\n+\t\t\t * This is telling us that the streamtype could be (and\n+\t\t\t * was) created at the proxy. It's not telling us that\n+\t\t\t * the onward peer connection could be connected.\n+\t\t\t *\n+\t\t\t * We'll get a proxied state() coming later that informs\n+\t\t\t * us about the situation with that.\n+\t\t\t */\n+\n+\t\t\tpar-\u003ersl_pos \u003d 0;\n+\t\t\tpar-\u003ersl_idx \u003d 0;\n+\t\t\th \u003d lws_container_of(par, lws_sspc_handle_t, parser);\n+\t\t\tmemset(\u0026h-\u003erideshare_ofs[0], 0, sizeof(h-\u003erideshare_ofs[0]));\n+\t\t\th-\u003erideshare_list[0] \u003d '\u005c0';\n+\t\t\th-\u003ersidx \u003d 0;\n+\n+\t\t\tif (!--par-\u003erem)\n+\t\t\t\tpar-\u003eps \u003d RPAR_TYPE;\n+\t\t\telse {\n+\t\t\t\tpar-\u003eps \u003d RPAR_RESULT_CREATION_RIDESHARE;\n+\t\t\t\tif (par-\u003erem \u003e\u003d sizeof(h-\u003erideshare_list))\n+\t\t\t\t\tgoto hangup;\n+\t\t\t}\n+\t\t\tbreak;\n+\n+\t\tcase RPAR_RESULT_CREATION_RIDESHARE:\n+\t\t\th \u003d lws_container_of(par, lws_sspc_handle_t, parser);\n+\t\t\tif (*cp \u003d\u003d ',') {\n+\t\t\t\tcp++;\n+\t\t\t\th-\u003erideshare_list[par-\u003ersl_pos++] \u003d '\u005c0';\n+\t\t\t\tif (par-\u003ersl_idx \u003d\u003d LWS_ARRAY_SIZE(h-\u003erideshare_ofs))\n+\t\t\t\t\tgoto hangup;\n+\t\t\t\th-\u003erideshare_ofs[++par-\u003ersl_idx] \u003d par-\u003ersl_pos;\n+\t\t\t} else\n+\t\t\t\th-\u003erideshare_list[par-\u003ersl_pos++] \u003d *cp++;\n+\t\t\tif (!--par-\u003erem)\n+\t\t\t\tpar-\u003eps \u003d RPAR_TYPE;\n+\t\t\tbreak;\n+\n+\t\tcase RPAR_STATEINDEX:\n+\t\t\tpar-\u003ectr \u003d *cp++;\n+\t\t\tpar-\u003eps \u003d RPAR_ORD3;\n+\t\t\tbreak;\n+\n+\t\tcase RPAR_ORD3:\n+\t\t\tpar-\u003eflags \u003d (*cp++) \u003c\u003c 24;\n+\t\t\tpar-\u003eps++;\n+\t\t\tbreak;\n+\n+\t\tcase RPAR_ORD2:\n+\t\t\tpar-\u003eflags |\u003d (*cp++) \u003c\u003c 16;\n+\t\t\tpar-\u003eps++;\n+\t\t\tbreak;\n+\n+\t\tcase RPAR_ORD1:\n+\t\t\tpar-\u003eflags |\u003d (*cp++) \u003c\u003c 8;\n+\t\t\tpar-\u003eps++;\n+\t\t\tbreak;\n+\n+\t\tcase RPAR_ORD0:\n+\t\t\tpar-\u003eflags |\u003d *cp++;\n+\t\t\tpar-\u003eps++;\n+\t\t\tpar-\u003eps \u003d RPAR_TYPE;\n+\n+\t\t\t/*\n+\t\t\t * we received a proxied state change\n+\t\t\t */\n+\n+\t\t\tswitch (par-\u003ectr) {\n+\t\t\tcase LWSSSCS_DISCONNECTED:\n+\t\t\tcase LWSSSCS_UNREACHABLE:\n+\t\t\tcase LWSSSCS_AUTH_FAILED:\n+\t\t\t\tlws_ss_serialize_state_transition(state,\n+\t\t\t\t\t\tLPCS_LOCAL_CONNECTED);\n+\t\t\t\tbreak;\n+\t\t\tcase LWSSSCS_CONNECTED:\n+\t\t\t\tlwsl_info(\u0022%s: CONNECTED %s\u005cn\u0022, __func__,\n+\t\t\t\t\t ssi-\u003estreamtype);\n+\t\t\t\tlws_ss_serialize_state_transition(state,\n+\t\t\t\t\t\tLPCS_OPERATIONAL);\n+\t\t\t\tbreak;\n+\t\t\tdefault:\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tif (par-\u003ectr \u003c 0 || par-\u003ectr \u003e 9)\n+\t\t\t\tgoto hangup;\n+\n+#if defined(_DEBUG)\n+\t\t\tlwsl_info(\u0022%s: forwarding proxied state %s\u005cn\u0022,\n+\t\t\t\t\t__func__, sn[par-\u003ectr]);\n+#endif\n+\t\t\tif (ssi-\u003estate((void *)pss, NULL, par-\u003ectr, par-\u003eflags))\n+\t\t\t\tgoto hangup;\n+\t\t\tbreak;\n+\n+\n+\t\tdefault:\n+\t\t\tgoto hangup;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+\n+hangup:\n+\treturn -1;\n+}\ndiff --git a/lib/secure-streams/secure-streams.c b/lib/secure-streams/secure-streams.c\nnew file mode 100644\nindex 0000000..652f6c9\n--- /dev/null\n+++ b/lib/secure-streams/secure-streams.c\n@@ -0,0 +1,569 @@\n+/*\n+ * libwebsockets - small server side websockets and web server implementation\n+ *\n+ * Copyright (C) 2019 - 2020 Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * Permission is hereby granted, free of charge, to any person obtaining a copy\n+ * of this software and associated documentation files (the \u0022Software\u0022), to\n+ * deal in the Software without restriction, including without limitation the\n+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n+ * sell copies of the Software, and to permit persons to whom the Software is\n+ * furnished to do so, subject to the following conditions:\n+ *\n+ * The above copyright notice and this permission notice shall be included in\n+ * all copies or substantial portions of the Software.\n+ *\n+ * THE SOFTWARE IS PROVIDED \u0022AS IS\u0022, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n+ * IN THE SOFTWARE.\n+ */\n+\n+#include \u003cprivate-lib-core.h\u003e\n+\n+static const struct ss_pcols *ss_pcols[] \u003d {\n+#if defined(LWS_ROLE_H1)\n+\t\u0026ss_pcol_h1,\t\t/* LWSSSP_H1 */\n+#else\n+\tNULL,\n+#endif\n+#if defined(LWS_ROLE_H2)\n+\t\u0026ss_pcol_h2,\t\t/* LWSSSP_H2 */\n+#else\n+\tNULL,\n+#endif\n+#if defined(LWS_ROLE_WS)\n+\t\u0026ss_pcol_ws,\t\t/* LWSSSP_WS */\n+#else\n+\tNULL,\n+#endif\n+#if defined(LWS_ROLE_MQTT)\n+\t\u0026ss_pcol_mqtt,\t\t/* LWSSSP_MQTT */\n+#else\n+\tNULL,\n+#endif\n+};\n+\n+static const char *state_names[] \u003d {\n+\t\u0022LWSSSCS_CREATING\u0022,\n+\t\u0022LWSSSCS_DISCONNECTED\u0022,\n+\t\u0022LWSSSCS_UNREACHABLE\u0022,\n+\t\u0022LWSSSCS_AUTH_FAILED\u0022,\n+\t\u0022LWSSSCS_CONNECTED\u0022,\n+\t\u0022LWSSSCS_CONNECTING\u0022,\n+\t\u0022LWSSSCS_DESTROYING\u0022,\n+\t\u0022LWSSSCS_POLL\u0022,\n+\t\u0022LWSSSCS_ALL_RETRIES_FAILED\u0022,\n+\t\u0022LWSSSCS_QOS_ACK_REMOTE\u0022,\n+\t\u0022LWSSSCS_QOS_NACK_REMOTE\u0022,\n+\t\u0022LWSSSCS_QOS_ACK_LOCAL\u0022,\n+\t\u0022LWSSSCS_QOS_NACK_LOCAL\u0022,\n+};\n+\n+const char *\n+lws_ss_state_name(int state)\n+{\n+\tif (state \u003e\u003d (int)LWS_ARRAY_SIZE(state_names))\n+\t\treturn \u0022unknown\u0022;\n+\n+\treturn state_names[state];\n+}\n+\n+int\n+lws_ss_event_helper(lws_ss_handle_t *h, lws_ss_constate_t cs)\n+{\n+\tif (!h)\n+\t\treturn 0;\n+\n+#if defined(LWS_WITH_SEQUENCER)\n+\t/*\n+\t * A parent sequencer for the ss is optional, if we have one, keep it\n+\t * informed of state changes on the ss connection\n+\t */\n+\tif (h-\u003eseq \u0026\u0026 cs !\u003d LWSSSCS_DESTROYING)\n+\t\tlws_seq_queue_event(h-\u003eseq, LWSSEQ_SS_STATE_BASE + cs,\n+\t\t\t\t (void *)h, NULL);\n+#endif\n+\n+\tif (h-\u003eh_sink \u0026\u0026h-\u003eh_sink-\u003einfo.state(h-\u003esink_obj, h-\u003eh_sink, cs, 0))\n+\t\treturn 1;\n+\n+\treturn h-\u003einfo.state(ss_to_userobj(h), NULL, cs, 0);\n+}\n+\n+static void\n+lws_ss_timeout_sul_check_cb(lws_sorted_usec_list_t *sul)\n+{\n+\tlws_ss_handle_t *h \u003d lws_container_of(sul, lws_ss_handle_t, sul);\n+\n+\tlwsl_err(\u0022%s: retrying ss h %p after backoff\u005cn\u0022, __func__, h);\n+\t/* we want to retry... */\n+\th-\u003eseqstate \u003d SSSEQ_DO_RETRY;\n+\n+\tlws_ss_request_tx(h);\n+}\n+\n+int\n+lws_ss_exp_cb_metadata(void *priv, const char *name, char *out, size_t *pos,\n+\t\t\tsize_t olen, size_t *exp_ofs)\n+{\n+\tlws_ss_handle_t *h \u003d (lws_ss_handle_t *)priv;\n+\tconst char *replace \u003d NULL;\n+\tsize_t total, budget;\n+\tlws_ss_metadata_t *md \u003d lws_ss_policy_metadata(h-\u003epolicy, name);\n+\n+\tif (!md) {\n+\t\tlwsl_err(\u0022%s: Unknown metadata %s\u005cn\u0022, __func__, name);\n+\n+\t\treturn LSTRX_FATAL_NAME_UNKNOWN;\n+\t}\n+\n+\tlwsl_info(\u0022%s %s %d\u005cn\u0022, __func__, name, (int)md-\u003elength);\n+\n+\treplace \u003d h-\u003emetadata[md-\u003elength].value;\n+\ttotal \u003d h-\u003emetadata[md-\u003elength].length;\n+\t// lwsl_hexdump_err(replace, total);\n+\n+\tbudget \u003d olen - *pos;\n+\ttotal -\u003d *exp_ofs;\n+\tif (total \u003c budget)\n+\t\tbudget \u003d total;\n+\n+\tmemcpy(out + *pos, replace + (*exp_ofs), budget);\n+\t*exp_ofs +\u003d budget;\n+\t*pos +\u003d budget;\n+\n+\tif (budget \u003d\u003d total)\n+\t\treturn LSTRX_DONE;\n+\n+\treturn LSTRX_FILLED_OUT;\n+}\n+\n+int\n+lws_ss_set_timeout_us(lws_ss_handle_t *h, lws_usec_t us)\n+{\n+\tstruct lws_context_per_thread *pt \u003d \u0026h-\u003econtext-\u003ept[h-\u003etsi];\n+\n+\th-\u003esul.cb \u003d lws_ss_timeout_sul_check_cb;\n+\t__lws_sul_insert(\u0026pt-\u003ept_sul_owner, \u0026h-\u003esul, us);\n+\n+\treturn 0;\n+}\n+\n+int\n+lws_ss_backoff(lws_ss_handle_t *h)\n+{\n+\tuint64_t ms;\n+\tchar conceal;\n+\n+\tif (h-\u003eseqstate \u003d\u003d SSSEQ_RECONNECT_WAIT)\n+\t\treturn 0;\n+\n+\t/* figure out what we should do about another retry */\n+\n+\tlwsl_info(\u0022%s: ss %p: retry backoff after failure\u005cn\u0022, __func__, h);\n+\tms \u003d lws_retry_get_delay_ms(h-\u003econtext, h-\u003epolicy-\u003eretry_bo,\n+\t\t\t\t \u0026h-\u003eretry, \u0026conceal);\n+\tif (!conceal) {\n+\t\tlwsl_info(\u0022%s: ss %p: abandon conn attempt \u005cn\u0022,__func__, h);\n+\t\th-\u003eseqstate \u003d SSSEQ_IDLE;\n+\t\tlws_ss_event_helper(h, LWSSSCS_ALL_RETRIES_FAILED);\n+\t\treturn 1;\n+\t}\n+\n+\th-\u003eseqstate \u003d SSSEQ_RECONNECT_WAIT;\n+\tlws_ss_set_timeout_us(h, ms * LWS_US_PER_MS);\n+\n+\tlwsl_info(\u0022%s: ss %p: retry wait %\u0022PRIu64\u0022ms\u005cn\u0022, __func__, h, ms);\n+\n+\treturn 0;\n+}\n+\n+int\n+lws_ss_client_connect(lws_ss_handle_t *h)\n+{\n+\tstruct lws_client_connect_info i;\n+\tconst struct ss_pcols *ssp;\n+\tunion lws_ss_contemp ct;\n+\tchar path[128];\n+\n+\tif (!h-\u003epolicy) {\n+\t\tlwsl_err(\u0022%s: ss with no policy\u005cn\u0022, __func__);\n+\n+\t\treturn -1;\n+\t}\n+\n+\t/*\n+\t * We are already bound to a sink?\n+\t */\n+\n+\tif (h-\u003eh_sink)\n+\t\treturn 0;\n+\n+\tmemset(\u0026i, 0, sizeof i); /* otherwise uninitialized garbage */\n+\ti.context \u003d h-\u003econtext;\n+\n+\tif (h-\u003epolicy-\u003eflags \u0026 LWSSSPOLF_TLS) {\n+\t\tlwsl_info(\u0022%s: using tls\u005cn\u0022, __func__);\n+\t\ti.ssl_connection \u003d LCCSCF_USE_SSL;\n+\n+\t\tif (!h-\u003epolicy-\u003etrust_store) {\n+\t\t\tlwsl_err(\u0022%s: tls required but no policy trust store\u005cn\u0022,\n+\t\t\t\t __func__);\n+\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\ti.vhost \u003d lws_get_vhost_by_name(h-\u003econtext,\n+\t\t\t\t\t\th-\u003epolicy-\u003etrust_store-\u003ename);\n+\t\tif (!i.vhost) {\n+\t\t\tlwsl_err(\u0022%s: missing vh for policy ca\u005cn\u0022, __func__);\n+\n+\t\t\treturn -1;\n+\t\t}\n+\t}\n+\n+\ti.address \u003d h-\u003epolicy-\u003eendpoint;\n+\ti.port \u003d h-\u003epolicy-\u003eport;\n+\ti.host \u003d i.address;\n+\ti.origin \u003d i.address;\n+\ti.opaque_user_data \u003d h;\n+\ti.seq \u003d h-\u003eseq;\n+\ti.retry_and_idle_policy \u003d h-\u003epolicy-\u003eretry_bo;\n+\ti.sys_tls_client_cert \u003d h-\u003epolicy-\u003eclient_cert;\n+\n+\ti.path \u003d \u0022\u0022;\n+\n+\tssp \u003d ss_pcols[(int)h-\u003epolicy-\u003eprotocol];\n+\tif (!ssp) {\n+\t\tlwsl_err(\u0022%s: unsupported protocol\u005cn\u0022, __func__);\n+\n+\t\treturn -1;\n+\t}\n+\ti.alpn \u003d ssp-\u003ealpn;\n+\n+\t/*\n+\t * For http, we can get the method from the http object, override in\n+\t * the protocol-specific munge callback below if not http\n+\t */\n+\ti.method \u003d h-\u003epolicy-\u003eu.http.method;\n+\ti.protocol \u003d ssp-\u003eprotocol_name; /* lws protocol name */\n+\ti.local_protocol_name \u003d i.protocol;\n+\n+\tssp-\u003emunge(h, path, sizeof(path), \u0026i, \u0026ct);\n+\n+\ti.pwsi \u003d \u0026h-\u003ewsi;\n+\n+\tif (h-\u003epolicy-\u003eplugins[0] \u0026\u0026 h-\u003epolicy-\u003eplugins[0]-\u003emunge)\n+\t\th-\u003epolicy-\u003eplugins[0]-\u003emunge(h, path, sizeof(path));\n+\n+\tlwsl_info(\u0022%s: connecting %s, '%s' '%s' %s\u005cn\u0022, __func__, i.method,\n+\t\t\ti.alpn, i.address, i.path);\n+\n+\th-\u003etxn_ok \u003d 0;\n+\tif (lws_ss_event_helper(h, LWSSSCS_CONNECTING))\n+\t\treturn -1;\n+\n+\tif (!lws_client_connect_via_info(\u0026i)) {\n+\t\tlws_ss_event_helper(h, LWSSSCS_UNREACHABLE);\n+\t\tlws_ss_backoff(h);\n+\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+\n+/*\n+ * Public API\n+ */\n+\n+/*\n+ * Create either a stream or a sink\n+ */\n+\n+int\n+lws_ss_create(struct lws_context *context, int tsi, const lws_ss_info_t *ssi,\n+\t void *opaque_user_data, lws_ss_handle_t **ppss,\n+\t struct lws_sequencer *seq_owner, const char **ppayload_fmt)\n+{\n+\tstruct lws_context_per_thread *pt \u003d \u0026context-\u003ept[tsi];\n+\tconst lws_ss_policy_t *pol;\n+\tlws_ss_metadata_t *smd;\n+\tlws_ss_handle_t *h;\n+\tsize_t size;\n+\tvoid **v;\n+\tchar *p;\n+\tint n;\n+\n+\tpol \u003d lws_ss_policy_lookup(context, ssi-\u003estreamtype);\n+\tif (!pol) {\n+\t\tlwsl_err(\u0022%s: unknown stream type %s\u005cn\u0022, __func__,\n+\t\t\t ssi-\u003estreamtype);\n+\t\treturn 1;\n+\t}\n+\n+\tif (ssi-\u003eregister_sink) {\n+\t\t/*\n+\t\t * This can register a secure streams sink as well as normal\n+\t\t * secure streams connections. If that's what's happening,\n+\t\t * confirm the policy agrees that this streamtype should be\n+\t\t * directed to a sink.\n+\t\t */\n+\t\tif (!(pol-\u003eflags \u0026 LWSSSPOLF_LOCAL_SINK)) {\n+\t\t\t/*\n+\t\t\t * Caller wanted to create a sink for this streamtype,\n+\t\t\t * but the policy does not agree the streamtype should\n+\t\t\t * be routed to a local sink.\n+\t\t\t */\n+\t\t\tlwsl_err(\u0022%s: %s policy does not allow local sink\u005cn\u0022,\n+\t\t\t\t __func__, ssi-\u003estreamtype);\n+\n+\t\t\treturn 1;\n+\t\t}\n+\t} else {\n+\n+\t\tif (!(pol-\u003eflags \u0026 LWSSSPOLF_LOCAL_SINK)) {\n+\n+\t\t}\n+//\t\tlws_dll2_foreach_safe(\u0026pt-\u003ess_owner, NULL, lws_ss_destroy_dll);\n+\t}\n+\n+\t/*\n+\t * We overallocate and point to things in the overallocation...\n+\t *\n+\t * 1) the user_alloc from the stream info\n+\t * 2) network auth plugin instantiation data\n+\t * 3) stream auth plugin instantiation data\n+\t * 4) as many metadata pointer structs as the policy tells\n+\t * 5) the streamtype name (length is not aligned)\n+\t *\n+\t * ... when we come to destroy it, just one free to do.\n+\t */\n+\n+\tsize \u003d sizeof(*h) + ssi-\u003euser_alloc + strlen(ssi-\u003estreamtype) + 1;\n+\tif (pol-\u003eplugins[0])\n+\t\tsize +\u003d pol-\u003eplugins[0]-\u003ealloc;\n+\tif (pol-\u003eplugins[1])\n+\t\tsize +\u003d pol-\u003eplugins[1]-\u003ealloc;\n+\tsize +\u003d pol-\u003emetadata_count * sizeof(lws_ss_metadata_t);\n+\n+\th \u003d lws_zalloc(size, __func__);\n+\tif (!h)\n+\t\treturn 2;\n+\n+\th-\u003einfo \u003d *ssi;\n+\th-\u003epolicy \u003d pol;\n+\th-\u003econtext \u003d context;\n+\th-\u003etsi \u003d tsi;\n+\th-\u003eseq \u003d seq_owner;\n+\n+\t/* start of overallocated area */\n+\tp \u003d (char *)\u0026h[1];\n+\n+\t/* set the handle pointer in the user data struct */\n+\tv \u003d (void **)(p + ssi-\u003ehandle_offset);\n+\t*v \u003d h;\n+\n+\t/* set the opaque user data in the user data struct */\n+\tv \u003d (void **)(p + ssi-\u003eopaque_user_data_offset);\n+\t*v \u003d opaque_user_data;\n+\n+\tp +\u003d ssi-\u003euser_alloc;\n+\n+\tif (pol-\u003eplugins[0]) {\n+\t\th-\u003enauthi \u003d p;\n+\t\tp +\u003d pol-\u003eplugins[0]-\u003ealloc;\n+\t}\n+\tif (pol-\u003eplugins[1]) {\n+\t\th-\u003esauthi \u003d p;\n+\t\tp +\u003d pol-\u003eplugins[1]-\u003ealloc;\n+\t}\n+\n+\tif (pol-\u003emetadata_count) {\n+\t\th-\u003emetadata \u003d (lws_ss_metadata_t *)p;\n+\t\tp +\u003d pol-\u003emetadata_count * sizeof(lws_ss_metadata_t);\n+\n+\t\tlwsl_info(\u0022%s: %s metadata count %d\u005cn\u0022, __func__,\n+\t\t\t pol-\u003estreamtype, pol-\u003emetadata_count);\n+\t}\n+\n+\tsmd \u003d pol-\u003emetadata;\n+\tfor (n \u003d 0; n \u003c pol-\u003emetadata_count; n++) {\n+\t\th-\u003emetadata[n].name \u003d smd-\u003ename;\n+\t\tif (n + 1 \u003d\u003d pol-\u003emetadata_count)\n+\t\t\th-\u003emetadata[n].next \u003d NULL;\n+\t\telse\n+\t\t\th-\u003emetadata[n].next \u003d \u0026h-\u003emetadata[n + 1];\n+\t\tsmd \u003d smd-\u003enext;\n+\t}\n+\n+\tmemcpy(p, ssi-\u003estreamtype, strlen(ssi-\u003estreamtype) + 1);\n+\th-\u003einfo.streamtype \u003d p;\n+\n+\tlws_pt_lock(pt, __func__);\n+\tlws_dll2_add_head(\u0026h-\u003elist, \u0026pt-\u003ess_owner);\n+\tlws_pt_unlock(pt);\n+\n+\tif (ppss)\n+\t\t*ppss \u003d h;\n+\n+\tif (ppayload_fmt)\n+\t\t*ppayload_fmt \u003d pol-\u003epayload_fmt;\n+\n+\tif (ssi-\u003eregister_sink) {\n+\t\t/*\n+\t\t *\n+\t\t */\n+\t}\n+\n+\tlws_ss_event_helper(h, LWSSSCS_CREATING);\n+\n+\tif (!ssi-\u003eregister_sink \u0026\u0026 (h-\u003epolicy-\u003eflags \u0026 LWSSSPOLF_NAILED_UP))\n+\t\tif (lws_ss_client_connect(h))\n+\t\t\tlws_ss_backoff(h);\n+\n+\treturn 0;\n+}\n+\n+void\n+lws_ss_destroy(lws_ss_handle_t **ppss)\n+{\n+\tstruct lws_context_per_thread *pt;\n+\tlws_ss_handle_t *h \u003d *ppss;\n+\tlws_ss_metadata_t *pmd;\n+\n+\tif (!h)\n+\t\treturn;\n+\n+\tif (h-\u003ewsi) {\n+\t\t/*\n+\t\t * Don't let the wsi point to us any more,\n+\t\t * we (the ss object bound to the wsi) are going away now\n+\t\t */\n+//\t\tlws_set_opaque_user_data(h-\u003ewsi, NULL);\n+\t\tlws_set_timeout(h-\u003ewsi, 1, LWS_TO_KILL_SYNC);\n+\t}\n+\n+\tpt \u003d \u0026h-\u003econtext-\u003ept[h-\u003etsi];\n+\n+\tlws_pt_lock(pt, __func__);\n+\t*ppss \u003d NULL;\n+\tlws_dll2_remove(\u0026h-\u003elist);\n+\tlws_dll2_remove(\u0026h-\u003eto_list);\n+\tlws_ss_event_helper(h, LWSSSCS_DESTROYING);\n+\tlws_pt_unlock(pt);\n+\n+\t/* in proxy case, metadata value on heap may need cleaning up */\n+\n+\tpmd \u003d h-\u003emetadata;\n+\twhile (pmd) {\n+\t\tlwsl_info(\u0022%s: pmd %p\u005cn\u0022, __func__, pmd);\n+\t\tif (pmd-\u003evalue_on_lws_heap)\n+\t\t\tlws_free_set_NULL(pmd-\u003evalue);\n+\t\tpmd \u003d pmd-\u003enext;\n+\t}\n+\n+\tlws_sul_schedule(h-\u003econtext, 0, \u0026h-\u003esul, NULL, LWS_SET_TIMER_USEC_CANCEL);\n+\n+\tlws_free_set_NULL(h);\n+}\n+\n+void\n+lws_ss_request_tx(lws_ss_handle_t *h)\n+{\n+\tlwsl_info(\u0022%s: wsi %p\u005cn\u0022, __func__, h-\u003ewsi);\n+\n+\tif (h-\u003ewsi) {\n+\t\tlws_callback_on_writable(h-\u003ewsi);\n+\n+\t\treturn;\n+\t}\n+\n+\tif (h-\u003eseqstate !\u003d SSSEQ_IDLE \u0026\u0026\n+\t h-\u003eseqstate !\u003d SSSEQ_DO_RETRY)\n+\t\treturn;\n+\n+\th-\u003eseqstate \u003d SSSEQ_TRY_CONNECT;\n+\tlws_ss_event_helper(h, LWSSSCS_POLL);\n+\n+\tif (lws_ss_client_connect(h))\n+\t\tlws_ss_backoff(h);\n+}\n+\n+void\n+lws_ss_request_tx_len(lws_ss_handle_t *h, unsigned long len)\n+{\n+\tif (h-\u003ewsi)\n+\t\th-\u003ewsi-\u003ehttp.writeable_len \u003d len;\n+\telse\n+\t\th-\u003ewriteable_len \u003d len;\n+\tlws_ss_request_tx(h);\n+}\n+\n+/*\n+ * private helpers\n+ */\n+\n+/* used on context destroy when iterating listed lws_ss on a pt */\n+\n+int\n+lws_ss_destroy_dll(struct lws_dll2 *d, void *user)\n+{\n+\tlws_ss_handle_t *h \u003d lws_container_of(d, lws_ss_handle_t, list);\n+\n+\tlws_ss_destroy(\u0026h);\n+\n+\treturn 0;\n+}\n+\n+struct lws_sequencer *\n+lws_ss_get_sequencer(lws_ss_handle_t *h)\n+{\n+\treturn h-\u003eseq;\n+}\n+\n+struct lws_context *\n+lws_ss_get_context(struct lws_ss_handle *h)\n+{\n+\treturn h-\u003econtext;\n+}\n+\n+const char *\n+lws_ss_rideshare(struct lws_ss_handle *h)\n+{\n+\tif (!h-\u003erideshare)\n+\t\treturn h-\u003epolicy-\u003estreamtype;\n+\n+\treturn h-\u003erideshare-\u003estreamtype;\n+}\n+\n+int\n+lws_ss_add_peer_tx_credit(struct lws_ss_handle *h, int32_t bump)\n+{\n+\tconst struct ss_pcols *ssp;\n+\n+\tssp \u003d ss_pcols[(int)h-\u003epolicy-\u003eprotocol];\n+\n+\tif (h-\u003ewsi \u0026\u0026 ssp \u0026\u0026 ssp-\u003etx_cr_add)\n+\t\treturn ssp-\u003etx_cr_add(h, bump);\n+\n+\treturn 0;\n+}\n+\n+int\n+lws_ss_get_est_peer_tx_credit(struct lws_ss_handle *h)\n+{\n+\tconst struct ss_pcols *ssp;\n+\n+\tssp \u003d ss_pcols[(int)h-\u003epolicy-\u003eprotocol];\n+\n+\tif (h-\u003ewsi \u0026\u0026 ssp \u0026\u0026 ssp-\u003etx_cr_add)\n+\t\treturn ssp-\u003etx_cr_est(h);\n+\n+\treturn 0;\n+}\ndiff --git a/lib/secure-streams/system/auth-api.amazon.com/auth.c b/lib/secure-streams/system/auth-api.amazon.com/auth.c\nnew file mode 100644\nindex 0000000..697ae77\n--- /dev/null\n+++ b/lib/secure-streams/system/auth-api.amazon.com/auth.c\n@@ -0,0 +1,276 @@\n+/*\n+ * LWA auth support for Secure Streams\n+ *\n+ * libwebsockets - small server side websockets and web server implementation\n+ *\n+ * Copyright (C) 2019 - 2020 Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * Permission is hereby granted, free of charge, to any person obtaining a copy\n+ * of this software and associated documentation files (the \u0022Software\u0022), to\n+ * deal in the Software without restriction, including without limitation the\n+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n+ * sell copies of the Software, and to permit persons to whom the Software is\n+ * furnished to do so, subject to the following conditions:\n+ *\n+ * The above copyright notice and this permission notice shall be included in\n+ * all copies or substantial portions of the Software.\n+ *\n+ * THE SOFTWARE IS PROVIDED \u0022AS IS\u0022, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n+ * IN THE SOFTWARE.\n+ */\n+\n+#include \u003cprivate-lib-core.h\u003e\n+\n+typedef struct ss_api_amazon_auth {\n+\tstruct lws_ss_handle \t*ss;\n+\tvoid\t\t\t*opaque_data;\n+\t/* ... application specific state ... */\n+\tstruct lejp_ctx\t\tjctx;\n+\tlws_sorted_usec_list_t\tsul;\n+\tsize_t\t\t\tpos;\n+\tint\t\t\texpires_secs;\n+} ss_api_amazon_auth_t;\n+\n+static const char * const lejp_tokens_lwa[] \u003d {\n+\t\u0022access_token\u0022,\n+\t\u0022expires_in\u0022,\n+};\n+\n+typedef enum {\n+\tLSSPPT_ACCESS_TOKEN,\n+\tLSSPPT_EXPIRES_IN,\n+} lejp_tokens_t;\n+\n+enum {\n+\tAUTH_IDX_LWA,\n+\tAUTH_IDX_ROOT,\n+};\n+\n+static void\n+lws_ss_sys_auth_api_amazon_com_kick(lws_sorted_usec_list_t *sul)\n+{\n+\tstruct lws_context *context \u003d lws_container_of(sul, struct lws_context,\n+\t\t\t\t\t\t\tsul_api_amazon_com_kick);\n+\n+\tlws_state_transition_steps(\u0026context-\u003emgr_system,\n+\t\t\t\t LWS_SYSTATE_OPERATIONAL);\n+}\n+\n+static void\n+lws_ss_sys_auth_api_amazon_com_renew(lws_sorted_usec_list_t *sul)\n+{\n+\tstruct lws_context *context \u003d lws_container_of(sul, struct lws_context,\n+\t\t\t\t\t\t\tsul_api_amazon_com);\n+\n+\t/* if nothing is there to intercept anything, go all the way */\n+\tlws_state_transition_steps(\u0026context-\u003emgr_system,\n+\t\t\t\t LWS_SYSTATE_OPERATIONAL);\n+}\n+\n+static signed char\n+auth_api_amazon_com_parser_cb(struct lejp_ctx *ctx, char reason)\n+{\n+\tss_api_amazon_auth_t *m \u003d (ss_api_amazon_auth_t *)ctx-\u003euser;\n+\tstruct lws_context *context \u003d (struct lws_context *)m-\u003eopaque_data;\n+\n+\tif (!(reason \u0026 LEJP_FLAG_CB_IS_VALUE) || !ctx-\u003epath_match)\n+\t\treturn 0;\n+\n+\tswitch (ctx-\u003epath_match - 1) {\n+\tcase LSSPPT_ACCESS_TOKEN:\n+\t\tif (!ctx-\u003enpos)\n+\t\t\tbreak;\n+\t\tif (lws_system_blob_heap_append(lws_system_get_blob(context,\n+\t\t\t\t\t\tLWS_SYSBLOB_TYPE_AUTH,\n+\t\t\t\t\t\tAUTH_IDX_LWA),\n+\t\t\t\t\t\t(const uint8_t *)ctx-\u003ebuf,\n+\t\t\t\t\t\tctx-\u003enpos)) {\n+\t\t\tlwsl_err(\u0022%s: unable to store auth token\u005cn\u0022, __func__);\n+\t\t\treturn -1;\n+\t\t}\n+\t\tbreak;\n+\tcase LSSPPT_EXPIRES_IN:\n+\t\tm-\u003eexpires_secs \u003d atoi(ctx-\u003ebuf);\n+\t\tlws_sul_schedule(context, 0, \u0026context-\u003esul_api_amazon_com,\n+\t\t\t\t lws_ss_sys_auth_api_amazon_com_renew,\n+\t\t\t\t (uint64_t)m-\u003eexpires_secs * LWS_US_PER_SEC);\n+\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/* secure streams payload interface */\n+\n+static int\n+ss_api_amazon_auth_rx(void *userobj, const uint8_t *buf, size_t len, int flags)\n+{\n+\tss_api_amazon_auth_t *m \u003d (ss_api_amazon_auth_t *)userobj;\n+\tstruct lws_context *context \u003d (struct lws_context *)m-\u003eopaque_data;\n+\tlws_system_blob_t *ab;\n+\tsize_t total;\n+\tint n;\n+\n+\tab \u003d lws_system_get_blob(context, LWS_SYSBLOB_TYPE_AUTH, AUTH_IDX_LWA);\n+\n+\tif (buf) {\n+\t\tif (flags \u0026 LWSSS_FLAG_SOM) {\n+\t\t\tlejp_construct(\u0026m-\u003ejctx, auth_api_amazon_com_parser_cb,\n+\t\t\t\t m, lejp_tokens_lwa,\n+\t\t\t\t LWS_ARRAY_SIZE(lejp_tokens_lwa));\n+\t\t\tlws_system_blob_heap_empty(ab);\n+\t\t}\n+\n+\t\tn \u003d (int)(signed char)lejp_parse(\u0026m-\u003ejctx, buf, len);\n+\t\tif (n \u003c 0) {\n+\t\t\tlejp_destruct(\u0026m-\u003ejctx);\n+\t\t\tlws_system_blob_destroy(\n+\t\t\t\tlws_system_get_blob(context,\n+\t\t\t\t\t\t LWS_SYSBLOB_TYPE_AUTH,\n+\t\t\t\t\t\t AUTH_IDX_LWA));\n+\n+\t\t\treturn -1;\n+\t\t}\n+\t}\n+\tif (!(flags \u0026 LWSSS_FLAG_EOM))\n+\t\treturn 0;\n+\n+\t/* we should have the auth token now */\n+\n+\ttotal \u003d lws_system_blob_get_size(ab);\n+\tlwsl_notice(\u0022%s: acquired %u-byte api.amazon.com auth token, exp %ds\u005cn\u0022,\n+\t\t\t__func__, (unsigned int)total, m-\u003eexpires_secs);\n+\n+\tlejp_destruct(\u0026m-\u003ejctx);\n+\n+\t/* we move the system state at auth connection close */\n+\n+\treturn 0;\n+}\n+\n+static int\n+ss_api_amazon_auth_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf,\n+\t\t size_t *len, int *flags)\n+{\n+\tss_api_amazon_auth_t *m \u003d (ss_api_amazon_auth_t *)userobj;\n+\tstruct lws_context *context \u003d (struct lws_context *)m-\u003eopaque_data;\n+\tlws_system_blob_t *ab;\n+\tsize_t total;\n+\tint n;\n+\n+\t/*\n+\t * We send out auth slot AUTH_IDX_ROOT, it's the LWA user / device\n+\t * identity token\n+\t */\n+\n+\tab \u003d lws_system_get_blob(context, LWS_SYSBLOB_TYPE_AUTH, AUTH_IDX_ROOT);\n+\ttotal \u003d lws_system_blob_get_size(ab);\n+\n+\tn \u003d lws_system_blob_get(ab, buf, len, m-\u003epos);\n+\tif (n \u003c 0)\n+\t\treturn 1;\n+\n+\tif (!m-\u003epos)\n+\t\t*flags |\u003d LWSSS_FLAG_SOM;\n+\n+\tm-\u003epos +\u003d *len;\n+\n+\tif (m-\u003epos \u003d\u003d total) {\n+\t\t*flags |\u003d LWSSS_FLAG_EOM;\n+\t\tm-\u003epos \u003d 0; /* for next time */\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int\n+ss_api_amazon_auth_state(void *userobj, void *sh, lws_ss_constate_t state,\n+\t\t\t lws_ss_tx_ordinal_t ack)\n+{\n+\tss_api_amazon_auth_t *m \u003d (ss_api_amazon_auth_t *)userobj;\n+\tstruct lws_context *context \u003d (struct lws_context *)m-\u003eopaque_data;\n+\tsize_t s;\n+\n+\tlwsl_info(\u0022%s: %s, ord 0x%x\u005cn\u0022, __func__, lws_ss_state_name(state),\n+\t\t (unsigned int)ack);\n+\n+\tswitch (state) {\n+\tcase LWSSSCS_CREATING:\n+\tcase LWSSSCS_CONNECTING:\n+\t\ts \u003d lws_system_blob_get_size(\n+\t\t\tlws_system_get_blob(context, LWS_SYSBLOB_TYPE_AUTH,\n+\t\t\t\t\t AUTH_IDX_ROOT));\n+\t\tlws_ss_request_tx_len(m-\u003ess, s);\n+\t\tm-\u003epos \u003d 0;\n+\t\tbreak;\n+\n+\tcase LWSSSCS_DISCONNECTED:\n+\t\t/*\n+\t\t * We defer moving the system state forward until we have\n+\t\t * closed our connection + tls for the auth action... this is\n+\t\t * because on small systems, we need that memory recovered\n+\t\t * before we can make another connection subsequently.\n+\t\t *\n+\t\t * At this point, we're ultimately being called from within\n+\t\t * the wsi close process, the tls tunnel is not freed yet.\n+\t\t * Use a sul to actually do it next time around the event loop\n+\t\t * when the close process for the auth wsi has completed and\n+\t\t * the related tls is already freed.\n+\t\t */\n+\t\ts \u003d lws_system_blob_get_size(\n+\t\t\tlws_system_get_blob(context, LWS_SYSBLOB_TYPE_AUTH,\n+\t\t\t\t\t AUTH_IDX_LWA));\n+\n+\t\tif (s)\n+\t\t\tlws_sul_schedule(context, 0,\n+\t\t\t\t\t \u0026context-\u003esul_api_amazon_com_kick,\n+\t\t\t\t\t lws_ss_sys_auth_api_amazon_com_kick, 1);\n+\t\tbreak;\n+\n+\tcase LWSSSCS_DESTROYING:\n+\t\tlws_sul_schedule(context, 0, \u0026context-\u003esul_api_amazon_com,\n+\t\t\t\t NULL, LWS_SET_TIMER_USEC_CANCEL);\n+\t\tlws_system_blob_destroy(\n+\t\t\tlws_system_get_blob(context, LWS_SYSBLOB_TYPE_AUTH,\n+\t\t\t\t\t AUTH_IDX_LWA));\n+\t\tbreak;\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int\n+lws_ss_sys_auth_api_amazon_com(struct lws_context *context)\n+{\n+\tlws_ss_info_t ssi;\n+\n+\tif (context-\u003ehss_auth) /* already exists */\n+\t\treturn 0;\n+\n+\t/* We're making an outgoing secure stream ourselves */\n+\n+\tmemset(\u0026ssi, 0, sizeof(ssi));\n+\tssi.handle_offset\t \u003d offsetof(ss_api_amazon_auth_t, ss);\n+\tssi.opaque_user_data_offset \u003d offsetof(ss_api_amazon_auth_t, opaque_data);\n+\tssi.rx\t\t\t \u003d ss_api_amazon_auth_rx;\n+\tssi.tx\t\t\t \u003d ss_api_amazon_auth_tx;\n+\tssi.state\t\t \u003d ss_api_amazon_auth_state;\n+\tssi.user_alloc\t\t \u003d sizeof(ss_api_amazon_auth_t);\n+\tssi.streamtype\t\t \u003d \u0022api_amazon_com_auth\u0022;\n+\n+\tif (lws_ss_create(context, 0, \u0026ssi, context, \u0026context-\u003ehss_auth,\n+\t\t\t NULL, NULL)) {\n+\t\tlwsl_info(\u0022%s: Create LWA auth ss failed (policy?)\u005cn\u0022, __func__);\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\ndiff --git a/lib/secure-streams/system/fetch-policy/fetch-policy.c b/lib/secure-streams/system/fetch-policy/fetch-policy.c\nnew file mode 100644\nindex 0000000..bd0a3e4\n--- /dev/null\n+++ b/lib/secure-streams/system/fetch-policy/fetch-policy.c\n@@ -0,0 +1,159 @@\n+/*\n+ * Policy fetching for Secure Streams\n+ *\n+ * libwebsockets - small server side websockets and web server implementation\n+ *\n+ * Copyright (C) 2019 - 2020 Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * Permission is hereby granted, free of charge, to any person obtaining a copy\n+ * of this software and associated documentation files (the \u0022Software\u0022), to\n+ * deal in the Software without restriction, including without limitation the\n+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n+ * sell copies of the Software, and to permit persons to whom the Software is\n+ * furnished to do so, subject to the following conditions:\n+ *\n+ * The above copyright notice and this permission notice shall be included in\n+ * all copies or substantial portions of the Software.\n+ *\n+ * THE SOFTWARE IS PROVIDED \u0022AS IS\u0022, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n+ * IN THE SOFTWARE.\n+ */\n+\n+#include \u003cprivate-lib-core.h\u003e\n+\n+typedef struct ss_fetch_policy {\n+\tstruct lws_ss_handle \t*ss;\n+\tvoid\t\t\t*opaque_data;\n+\t/* ... application specific state ... */\n+\n+\tlws_sorted_usec_list_t\tsul;\n+\n+\tuint8_t\t\t\tpartway;\n+} ss_fetch_policy_t;\n+\n+/* secure streams payload interface */\n+\n+static int\n+ss_fetch_policy_rx(void *userobj, const uint8_t *buf, size_t len, int flags)\n+{\n+\tss_fetch_policy_t *m \u003d (ss_fetch_policy_t *)userobj;\n+\tstruct lws_context *context \u003d (struct lws_context *)m-\u003eopaque_data;\n+\n+\tif (flags \u0026 LWSSS_FLAG_SOM) {\n+\t\tif (lws_ss_policy_parse_begin(context))\n+\t\t\treturn 1;\n+\t\tm-\u003epartway \u003d 1;\n+\t}\n+\n+\tif (len \u0026\u0026 lws_ss_policy_parse(context, buf, len) \u003c 0)\n+\t\treturn 1;\n+\n+\tif (flags \u0026 LWSSS_FLAG_EOM)\n+\t\tm-\u003epartway \u003d 2;\n+\n+\treturn 0;\n+}\n+\n+static int\n+ss_fetch_policy_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf,\n+\t\t size_t *len, int *flags)\n+{\n+\treturn 1;\n+}\n+\n+static void\n+policy_set(lws_sorted_usec_list_t *sul)\n+{\n+\tss_fetch_policy_t *m \u003d lws_container_of(sul, ss_fetch_policy_t, sul);\n+\tstruct lws_context *context \u003d (struct lws_context *)m-\u003eopaque_data;\n+\n+\t/*\n+\t * We get called if the policy parse was successful, just after the\n+\t * ss connection close that was using the vhost from the old policy\n+\t */\n+\n+\tif (lws_ss_policy_set(context, \u0022updated\u0022))\n+\t\tlwsl_err(\u0022%s: policy set failed\u005cn\u0022, __func__);\n+\telse {\n+\t\tcontext-\u003epolicy_updated \u003d 1;\n+\t\tlws_state_transition_steps(\u0026context-\u003emgr_system,\n+\t\t\t\t\t LWS_SYSTATE_OPERATIONAL);\n+\t}\n+}\n+\n+static int\n+ss_fetch_policy_state(void *userobj, void *sh, lws_ss_constate_t state,\n+\t\t lws_ss_tx_ordinal_t ack)\n+{\n+\tss_fetch_policy_t *m \u003d (ss_fetch_policy_t *)userobj;\n+\tstruct lws_context *context \u003d (struct lws_context *)m-\u003eopaque_data;\n+\n+\tlwsl_info(\u0022%s: %s, ord 0x%x\u005cn\u0022, __func__, lws_ss_state_name(state),\n+\t\t (unsigned int)ack);\n+\n+\tswitch (state) {\n+\tcase LWSSSCS_CREATING:\n+\t\tlws_ss_request_tx(m-\u003ess);\n+\t\tbreak;\n+\tcase LWSSSCS_CONNECTING:\n+\t\tbreak;\n+\n+\tcase LWSSSCS_DISCONNECTED:\n+\t\tlwsl_info(\u0022%s: DISCONNECTED\u005cn\u0022, __func__);\n+\t\tswitch (m-\u003epartway) {\n+\t\tcase 1:\n+\t\t\tlws_ss_policy_parse_abandon(context);\n+\t\t\tbreak;\n+\n+\t\tcase 2:\n+\t\t\tlws_sul_schedule(context, 0, \u0026m-\u003esul, policy_set, 1);\n+\t\t\tbreak;\n+\t\t}\n+\t\tm-\u003epartway \u003d 0;\n+\t\tbreak;\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int\n+lws_ss_sys_fetch_policy(struct lws_context *context)\n+{\n+\tlws_ss_info_t ssi;\n+\n+\tif (context-\u003ehss_fetch_policy) /* already exists */\n+\t\treturn 0;\n+\n+\t/* We're making an outgoing secure stream ourselves */\n+\n+\tmemset(\u0026ssi, 0, sizeof(ssi));\n+\tssi.handle_offset\t \u003d offsetof(ss_fetch_policy_t, ss);\n+\tssi.opaque_user_data_offset \u003d offsetof(ss_fetch_policy_t, opaque_data);\n+\tssi.rx\t\t\t \u003d ss_fetch_policy_rx;\n+\tssi.tx\t\t\t \u003d ss_fetch_policy_tx;\n+\tssi.state\t\t \u003d ss_fetch_policy_state;\n+\tssi.user_alloc\t\t \u003d sizeof(ss_fetch_policy_t);\n+\tssi.streamtype\t\t \u003d \u0022fetch_policy\u0022;\n+\n+\tif (lws_ss_create(context, 0, \u0026ssi, context, \u0026context-\u003ehss_fetch_policy,\n+\t\t\t NULL, NULL)) {\n+\t\t/*\n+\t\t * If there's no fetch_policy streamtype, it can just be we're\n+\t\t * running on a proxied client with no policy of its own,\n+\t\t * it's OK.\n+\t\t */\n+\t\tlwsl_info(\u0022%s: Create LWA auth ss failed (policy?)\u005cn\u0022, __func__);\n+\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\ndiff --git a/lib/system/async-dns/async-dns-parse.c b/lib/system/async-dns/async-dns-parse.c\nindex 00f53f2..48bc56a 100644\n--- a/lib/system/async-dns/async-dns-parse.c\n+++ b/lib/system/async-dns/async-dns-parse.c\n@@ -340,14 +340,17 @@ do_cb:\n \n \t\t\tif (p + 14 \u003e e)\n \t\t\t\treturn -1;\n-\n-\t\t\t/* it should have exactly reached rrpaylen */\n+#if 0\n+\t\t\t/* it should have exactly reached rrpaylen if only one\n+\t\t\t * CNAME, else somewhere in the middle */\n \n \t\t\tif (p !\u003d pay + rrpaylen) {\n \t\t\t\tlwsl_err(\u0022%s: cname name bad len %d\u005cn\u0022, __func__, rrpaylen);\n \n \t\t\t\treturn -1;\n \t\t\t}\n+#endif\n+\t\t\tlwsl_notice(\u0022%s: recursing looking for %s\u005cn\u0022, __func__, stack[stp].name);\n \n \t\t\tlwsl_info(\u0022%s: recursing looking for %s\u005cn\u0022, __func__,\n \t\t\t\t\tstack[stp].name);\ndiff --git a/lib/system/ntpclient/ntpclient.c b/lib/system/ntpclient/ntpclient.c\nindex 1ab4cff..2316fdd 100644\n--- a/lib/system/ntpclient/ntpclient.c\n+++ b/lib/system/ntpclient/ntpclient.c\n@@ -134,7 +134,7 @@ callback_ntpc(struct lws *wsi, enum lws_callback_reasons reason, void *user,\n \t\tif (v)\n \t\t\tbreak;\n \n-\t\tlwsl_debug(\u0022%s: LWS_CALLBACK_PROTOCOL_INIT\u005cn\u0022, __func__);\n+\t\tlwsl_debug(\u0022%s: LWS_CALLBACK_PROTOCOL_INIT:\u005cn\u0022, __func__);\n \t\tlws_protocol_vh_priv_zalloc(wsi-\u003evhost, wsi-\u003eprotocol,\n \t\t\t\t\t sizeof(*v));\n \t\tv \u003d (struct vhd_ntpc *)lws_protocol_vh_priv_get(wsi-\u003evhost,\ndiff --git a/lib/system/system.c b/lib/system/system.c\nindex 7b6d704..e5c8723 100644\n--- a/lib/system/system.c\n+++ b/lib/system/system.c\n@@ -56,6 +56,8 @@ lws_system_blob_heap_append(lws_system_blob_t *b, const uint8_t *buf, size_t len\n {\n \tassert(!b-\u003eis_direct);\n \n+\tlwsl_debug(\u0022%s: blob %p\u005cn\u0022, __func__, b);\n+\n \tif (lws_buflist_append_segment(\u0026b-\u003eu.bl, buf, len) \u003c 0)\n \t\treturn -1;\n \n@@ -126,6 +128,7 @@ lws_system_blob_destroy(lws_system_blob_t *b)\n {\n \tif (!b)\n \t\treturn;\n+\tlwsl_info(\u0022%s: blob %p\u005cn\u0022, __func__, b);\n \tif (!b-\u003eis_direct)\n \t\tlws_buflist_destroy_all_segments(\u0026b-\u003eu.bl);\n }\ndiff --git a/lib/tls/openssl/openssl-ssl.c b/lib/tls/openssl/openssl-ssl.c\nindex 6e59caf..2a6ff95 100644\n--- a/lib/tls/openssl/openssl-ssl.c\n+++ b/lib/tls/openssl/openssl-ssl.c\n@@ -60,6 +60,8 @@ int lws_ssl_get_error(struct lws *wsi, int n)\n \tlwsl_debug(\u0022%s: %p %d -\u003e %d (errno %d)\u005cn\u0022, __func__, wsi-\u003etls.ssl, n, m,\n \t\t errno);\n \n+\tassert (errno !\u003d 9);\n+\n \treturn m;\n }\n \ndiff --git a/minimal-examples/README.md b/minimal-examples/README.md\nindex 2f271db..956256d 100644\n--- a/minimal-examples/README.md\n+++ b/minimal-examples/README.md\n@@ -6,6 +6,7 @@ dbus-server|Minimal examples showing how to integrate DBUS into lws event loop\n http-client|Minimal examples providing an http client\n http-server|Minimal examples providing an http server\n raw|Minimal examples related to adopting raw file or socket descriptors into the event loop\n+secure-streams|Minimal examples related to the Secure Streams client api\n ws-client|Minimal examples providing a ws client\n ws-server|Minimal examples providing a ws server (and an http server)\n \ndiff --git a/minimal-examples/http-client/minimal-http-client-h2-rxflow/minimal-http-client.c b/minimal-examples/http-client/minimal-http-client-h2-rxflow/minimal-http-client.c\nindex 9c9149b..4d0d88a 100644\n--- a/minimal-examples/http-client/minimal-http-client-h2-rxflow/minimal-http-client.c\n+++ b/minimal-examples/http-client/minimal-http-client-h2-rxflow/minimal-http-client.c\n@@ -39,7 +39,7 @@ drain_cb(lws_sorted_usec_list_t *sul)\n {\n \tstruct pss *pss \u003d lws_container_of(sul, struct pss, sul);\n \n-\tlws_h2_update_peer_txcredit(pss-\u003ewsi, LWS_H2_STREAM_SID, each);\n+\tlws_wsi_tx_credit(pss-\u003ewsi, LWSTXCR_PEER_TO_US, each);\n \n \tlws_sul_schedule(lws_get_context(pss-\u003ewsi), 0, \u0026pss-\u003esul, drain_cb,\n \t\t\t 250 * LWS_US_PER_MS);\ndiff --git a/minimal-examples/secure-streams/README.md b/minimal-examples/secure-streams/README.md\nnew file mode 100644\nindex 0000000..5815630\n--- /dev/null\n+++ b/minimal-examples/secure-streams/README.md\n@@ -0,0 +1,14 @@\n+# Secure Streams\n+\n+Secure Streams is a client API that strictly decouples the policy for connections\n+from the payloads. The user code only deals with the stream type name and payloads,\n+a policy database set at `lws_context` creation time decides all policy about the\n+connection, including the endpoint, tls CA, and even the wire protocol.\n+\n+|name|demonstrates|\n+---|---\n+minimal-secure-streams|Minimal secure streams client / proxy example\n+minimal-secure-streams-tx|Proxy used for client-tx test below\n+minimal-secure-streams-client-tx|Secure streams client showing tx and rx\n+\n+\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-alexa/CMakeLists.txt b/minimal-examples/secure-streams/minimal-secure-streams-alexa/CMakeLists.txt\nnew file mode 100644\nindex 0000000..b28d84e\n--- /dev/null\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-alexa/CMakeLists.txt\n@@ -0,0 +1,93 @@\n+cmake_minimum_required(VERSION 2.8)\n+include(CheckCSourceCompiles)\n+\n+set(SAMP lws-minimal-secure-streams-alexa)\n+set(SRCS main.c alexa.c audio.c)\n+\n+# If we are being built as part of lws, confirm current build config supports\n+# reqconfig, else skip building ourselves.\n+#\n+# If we are being built externally, confirm installed lws was configured to\n+# support reqconfig, else error out with a helpful message about the problem.\n+#\n+MACRO(require_lws_config reqconfig _val result)\n+\n+\tif (DEFINED ${reqconfig})\n+\tif (${reqconfig})\n+\t\tset (rq 1)\n+\telse()\n+\t\tset (rq 0)\n+\tendif()\n+\telse()\n+\t\tset(rq 0)\n+\tendif()\n+\n+\tif (${_val} EQUAL ${rq})\n+\t\tset(SAME 1)\n+\telse()\n+\t\tset(SAME 0)\n+\tendif()\n+\n+\tif (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})\n+\t\tif (${_val})\n+\t\t\tmessage(\u0022${SAMP}: skipping as lws being built without ${reqconfig}\u0022)\n+\t\telse()\n+\t\t\tmessage(\u0022${SAMP}: skipping as lws built with ${reqconfig}\u0022)\n+\t\tendif()\n+\t\tset(${result} 0)\n+\telse()\n+\t\tif (LWS_WITH_MINIMAL_EXAMPLES)\n+\t\t\tset(MET ${SAME})\n+\t\telse()\n+\t\t\tCHECK_C_SOURCE_COMPILES(\u0022#include \u003clibwebsockets.h\u003e\u005cnint main(void) {\u005cn#if defined(${reqconfig})\u005cn return 0;\u005cn#else\u005cn fail;\u005cn#endif\u005cn return 0;\u005cn}\u005cn\u0022 HAS_${reqconfig})\n+\t\t\tif (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})\n+\t\t\t\tset(HAS_${reqconfig} 0)\n+\t\t\telse()\n+\t\t\t\tset(HAS_${reqconfig} 1)\n+\t\t\tendif()\n+\t\t\tif ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))\n+\t\t\t\tset(MET 1)\n+\t\t\telse()\n+\t\t\t\tset(MET 0)\n+\t\t\tendif()\n+\t\tendif()\n+\t\tif (NOT MET)\n+\t\t\tif (${_val})\n+\t\t\t\tmessage(FATAL_ERROR \u0022This project requires lws must have been configured with ${reqconfig}\u0022)\n+\t\t\telse()\n+\t\t\t\tmessage(FATAL_ERROR \u0022Lws configuration of ${reqconfig} is incompatible with this project\u0022)\n+\t\t\tendif()\n+\t\tendif()\n+\tendif()\n+ENDMACRO()\n+\n+\n+set(requirements 1)\n+require_lws_config(LWS_ROLE_H1 1 requirements)\n+require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)\n+require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements)\n+require_lws_config(LWS_WITH_ALSA 1 requirements)\n+\n+if (requirements)\n+\tadd_executable(${SAMP} ${SRCS})\n+\n+\tif (websockets_shared)\n+\t\ttarget_link_libraries(${SAMP} websockets_shared asound pv_porcupine mpg123)\n+\t\tadd_dependencies(${SAMP} websockets_shared)\n+\telse()\n+\t\ttarget_link_libraries(${SAMP} websockets asound pv_porcupine mpg123)\n+\tendif()\n+\n+\tif (LWS_WITH_SECURE_STREAMS_PROXY_API)\t\n+\t\tadd_compile_options(-DLWS_SS_USE_SSPC)\n+\n+\t\tadd_executable(${SAMP}-client ${SRCS})\n+\t\tif (websockets_shared)\n+\t\t\ttarget_link_libraries(${SAMP}-client websockets_shared asound pv_porcupine mpg123)\n+\t\t\tadd_dependencies(${SAMP}-client websockets_shared)\n+\t\telse()\n+\t\t\ttarget_link_libraries(${SAMP}-client websockets asound pv_porcupine mpg123)\n+\t\tendif()\n+\tendif()\n+\t\n+endif()\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-alexa/README.md b/minimal-examples/secure-streams/minimal-secure-streams-alexa/README.md\nnew file mode 100644\nindex 0000000..66fed37\n--- /dev/null\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-alexa/README.md\n@@ -0,0 +1,77 @@\n+# lws secure streams alexa\n+\n+This demonstrates AVS Alexa usage using secure streams. It connects to AVS,\n+uses your linux computer's microphone to wait for the 'alexa' wakeword, sends\n+the utterance to AVS and plays back the result.\n+\n+## build\n+\n+There are some special build considerations:\n+\n+1) Build lws with cmake options `-DLWS_WITH_ALSA\u003d1 -DLWS_WITH_SECURE_STREAMS\u003d1`\n+\n+2) Install distro build dependency packages:\n+\n+ |Dependency|Ubuntu package|Fedora Package|\n+ |---|---|---|\n+ |libasound|libasound2-dev|alsa-lib-devel|\n+ |mpg123|libmpg123-dev|mpg123-devel|\n+\n+3) Clone Picovoice Porcupine Apache-licensed demo version from here\n+\n+ https://github.com/Picovoice/porcupine\n+\n+ It provides binary libs for wakeword detection on various platforms. Copy\n+ the headers and binary lib to your build context, eg, for native x86_64\n+\n+```\n+ $ sudo cp ./include/* /usr/include\n+ $ sudo cp ./lib/linux/x86_64/libpv_porcupine.* /usr/lib\n+ $ sudo ldconfig\n+```\n+\n+ Enter the minimal example dir for secure-streams-alexa and make the sample\n+\n+```\n+ $ cd ./minimal-examples/secure-streams/minimal-secure-streams-alexa\n+ $ cmake .\n+ $ make\n+```\n+\n+## usage\n+\n+```\n+ $ ./lws-minimal-secure-streams-alexa\n+[2019/10/16 16:22:01:1097] U: LWS secure streams - Alex voice test [-d\u003cverb\u003e]\n+[2019/10/16 16:22:01:1115] N: lws_create_context: creating Secure Streams policy\n+[2019/10/16 16:22:01:1115] N: lwsac_use: alloc 1532 for 1\n+[2019/10/16 16:22:01:1119] N: lwsac_use: alloc 288 for 168\n+[2019/10/16 16:22:01:1119] N: lws_ss_policy_set: policy lwsac size: 1.796KiB, pad 11%\n+[2019/10/16 16:22:02:4114] N: lws_ss_client_connect: connecting 0 api.amazon.com /auth/o2/token\n+[2019/10/16 16:22:02:8686] N: auth_api_amazon_com_parser_cb: expires in 3600\n+[2019/10/16 16:22:02:8686] N: ss_api_amazon_auth_rx: acquired 656-byte api.amazon.com auth token\n+[2019/10/16 16:22:02:8754] N: lws_ss_client_connect: connecting 1 alexa.na.gateway.devices.a2z.com /v20160207/directives\n+[2019/10/16 16:22:02:3182] N: secstream_h2: h2 client entering LONG_POLL\n+[2019/10/16 16:22:02:3183] U: Connected to Alexa... speak \u0022Alexa, ...\u0022\n+[2019/10/16 16:22:06:9380] W: ************* Wakeword\n+[2019/10/16 16:22:06:9380] N: avs_query_start:\n+[2019/10/16 16:22:06:9381] N: lws_ss_client_connect: connecting 1 alexa.na.gateway.devices.a2z.com /v20160207/events\n+[2019/10/16 16:22:06:9381] N: lws_vhost_active_conns: just join h2 directly\n+[2019/10/16 16:22:06:9384] N: metadata done\n+[2019/10/16 16:22:06:1524] N: est: 42 1\n+[2019/10/16 16:22:06:3723] N: est: 108 1\n+[2019/10/16 16:22:07:5914] N: est: 352 1\n+[2019/10/16 16:22:07:8112] N: est: 4284 1\n+[2019/10/16 16:22:07:0300] N: est: 3369 1\n+[2019/10/16 16:22:07:2325] N: est: 577 1\n+[2019/10/16 16:22:08:4519] N: est: 9 1\n+[2019/10/16 16:22:08:6716] N: est: 3 1\n+[2019/10/16 16:22:08:6718] N: est: 11 1\n+[2019/10/16 16:22:08:8915] N: est: 10 1\n+[2019/10/16 16:22:08:8915] W: callback_audio: ended capture\n+[2019/10/16 16:22:09:0993] N: identified reply...\n+^C[2019/10/16 16:22:14:3067] U: Disconnected from Alexa\n+[2019/10/16 16:22:14:3123] U: Completed\n+$\n+\n+```\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-alexa/alexa.c b/minimal-examples/secure-streams/minimal-secure-streams-alexa/alexa.c\nnew file mode 100644\nindex 0000000..ba1fa7e\n--- /dev/null\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-alexa/alexa.c\n@@ -0,0 +1,678 @@\n+/*\n+ * lws-minimal-secure-streams-alexa\n+ *\n+ * This file is made available under the Creative Commons CC0 1.0\n+ * Universal Public Domain Dedication.\n+ */\n+\n+#include \u003clibwebsockets.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003csys/types.h\u003e\n+#include \u003csys/stat.h\u003e\n+#include \u003cunistd.h\u003e\n+#include \u003cfcntl.h\u003e\n+\n+#include \u003cmpg123.h\u003e\n+\n+#include \u0022private.h\u0022\n+\n+struct lws_ss_handle *hss_avs_event, *hss_avs_sync;\n+\n+/* this is the type for the long poll event channel */\n+\n+typedef struct ss_avs_event {\n+\tstruct lws_ss_handle \t*ss;\n+\tvoid\t\t\t*opaque_data;\n+\t/* ... application specific state ... */\n+\n+\tstruct lejp_ctx\t\tjctx;\n+} ss_avs_event_t;\n+\n+enum {\n+\tLAMP3STATE_IDLE,\n+\tLAMP3STATE_SPOOLING,\n+\tLAMP3STATE_DRAINING,\n+};\n+\n+/* this is the type for the utterance metadata (and audio rideshares) */\n+\n+typedef struct ss_avs_metadata {\n+\tstruct lws_ss_handle \t*ss;\n+\tvoid\t\t\t*opaque_data;\n+\t/* ... application specific state ... */\n+\n+\tstruct lws_buflist\t*dribble; /* next mp3 data while draining last */\n+\n+\tstruct lejp_ctx\t\tjctx;\n+\tsize_t\t\t\tpos;\n+\tsize_t\t\t\tmp3_in;\n+\tmpg123_handle\t\t*mh;\n+\n+\tlws_sorted_usec_list_t\tsul;\n+\n+\tuint8_t\t\t\tstash_eom[16];\n+\n+\tuint8_t\t\t\tse_head;\n+\tuint8_t\t\t\tse_tail;\n+\n+\tchar\t\t\tmp3_state;\n+\tchar\t\t\tfirst_mp3;\n+\tuint8_t\t\t\tmp3_mime_match;\n+\tuint8_t\t\t\tseen;\n+\tuint8_t\t\t\tinside_mp3;\n+\n+} ss_avs_metadata_t;\n+\n+/*\n+ * The remote server only seems to give us a budget of 10s to consume the\n+ * results, after that it doesn't drop the stream, but doesn't send us anything\n+ * further on it.\n+ *\n+ * This makes it impossible to optimize buffering for incoming mp3 since we\n+ * have to go ahead and take it before the 10s is up.\n+ */\n+\n+#define MAX_MP3_IN_BUFFERING_BYTES 32768\n+\n+/*\n+ * Structure of JSON metadata for utterance handling\n+ */\n+\n+static const char *metadata \u003d \u0022{\u0022\n+\t\u0022\u005c\u0022event\u005c\u0022: {\u0022\n+\t\t\u0022\u005c\u0022header\u005c\u0022: {\u0022\n+\t\t\t\u0022\u005c\u0022namespace\u005c\u0022: \u005c\u0022SpeechRecognizer\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022name\u005c\u0022: \u005c\u0022Recognize\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022messageId\u005c\u0022: \u005c\u0022message-123\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022dialogRequestId\u005c\u0022: \u005c\u0022dialog-request-321\u005c\u0022\u0022\n+\t\t\u0022},\u0022\n+\t\t\u0022\u005c\u0022payload\u005c\u0022: {\u0022\n+\t\t\t\u0022\u005c\u0022profile\u005c\u0022:\u0022\t\u0022\u005c\u0022CLOSE_TALK\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022format\u005c\u0022:\u0022\t\u0022\u005c\u0022AUDIO_L16_RATE_16000_CHANNELS_1\u005c\u0022\u0022\n+\t\t\u0022}\u0022\n+\t\u0022}\u0022\n+\u0022}\u0022;\n+\n+/*\n+ * avs metadata\n+ */\n+\n+static void\n+use_buffer_250ms(lws_sorted_usec_list_t *sul)\n+{\n+\tss_avs_metadata_t *m \u003d lws_container_of(sul, ss_avs_metadata_t, sul);\n+\tstruct lws_context *context \u003d (struct lws_context *)m-\u003eopaque_data;\n+\tint est \u003d lws_ss_get_est_peer_tx_credit(m-\u003ess);\n+\n+\tlwsl_notice(\u0022%s: est txcr %d\u005cn\u0022, __func__, est);\n+\n+\tif (est \u003c MAX_MP3_IN_BUFFERING_BYTES - (MAX_MP3_IN_BUFFERING_BYTES / 4)) {\n+\t\tlwsl_notice(\u0022 adding %d\u005cn\u0022, MAX_MP3_IN_BUFFERING_BYTES / 4);\n+\t\tlws_ss_add_peer_tx_credit(m-\u003ess, MAX_MP3_IN_BUFFERING_BYTES / 4);\n+\t}\n+\n+\tlws_sul_schedule(context, 0, \u0026m-\u003esul, use_buffer_250ms,\n+\t\t\t 250 * LWS_US_PER_MS);\n+}\n+\n+static const char *mp3_mimetype \u003d \u0022application/octet-stream\u0022,\n+\t\t *match2 \u003d \u0022\u005cx0d\u005cx0a\u005cx0d\u005cx0a\u0022;\n+\n+static int\n+ss_avs_mp3_open(ss_avs_metadata_t *m)\n+{\n+\tint r;\n+\n+\tlwsl_notice(\u0022%s\u005cn\u0022, __func__);\n+\n+\tm-\u003efirst_mp3 \u003d 1;\n+\tm-\u003emh \u003d mpg123_new(NULL, NULL);\n+\tif (!m-\u003emh) {\n+\t\tlwsl_err(\u0022%s: unable to make new mp3\u005cn\u0022,\n+\t\t\t\t__func__);\n+\t\tgoto bail;\n+\t}\n+\tmpg123_format_none(m-\u003emh);\n+\tr \u003d mpg123_format(m-\u003emh, 16000, MPG123_M_MONO,\n+\t\t\t MPG123_ENC_SIGNED_16);\n+\tif (r) {\n+\t\tlwsl_err(\u0022%s: mpg123 format failed %d\u005cn\u0022,\n+\t\t\t\t__func__, r);\n+\t\tgoto bail1;\n+\t}\n+\tr \u003d mpg123_open_feed(m-\u003emh);\n+\tif (r) {\n+\t\tlwsl_err(\u0022%s: mpg123 open feed failed %d\u005cn\u0022,\n+\t\t\t\t__func__, r);\n+\t\tgoto bail1;\n+\t}\n+\n+\treturn 0;\n+\n+bail1:\n+\tmpg123_delete(m-\u003emh);\n+\tm-\u003emh \u003d NULL;\n+\n+bail:\n+\treturn 1;\n+}\n+\n+static int\n+ss_avs_metadata_rx(void *userobj, const uint8_t *buf, size_t len, int flags);\n+\n+/*\n+ * This is called when the mp3 has drained it's input buffer and destroyed\n+ * itself.\n+ */\n+\n+static int\n+drain_end_cb(void *v)\n+{\n+\tss_avs_metadata_t *m \u003d (ss_avs_metadata_t *)v;\n+\tstruct lws_context *context \u003d (struct lws_context *)m-\u003eopaque_data;\n+\tint tot \u003d 0;\n+\n+\tlwsl_err(\u0022%s\u005cn\u0022, __func__);\n+\n+\t/*\n+\t * We have drained and destroyed the existing mp3 session. Is there\n+\t * a new one pending?\n+\t */\n+\n+\tm-\u003efirst_mp3 \u003d 1;\n+\tm-\u003emp3_state \u003d LAMP3STATE_IDLE;\n+\n+\tif (lws_buflist_total_len(\u0026m-\u003edribble)) {\n+\t\t/* we started another one */\n+\n+\t\t/* resume tx credit top up */\n+\t\tlws_sul_schedule(context, 0, \u0026m-\u003esul, use_buffer_250ms, 1);\n+\n+\t\tif (ss_avs_mp3_open(m))\n+\t\t\treturn 1;\n+\n+\t\tm-\u003emp3_state \u003d LAMP3STATE_SPOOLING;\n+\n+\t\t/*\n+\t\t * Dump what we stashed from draining into the new mp3\n+\t\t */\n+\n+\t\twhile (lws_buflist_total_len(\u0026m-\u003edribble)) {\n+\t\t\tsize_t s;\n+\t\t\tuint8_t *u, t;\n+\n+\t\t\ts \u003d lws_buflist_next_segment_len(\u0026m-\u003edribble, \u0026u);\n+\t\t\tt \u003d m-\u003estash_eom[m-\u003ese_tail];\n+\t\t\tlwsl_notice(\u0022%s: preload %d: %d\u005cn\u0022, __func__, (int)s, t);\n+\n+\t\t\tmpg123_feed(m-\u003emh, u, s);\n+\t\t\tlws_buflist_use_segment(\u0026m-\u003edribble, s);\n+\t\t\tif (m-\u003efirst_mp3) {\n+\t\t\t\tplay_mp3(m-\u003emh, NULL, NULL);\n+\t\t\t\tm-\u003efirst_mp3 \u003d 0;\n+\t\t\t}\n+\n+\t\t\ttot +\u003d s;\n+\n+\t\t\tm-\u003ese_tail \u003d (m-\u003ese_tail + 1) % sizeof(m-\u003estash_eom);\n+\t\t\tif (t) {\n+\t\t\t\tlwsl_notice(\u0022%s: preloaded EOM\u005cn\u0022, __func__);\n+\n+\t\t\t\t/*\n+\t\t\t\t * We stashed the whole of the message, we need\n+\t\t\t\t * to also do the EOM processing. We will come\n+\t\t\t\t * back here if there's another message in the\n+\t\t\t\t * stash.\n+\t\t\t\t */\n+\n+\t\t\t\tm-\u003emp3_state \u003d LAMP3STATE_DRAINING;\n+\t\t\t\tif (m-\u003emh)\n+\t\t\t\t\tplay_mp3(NULL, drain_end_cb, m);\n+\n+\t\t\t\tlws_ss_add_peer_tx_credit(m-\u003ess, tot);\n+#if 0\n+\t\t\t\t/*\n+\t\t\t\t * Put a hold on bringing in any more data\n+\t\t\t\t */\n+\t\t\t\tlws_sul_schedule(context, 0, \u0026m-\u003esul, NULL,\n+\t\t\t\t\t\t LWS_SET_TIMER_USEC_CANCEL);\n+#endif\n+\t\t\t\t/* destroy our copy of the handle */\n+\t\t\t\tm-\u003emh \u003d NULL;\n+\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n+\t\tlws_ss_add_peer_tx_credit(m-\u003ess, tot);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int\n+ss_avs_metadata_rx(void *userobj, const uint8_t *buf, size_t len, int flags)\n+{\n+\tss_avs_metadata_t *m \u003d (ss_avs_metadata_t *)userobj;\n+\tstruct lws_context *context \u003d (struct lws_context *)m-\u003eopaque_data;\n+\tint n \u003d 0, hit \u003d 0;\n+\n+\tlwsl_notice(\u0022%s: len %d, flags %d (est peer txcr %d)\u005cn\u0022, __func__,\n+\t\t (int)len, flags, lws_ss_get_est_peer_tx_credit(m-\u003ess));\n+\n+\t// lwsl_hexdump_warn(buf, len);\n+\n+\tif ((flags \u0026 LWSSS_FLAG_SOM) \u0026\u0026 !m-\u003emh \u0026\u0026 !m-\u003eseen) {\n+\t\tm-\u003emp3_mime_match \u003d 0;\n+\t\tm-\u003eseen \u003d 0;\n+\t\tm-\u003einside_mp3 \u003d 0;\n+\t}\n+\n+\tif (!m-\u003einside_mp3) {\n+\t\t/*\n+\t\t * Identify the part with the mp3 in, if any\n+\t\t */\n+\n+\t\twhile (n \u003c (int)len - 24) {\n+\t\t\tif (!m-\u003eseen) {\n+\t\t\t\tif (buf[n] \u003d\u003d mp3_mimetype[m-\u003emp3_mime_match]) {\n+\t\t\t\t\tm-\u003emp3_mime_match++;\n+\t\t\t\t\tif (m-\u003emp3_mime_match \u003d\u003d 24) {\n+\t\t\t\t\t\tm-\u003emp3_mime_match \u003d 0;\n+\t\t\t\t\t\tm-\u003eseen \u003d 1;\n+\t\t\t\t\t\tn++;\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t\t}\n+\t\t\t\t} else\n+\t\t\t\t\tm-\u003emp3_mime_match \u003d 0;\n+\t\t\t} else {\n+\t\t\t\tif (buf[n] \u003d\u003d match2[m-\u003emp3_mime_match]) {\n+\t\t\t\t\tm-\u003emp3_mime_match++;\n+\t\t\t\t\tif (m-\u003emp3_mime_match \u003d\u003d 4) {\n+\t\t\t\t\t\tm-\u003eseen \u003d 0;\n+\t\t\t\t\t\tm-\u003emp3_mime_match \u003d 0;\n+\t\t\t\t\t\thit \u003d 1;\n+\t\t\t\t\t\tn++;\n+\t\t\t\t\t\tbuf +\u003d n;\n+\t\t\t\t\t\tlen -\u003d n;\n+\t\t\t\t\t\tlwsl_notice(\u0022identified reply...\u005cn\u0022);\n+\t\t\t\t\t\tm-\u003einside_mp3 \u003d 1;\n+\t\t\t\t\t\tbreak;\n+\t\t\t\t\t}\n+\t\t\t\t} else\n+\t\t\t\t\tm-\u003emp3_mime_match \u003d 0;\n+\t\t\t}\n+\n+\t\t\tn++;\n+\t\t}\n+\n+\t\tif (!hit) {\n+\t\t\tlws_ss_add_peer_tx_credit(m-\u003ess, len);\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\t// lwsl_notice(\u0022%s: state %d\u005cn\u0022, __func__, m-\u003emp3_state);\n+\n+\tswitch (m-\u003emp3_state) {\n+\tcase LAMP3STATE_IDLE:\n+\n+\t\tif (hit) {\n+\n+\t\t\tlws_ss_add_peer_tx_credit(m-\u003ess, n);\n+\n+\t\t\tif (ss_avs_mp3_open(m))\n+\t\t\t\tgoto bail;\n+\n+\t\t\tlws_sul_schedule(context, 0, \u0026m-\u003esul, use_buffer_250ms, 1);\n+\t\t\tm-\u003emp3_state \u003d LAMP3STATE_SPOOLING;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tlws_ss_add_peer_tx_credit(m-\u003ess, len);\n+\n+\t\tif (!m-\u003einside_mp3)\n+\t\t\tbreak;\n+\n+\t\t/* fallthru */\n+\n+\tcase LAMP3STATE_SPOOLING:\n+\n+\t\tif (m-\u003edribble)\n+\t\t\tgoto draining;\n+\n+\t\tif (len) {\n+\t\t\t/*\n+\t\t\t * We are shoving encoded mp3 into mpg123-allocated heap\n+\t\t\t * buffers... unfortunately mpg123 doesn't seem to\n+\t\t\t * expose where it is in its allocated input so we can\n+\t\t\t * track how much is stashed. Instead while in playback\n+\t\t\t * mode, we assume 64kbps mp3 encoding, ie, 8KB/s, and\n+\t\t\t * run a sul that allows an additional 2KB tx credit\n+\t\t\t * every 250ms, with 4KB initial credit.\n+\t\t\t */\n+\t\t\tlwsl_notice(\u0022%s: SPOOL %d\u005cn\u0022, __func__, (int)len);\n+\t\t\tmpg123_feed(m-\u003emh, buf, len);\n+\n+\t\t\tif (m-\u003efirst_mp3) {\n+\t\t\t\tlws_sul_schedule(context, 0, \u0026m-\u003esul,\n+\t\t\t\t\t\t use_buffer_250ms, 1);\n+\t\t//\t\tlws_ss_add_peer_tx_credit(m-\u003ess,\n+\t\t//\t\t\tlen + (MAX_MP3_IN_BUFFERING_BYTES / 2));\n+\t\t\t\tplay_mp3(m-\u003emh, NULL, NULL);\n+\t\t\t} //else\n+\t\t//\t\tlws_ss_add_peer_tx_credit(m-\u003ess, len);\n+\t\t\tm-\u003efirst_mp3 \u003d 0;\n+\t\t}\n+\n+\t\tif (flags \u0026 LWSSS_FLAG_EOM) {\n+\t\t\t/*\n+\t\t\t * This means one \u0022message\u0022 / mime part with mp3 data\n+\t\t\t * has finished coming in. But there may be whole other\n+\t\t\t * parts with other mp3s following, with potentially\n+\t\t\t * different mp3 parameters. So we want to tell this\n+\t\t\t * one to drain and finish and destroy the current mp3\n+\t\t\t * object before we go on.\n+\t\t\t *\n+\t\t\t * But not knowing the length of the current one, there\n+\t\t\t * will already be outstanding tx credit at the server,\n+\t\t\t * so it's going to spam us with the next part before we\n+\t\t\t * have the new mp3 sink for it.\n+\t\t\t */\n+\t\t\tlwsl_notice(\u0022%s: EOM\u005cn\u0022, __func__);\n+\t\t\tm-\u003emp3_mime_match \u003d 0;\n+\t\t\tm-\u003eseen \u003d 0;\n+\t\t\tm-\u003emp3_state \u003d LAMP3STATE_DRAINING;\n+\t\t\t/* from input POV, we're no longer inside an mp3 */\n+\t\t\tm-\u003einside_mp3 \u003d 0;\n+\t\t\tif (m-\u003emh)\n+\t\t\t\tplay_mp3(NULL, drain_end_cb, m);\n+#if 0\n+\t\t\t/*\n+\t\t\t * Put a hold on bringing in any more data\n+\t\t\t */\n+\t\t\tlws_sul_schedule(context, 0, \u0026m-\u003esul, NULL,\n+\t\t\t\t\t LWS_SET_TIMER_USEC_CANCEL);\n+#endif\n+\t\t\t/* destroy our copy of the handle */\n+\t\t\tm-\u003emh \u003d NULL;\n+\t\t}\n+\t\tbreak;\n+\n+\tcase LAMP3STATE_DRAINING:\n+\n+draining:\n+\t\tif (buf \u0026\u0026 len \u0026\u0026 m-\u003einside_mp3) {\n+\t\t\tlwsl_notice(\u0022%s: DRAINING: stashing %d: %d %d %d\u005cn\u0022,\n+\t\t\t\t __func__, (int)len, !!(flags \u0026 LWSSS_FLAG_EOM),\n+\t\t\t\t m-\u003ese_head, m-\u003ese_tail);\n+\t\t\tlwsl_hexdump_notice(buf, len);\n+\t\t\tif (lws_buflist_append_segment(\u0026m-\u003edribble, buf, len) \u003c 0)\n+\t\t\t\tgoto bail;\n+\n+\t\t\tm-\u003estash_eom[m-\u003ese_head] \u003d !!(flags \u0026 LWSSS_FLAG_EOM);\n+\t\t\tm-\u003ese_head \u003d (m-\u003ese_head + 1) % sizeof(m-\u003estash_eom);\n+\t\t\tlwsl_notice(\u0022%s: next head %d\u005cn\u0022, __func__, m-\u003ese_head);\n+\n+\t\t\tlws_ss_add_peer_tx_credit(m-\u003ess, len);\n+\t\t}\n+\n+\t\tif (flags \u0026 LWSSS_FLAG_EOM) {\n+\t\t\tif (!len \u0026\u0026 m-\u003ese_head !\u003d m-\u003ese_tail) {\n+\t\t\t\t/* 0-len EOM... retrospectively mark last stash */\n+\t\t\t\tlwsl_notice(\u0022%s: retro EOM\u005cn\u0022, __func__);\n+\t\t\t\tm-\u003estash_eom[(m-\u003ese_head - 1) % sizeof(m-\u003estash_eom)] \u003d 1;\n+\t\t\t}\n+\n+\t\t\tlwsl_notice(\u0022%s: Draining EOM\u005cn\u0022, __func__);\n+\t\t\tm-\u003einside_mp3 \u003d 0;\n+\t\t}\n+\t\t/*\n+\t\t * Don't provide any additional tx credit... we're just\n+\t\t * mopping up the overspill from the previous mp3 credit\n+\t\t */\n+\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+\n+bail:\n+\treturn -1;\n+}\n+\n+/*\n+ * Because this is multipart mime in h2 currently, use a \u0022rideshare\u0022 to handle\n+ * first the native metadata on this secure stream, then the \u0022rideshare\u0022 audio\n+ * stream mentioned in the policy.\n+ *\n+ * Lws takes care of interleaving the multipart mime pieces since the policy\n+ * calls for it.\n+ */\n+\n+static int\n+ss_avs_metadata_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf,\n+\t\t size_t *len, int *flags)\n+{\n+\tss_avs_metadata_t *m \u003d (ss_avs_metadata_t *)userobj;\n+\tsize_t tot;\n+\tint n;\n+\n+\t// lwsl_notice(\u0022%s %d\u005cn\u0022, __func__, (int)m-\u003epos);\n+\n+\tif ((long)m-\u003epos \u003c 0) {\n+\t\t*len \u003d 0;\n+\t\tlwsl_info(\u0022%s: skip\u005cn\u0022, __func__);\n+\t\treturn 1;\n+\t}\n+\n+\tif (!strcmp(lws_ss_rideshare(m-\u003ess), \u0022avs_audio\u0022)) {\n+\n+\t\t/* audio rideshare part */\n+\n+\t\tif (!m-\u003epos)\n+\t\t\t*flags |\u003d LWSSS_FLAG_SOM;\n+\n+\t\tn \u003d spool_capture(buf, *len);\n+\t\tif (n \u003e 0)\n+\t\t\t*len \u003d n;\n+\t\telse\n+\t\t\t*len \u003d 0;\n+\t\tif (!n) {\n+\t\t\tlwsl_info(\u0022%s: trying to skip tx\u005cn\u0022, __func__);\n+\t\t\treturn 1;\n+\t\t}\n+\n+\t\tm-\u003epos +\u003d *len;\n+\n+\t\tif (n \u003c 0) {\n+\t\t\t*flags |\u003d LWSSS_FLAG_EOM;\n+\t\t\tm-\u003epos \u003d (long)-1l; /* ban subsequent until new stream */\n+\t\t}\n+\n+\t\tlwsl_notice(\u0022%s: tx audio %d\u005cn\u0022, __func__, (int)*len);\n+\n+#if 0\n+\t\t{\n+\t\t\tint ff \u003d open(\u0022/tmp/z1\u0022, O_RDWR | O_CREAT | O_APPEND, 0666);\n+\t\t\tif (ff \u003d\u003d -1)\n+\t\t\t\tlwsl_err(\u0022%s: errno %d\u005cn\u0022, __func__, errno);\n+\t\t\twrite(ff, buf, *len);\n+\t\t\tclose(ff);\n+\t\t}\n+#endif\n+\n+\t\treturn 0;\n+\t}\n+\n+\t/* metadata part */\n+\n+\ttot \u003d strlen(metadata);\n+\n+\tif (!m-\u003epos)\n+\t\t*flags |\u003d LWSSS_FLAG_SOM;\n+\n+\tif (*len \u003e tot - m-\u003epos)\n+\t\t*len \u003d tot - m-\u003epos;\n+\n+\tmemcpy(buf, metadata + m-\u003epos, *len);\n+\n+\tm-\u003epos +\u003d *len;\n+\n+\tif (m-\u003epos \u003d\u003d tot) {\n+\t\tlwsl_notice(\u0022metadata done\u005cn\u0022);\n+\t\t*flags |\u003d LWSSS_FLAG_EOM;\n+\t\tm-\u003epos \u003d 0; /* for next time */\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int\n+ss_avs_metadata_state(void *userobj, void *sh,\n+\t\t lws_ss_constate_t state, lws_ss_tx_ordinal_t ack)\n+{\n+\tss_avs_metadata_t *m \u003d (ss_avs_metadata_t *)userobj;\n+\tstruct lws_context *context \u003d (struct lws_context *)m-\u003eopaque_data;\n+\n+\tlwsl_notice(\u0022%s: %p: %s, ord 0x%x\u005cn\u0022, __func__, m-\u003ess,\n+\t\t lws_ss_state_name(state), (unsigned int)ack);\n+\n+\tswitch (state) {\n+\tcase LWSSSCS_CREATING:\n+\t\tlws_ss_client_connect(m-\u003ess);\n+\t\tbreak;\n+\tcase LWSSSCS_CONNECTING:\n+\t\tm-\u003epos \u003d 0;\n+\t\tbreak;\n+\tcase LWSSSCS_CONNECTED:\n+\t\tlwsl_info(\u0022%s: CONNECTED\u005cn\u0022, __func__);\n+\t\tlws_ss_request_tx(m-\u003ess);\n+\t\tbreak;\n+\tcase LWSSSCS_DISCONNECTED:\n+\t\tlws_sul_schedule(context, 0, \u0026m-\u003esul, NULL,\n+\t\t\t\t LWS_SET_TIMER_USEC_CANCEL);\n+\t\t//if (m-\u003emh) {\n+\t\t\tplay_mp3(NULL, NULL, NULL);\n+\t\t\tm-\u003emh \u003d NULL;\n+\t\t//}\n+\t\t/*\n+\t\t * For this stream encapsulating an alexa exchange, dropping\n+\t\t * is the end of its life\n+\t\t */\n+\t\treturn 1;\n+\n+\tcase LWSSSCS_DESTROYING:\n+\t\tlws_buflist_destroy_all_segments(\u0026m-\u003edribble);\n+\t\tbreak;\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * avs event\n+ */\n+\n+static int\n+ss_avs_event_rx(void *userobj, const uint8_t *buf, size_t len, int flags)\n+{\n+\treturn 0;\n+}\n+\n+static int\n+ss_avs_event_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf,\n+\t\t size_t *len, int *flags)\n+{\n+\treturn 1; /* don't transmit anything */\n+}\n+\n+static int\n+ss_avs_event_state(void *userobj, void *sh,\n+\t\t lws_ss_constate_t state, lws_ss_tx_ordinal_t ack)\n+{\n+\tlwsl_info(\u0022%s: %s, ord 0x%x\u005cn\u0022, __func__, lws_ss_state_name(state),\n+\t\t (unsigned int)ack);\n+\n+\tswitch (state) {\n+\tcase LWSSSCS_CREATING:\n+\t\tmpg123_init();\n+\t\tbreak;\n+\tcase LWSSSCS_CONNECTING:\n+\t\tbreak;\n+\tcase LWSSSCS_CONNECTED:\n+\t\tlwsl_user(\u0022Connected to Alexa... speak \u005c\u0022Alexa, ...\u005c\u0022\u005cn\u0022);\n+\t\tbreak;\n+\tcase LWSSSCS_DISCONNECTED:\n+\t\tlwsl_user(\u0022Disconnected from Alexa\u005cn\u0022);\n+\t\tbreak;\n+\tcase LWSSSCS_DESTROYING:\n+\t\tmpg123_exit();\n+\t\tbreak;\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int\n+avs_query_start(struct lws_context *context)\n+{\n+\tlws_ss_info_t ssi;\n+\n+\tlwsl_notice(\u0022%s:\u005cn\u0022, __func__);\n+\n+\tmemset(\u0026ssi, 0, sizeof(ssi));\n+\tssi.handle_offset\t \u003d offsetof(ss_avs_metadata_t, ss);\n+\tssi.opaque_user_data_offset \u003d offsetof(ss_avs_metadata_t, opaque_data);\n+\tssi.rx\t\t\t \u003d ss_avs_metadata_rx;\n+\tssi.tx\t\t\t \u003d ss_avs_metadata_tx;\n+\tssi.state\t\t \u003d ss_avs_metadata_state;\n+\tssi.user_alloc\t\t \u003d sizeof(ss_avs_metadata_t);\n+\tssi.streamtype\t\t \u003d \u0022avs_metadata\u0022;\n+\n+\tssi.manual_initial_tx_credit \u003d 8192;\n+\n+\tif (lws_ss_create(context, 0, \u0026ssi, context, \u0026hss_avs_sync, NULL, NULL)) {\n+\t\tlwsl_err(\u0022%s: failed to create avs metadata secstream\u005cn\u0022,\n+\t\t\t __func__);\n+\n+\t\treturn 1;\n+\t}\n+\n+\tlwsl_user(\u0022%s: created query stream %p\u005cn\u0022, __func__, hss_avs_sync);\n+\n+\treturn 0;\n+}\n+\n+int\n+avs_example_start(struct lws_context *context)\n+{\n+\tlws_ss_info_t ssi;\n+\n+\tif (hss_avs_event)\n+\t\treturn 0;\n+\n+\tlwsl_info(\u0022%s: Starting AVS stream\u005cn\u0022, __func__);\n+\n+\t/* AVS wants us to establish the long poll event stream first */\n+\n+\tmemset(\u0026ssi, 0, sizeof(ssi));\n+\tssi.handle_offset\t \u003d offsetof(ss_avs_event_t, ss);\n+\tssi.opaque_user_data_offset \u003d offsetof(ss_avs_event_t, opaque_data);\n+\tssi.rx\t\t\t \u003d ss_avs_event_rx;\n+\tssi.tx\t\t\t \u003d ss_avs_event_tx;\n+\tssi.state\t\t \u003d ss_avs_event_state;\n+\tssi.user_alloc\t\t \u003d sizeof(ss_avs_event_t);\n+\tssi.streamtype\t\t \u003d \u0022avs_event\u0022;\n+\n+\tif (lws_ss_create(context, 0, \u0026ssi, context, \u0026hss_avs_event, NULL, NULL)) {\n+\t\tlwsl_err(\u0022%s: failed to create avs event secure stream\u005cn\u0022,\n+\t\t\t __func__);\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-alexa/alexa_linux.ppn b/minimal-examples/secure-streams/minimal-secure-streams-alexa/alexa_linux.ppn\nnew file mode 100644\nindex 0000000..839156d\nBinary files /dev/null and b/minimal-examples/secure-streams/minimal-secure-streams-alexa/alexa_linux.ppn differ\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-alexa/audio.c b/minimal-examples/secure-streams/minimal-secure-streams-alexa/audio.c\nnew file mode 100644\nindex 0000000..6233db4\n--- /dev/null\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-alexa/audio.c\n@@ -0,0 +1,469 @@\n+/*\n+ * alsa audio handling\n+ *\n+ * Written in 2010-2020 by Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * This file is made available under the Creative Commons CC0 1.0\n+ * Universal Public Domain Dedication.\n+ */\n+\n+#include \u003clibwebsockets.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003csignal.h\u003e\n+#include \u003csys/types.h\u003e\n+#include \u003csys/stat.h\u003e\n+#include \u003cfcntl.h\u003e\n+\n+#include \u003calsa/asoundlib.h\u003e\n+#include \u003cpv_porcupine.h\u003e\n+\n+#include \u003cmpg123.h\u003e\n+\n+#include \u0022private.h\u0022\n+\n+extern struct lws_ss_handle *hss_avs_event, *hss_avs_sync;\n+\n+int\n+avs_query_start(struct lws_context *context);\n+\n+enum {\n+\tMODE_IDLE,\n+\tMODE_CAPTURING,\n+\tMODE_PLAYING\n+};\n+\n+struct raw_vhd {\n+\tint16_t\t\t\tp[8 * 1024]; /* 500ms at 16kHz 16-bit PCM */\n+\tpv_porcupine_object_t\t*porc;\n+\tsnd_pcm_t\t\t*pcm_capture;\n+\tsnd_pcm_t\t\t*pcm_playback;\n+\tsnd_pcm_hw_params_t\t*params;\n+\tsnd_pcm_uframes_t\tframes;\n+\tint16_t\t\t\t*porcbuf;\n+\n+\tmpg123_handle\t\t*mh;\n+\n+\tmp3_done_cb\t\tdone_cb;\n+\tvoid\t\t\t*opaque;\n+\n+\tint\t\t\tmode;\n+\tint\t\t\trate;\n+\n+\tint\t\t\tporc_spf;\n+\tint\t\t\tfilefd;\n+\tint\t\t\trpos;\n+\tint\t\t\twpos;\n+\tint\t\t\tporcpos;\n+\tint\t\t\tnpos;\n+\tint\t\t\ttimes;\n+\tint\t\t\tquietcount;\n+\tint\t\t\tanycount;\n+\n+\tint\t\t\twplay;\n+\tint\t\t\trplay;\n+\n+\tchar\t\t\tlast_wake_detect;\n+\tchar\t\t\tdestroy_mh_on_drain;\n+};\n+\n+static struct raw_vhd *avhd;\n+\n+/*\n+ * called from alexa.c to grab the next chunk of audio capture buffer\n+ * for upload\n+ */\n+\n+int\n+spool_capture(uint8_t *buf, size_t len)\n+{\n+\tint16_t *sam \u003d (int16_t *)buf;\n+\tsize_t s, os;\n+\n+\tif (avhd-\u003emode !\u003d MODE_CAPTURING)\n+\t\treturn -1;\n+\n+\tos \u003d s \u003d len / 2;\n+\n+\twhile (s \u0026\u0026 avhd-\u003ewpos !\u003d avhd-\u003enpos) {\n+\t\t*sam++ \u003d avhd-\u003ep[avhd-\u003enpos];\n+\t\tavhd-\u003enpos \u003d (avhd-\u003enpos + 1) % LWS_ARRAY_SIZE(avhd-\u003ep);\n+\t\ts--;\n+\t}\n+\n+\tlwsl_info(\u0022Copied %d samples (%d %d)\u005cn\u0022, (int)(os - s),\n+\t\t\tavhd-\u003ewpos, avhd-\u003enpos);\n+\n+\treturn (os - s) * 2;\n+}\n+\n+/*\n+ * Called from alexa.c to control when the mp3 playback should begin and end\n+ */\n+\n+int\n+play_mp3(mpg123_handle *mh, mp3_done_cb cb, void *opaque)\n+{\n+\tif (mh) {\n+\t\tavhd-\u003emh \u003d mh;\n+\t\tavhd-\u003emode \u003d MODE_PLAYING;\n+\t\tsnd_pcm_prepare(avhd-\u003epcm_playback);\n+\n+\t\treturn 0;\n+\t}\n+\n+\tavhd-\u003edestroy_mh_on_drain \u003d 1;\n+\tavhd-\u003edone_cb \u003d cb;\n+\tavhd-\u003eopaque \u003d opaque;\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Helper used to set alsa hwparams on both capture and playback channels\n+ */\n+\n+static int\n+set_hw_params(struct lws_vhost *vh, snd_pcm_t **pcm, int type)\n+{\n+\tunsigned int rate \u003d pv_sample_rate(); /* it's 16kHz */\n+\tsnd_pcm_hw_params_t *params;\n+\tlws_sock_file_fd_type u;\n+\tstruct pollfd pfd;\n+\tstruct lws *wsi1;\n+\tint n;\n+\n+\tn \u003d snd_pcm_open(pcm, \u0022default\u0022, type, SND_PCM_NONBLOCK);\n+\tif (n \u003c 0) {\n+\t\tlwsl_err(\u0022%s: Can't open default for playback: %s\u005cn\u0022,\n+\t\t\t __func__, snd_strerror(n));\n+\n+\t\treturn -1;\n+\t}\n+\n+\tif (snd_pcm_poll_descriptors(*pcm, \u0026pfd, 1) !\u003d 1) {\n+\t\tlwsl_err(\u0022%s: failed to get playback desc\u005cn\u0022, __func__);\n+\t\treturn -1;\n+\t}\n+\n+\tu.filefd \u003d (lws_filefd_type)(long long)pfd.fd;\n+\twsi1 \u003d lws_adopt_descriptor_vhost(vh, LWS_ADOPT_RAW_FILE_DESC, u,\n+\t\t\t\t\t \u0022lws-audio-test\u0022, NULL);\n+\tif (!wsi1) {\n+\t\tlwsl_err(\u0022%s: Failed to adopt playback desc\u005cn\u0022, __func__);\n+\t\tgoto bail;\n+\t}\n+\tif (type \u003d\u003d SND_PCM_STREAM_PLAYBACK)\n+\t\tlws_rx_flow_control(wsi1, 0); /* no POLLIN */\n+\n+\tsnd_pcm_hw_params_malloc(\u0026params);\n+\tsnd_pcm_hw_params_any(*pcm, params);\n+\n+\tn \u003d snd_pcm_hw_params_set_access(*pcm, params,\n+\t\t\t\t\t SND_PCM_ACCESS_RW_INTERLEAVED);\n+\tif (n \u003c 0)\n+\t\tgoto bail1;\n+\n+\tn \u003d snd_pcm_hw_params_set_format(*pcm, params, SND_PCM_FORMAT_S16_LE);\n+\tif (n \u003c 0)\n+\t\tgoto bail1;\n+\n+\tn \u003d snd_pcm_hw_params_set_channels(*pcm, params, 1);\n+\tif (n \u003c 0)\n+\t\tgoto bail1;\n+\n+\tn \u003d snd_pcm_hw_params_set_rate_near(*pcm, params, \u0026rate, 0);\n+\tif (n \u003c 0)\n+\t\tgoto bail1;\n+\n+\tlwsl_notice(\u0022%s: %s rate %d\u005cn\u0022, __func__,\n+\t\ttype \u003d\u003d SND_PCM_STREAM_PLAYBACK ? \u0022Playback\u0022 : \u0022Capture\u0022, rate);\n+\n+\tn \u003d snd_pcm_hw_params(*pcm, params);\n+\tsnd_pcm_hw_params_free(params);\n+\tif (n \u003c 0)\n+\t\tgoto bail;\n+\n+\treturn 0;\n+\n+bail1:\n+\tsnd_pcm_hw_params_free(params);\n+bail:\n+\tlwsl_err(\u0022%s: Set hw params failed: %s\u005cn\u0022, __func__, snd_strerror(n));\n+\n+\treturn -1;\n+}\n+\n+/*\n+ * The lws RAW file protocol handler that wraps ALSA.\n+ *\n+ * The timing is coming from ALSA capture channel... since they are both set to\n+ * 16kHz, it's enough just to have the one.\n+ */\n+\n+static int\n+callback_audio(struct lws *wsi, enum lws_callback_reasons reason, void *user,\n+\t void *in, size_t len)\n+{\n+\tstruct raw_vhd *vhd \u003d (struct raw_vhd *)lws_protocol_vh_priv_get(\n+\t\t\t\t lws_get_vhost(wsi), lws_get_protocol(wsi));\n+\tuint16_t rands[50];\n+\tint16_t temp[256];\n+\tbool det;\n+\tlong avg;\n+\tint n, s;\n+\n+\tswitch (reason) {\n+\tcase LWS_CALLBACK_PROTOCOL_INIT:\n+\n+\t\tif (avhd) /* just on one vhost */\n+\t\t\treturn 0;\n+\n+\t\tavhd \u003d vhd \u003d lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),\n+\t\t\t\tlws_get_protocol(wsi), sizeof(struct raw_vhd));\n+\n+\t\t/*\n+\t\t * Set up the wakeword library\n+\t\t */\n+\n+\t\tn \u003d pv_porcupine_init(\u0022porcupine_params.pv\u0022, \u0022alexa_linux.ppn\u0022,\n+\t\t\t\t\t1.0, \u0026vhd-\u003eporc);\n+\t\tif (n) {\n+\t\t\tlwsl_err(\u0022%s: porcupine init fail %d\u005cn\u0022, __func__, n);\n+\n+\t\t\treturn -1;\n+\t\t}\n+\t\tvhd-\u003eporc_spf \u003d pv_porcupine_frame_length();\n+\t\tvhd-\u003eporcbuf \u003d malloc(vhd-\u003eporc_spf * 2);\n+\t\tlwsl_info(\u0022%s: %s porc frame length is %d samples\u005cn\u0022, __func__,\n+\t\t\t\tlws_get_vhost_name(lws_get_vhost(wsi)),\n+\t\t\t\tvhd-\u003eporc_spf);\n+\n+\t\tvhd-\u003erate \u003d pv_sample_rate(); /* 16kHz */\n+\n+\t\t/* set up alsa */\n+\n+\t\tif (set_hw_params(lws_get_vhost(wsi), \u0026vhd-\u003epcm_playback,\n+\t\t\t\t SND_PCM_STREAM_PLAYBACK)) {\n+\t\t\tlwsl_err(\u0022%s: Can't open default for playback\u005cn\u0022,\n+\t\t\t\t __func__);\n+\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tif (set_hw_params(lws_get_vhost(wsi), \u0026vhd-\u003epcm_capture,\n+\t\t\t\t SND_PCM_STREAM_CAPTURE)) {\n+\t\t\tlwsl_err(\u0022%s: Can't open default for capture\u005cn\u0022,\n+\t\t\t\t __func__);\n+\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tsnd_config_update_free_global();\n+\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_PROTOCOL_DESTROY:\n+\t\tlwsl_info(\u0022%s: LWS_CALLBACK_PROTOCOL_DESTROY\u005cn\u0022, __func__);\n+\t\tif (!vhd)\n+\t\t\tbreak;\n+\n+\t\tif (vhd-\u003eporcbuf) {\n+\t\t\tfree(vhd-\u003eporcbuf);\n+\t\t\tvhd-\u003eporcbuf \u003d NULL;\n+\t\t}\n+\t\tif (vhd-\u003epcm_playback) {\n+\t\t\tsnd_pcm_drop(vhd-\u003epcm_playback);\n+\t\t\tsnd_pcm_close(vhd-\u003epcm_playback);\n+\t\t\tvhd-\u003epcm_playback \u003d NULL;\n+\t\t}\n+\t\tif (vhd-\u003epcm_capture) {\n+\t\t\tsnd_pcm_drop(vhd-\u003epcm_capture);\n+\t\t\tsnd_pcm_close(vhd-\u003epcm_capture);\n+\t\t\tvhd-\u003epcm_capture \u003d NULL;\n+\t\t}\n+\t\tif (vhd-\u003eporc) {\n+\t\t\tpv_porcupine_delete(vhd-\u003eporc);\n+\t\t\tvhd-\u003eporc \u003d NULL;\n+\t\t}\n+\n+\t\t/* avoid most of the valgrind mess from alsa */\n+\t\tsnd_config_update_free_global();\n+\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_RAW_CLOSE_FILE:\n+\t\tlwsl_info(\u0022%s: closed\u005cn\u0022, __func__);\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_RAW_RX_FILE:\n+\t\t/* we come here about every 250ms */\n+\n+\t\t/*\n+\t\t * Playing back the mp3?\n+\t\t */\n+\t\tif (vhd-\u003emode \u003d\u003d MODE_PLAYING \u0026\u0026 vhd-\u003emh) {\n+\t\t\tsize_t amt, try;\n+\n+\t\t\tdo {\n+\t\t\t\ttry \u003d snd_pcm_avail(vhd-\u003epcm_playback);\n+\t\t\t\tif (try \u003e LWS_ARRAY_SIZE(vhd-\u003ep))\n+\t\t\t\t\ttry \u003d LWS_ARRAY_SIZE(vhd-\u003ep);\n+\n+\t\t\t\tn \u003d mpg123_read(vhd-\u003emh, (uint8_t *)vhd-\u003ep,\n+\t\t\t\t\t\ttry * 2, \u0026amt);\n+\t\t\t\tlwsl_info(\u0022%s: PLAYING: mpg123 read %d, n %d\u005cn\u0022,\n+\t\t\t\t\t\t__func__, (int)amt, n);\n+\t\t\t\tif (n \u003d\u003d MPG123_NEW_FORMAT) {\n+\t\t\t\t\tsnd_pcm_start(vhd-\u003epcm_playback);\n+\t\t\t\t\tmemset(vhd-\u003ep, 0, try);\n+\t\t\t\t\tsnd_pcm_writei(vhd-\u003epcm_playback,\n+\t\t\t\t\t\t vhd-\u003ep, try / 2);\n+\t\t\t\t\tsnd_pcm_prepare(vhd-\u003epcm_playback);\n+\t\t\t\t}\n+\t\t\t} while (n \u003d\u003d MPG123_NEW_FORMAT);\n+\n+\t\t\tif (amt) {\n+\t\t\t\tn \u003d snd_pcm_writei(vhd-\u003epcm_playback,\n+\t\t\t\t\t\t vhd-\u003ep, amt / 2);\n+\t\t\t\tif (n \u003c 0)\n+\t\t\t\t\tlwsl_notice(\u0022%s: snd_pcm_writei: %d %s\u005cn\u0022,\n+\t\t\t\t\t\t __func__, n, snd_strerror(n));\n+\t\t\t\tif (n \u003d\u003d -EPIPE) {\n+\t\t\t\t\tlwsl_err(\u0022%s: did EPIPE prep\u005cn\u0022, __func__);\n+\t\t\t\t\tsnd_pcm_prepare(vhd-\u003epcm_playback);\n+\t\t\t\t}\n+\t\t\t} else\n+\t\t\t\tif (vhd-\u003edestroy_mh_on_drain \u0026\u0026\n+\t\t\t\t n !\u003d MPG123_NEW_FORMAT) {\n+\t\t\t\t\tsnd_pcm_drain(vhd-\u003epcm_playback);\n+\t\t\t\t\tvhd-\u003edestroy_mh_on_drain \u003d 0;\n+\t\t\t\t\tlwsl_notice(\u0022%s: mp3 destroyed\u005cn\u0022,\n+\t\t\t\t\t\t\t__func__);\n+\t\t\t\t\tmpg123_close(vhd-\u003emh);\n+\t\t\t\t\tmpg123_delete(vhd-\u003emh);\n+\t\t\t\t\tvhd-\u003emh \u003d NULL;\n+\t\t\t\t\tvhd-\u003emode \u003d MODE_IDLE;\n+\n+\t\t\t\t\tif (vhd-\u003edone_cb)\n+\t\t\t\t\t\tvhd-\u003edone_cb(vhd-\u003eopaque);\n+\t\t\t\t}\n+\t\t}\n+\n+\t\t/*\n+\t\t * Get the capture data\n+\t\t */\n+\n+\t\tn \u003d snd_pcm_readi(vhd-\u003epcm_capture, temp, LWS_ARRAY_SIZE(temp));\n+\t\ts \u003d 0;\n+\t\twhile (s \u003c n) {\n+\t\t\tvhd-\u003ep[(vhd-\u003ewpos + s) % LWS_ARRAY_SIZE(vhd-\u003ep)] \u003d temp[s];\n+\t\t\ts++;\n+\t\t}\n+\n+\t\tif (vhd-\u003emode \u003d\u003d MODE_CAPTURING) {\n+\n+\t\t\t/*\n+\t\t\t * We are recording an utterance.\n+\t\t\t *\n+\t\t\t * Estimate the sound density in the frame by picking 50\n+\t\t\t * samples at random and averaging the sampled\n+\t\t\t * [abs()^2] / 10000 to create a Figure of Merit.\n+\t\t\t *\n+\t\t\t * Speaking on my laptop gets us 1000 - 5000, silence\n+\t\t\t * is typ under 30. The wakeword tells us there was\n+\t\t\t * speech at the start, end the capture when there's\n+\t\t\t * ~750ms (12000 samples) under 125 FOM.\n+\t\t\t */\n+\n+#define SILENCE_THRESH 125\n+\n+\t\t\tavg \u003d 0;\n+\t\t\tlws_get_random(lws_get_context(wsi), rands, sizeof(rands));\n+\t\t\tfor (s \u003d 0; s \u003c (int)LWS_ARRAY_SIZE(rands); s++) {\n+\t\t\t\tlong q;\n+\n+\t\t\t\tq \u003d temp[rands[s] % n];\n+\n+\t\t\t\tavg +\u003d (q * q);\n+\t\t\t}\n+\t\t\tavg \u003d (avg / (int)LWS_ARRAY_SIZE(rands)) / 10000;\n+\n+\t\t\tlwsl_notice(\u0022est audio energy: %ld %d\u005cn\u0022, avg, vhd-\u003emode);\n+\n+\t\t\t/*\n+\t\t\t * Only start looking for \u0022silence\u0022 after 1.5s, in case\n+\t\t\t * he does a long pause after the wakeword\n+\t\t\t */\n+\n+\t\t\tif (vhd-\u003eanycount \u003c (3 *vhd-\u003erate) / 2 \u0026\u0026\n+\t\t\t avg \u003c SILENCE_THRESH) {\n+\t\t\t\tvhd-\u003equietcount +\u003d n;\n+\t\t\t\t/* then 500ms of \u0022silence\u0022 does it for us */\n+\t\t\t\tif (vhd-\u003equietcount \u003e\u003d ((vhd-\u003erate * 3) / 4)) {\n+\t\t\t\t\tlwsl_warn(\u0022%s: ended capture\u005cn\u0022, __func__);\n+\t\t\t\t\tvhd-\u003emode \u003d MODE_IDLE;\n+\t\t\t\t\tvhd-\u003equietcount \u003d 0;\n+\t\t\t\t}\n+\t\t\t}\n+\n+\t\t\t/* if we're not \u0022silent\u0022, reset the count */\n+\t\t\tif (avg \u003e SILENCE_THRESH * 2)\n+\t\t\t\tvhd-\u003equietcount \u003d 0;\n+\n+\t\t\t/*\n+\t\t\t * Since we are in capturing mode, we have something\n+\t\t\t * new to send now.\n+\t\t\t *\n+\t\t\t * We must send an extra one at the end so we can finish\n+\t\t\t * the tx.\n+\t\t\t */\n+\t\t\tlws_ss_request_tx(hss_avs_sync);\n+\t\t}\n+\n+\t\t/*\n+\t\t * Just waiting for a wakeword\n+\t\t */\n+\n+\t\twhile (vhd-\u003emode \u003d\u003d MODE_IDLE) {\n+\t\t\tint m \u003d 0, ppold \u003d vhd-\u003eporcpos;\n+\n+\t\t\ts \u003d (vhd-\u003ewpos - vhd-\u003eporcpos) % LWS_ARRAY_SIZE(vhd-\u003ep);\n+\t\t\tif (s \u003c vhd-\u003eporc_spf)\n+\t\t\t\tgoto eol;\n+\n+\t\t\twhile (m \u003c vhd-\u003eporc_spf) {\n+\t\t\t\tvhd-\u003eporcbuf[m++] \u003d avhd-\u003ep[vhd-\u003eporcpos];\n+\t\t\t\tvhd-\u003eporcpos \u003d (vhd-\u003eporcpos + 1) %\n+\t\t\t\t\t\t\tLWS_ARRAY_SIZE(vhd-\u003ep);\n+\t\t\t}\n+\n+\t\t\tif (pv_porcupine_process(vhd-\u003eporc, vhd-\u003eporcbuf, \u0026det))\n+\t\t\t\tlwsl_err(\u0022%s: porc_process failed\u005cn\u0022, __func__);\n+\n+\t\t\tif (!det \u0026\u0026 vhd-\u003elast_wake_detect \u0026\u0026\n+\t\t\t vhd-\u003emode \u003d\u003d MODE_IDLE) {\n+\t\t\t\tlwsl_warn(\u0022************* Wakeword\u005cn\u0022);\n+\t\t\t\tif (!avs_query_start(lws_get_context(wsi))) {\n+\t\t\t\t\tvhd-\u003emode \u003d MODE_CAPTURING;\n+\t\t\t\t\tvhd-\u003equietcount \u003d 0;\n+\t\t\t\t\tvhd-\u003elast_wake_detect \u003d det;\n+\t\t\t\t\tvhd-\u003enpos \u003d ppold;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tvhd-\u003elast_wake_detect \u003d det;\n+\t\t}\n+\n+eol:\n+\t\tvhd-\u003ewpos \u003d (vhd-\u003ewpos + n) % LWS_ARRAY_SIZE(vhd-\u003ep);\n+\t\tbreak;\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+struct lws_protocols protocol_audio_test \u003d\n+\t{ \u0022lws-audio-test\u0022, callback_audio, 0, 0 };\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-alexa/main.c b/minimal-examples/secure-streams/minimal-secure-streams-alexa/main.c\nnew file mode 100644\nindex 0000000..d498b42\n--- /dev/null\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-alexa/main.c\n@@ -0,0 +1,417 @@\n+/*\n+ * lws-minimal-secure-streams-alexa\n+ *\n+ * This file is made available under the Creative Commons CC0 1.0\n+ * Universal Public Domain Dedication.\n+ */\n+\n+#include \u003clibwebsockets.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003csignal.h\u003e\n+\n+extern int\n+avs_example_start(struct lws_context *context);\n+\n+static int interrupted;\n+static lws_state_notify_link_t nl;\n+\n+#if !defined(LWS_SS_USE_SSPC)\n+\n+/*\n+ * If not using the proxy, we need to bring our own policy\n+ */\n+\n+static const char * const default_ss_policy \u003d\n+\t\u0022{\u0022\n+\t \u0022\u005c\u0022release\u005c\u0022:\u0022\t\t\t\u0022\u005c\u002201234567\u005c\u0022,\u0022\n+\t \u0022\u005c\u0022product\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022myproduct\u005c\u0022,\u0022\n+\t \u0022\u005c\u0022schema-version\u005c\u0022:\u0022\t\t\t\u00221,\u0022\n+\t \u0022\u005c\u0022retry\u005c\u0022: [\u0022\t/* named backoff / retry strategies */\n+\t\t\u0022{\u005c\u0022default\u005c\u0022: {\u0022\n+\t\t\t\u0022\u005c\u0022backoff\u005c\u0022: [\u0022\t \u00221000,\u0022\n+\t\t\t\t\t\t \u00222000,\u0022\n+\t\t\t\t\t\t \u00223000,\u0022\n+\t\t\t\t\t\t \u00225000,\u0022\n+\t\t\t\t\t\t\u002210000\u0022\n+\t\t\t\t\u0022],\u0022\n+\t\t\t\u0022\u005c\u0022conceal\u005c\u0022:\u0022\t\t\u00225,\u0022\n+\t\t\t\u0022\u005c\u0022jitterpc\u005c\u0022:\u0022\t\t\u002220,\u0022\n+\t\t\t\u0022\u005c\u0022svalidping\u005c\u0022:\u0022\t\u002260,\u0022\n+\t\t\t\u0022\u005c\u0022svalidhup\u005c\u0022:\u0022\t\u002264\u0022\n+\t\t\u0022}}\u0022\n+\t \u0022],\u0022\n+\t \u0022\u005c\u0022certs\u005c\u0022: [\u0022 /* named individual certificates in BASE64 DER */\n+\t\t\u0022{\u005c\u0022digicert_global_root_g2\u005c\u0022: \u005c\u0022\u0022 /* api.amazon.com 2038-01 */\n+\t\u0022MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh\u0022\n+\t\u0022MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\u0022\n+\t\u0022d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\u0022\n+\t\u0022MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT\u0022\n+\t\u0022MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\u0022\n+\t\u0022b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG\u0022\n+\t\u00229w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI\u0022\n+\t\u00222/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx\u0022\n+\t\u00221x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ\u0022\n+\t\u0022q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz\u0022\n+\t\u0022tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ\u0022\n+\t\u0022vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP\u0022\n+\t\u0022BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV\u0022\n+\t\u00225uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY\u0022\n+\t\u00221Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4\u0022\n+\t\u0022NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG\u0022\n+\t\u0022Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91\u0022\n+\t\u00228rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe\u0022\n+\t\u0022pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl\u0022\n+\t\u0022MrY\u003d\u0022\n+\t\t\u0022\u005c\u0022},\u0022\n+\t\t\u0022{\u005c\u0022digicert_global_ca_g2\u005c\u0022: \u005c\u0022\u0022 /* api.amazon.com 2028-08 */\n+\t\u0022MIIEizCCA3OgAwIBAgIQDI7gyQ1qiRWIBAYe4kH5rzANBgkqhkiG9w0BAQsFADBh\u0022\n+\t\u0022MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\u0022\n+\t\u0022d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\u0022\n+\t\u0022MjAeFw0xMzA4MDExMjAwMDBaFw0yODA4MDExMjAwMDBaMEQxCzAJBgNVBAYTAlVT\u0022\n+\t\u0022MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxHjAcBgNVBAMTFURpZ2lDZXJ0IEdsb2Jh\u0022\n+\t\u0022bCBDQSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANNIfL7zBYZd\u0022\n+\t\u0022W9UvhU5L4IatFaxhz1uvPmoKR/uadpFgC4przc/cV35gmAvkVNlW7SHMArZagV+X\u0022\n+\t\u0022au4CLyMnuG3UsOcGAngLH1ypmTb+u6wbBfpXzYEQQGfWMItYNdSWYb7QjHqXnxr5\u0022\n+\t\u0022IuYUL6nG6AEfq/gmD6yOTSwyOR2Bm40cZbIc22GoiS9g5+vCShjEbyrpEJIJ7RfR\u0022\n+\t\u0022ACvmfe8EiRROM6GyD5eHn7OgzS+8LOy4g2gxPR/VSpAQGQuBldYpdlH5NnbQtwl6\u0022\n+\t\u0022OErXb4y/E3w57bqukPyV93t4CTZedJMeJfD/1K2uaGvG/w/VNfFVbkhJ+Pi474j4\u0022\n+\t\u00228V4Rd6rfArMCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0P\u0022\n+\t\u0022AQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYYaHR0cDovL29j\u0022\n+\t\u0022c3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6Ly9jcmw0LmRp\u0022\n+\t\u0022Z2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5jcmwwN6A1oDOGMWh0dHA6\u0022\n+\t\u0022Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5jcmwwPQYD\u0022\n+\t\u0022VR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2lj\u0022\n+\t\u0022ZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFCRuKy3QapJRUSVpAaqaR6aJ50AgMB8GA1Ud\u0022\n+\t\u0022IwQYMBaAFE4iVCAYlebjbuYP+vq5Eu0GF485MA0GCSqGSIb3DQEBCwUAA4IBAQAL\u0022\n+\t\u0022OYSR+ZfrqoGvhOlaOJL84mxZvzbIRacxAxHhBsCsMsdaVSnaT0AC9aHesO3ewPj2\u0022\n+\t\u0022dZ12uYf+QYB6z13jAMZbAuabeGLJ3LhimnftiQjXS8X9Q9ViIyfEBFltcT8jW+rZ\u0022\n+\t\u00228uckJ2/0lYDblizkVIvP6hnZf1WZUXoOLRg9eFhSvGNoVwvdRLNXSmDmyHBwW4co\u0022\n+\t\u0022atc7TlJFGa8kBpJIERqLrqwYElesA8u49L3KJg6nwd3jM+/AVTANlVlOnAM2BvjA\u0022\n+\t\u0022jxSZnE0qnsHhfTuvcqdFuhOWKU4Z0BqYBvQ3lBetoxi6PrABDJXWKTUgNX31EGDk\u0022\n+\t\u002292hiHuwZ4STyhxGs6QiA\u0022\n+\t\t\u0022\u005c\u0022},\u0022\n+\t\t\u0022{\u005c\u0022amazon_root_ca_1\u005c\u0022: \u005c\u0022\u0022\n+\t\u0022MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF\u0022\n+\t\u0022ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6\u0022\n+\t\u0022b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL\u0022\n+\t\u0022MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv\u0022\n+\t\u0022b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj\u0022\n+\t\u0022ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM\u0022\n+\t\u00229O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw\u0022\n+\t\u0022IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6\u0022\n+\t\u0022VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L\u0022\n+\t\u002293FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm\u0022\n+\t\u0022jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\u0022\n+\t\u0022AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA\u0022\n+\t\u0022A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI\u0022\n+\t\u0022U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs\u0022\n+\t\u0022N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv\u0022\n+\t\u0022o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU\u0022\n+\t\u00225MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy\u0022\n+\t\u0022rqXRfboQnoZsG4q5WTP468SQvvG5\u0022\n+\t\t\u0022\u005c\u0022},\u0022\n+\t\t\u0022{\u005c\u0022starfield_services_root_ca\u005c\u0022: \u005c\u0022\u0022\n+\t\u0022MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx\u0022\n+\t\u0022EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT\u0022\n+\t\u0022HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs\u0022\n+\t\u0022ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5\u0022\n+\t\u0022MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD\u0022\n+\t\u0022VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy\u0022\n+\t\u0022ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy\u0022\n+\t\u0022dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI\u0022\n+\t\u0022hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p\u0022\n+\t\u0022OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2\u0022\n+\t\u00228xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K\u0022\n+\t\u0022Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe\u0022\n+\t\u0022hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk\u0022\n+\t\u00226mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw\u0022\n+\t\u0022DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q\u0022\n+\t\u0022AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI\u0022\n+\t\u0022bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB\u0022\n+\t\u0022ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z\u0022\n+\t\u0022qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd\u0022\n+\t\u0022iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn\u0022\n+\t\u00220q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN\u0022\n+\t\u0022sSi6\u0022\n+\t\t\u0022\u005c\u0022},\u0022\n+\t\t\u0022{\u005c\u0022starfield_class_2_ca\u005c\u0022: \u005c\u0022\u0022\n+\t\u0022MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl\u0022\n+\t\u0022MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp\u0022\n+\t\u0022U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw\u0022\n+\t\u0022NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE\u0022\n+\t\u0022ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp\u0022\n+\t\u0022ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3\u0022\n+\t\u0022DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf\u0022\n+\t\u00228MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN\u0022\n+\t\u0022+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0\u0022\n+\t\u0022X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa\u0022\n+\t\u0022K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA\u0022\n+\t\u00221W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G\u0022\n+\t\u0022A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR\u0022\n+\t\u0022zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0\u0022\n+\t\u0022YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD\u0022\n+\t\u0022bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w\u0022\n+\t\u0022DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3\u0022\n+\t\u0022L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D\u0022\n+\t\u0022eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl\u0022\n+\t\u0022xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp\u0022\n+\t\u0022VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY\u0022\n+\t\u0022WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q\u003d\u0022\n+\t\t\u0022\u005c\u0022}\u0022\n+\t \u0022],\u0022\n+\t \u0022\u005c\u0022trust_stores\u005c\u0022: [\u0022 /* named cert chains */\n+\t\t\u0022{\u0022 /* chain for alexa.na.gateway.devices.a2z.com */\n+\t\t\t\u0022\u005c\u0022name\u005c\u0022: \u005c\u0022avs_via_starfield\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022stack\u005c\u0022: [\u0022\n+\t\t\t\t\u0022\u005c\u0022starfield_class_2_ca\u005c\u0022,\u0022\n+\t\t\t\t\u0022\u005c\u0022starfield_services_root_ca\u005c\u0022\u0022\n+\t\t\t\u0022]\u0022\n+\t\t\u0022},\u0022\n+\t\t\u0022{\u0022 /* chain for api.amazon.com */\n+\t\t\t\u0022\u005c\u0022name\u005c\u0022: \u005c\u0022api_amazon_com\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022stack\u005c\u0022: [\u0022\n+\t\t\t\t\u0022\u005c\u0022digicert_global_ca_g2\u005c\u0022,\u0022\n+\t\t\t\t\u0022\u005c\u0022digicert_global_root_g2\u005c\u0022\u0022\n+\t\t\t\u0022]\u0022\n+\t\t\u0022}\u0022\n+\t \u0022],\u0022\n+\t \u0022\u005c\u0022s\u005c\u0022: [\u0022 /* the supported stream types */\n+\t\t\u0022{\u005c\u0022api_amazon_com_auth\u005c\u0022: {\u0022\n+\t\t\t\u0022\u005c\u0022endpoint\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022api.amazon.com\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022port\u005c\u0022:\u0022\t\t\t\u0022443,\u0022\n+\t\t\t\u0022\u005c\u0022protocol\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022h1\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_method\u005c\u0022:\u0022\t\t\u0022\u005c\u0022POST\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_url\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022auth/o2/token\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022plugins\u005c\u0022:\u0022\t\t\t\u0022[],\u0022\n+\t\t\t\u0022\u005c\u0022opportunistic\u005c\u0022:\u0022\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022tls\u005c\u0022:\u0022\t\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022h2q_oflow_txcr\u005c\u0022:\u0022\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022http_www_form_urlencoded\u005c\u0022:\u0022\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022http_no_content_length\u005c\u0022:\u0022\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022retry\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022default\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022tls_trust_store\u005c\u0022:\u0022\t\t\u0022\u005c\u0022api_amazon_com\u005c\u0022\u0022\n+\t\t\u0022}},\u0022\n+\t\t/*\n+\t\t * long poll event listener\n+\t\t */\n+\t\t\u0022{\u005c\u0022avs_event\u005c\u0022: {\u0022\n+\t\t\t\u0022\u005c\u0022endpoint\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022alexa.na.gateway.devices.a2z.com\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022port\u005c\u0022:\u0022\t\t\t\u0022443,\u0022\n+\t\t\t\u0022\u005c\u0022protocol\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022h2\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_method\u005c\u0022:\u0022\t\t\u0022\u005c\u0022GET\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_url\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022v20160207/directives\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022h2q_oflow_txcr\u005c\u0022:\u0022\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022http_auth_header\u005c\u0022:\u0022\t\t\u0022\u005c\u0022authorization:\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_auth_preamble\u005c\u0022:\u0022\t\u0022\u005c\u0022Bearer \u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022nailed_up\u005c\u0022:\u0022\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022long_poll\u005c\u0022:\u0022\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022retry\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022default\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022plugins\u005c\u0022:\u0022\t\t\t\u0022[],\u0022\n+\t\t\t\u0022\u005c\u0022tls\u005c\u0022:\u0022\t\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022tls_trust_store\u005c\u0022:\u0022\t\t\u0022\u005c\u0022avs_via_starfield\u005c\u0022\u0022\n+\t\t\u0022}},\u0022\n+\t\t/*\n+\t\t * Utterance metadata and audio send and reply processing.\n+\t\t *\n+\t\t * \u0022Rideshare\u0022 and http_multipart_mime means these both go out\n+\t\t * in one multipart http transaction.\n+\t\t */\n+\t\t\u0022{\u005c\u0022avs_metadata\u005c\u0022: {\u0022\n+\t\t\t\u0022\u005c\u0022endpoint\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022alexa.na.gateway.devices.a2z.com\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022port\u005c\u0022:\u0022\t\t\t\u0022443,\u0022\n+\t\t\t\u0022\u005c\u0022protocol\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022h2\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_method\u005c\u0022:\u0022\t\t\u0022\u005c\u0022POST\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_url\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022v20160207/events\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022opportunistic\u005c\u0022:\u0022\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022h2q_oflow_txcr\u005c\u0022:\u0022\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022http_auth_header\u005c\u0022:\u0022\t\t\u0022\u005c\u0022authorization:\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_auth_preamble\u005c\u0022:\u0022\t\u0022\u005c\u0022Bearer \u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_multipart_name\u005c\u0022:\u0022\t\u0022\u005c\u0022metadata\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_mime_content_type\u005c\u0022:\u0022\t\u0022\u005c\u0022application/json; charset\u003dUTF-8\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_no_content_length\u005c\u0022:\u0022\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022rideshare\u005c\u0022:\u0022\t\t\u0022\u005c\u0022avs_audio\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022retry\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022default\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022plugins\u005c\u0022:\u0022\t\t\t\u0022[],\u0022\n+\t\t\t\u0022\u005c\u0022tls\u005c\u0022:\u0022\t\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022tls_trust_store\u005c\u0022:\u0022\t\t\u0022\u005c\u0022avs_via_starfield\u005c\u0022\u0022\n+\t\t\u0022}},\u0022\n+\t\t\u0022{\u005c\u0022avs_audio\u005c\u0022: {\u0022\n+\t\t\t\u0022\u005c\u0022endpoint\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022alexa.na.gateway.devices.a2z.com\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022port\u005c\u0022:\u0022\t\t\t\u0022443,\u0022\n+\t\t\t\u0022\u005c\u0022protocol\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022h2\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_method\u005c\u0022:\u0022\t\t\u0022\u005c\u0022POST\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_url\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022v20160207/events\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022plugins\u005c\u0022:\u0022\t\t\t\u0022[],\u0022\n+\t\t\t\u0022\u005c\u0022tls\u005c\u0022:\u0022\t\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022h2q_oflow_txcr\u005c\u0022:\u0022\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022http_auth_header\u005c\u0022:\u0022\t\t\u0022\u005c\u0022authorization:\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_auth_preamble\u005c\u0022:\u0022\t\u0022\u005c\u0022Bearer \u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_multipart_name\u005c\u0022:\u0022\t\u0022\u005c\u0022audio\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_mime_content_type\u005c\u0022:\u0022\t\u0022\u005c\u0022application/octet-stream\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_no_content_length\u005c\u0022:\u0022\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022retry\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022default\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022tls_trust_store\u005c\u0022:\u0022\t\t\u0022\u005c\u0022avs_via_starfield\u005c\u0022\u0022\n+\t\t\u0022}}\u0022\n+\t \u0022]\u0022\n+\t\u0022}\u0022\n+;\n+\n+#endif\n+\n+static const char *canned_root_token_payload \u003d\n+\t\u0022grant_type\u003drefresh_token\u0022\n+\t\u0022\u0026refresh_token\u003dAtzr|IwEBIJedGXjDqsU_vMxykqOMg\u0022\n+\t\u0022SHfYe3CPcedueWEMWSDMaDnEmiW8RlR1Kns7Cb4B-TOSnqp7ifVsY4BMY2B8tpHfO39XP\u0022\n+\t\u0022zfu9HapGjTR458IyHX44FE71pWJkGZ79uVBpljP4sazJuk8XS3Oe_yLnm_DIO6fU1nU3Y\u0022\n+\t\u00220flYmsOiOAQE_gRk_pdlmEtHnpMA-9rLw3mkY5L89Ty9kUygBsiFaYatouROhbsTn8-jW\u0022\n+\t\u0022k1zZLUDpT6ICtBXSnrCIg0pUbZevPFhTwdXd6eX-u4rq0W-XaDvPWFO7au-iPb4Zk5eZE\u0022\n+\t\u0022iX6sissYrtNmuEXc2uHu7MnQO1hHCaTdIO2CANVumf-PHSD8xseamyh04sLV5JgFzY45S\u0022\n+\t\u0022KvKMajiUZuLkMokOx86rjC2Hdkx5DO7G-dbG1ufBDG-N79pFMSs7Ck5pc283IdLoJkCQc\u0022\n+\t\u0022AGvTX8o8I29QqkcGou-9TKhOJmpX8As94T61ok0UqqEKPJ7RhfQHHYdCtsdwxgvfVr9qI\u0022\n+\t\u0022xL_hDCcTho8opCVX-6QhJHl6SQFlTw13\u0022\n+\t\u0022\u0026client_id\u003d\u0022\n+\t\t\u0022amzn1.application-oa2-client.4823334c434b4190a2b5a42c07938a2d\u0022;\n+\n+/*\n+ * Register the root token, and make the sticky AVS connection at the\n+ * appropriate times during system startup\n+ */\n+\n+static int\n+app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link,\n+\t\t int current, int target)\n+{\n+\tstruct lws_context *context \u003d lws_system_context_from_system_mgr(mgr);\n+\tlws_system_blob_t *ab \u003d lws_system_get_blob(context,\n+\t\t\t\tLWS_SYSBLOB_TYPE_AUTH, 1 /* AUTH_IDX_ROOT */);\n+\tsize_t size;\n+\n+\t/*\n+\t * For the things we care about, let's notice if we are trying to get\n+\t * past them when we haven't solved them yet, and make the system\n+\t * state wait while we trigger the dependent action.\n+\t */\n+\tswitch (target) {\n+\tcase LWS_SYSTATE_REGISTERED:\n+\t\tsize \u003d lws_system_blob_get_size(ab);\n+\t\tif (size)\n+\t\t\tbreak;\n+\n+\t\t/* let's register our canned root token so auth can use it */\n+\t\tlws_system_blob_direct_set(ab,\n+\t\t\t\t(const uint8_t *)canned_root_token_payload,\n+\t\t\t\tstrlen(canned_root_token_payload));\n+\t\tbreak;\n+\tcase LWS_SYSTATE_OPERATIONAL:\n+\t\tif (current \u003d\u003d target)\n+\t\t\tavs_example_start(context);\n+\t\tbreak;\n+\tcase LWS_SYSTATE_POLICY_INVALID:\n+\t\t/*\n+\t\t * This is a NOP since we used direct set... but in a real\n+\t\t * system this could easily change to be done on the heap, then\n+\t\t * this would be important\n+\t\t */\n+\t\tlws_system_blob_destroy(lws_system_get_blob(context,\n+\t\t\t\t\tLWS_SYSBLOB_TYPE_AUTH,\n+\t\t\t\t\t1 /* AUTH_IDX_ROOT */));\n+\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void\n+sigint_handler(int sig)\n+{\n+\tinterrupted \u003d 1;\n+}\n+\n+static lws_state_notify_link_t * const app_notifier_list[] \u003d {\n+\t\u0026nl, NULL\n+};\n+\n+extern struct lws_protocols protocol_audio_test;\n+static const struct lws_protocols *protocols[] \u003d {\n+\t\u0026protocol_audio_test,\n+#if defined(LWS_SS_USE_SSPC)\n+\tlws_sspc_protocols,\n+#endif\n+\tNULL\n+};\n+\n+int main(int argc, const char **argv)\n+{\n+\tstruct lws_context_creation_info info;\n+\tstruct lws_context *context;\n+\tint n \u003d 0;\n+\n+\tsignal(SIGINT, sigint_handler);\n+\tmemset(\u0026info, 0, sizeof info); /* otherwise uninitialized garbage */\n+\tlws_cmdline_option_handle_builtin(argc, argv, \u0026info);\n+\n+\tlwsl_user(\u0022LWS secure streams - Alexa voice test [-d\u003cverb\u003e]\u005cn\u0022);\n+\n+\tinfo.fd_limit_per_thread \u003d 1 + 6 + 1;\n+#if !defined(LWS_SS_USE_SSPC)\n+\tinfo.options \u003d LWS_SERVER_OPTION_EXPLICIT_VHOSTS |\n+\t\t LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;\n+\tinfo.pss_policies_json \u003d default_ss_policy;\n+#else\n+\t{\n+\t\tconst char *p;\n+\n+\t\t/* connect to ssproxy via UDS by default, else via\n+\t\t * tcp connection to this port */\n+\t\tif ((p \u003d lws_cmdline_option(argc, argv, \u0022-p\u0022)))\n+\t\t\tinfo.ss_proxy_port \u003d atoi(p);\n+\n+\t\t/* UDS \u0022proxy.ss.lws\u0022 in abstract namespace, else this socket\n+\t\t * path; when -p given this can specify the network interface\n+\t\t * to bind to */\n+\t\tif ((p \u003d lws_cmdline_option(argc, argv, \u0022-i\u0022)))\n+\t\t\tinfo.ss_proxy_bind \u003d p;\n+\n+\t\t/* if -p given, -a specifies the proxy address to connect to */\n+\t\tif ((p \u003d lws_cmdline_option(argc, argv, \u0022-a\u0022)))\n+\t\t\tinfo.ss_proxy_address \u003d p;\n+\t}\n+#endif\n+\tinfo.port \u003d CONTEXT_PORT_NO_LISTEN;\n+\tinfo.pprotocols \u003d protocols;\n+\n+#if defined(LWS_WITH_DETAILED_LATENCY)\n+\tinfo.detailed_latency_cb \u003d lws_det_lat_plot_cb;\n+\tinfo.detailed_latency_filepath \u003d \u0022/tmp/lws-latency-ssproxy\u0022;\n+#endif\n+\n+\t/* integrate us with lws system state management when context created */\n+\tnl.name \u003d \u0022app\u0022;\n+\tnl.notify_cb \u003d app_system_state_nf;\n+\tinfo.register_notifier_list \u003d app_notifier_list;\n+\n+\tcontext \u003d lws_create_context(\u0026info);\n+\tif (!context) {\n+\t\tlwsl_err(\u0022lws init failed\u005cn\u0022);\n+\t\treturn 1;\n+\t}\n+\n+\t/* create an explicit vhost so the sound protocol is initialized */\n+\n+\tinfo.vhost_name \u003d \u0022asound\u0022;\n+\tif (!lws_create_vhost(context, \u0026info)) {\n+\t\tlwsl_err(\u0022lws init failed\u005cn\u0022);\n+\t\tgoto bail;\n+\t}\n+\n+\t/* the event loop */\n+\n+\twhile (n \u003e\u003d 0 \u0026\u0026 !interrupted)\n+\t\tn \u003d lws_service(context, 0);\n+\n+bail:\n+\tlws_context_destroy(context);\n+\tlwsl_user(\u0022Completed\u005cn\u0022);\n+\n+\treturn 0;\n+}\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-alexa/porcupine_params.pv b/minimal-examples/secure-streams/minimal-secure-streams-alexa/porcupine_params.pv\nnew file mode 100644\nindex 0000000..4d88bb5\nBinary files /dev/null and b/minimal-examples/secure-streams/minimal-secure-streams-alexa/porcupine_params.pv differ\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-alexa/private.h b/minimal-examples/secure-streams/minimal-secure-streams-alexa/private.h\nnew file mode 100644\nindex 0000000..a4920b1\n--- /dev/null\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-alexa/private.h\n@@ -0,0 +1,8 @@\n+typedef int (*mp3_done_cb)(void *opaque);\n+\n+int\n+play_mp3(mpg123_handle *mh, mp3_done_cb cb, void *opaque);\n+\n+\n+int\n+spool_capture(uint8_t *buf, size_t len);\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-avs/CMakeLists.txt b/minimal-examples/secure-streams/minimal-secure-streams-avs/CMakeLists.txt\nnew file mode 100644\nindex 0000000..7e58274\n--- /dev/null\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-avs/CMakeLists.txt\n@@ -0,0 +1,91 @@\n+cmake_minimum_required(VERSION 2.8)\n+include(CheckCSourceCompiles)\n+\n+set(SAMP lws-minimal-secure-streams-avs)\n+\n+# If we are being built as part of lws, confirm current build config supports\n+# reqconfig, else skip building ourselves.\n+#\n+# If we are being built externally, confirm installed lws was configured to\n+# support reqconfig, else error out with a helpful message about the problem.\n+#\n+MACRO(require_lws_config reqconfig _val result)\n+\n+\tif (DEFINED ${reqconfig})\n+\tif (${reqconfig})\n+\t\tset (rq 1)\n+\telse()\n+\t\tset (rq 0)\n+\tendif()\n+\telse()\n+\t\tset(rq 0)\n+\tendif()\n+\n+\tif (${_val} EQUAL ${rq})\n+\t\tset(SAME 1)\n+\telse()\n+\t\tset(SAME 0)\n+\tendif()\n+\n+\tif (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})\n+\t\tif (${_val})\n+\t\t\tmessage(\u0022${SAMP}: skipping as lws being built without ${reqconfig}\u0022)\n+\t\telse()\n+\t\t\tmessage(\u0022${SAMP}: skipping as lws built with ${reqconfig}\u0022)\n+\t\tendif()\n+\t\tset(${result} 0)\n+\telse()\n+\t\tif (LWS_WITH_MINIMAL_EXAMPLES)\n+\t\t\tset(MET ${SAME})\n+\t\telse()\n+\t\t\tCHECK_C_SOURCE_COMPILES(\u0022#include \u003clibwebsockets.h\u003e\u005cnint main(void) {\u005cn#if defined(${reqconfig})\u005cn return 0;\u005cn#else\u005cn fail;\u005cn#endif\u005cn return 0;\u005cn}\u005cn\u0022 HAS_${reqconfig})\n+\t\t\tif (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})\n+\t\t\t\tset(HAS_${reqconfig} 0)\n+\t\t\telse()\n+\t\t\t\tset(HAS_${reqconfig} 1)\n+\t\t\tendif()\n+\t\t\tif ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))\n+\t\t\t\tset(MET 1)\n+\t\t\telse()\n+\t\t\t\tset(MET 0)\n+\t\t\tendif()\n+\t\tendif()\n+\t\tif (NOT MET)\n+\t\t\tif (${_val})\n+\t\t\t\tmessage(FATAL_ERROR \u0022This project requires lws must have been configured with ${reqconfig}\u0022)\n+\t\t\telse()\n+\t\t\t\tmessage(FATAL_ERROR \u0022Lws configuration of ${reqconfig} is incompatible with this project\u0022)\n+\t\t\tendif()\n+\t\tendif()\n+\tendif()\n+ENDMACRO()\n+\n+\n+set(requirements 1)\n+require_lws_config(LWS_ROLE_H1 1 requirements)\n+require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)\n+require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements)\n+\n+if (requirements)\n+\tadd_executable(${SAMP} main.c avs.c)\n+\n+\tif (websockets_shared)\n+\t\ttarget_link_libraries(${SAMP} websockets_shared)\n+\t\tadd_dependencies(${SAMP} websockets_shared)\n+\telse()\n+\t\ttarget_link_libraries(${SAMP} websockets)\n+\tendif()\n+\n+\tif (LWS_WITH_SECURE_STREAMS_PROXY_API)\n+\t\tadd_compile_options(-DLWS_SS_USE_SSPC)\n+\n+\t\tadd_executable(${SAMP}-client main-client.c avs.c)\n+\t\tif (websockets_shared)\n+\t\t\ttarget_link_libraries(${SAMP}-client websockets_shared)\n+\t\t\tadd_dependencies(${SAMP}-client websockets_shared)\n+\t\telse()\n+\t\t\ttarget_link_libraries(${SAMP}-client websockets)\n+\t\tendif()\n+\tendif()\n+\n+endif()\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-avs/avs.c b/minimal-examples/secure-streams/minimal-secure-streams-avs/avs.c\nnew file mode 100644\nindex 0000000..722b4a0\n--- /dev/null\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-avs/avs.c\n@@ -0,0 +1,418 @@\n+/*\n+ * lws-minimal-secure-streams-avs\n+ *\n+ * Written in 2019-2020 by Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * This file is made available under the Creative Commons CC0 1.0\n+ * Universal Public Domain Dedication.\n+ *\n+ * This sends a canned WAV and received (and discards) the mp3 response.\n+ * However it rate-limits the response reception to manage a small ringbuffer\n+ * using ss / h2 flow control apis, reflecting consumption at 64kbps and only\n+ * and 8KB buffer, indtended to model optimizing rx buffering on mp3 playback\n+ * on a constrained device.\n+ */\n+\n+#include \u003clibwebsockets.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003csys/types.h\u003e\n+#include \u003csys/stat.h\u003e\n+#include \u003cunistd.h\u003e\n+#include \u003cassert.h\u003e\n+#include \u003cfcntl.h\u003e\n+\n+extern int interrupted, bad;\n+static struct lws_ss_handle *hss_avs_event, *hss_avs_sync;\n+static uint8_t *wav;\n+static size_t wav_len;\n+\n+typedef struct ss_avs_event {\n+\tstruct lws_ss_handle \t*ss;\n+\tvoid\t\t\t*opaque_data;\n+\t/* ... application specific state ... */\n+\tstruct lejp_ctx\t\tjctx;\n+} ss_avs_event_t;\n+\n+typedef struct ss_avs_metadata {\n+\tstruct lws_ss_handle \t*ss;\n+\tvoid\t\t\t*opaque_data;\n+\t/* ... application specific state ... */\n+\tstruct lejp_ctx\t\tjctx;\n+\tsize_t\t\t\tpos;\n+\n+\t/*\n+\t * We simulate a ringbuffer that is used up by a sul at 64Kbit/sec\n+\t * rate, and managed at the same rate using tx credit\n+\t */\n+\n+\tlws_sorted_usec_list_t\tsul;\n+\tuint8_t\t\t\tbuf[8192];\n+\tint\t\t\thead;\n+\tint\t\t\ttail;\n+\n+\tchar\t\t\tfilled;\n+\n+} ss_avs_metadata_t;\n+\n+static const char *metadata \u003d \u0022{\u0022\n+\t\u0022\u005c\u0022event\u005c\u0022: {\u0022\n+\t\t\u0022\u005c\u0022header\u005c\u0022: {\u0022\n+\t\t\t\u0022\u005c\u0022namespace\u005c\u0022: \u005c\u0022SpeechRecognizer\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022name\u005c\u0022: \u005c\u0022Recognize\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022messageId\u005c\u0022: \u005c\u0022message-123\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022dialogRequestId\u005c\u0022: \u005c\u0022dialog-request-321\u005c\u0022\u0022\n+\t\t\u0022},\u0022\n+\t\t\u0022\u005c\u0022payload\u005c\u0022: {\u0022\n+\t\t\t\u0022\u005c\u0022profile\u005c\u0022:\u0022\t\u0022\u005c\u0022CLOSE_TALK\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022format\u005c\u0022:\u0022\t\u0022\u005c\u0022AUDIO_L16_RATE_16000_CHANNELS_1\u005c\u0022\u0022\n+\t\t\u0022}\u0022\n+\t\u0022}\u0022\n+\u0022}\u0022;\n+\n+/*\n+ * avs metadata\n+ */\n+\n+static void\n+use_buffer_50ms(lws_sorted_usec_list_t *sul)\n+{\n+\tss_avs_metadata_t *m \u003d lws_container_of(sul, ss_avs_metadata_t, sul);\n+\tstruct lws_context *context \u003d (struct lws_context *)m-\u003eopaque_data;\n+\tsize_t n;\n+\tint e;\n+\n+\t/*\n+\t * Use up 50ms-worth (8KB / 20) \u003d\u003d 401 bytes of buffered data\n+\t */\n+\n+\t/* remaining data in buffer */\n+\tn \u003d ((m-\u003ehead - m-\u003etail) % sizeof(m-\u003ebuf));\n+\tlwsl_info(\u0022%s: avail %d\u005cn\u0022, __func__, (int)n);\n+\n+\tif (n \u003c 401)\n+\t\tlwsl_err(\u0022%s: underrun\u005cn\u0022, __func__);\n+\n+\tm-\u003etail \u003d (m-\u003etail + 401) % sizeof(m-\u003ebuf);\n+\tn \u003d ((m-\u003ehead - m-\u003etail) % sizeof(m-\u003ebuf));\n+\n+\te \u003d lws_ss_get_est_peer_tx_credit(m-\u003ess);\n+\n+\tlwsl_info(\u0022%s: avail after: %d, curr est %d\u005cn\u0022, __func__, (int)n, e);\n+\n+\tif (n \u003c (sizeof(m-\u003ebuf) * 2) / 3 \u0026\u0026 e \u003c (int)(sizeof(m-\u003ebuf) - 1 - n)) {\n+\t\tlwsl_info(\u0022%s: requesting additional %d\u005cn\u0022, __func__,\n+\t\t\t\t(int)(sizeof(m-\u003ebuf) - 1 - e - n));\n+\t\tlws_ss_add_peer_tx_credit(m-\u003ess, (sizeof(m-\u003ebuf) - 1 - e - n));\n+\t}\n+\n+\tlws_sul_schedule(context, 0, \u0026m-\u003esul, use_buffer_50ms,\n+\t\t\t 50 * LWS_US_PER_MS);\n+}\n+\n+static int\n+ss_avs_metadata_rx(void *userobj, const uint8_t *buf, size_t len, int flags)\n+{\n+\tss_avs_metadata_t *m \u003d (ss_avs_metadata_t *)userobj;\n+\tstruct lws_context *context \u003d (struct lws_context *)m-\u003eopaque_data;\n+\tsize_t n, n1;\n+\n+\tlwsl_notice(\u0022%s: rideshare %s, len %d, flags 0x%x\u005cn\u0022, __func__,\n+\t\t\tlws_ss_rideshare(m-\u003ess), (int)len, flags);\n+\t// lwsl_hexdump_warn(buf, len);\n+\n+\tn \u003d sizeof(m-\u003ebuf) - ((m-\u003ehead - m-\u003etail) % sizeof(m-\u003ebuf));\n+\tlwsl_info(\u0022%s: len %d, buf h %d, t %d, space %d\u005cn\u0022, __func__,\n+\t\t (int)len, (int)m-\u003ehead, (int)m-\u003etail, (int)n);\n+\tlws_ss_get_est_peer_tx_credit(m-\u003ess);\n+\tassert (len \u003c\u003d n);\n+\n+\tif (m-\u003ehead \u003c m-\u003etail)\t\t\t\t/* |****h-------t**| */\n+\t\tmemcpy(\u0026m-\u003ebuf[m-\u003ehead], buf, len);\n+\telse {\t\t\t\t\t\t/* |---t*****h-----| */\n+\t\tn1 \u003d sizeof(m-\u003ebuf) - m-\u003ehead;\n+\t\tif (len \u003c n1)\n+\t\t\tn1 \u003d len;\n+\t\tmemcpy(\u0026m-\u003ebuf[m-\u003ehead], buf, n1);\n+\t\tif (n1 !\u003d len)\n+\t\t\tmemcpy(m-\u003ebuf, buf, len - n1);\n+\t}\n+\n+\tm-\u003ehead \u003d (m-\u003ehead + len) % sizeof(m-\u003ebuf);\n+\n+\tlws_sul_schedule(context, 0, \u0026m-\u003esul, use_buffer_50ms,\n+\t\t\t 50 * LWS_US_PER_MS);\n+\n+\treturn 0;\n+}\n+\n+static int\n+ss_avs_metadata_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf,\n+\t\t size_t *len, int *flags)\n+{\n+\tss_avs_metadata_t *m \u003d (ss_avs_metadata_t *)userobj;\n+\t//struct lws_context *context \u003d (struct lws_context *)m-\u003eopaque_data;\n+\tsize_t tot;\n+\n+\tif ((long)m-\u003epos \u003c 0) {\n+\t\t*len \u003d 0;\n+\t\tlwsl_notice(\u0022%s: skip tx\u005cn\u0022, __func__);\n+\t\treturn 1;\n+\t}\n+\n+\tlwsl_notice(\u0022%s: rideshare '%s'\u005cn\u0022, __func__, lws_ss_rideshare(m-\u003ess));\n+\n+\tif (!strcmp(lws_ss_rideshare(m-\u003ess), \u0022avs_audio\u0022)) {\n+\t\t/* audio rideshare */\n+\n+\t\tif (!m-\u003epos)\n+\t\t\t*flags |\u003d LWSSS_FLAG_SOM;\n+\n+\t\tif (*len \u003e wav_len - m-\u003epos)\n+\t\t\t*len \u003d wav_len - m-\u003epos;\n+\n+\t\tmemcpy(buf, wav + m-\u003epos, *len);\n+\t\tm-\u003epos +\u003d *len;\n+\n+\t\tif (m-\u003epos \u003d\u003d wav_len) {\n+\t\t\t*flags |\u003d LWSSS_FLAG_EOM;\n+\t\t\tlwsl_info(\u0022%s: tx done\u005cn\u0022, __func__);\n+\t\t\tm-\u003epos \u003d (long)-1l; /* ban subsequent until new stream */\n+\t\t} else\n+\t\t\tlws_ss_request_tx(m-\u003ess);\n+\n+\t\tlwsl_hexdump_info(buf, *len);\n+\n+\t\treturn 0;\n+\t}\n+\n+\t/* metadata part */\n+\n+\ttot \u003d strlen(metadata);\n+\n+\tif (!m-\u003epos)\n+\t\t*flags |\u003d LWSSS_FLAG_SOM;\n+\n+\tif (*len \u003e tot - m-\u003epos)\n+\t\t*len \u003d tot - m-\u003epos;\n+\n+\tmemcpy(buf, metadata + m-\u003epos, *len);\n+\n+\tm-\u003epos +\u003d *len;\n+\n+\tif (m-\u003epos \u003d\u003d tot) {\n+\t\t*flags |\u003d LWSSS_FLAG_EOM;\n+\t\tm-\u003epos \u003d 0; /* for next time */\n+\t\tlws_ss_request_tx(m-\u003ess);\n+\t}\n+\n+\tlwsl_hexdump_info(buf, *len);\n+\n+\treturn 0;\n+}\n+\n+static int\n+ss_avs_metadata_state(void *userobj, void *sh,\n+\t\t lws_ss_constate_t state, lws_ss_tx_ordinal_t ack)\n+{\n+\n+\tss_avs_metadata_t *m \u003d (ss_avs_metadata_t *)userobj;\n+\tstruct lws_context *context \u003d (struct lws_context *)m-\u003eopaque_data;\n+\n+\tlwsl_user(\u0022%s: %s, ord 0x%x\u005cn\u0022, __func__, lws_ss_state_name(state),\n+\t\t (unsigned int)ack);\n+\n+\tswitch (state) {\n+\tcase LWSSSCS_CREATING:\n+\t\tlwsl_user(\u0022%s: CREATING\u005cn\u0022, __func__);\n+\t\tlws_ss_client_connect(m-\u003ess);\n+\t\tm-\u003epos \u003d 0;\n+\t\tbreak;\n+\tcase LWSSSCS_CONNECTING:\n+\t\tbreak;\n+\tcase LWSSSCS_CONNECTED:\n+\t\tlws_ss_request_tx(m-\u003ess);\n+\t\tbreak;\n+\tcase LWSSSCS_ALL_RETRIES_FAILED:\n+\t\t/* for this demo app, we want to exit on fail to connect */\n+\tcase LWSSSCS_DISCONNECTED:\n+\t\t/* for this demo app, we want to exit after complete flow */\n+\t\tlws_sul_schedule(context, 0, \u0026m-\u003esul, use_buffer_50ms,\n+\t\t\t\t LWS_SET_TIMER_USEC_CANCEL);\n+\t\tinterrupted \u003d 1;\n+\t\tbreak;\n+\tcase LWSSSCS_DESTROYING:\n+\t\tlws_sul_schedule(context, 0, \u0026m-\u003esul, use_buffer_50ms,\n+\t\t\t\t LWS_SET_TIMER_USEC_CANCEL);\n+\t\tbreak;\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * avs event\n+ */\n+\n+static int\n+ss_avs_event_rx(void *userobj, const uint8_t *buf, size_t len, int flags)\n+{\n+\tss_avs_event_t *m \u003d (ss_avs_event_t *)userobj;\n+\t// struct lws_context *context \u003d (struct lws_context *)m-\u003eopaque_data;\n+\n+\tlwsl_notice(\u0022%s: rideshare %s, len %d, flags 0x%x\u005cn\u0022, __func__,\n+\t\t\tlws_ss_rideshare(m-\u003ess), (int)len, flags);\n+\n+\t//lwsl_hexdump_warn(buf, len);\n+\n+\tbad \u003d 0; /* for this demo, receiving something here \u003d\u003d success */\n+\n+\treturn 0;\n+}\n+\n+static int\n+ss_avs_event_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf,\n+\t\t size_t *len, int *flags)\n+{\n+\tss_avs_event_t *m \u003d (ss_avs_event_t *)userobj;\n+\tlwsl_notice(\u0022%s: rideshare %s\u005cn\u0022, __func__, lws_ss_rideshare(m-\u003ess));\n+\n+\treturn 1; /* don't transmit anything */\n+}\n+\n+static int\n+ss_avs_event_state(void *userobj, void *sh,\n+\t\t lws_ss_constate_t state, lws_ss_tx_ordinal_t ack)\n+{\n+\tss_avs_event_t *m \u003d (ss_avs_event_t *)userobj;\n+\tstruct lws_context *context \u003d (struct lws_context *)m-\u003eopaque_data;\n+\tlws_ss_info_t ssi;\n+\n+\tlwsl_user(\u0022%s: %s, ord 0x%x\u005cn\u0022, __func__, lws_ss_state_name(state),\n+\t\t (unsigned int)ack);\n+\n+\tswitch (state) {\n+\tcase LWSSSCS_CREATING:\n+\tcase LWSSSCS_CONNECTING:\n+\t\tbreak;\n+\tcase LWSSSCS_CONNECTED:\n+\t\tif (hss_avs_sync)\n+\t\t\tbreak;\n+\n+\t\tlwsl_notice(\u0022%s: starting the second avs stream\u005cn\u0022, __func__);\n+\n+\t\t/*\n+\t\t * When we have established the event stream, we must POST\n+\t\t * on another stream within 10s\n+\t\t */\n+\n+\t\tmemset(\u0026ssi, 0, sizeof(ssi));\n+\t\tssi.handle_offset\t \u003d offsetof(ss_avs_metadata_t, ss);\n+\t\tssi.opaque_user_data_offset \u003d offsetof(ss_avs_metadata_t,\n+\t\t\t\t\t\t opaque_data);\n+\t\tssi.rx\t\t\t \u003d ss_avs_metadata_rx;\n+\t\tssi.tx\t\t\t \u003d ss_avs_metadata_tx;\n+\t\tssi.state\t\t \u003d ss_avs_metadata_state;\n+\t\tssi.user_alloc\t\t \u003d sizeof(ss_avs_metadata_t);\n+\t\tssi.streamtype\t\t \u003d \u0022avs_metadata\u0022;\n+\n+\t\t/*\n+\t\t * We want to allow the other side to fill our buffer, but no\n+\t\t * more. But it's a bit tricky when the payload is inside\n+\t\t * framing like multipart MIME and contains other parts\n+\t\t */\n+\n+\t\tssi.manual_initial_tx_credit \u003d\n+\t\t\t\tsizeof(((ss_avs_metadata_t *)0)-\u003ebuf) / 2;\n+\n+\t\tif (lws_ss_create(context, 0, \u0026ssi, context, \u0026hss_avs_sync,\n+\t\t\t\t NULL, NULL)) {\n+\t\t\tlwsl_err(\u0022%s: failed to create avs metadata secstream\u005cn\u0022,\n+\t\t\t\t __func__);\n+\t\t}\n+\t\tbreak;\n+\tcase LWSSSCS_ALL_RETRIES_FAILED:\n+\t\t/* for this demo app, we want to exit on fail to connect */\n+\t\tinterrupted \u003d 1;\n+\t\tbreak;\n+\tcase LWSSSCS_DISCONNECTED:\n+\t\tbreak;\n+\tcase LWSSSCS_DESTROYING:\n+\t\tlwsl_notice(\u0022%s: DESTROYING\u005cn\u0022, __func__);\n+\t\tif (wav) {\n+\t\t\tfree(wav);\n+\t\t\twav \u003d NULL;\n+\t\t}\n+\t\tbreak;\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int\n+avs_example_start(struct lws_context *context)\n+{\n+\tlws_ss_info_t ssi;\n+\tstruct stat stat;\n+\tint fd;\n+\n+\tif (hss_avs_event)\n+\t\treturn 0;\n+\n+\tfd \u003d open(\u0022./year.wav\u0022, O_RDONLY);\n+\tif (fd \u003c 0) {\n+\t\tlwsl_err(\u0022%s: failed to open wav file\u005cn\u0022, __func__);\n+\n+\t\treturn 1;\n+\t}\n+\tif (fstat(fd, \u0026stat) \u003c 0) {\n+\t\tlwsl_err(\u0022%s: failed to stat wav file\u005cn\u0022, __func__);\n+\n+\t\tgoto bail;\n+\t}\n+\n+\twav_len \u003d stat.st_size;\n+\twav \u003d malloc(wav_len);\n+\tif (!wav) {\n+\t\tlwsl_err(\u0022%s: failed to alloc wav buffer\u0022, __func__);\n+\n+\t\tgoto bail;\n+\t}\n+\tif (read(fd, wav, wav_len) !\u003d (int)wav_len) {\n+\t\tlwsl_err(\u0022%s: failed to read wav\u005cn\u0022, __func__);\n+\n+\t\tgoto bail;\n+\t}\n+\tclose(fd);\n+\n+\tlwsl_user(\u0022%s: Starting AVS stream\u005cn\u0022, __func__);\n+\n+\t/* AVS wants us to establish the long poll event stream first */\n+\n+\tmemset(\u0026ssi, 0, sizeof(ssi));\n+\tssi.handle_offset\t \u003d offsetof(ss_avs_event_t, ss);\n+\tssi.opaque_user_data_offset \u003d offsetof(ss_avs_event_t, opaque_data);\n+\tssi.rx\t\t\t \u003d ss_avs_event_rx;\n+\tssi.tx\t\t\t \u003d ss_avs_event_tx;\n+\tssi.state\t\t \u003d ss_avs_event_state;\n+\tssi.user_alloc\t\t \u003d sizeof(ss_avs_event_t);\n+\tssi.streamtype\t\t \u003d \u0022avs_event\u0022;\n+\n+\tif (lws_ss_create(context, 0, \u0026ssi, context, \u0026hss_avs_event, NULL, NULL)) {\n+\t\tlwsl_err(\u0022%s: failed to create avs event secure stream\u005cn\u0022,\n+\t\t\t __func__);\n+\t\tfree(wav);\n+\t\twav \u003d NULL;\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+\n+bail:\n+\tclose(fd);\n+\n+\treturn 1;\n+}\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-avs/main-client.c b/minimal-examples/secure-streams/minimal-secure-streams-avs/main-client.c\nnew file mode 100644\nindex 0000000..bb9b894\n--- /dev/null\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-avs/main-client.c\n@@ -0,0 +1,128 @@\n+/*\n+ * lws-minimal-secure-streams-avs\n+ *\n+ * This file is made available under the Creative Commons CC0 1.0\n+ * Universal Public Domain Dedication.\n+ */\n+\n+#include \u003clibwebsockets.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003csignal.h\u003e\n+\n+extern int\n+avs_example_start(struct lws_context *context);\n+\n+int interrupted, bad \u003d 1;\n+static lws_state_notify_link_t nl;\n+\n+static const char *canned_root_token_payload \u003d\n+\t\u0022grant_type\u003drefresh_token\u0022\n+\t\u0022\u0026refresh_token\u003dAtzr|IwEBIJedGXjDqsU_vMxykqOMg\u0022\n+\t\u0022SHfYe3CPcedueWEMWSDMaDnEmiW8RlR1Kns7Cb4B-TOSnqp7ifVsY4BMY2B8tpHfO39XP\u0022\n+\t\u0022zfu9HapGjTR458IyHX44FE71pWJkGZ79uVBpljP4sazJuk8XS3Oe_yLnm_DIO6fU1nU3Y\u0022\n+\t\u00220flYmsOiOAQE_gRk_pdlmEtHnpMA-9rLw3mkY5L89Ty9kUygBsiFaYatouROhbsTn8-jW\u0022\n+\t\u0022k1zZLUDpT6ICtBXSnrCIg0pUbZevPFhTwdXd6eX-u4rq0W-XaDvPWFO7au-iPb4Zk5eZE\u0022\n+\t\u0022iX6sissYrtNmuEXc2uHu7MnQO1hHCaTdIO2CANVumf-PHSD8xseamyh04sLV5JgFzY45S\u0022\n+\t\u0022KvKMajiUZuLkMokOx86rjC2Hdkx5DO7G-dbG1ufBDG-N79pFMSs7Ck5pc283IdLoJkCQc\u0022\n+\t\u0022AGvTX8o8I29QqkcGou-9TKhOJmpX8As94T61ok0UqqEKPJ7RhfQHHYdCtsdwxgvfVr9qI\u0022\n+\t\u0022xL_hDCcTho8opCVX-6QhJHl6SQFlTw13\u0022\n+\t\u0022\u0026client_id\u003d\u0022\n+\t\t\u0022amzn1.application-oa2-client.4823334c434b4190a2b5a42c07938a2d\u0022;\n+\n+static int\n+app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link,\n+\t\t int current, int target)\n+{\n+\tstruct lws_context *context \u003d lws_system_context_from_system_mgr(mgr);\n+\tlws_system_blob_t *ab \u003d lws_system_get_blob(context,\n+\t\t\t\tLWS_SYSBLOB_TYPE_AUTH, 1 /* AUTH_IDX_ROOT */);\n+\tsize_t size;\n+\n+\t/*\n+\t * For the things we care about, let's notice if we are trying to get\n+\t * past them when we haven't solved them yet, and make the system\n+\t * state wait while we trigger the dependent action.\n+\t */\n+\tswitch (target) {\n+\tcase LWS_SYSTATE_REGISTERED:\n+\t\tsize \u003d lws_system_blob_get_size(ab);\n+\t\tif (size)\n+\t\t\tbreak;\n+\n+\t\t/* let's register our canned root token so auth can use it */\n+\t\tlws_system_blob_direct_set(ab,\n+\t\t\t\t(const uint8_t *)canned_root_token_payload,\n+\t\t\t\tstrlen(canned_root_token_payload));\n+\t\tbreak;\n+\tcase LWS_SYSTATE_OPERATIONAL:\n+\t\tif (current \u003d\u003d LWS_SYSTATE_OPERATIONAL)\n+\t\t\tavs_example_start(context);\n+\t\tbreak;\n+\tcase LWS_SYSTATE_POLICY_INVALID:\n+\t\t/*\n+\t\t * This is a NOP since we used direct set... but in a real\n+\t\t * system this could easily change to be done on the heap, then\n+\t\t * this would be important\n+\t\t */\n+\t\tlws_system_blob_destroy(lws_system_get_blob(context,\n+\t\t\t\t\tLWS_SYSBLOB_TYPE_AUTH,\n+\t\t\t\t\t1 /* AUTH_IDX_ROOT */));\n+\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void\n+sigint_handler(int sig)\n+{\n+\tinterrupted \u003d 1;\n+}\n+\n+static lws_state_notify_link_t * const app_notifier_list[] \u003d {\n+\t\u0026nl, NULL\n+};\n+\n+int main(int argc, const char **argv)\n+{\n+\tstruct lws_context_creation_info info;\n+\tstruct lws_context *context;\n+\tint n \u003d 0;\n+\n+\tsignal(SIGINT, sigint_handler);\n+\tmemset(\u0026info, 0, sizeof info); /* otherwise uninitialized garbage */\n+\tlws_cmdline_option_handle_builtin(argc, argv, \u0026info);\n+\n+\tlwsl_user(\u0022LWS secure streams - AVS test client [-d\u003cverb\u003e]\u005cn\u0022);\n+\n+\tinfo.options \u003d LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;\n+\tinfo.fd_limit_per_thread \u003d 1 + 6 + 1;\n+\tinfo.protocols \u003d lws_sspc_protocols;\n+\tinfo.port \u003d CONTEXT_PORT_NO_LISTEN;\n+\n+#if defined(LWS_WITH_DETAILED_LATENCY)\n+\tinfo.detailed_latency_cb \u003d lws_det_lat_plot_cb;\n+\tinfo.detailed_latency_filepath \u003d \u0022/tmp/lws-latency-ssproxy\u0022;\n+#endif\n+\n+\t/* integrate us with lws system state management when context created */\n+\tnl.name \u003d \u0022app\u0022;\n+\tnl.notify_cb \u003d app_system_state_nf;\n+\tinfo.register_notifier_list \u003d app_notifier_list;\n+\n+\tcontext \u003d lws_create_context(\u0026info);\n+\tif (!context) {\n+\t\tlwsl_err(\u0022lws init failed\u005cn\u0022);\n+\t\treturn 1;\n+\t}\n+\n+\t/* the event loop */\n+\n+\twhile (n \u003e\u003d 0 \u0026\u0026 !interrupted)\n+\t\tn \u003d lws_service(context, 0);\n+\n+\tlws_context_destroy(context);\n+\tlwsl_user(\u0022Completed: %s\u005cn\u0022, bad ? \u0022failed\u0022 : \u0022OK\u0022);\n+\n+\treturn bad;\n+}\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-avs/main.c b/minimal-examples/secure-streams/minimal-secure-streams-avs/main.c\nnew file mode 100644\nindex 0000000..fc15432\n--- /dev/null\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-avs/main.c\n@@ -0,0 +1,357 @@\n+/*\n+ * lws-minimal-secure-streams-avs\n+ *\n+ * Written in 2019-2020 by Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * This file is made available under the Creative Commons CC0 1.0\n+ * Universal Public Domain Dedication.\n+ */\n+\n+#include \u003clibwebsockets.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003csignal.h\u003e\n+\n+extern int\n+avs_example_start(struct lws_context *context);\n+\n+int interrupted, bad \u003d 1;\n+static lws_state_notify_link_t nl;\n+static const char * const default_ss_policy \u003d\n+\t\u0022{\u0022\n+\t \u0022\u005c\u0022release\u005c\u0022:\u0022\t\t\t\u0022\u005c\u002201234567\u005c\u0022,\u0022\n+\t \u0022\u005c\u0022product\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022myproduct\u005c\u0022,\u0022\n+\t \u0022\u005c\u0022schema-version\u005c\u0022:\u0022\t\t\t\u00221,\u0022\n+//\t \u0022\u005c\u0022via-socks5\u005c\u0022:\u0022 \u0022\u005c\u0022127.0.0.1:1080\u005c\u0022,\u0022\n+\t \u0022\u005c\u0022retry\u005c\u0022: [\u0022\t/* named backoff / retry strategies */\n+\t\t\u0022{\u005c\u0022default\u005c\u0022: {\u0022\n+\t\t\t\u0022\u005c\u0022backoff\u005c\u0022: [\u0022\t \u00221000,\u0022\n+\t\t\t\t\t\t \u00222000,\u0022\n+\t\t\t\t\t\t \u00223000,\u0022\n+\t\t\t\t\t\t \u00225000,\u0022\n+\t\t\t\t\t\t\u002210000\u0022\n+\t\t\t\t\u0022],\u0022\n+\t\t\t\u0022\u005c\u0022conceal\u005c\u0022:\u0022\t\t\u00225,\u0022\n+\t\t\t\u0022\u005c\u0022jitterpc\u005c\u0022:\u0022\t\t\u002220,\u0022\n+\t\t\t\u0022\u005c\u0022svalidping\u005c\u0022:\u0022\t\u002260,\u0022\n+\t\t\t\u0022\u005c\u0022svalidhup\u005c\u0022:\u0022\t\u002264\u0022\n+\t\t\u0022}}\u0022\n+\t \u0022],\u0022\n+\t \u0022\u005c\u0022certs\u005c\u0022: [\u0022 /* named individual certificates in BASE64 DER */\n+\t\t\u0022{\u005c\u0022digicert_global_root_g2\u005c\u0022: \u005c\u0022\u0022 /* api.amazon.com 2038-01 */\n+\t\u0022MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh\u0022\n+\t\u0022MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\u0022\n+\t\u0022d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\u0022\n+\t\u0022MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT\u0022\n+\t\u0022MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\u0022\n+\t\u0022b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG\u0022\n+\t\u00229w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI\u0022\n+\t\u00222/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx\u0022\n+\t\u00221x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ\u0022\n+\t\u0022q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz\u0022\n+\t\u0022tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ\u0022\n+\t\u0022vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP\u0022\n+\t\u0022BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV\u0022\n+\t\u00225uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY\u0022\n+\t\u00221Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4\u0022\n+\t\u0022NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG\u0022\n+\t\u0022Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91\u0022\n+\t\u00228rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe\u0022\n+\t\u0022pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl\u0022\n+\t\u0022MrY\u003d\u0022\n+\t\t\u0022\u005c\u0022},\u0022\n+\t\t\u0022{\u005c\u0022digicert_global_ca_g2\u005c\u0022: \u005c\u0022\u0022 /* api.amazon.com 2028-08 */\n+\t\u0022MIIEizCCA3OgAwIBAgIQDI7gyQ1qiRWIBAYe4kH5rzANBgkqhkiG9w0BAQsFADBh\u0022\n+\t\u0022MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\u0022\n+\t\u0022d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\u0022\n+\t\u0022MjAeFw0xMzA4MDExMjAwMDBaFw0yODA4MDExMjAwMDBaMEQxCzAJBgNVBAYTAlVT\u0022\n+\t\u0022MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxHjAcBgNVBAMTFURpZ2lDZXJ0IEdsb2Jh\u0022\n+\t\u0022bCBDQSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANNIfL7zBYZd\u0022\n+\t\u0022W9UvhU5L4IatFaxhz1uvPmoKR/uadpFgC4przc/cV35gmAvkVNlW7SHMArZagV+X\u0022\n+\t\u0022au4CLyMnuG3UsOcGAngLH1ypmTb+u6wbBfpXzYEQQGfWMItYNdSWYb7QjHqXnxr5\u0022\n+\t\u0022IuYUL6nG6AEfq/gmD6yOTSwyOR2Bm40cZbIc22GoiS9g5+vCShjEbyrpEJIJ7RfR\u0022\n+\t\u0022ACvmfe8EiRROM6GyD5eHn7OgzS+8LOy4g2gxPR/VSpAQGQuBldYpdlH5NnbQtwl6\u0022\n+\t\u0022OErXb4y/E3w57bqukPyV93t4CTZedJMeJfD/1K2uaGvG/w/VNfFVbkhJ+Pi474j4\u0022\n+\t\u00228V4Rd6rfArMCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0P\u0022\n+\t\u0022AQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYYaHR0cDovL29j\u0022\n+\t\u0022c3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6Ly9jcmw0LmRp\u0022\n+\t\u0022Z2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5jcmwwN6A1oDOGMWh0dHA6\u0022\n+\t\u0022Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5jcmwwPQYD\u0022\n+\t\u0022VR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2lj\u0022\n+\t\u0022ZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFCRuKy3QapJRUSVpAaqaR6aJ50AgMB8GA1Ud\u0022\n+\t\u0022IwQYMBaAFE4iVCAYlebjbuYP+vq5Eu0GF485MA0GCSqGSIb3DQEBCwUAA4IBAQAL\u0022\n+\t\u0022OYSR+ZfrqoGvhOlaOJL84mxZvzbIRacxAxHhBsCsMsdaVSnaT0AC9aHesO3ewPj2\u0022\n+\t\u0022dZ12uYf+QYB6z13jAMZbAuabeGLJ3LhimnftiQjXS8X9Q9ViIyfEBFltcT8jW+rZ\u0022\n+\t\u00228uckJ2/0lYDblizkVIvP6hnZf1WZUXoOLRg9eFhSvGNoVwvdRLNXSmDmyHBwW4co\u0022\n+\t\u0022atc7TlJFGa8kBpJIERqLrqwYElesA8u49L3KJg6nwd3jM+/AVTANlVlOnAM2BvjA\u0022\n+\t\u0022jxSZnE0qnsHhfTuvcqdFuhOWKU4Z0BqYBvQ3lBetoxi6PrABDJXWKTUgNX31EGDk\u0022\n+\t\u002292hiHuwZ4STyhxGs6QiA\u0022\n+\t\t\u0022\u005c\u0022},\u0022\n+\t\t\u0022{\u005c\u0022starfield_services_root_ca\u005c\u0022: \u005c\u0022\u0022\n+\t\u0022MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx\u0022\n+\t\u0022EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT\u0022\n+\t\u0022HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs\u0022\n+\t\u0022ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5\u0022\n+\t\u0022MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD\u0022\n+\t\u0022VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy\u0022\n+\t\u0022ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy\u0022\n+\t\u0022dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI\u0022\n+\t\u0022hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p\u0022\n+\t\u0022OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2\u0022\n+\t\u00228xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K\u0022\n+\t\u0022Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe\u0022\n+\t\u0022hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk\u0022\n+\t\u00226mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw\u0022\n+\t\u0022DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q\u0022\n+\t\u0022AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI\u0022\n+\t\u0022bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB\u0022\n+\t\u0022ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z\u0022\n+\t\u0022qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd\u0022\n+\t\u0022iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn\u0022\n+\t\u00220q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN\u0022\n+\t\u0022sSi6\u0022\n+\t\t\u0022\u005c\u0022},\u0022\n+\t\t\u0022{\u005c\u0022starfield_class_2_ca\u005c\u0022: \u005c\u0022\u0022\n+\t\u0022MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl\u0022\n+\t\u0022MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp\u0022\n+\t\u0022U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw\u0022\n+\t\u0022NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE\u0022\n+\t\u0022ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp\u0022\n+\t\u0022ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3\u0022\n+\t\u0022DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf\u0022\n+\t\u00228MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN\u0022\n+\t\u0022+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0\u0022\n+\t\u0022X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa\u0022\n+\t\u0022K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA\u0022\n+\t\u00221W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G\u0022\n+\t\u0022A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR\u0022\n+\t\u0022zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0\u0022\n+\t\u0022YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD\u0022\n+\t\u0022bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w\u0022\n+\t\u0022DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3\u0022\n+\t\u0022L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D\u0022\n+\t\u0022eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl\u0022\n+\t\u0022xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp\u0022\n+\t\u0022VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY\u0022\n+\t\u0022WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q\u003d\u0022\n+\t\t\u0022\u005c\u0022}\u0022\n+\t \u0022],\u0022\n+\t \u0022\u005c\u0022trust_stores\u005c\u0022: [\u0022 /* named cert chains */\n+\t\t\u0022{\u0022 /* chain for alexa.na.gateway.devices.a2z.com */\n+\t\t\t\u0022\u005c\u0022name\u005c\u0022: \u005c\u0022avs_via_starfield\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022stack\u005c\u0022: [\u0022\n+\t\t\t\t\u0022\u005c\u0022starfield_class_2_ca\u005c\u0022,\u0022\n+\t\t\t\t\u0022\u005c\u0022starfield_services_root_ca\u005c\u0022\u0022\n+\t\t\t\u0022]\u0022\n+\t\t\u0022},\u0022\n+\t\t\u0022{\u0022 /* chain for api.amazon.com */\n+\t\t\t\u0022\u005c\u0022name\u005c\u0022: \u005c\u0022api_amazon_com\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022stack\u005c\u0022: [\u0022\n+\t\t\t\t\u0022\u005c\u0022digicert_global_ca_g2\u005c\u0022,\u0022\n+\t\t\t\t\u0022\u005c\u0022digicert_global_root_g2\u005c\u0022\u0022\n+\t\t\t\u0022]\u0022\n+\t\t\u0022}\u0022\n+\t \u0022],\u0022\n+\t \u0022\u005c\u0022s\u005c\u0022: [\u0022 /* the supported stream types */\n+\t\t\u0022{\u005c\u0022api_amazon_com_auth\u005c\u0022: {\u0022\n+\t\t\t\u0022\u005c\u0022endpoint\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022api.amazon.com\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022port\u005c\u0022:\u0022\t\t\t\u0022443,\u0022\n+\t\t\t\u0022\u005c\u0022protocol\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022h1\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_method\u005c\u0022:\u0022\t\t\u0022\u005c\u0022POST\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_url\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022auth/o2/token\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022plugins\u005c\u0022:\u0022\t\t\t\u0022[],\u0022\n+\t\t\t\u0022\u005c\u0022opportunistic\u005c\u0022:\u0022\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022tls\u005c\u0022:\u0022\t\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022h2q_oflow_txcr\u005c\u0022:\u0022\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022http_www_form_urlencoded\u005c\u0022:\u0022\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022http_no_content_length\u005c\u0022:\u0022\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022retry\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022default\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022tls_trust_store\u005c\u0022:\u0022\t\t\u0022\u005c\u0022api_amazon_com\u005c\u0022\u0022\n+\t\t\u0022}},\u0022\n+\t\t\u0022{\u005c\u0022avs_event\u005c\u0022: {\u0022\n+\t\t\t\u0022\u005c\u0022endpoint\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022alexa.na.gateway.devices.a2z.com\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022port\u005c\u0022:\u0022\t\t\t\u0022443,\u0022\n+\t\t\t\u0022\u005c\u0022protocol\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022h2\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_method\u005c\u0022:\u0022\t\t\u0022\u005c\u0022GET\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_url\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022v20160207/directives\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022h2q_oflow_txcr\u005c\u0022:\u0022\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022http_auth_header\u005c\u0022:\u0022\t\t\u0022\u005c\u0022authorization:\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_auth_preamble\u005c\u0022:\u0022\t\u0022\u005c\u0022Bearer \u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022nailed_up\u005c\u0022:\u0022\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022long_poll\u005c\u0022:\u0022\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022retry\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022default\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022plugins\u005c\u0022:\u0022\t\t\t\u0022[],\u0022\n+\t\t\t\u0022\u005c\u0022tls\u005c\u0022:\u0022\t\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022tls_trust_store\u005c\u0022:\u0022\t\t\u0022\u005c\u0022avs_via_starfield\u005c\u0022\u0022\n+\t\t\u0022}},\u0022\n+\t\t\u0022{\u005c\u0022avs_metadata\u005c\u0022: {\u0022\n+\t\t\t\u0022\u005c\u0022endpoint\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022alexa.na.gateway.devices.a2z.com\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022port\u005c\u0022:\u0022\t\t\t\u0022443,\u0022\n+\t\t\t\u0022\u005c\u0022protocol\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022h2\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_method\u005c\u0022:\u0022\t\t\u0022\u005c\u0022POST\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_url\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022v20160207/events\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_no_content_length\u005c\u0022:\u0022\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022h2q_oflow_txcr\u005c\u0022:\u0022\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022http_auth_header\u005c\u0022:\u0022\t\t\u0022\u005c\u0022authorization:\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_auth_preamble\u005c\u0022:\u0022\t\u0022\u005c\u0022Bearer \u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_multipart_name\u005c\u0022:\u0022\t\u0022\u005c\u0022metadata\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_mime_content_type\u005c\u0022:\u0022\t\u0022\u005c\u0022application/json; charset\u003dUTF-8\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022rideshare\u005c\u0022:\u0022\t\t\u0022\u005c\u0022avs_audio\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022retry\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022default\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022plugins\u005c\u0022:\u0022\t\t\t\u0022[],\u0022\n+\t\t\t\u0022\u005c\u0022tls\u005c\u0022:\u0022\t\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022tls_trust_store\u005c\u0022:\u0022\t\t\u0022\u005c\u0022avs_via_starfield\u005c\u0022\u0022\n+\t\t\u0022}},\u0022\n+\t\t\u0022{\u005c\u0022avs_audio\u005c\u0022: {\u0022\n+\t\t\t\u0022\u005c\u0022endpoint\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022alexa.na.gateway.devices.a2z.com\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022port\u005c\u0022:\u0022\t\t\t\u0022443,\u0022\n+\t\t\t\u0022\u005c\u0022protocol\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022h2\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_method\u005c\u0022:\u0022\t\t\u0022\u005c\u0022POST\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_url\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022v20160207/events\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_no_content_length\u005c\u0022:\u0022\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022plugins\u005c\u0022:\u0022\t\t\t\u0022[],\u0022\n+\t\t\t\u0022\u005c\u0022tls\u005c\u0022:\u0022\t\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022h2q_oflow_txcr\u005c\u0022:\u0022\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022http_auth_header\u005c\u0022:\u0022\t\t\u0022\u005c\u0022authorization:\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_auth_preamble\u005c\u0022:\u0022\t\u0022\u005c\u0022Bearer \u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_multipart_name\u005c\u0022:\u0022\t\u0022\u005c\u0022audio\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_mime_content_type\u005c\u0022:\u0022\t\u0022\u005c\u0022application/octet-stream\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022retry\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022default\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022tls_trust_store\u005c\u0022:\u0022\t\t\u0022\u005c\u0022avs_via_starfield\u005c\u0022\u0022\n+\t\t\u0022}}\u0022\n+\t \u0022]\u0022\n+\t\u0022}\u0022\n+;\n+\n+static const char *canned_root_token_payload \u003d\n+\t\u0022grant_type\u003drefresh_token\u0022\n+\t\u0022\u0026refresh_token\u003dAtzr|IwEBIJedGXjDqsU_vMxykqOMg\u0022\n+\t\u0022SHfYe3CPcedueWEMWSDMaDnEmiW8RlR1Kns7Cb4B-TOSnqp7ifVsY4BMY2B8tpHfO39XP\u0022\n+\t\u0022zfu9HapGjTR458IyHX44FE71pWJkGZ79uVBpljP4sazJuk8XS3Oe_yLnm_DIO6fU1nU3Y\u0022\n+\t\u00220flYmsOiOAQE_gRk_pdlmEtHnpMA-9rLw3mkY5L89Ty9kUygBsiFaYatouROhbsTn8-jW\u0022\n+\t\u0022k1zZLUDpT6ICtBXSnrCIg0pUbZevPFhTwdXd6eX-u4rq0W-XaDvPWFO7au-iPb4Zk5eZE\u0022\n+\t\u0022iX6sissYrtNmuEXc2uHu7MnQO1hHCaTdIO2CANVumf-PHSD8xseamyh04sLV5JgFzY45S\u0022\n+\t\u0022KvKMajiUZuLkMokOx86rjC2Hdkx5DO7G-dbG1ufBDG-N79pFMSs7Ck5pc283IdLoJkCQc\u0022\n+\t\u0022AGvTX8o8I29QqkcGou-9TKhOJmpX8As94T61ok0UqqEKPJ7RhfQHHYdCtsdwxgvfVr9qI\u0022\n+\t\u0022xL_hDCcTho8opCVX-6QhJHl6SQFlTw13\u0022\n+\t\u0022\u0026client_id\u003d\u0022\n+\t\t\u0022amzn1.application-oa2-client.4823334c434b4190a2b5a42c07938a2d\u0022;\n+\n+static int\n+app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link,\n+\t\t int current, int target)\n+{\n+\tstruct lws_context *context \u003d lws_system_context_from_system_mgr(mgr);\n+\tlws_system_blob_t *ab \u003d lws_system_get_blob(context,\n+\t\t\t\tLWS_SYSBLOB_TYPE_AUTH, 1 /* AUTH_IDX_ROOT */);\n+\tsize_t size;\n+\n+\t/*\n+\t * For the things we care about, let's notice if we are trying to get\n+\t * past them when we haven't solved them yet, and make the system\n+\t * state wait while we trigger the dependent action.\n+\t */\n+\tswitch (target) {\n+\tcase LWS_SYSTATE_REGISTERED:\n+\t\tsize \u003d lws_system_blob_get_size(ab);\n+\t\tif (size)\n+\t\t\tbreak;\n+\n+\t\t/* let's register our canned root token so auth can use it */\n+\t\tlws_system_blob_direct_set(ab,\n+\t\t\t\t(const uint8_t *)canned_root_token_payload,\n+\t\t\t\tstrlen(canned_root_token_payload));\n+\t\tbreak;\n+\tcase LWS_SYSTATE_OPERATIONAL:\n+\t\tif (current \u003d\u003d LWS_SYSTATE_OPERATIONAL)\n+\t\t\tavs_example_start(context);\n+\t\tbreak;\n+\tcase LWS_SYSTATE_POLICY_INVALID:\n+\t\t/*\n+\t\t * This is a NOP since we used direct set... but in a real\n+\t\t * system this could easily change to be done on the heap, then\n+\t\t * this would be important\n+\t\t */\n+\t\tlws_system_blob_destroy(lws_system_get_blob(context,\n+\t\t\t\t\tLWS_SYSBLOB_TYPE_AUTH,\n+\t\t\t\t\t1 /* AUTH_IDX_ROOT */));\n+\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void\n+sigint_handler(int sig)\n+{\n+\tinterrupted \u003d 1;\n+}\n+\n+static lws_state_notify_link_t * const app_notifier_list[] \u003d {\n+\t\u0026nl, NULL\n+};\n+\n+int main(int argc, const char **argv)\n+{\n+\tstruct lws_context_creation_info info;\n+\tstruct lws_context *context;\n+\tint n \u003d 0;\n+\n+\tsignal(SIGINT, sigint_handler);\n+\tmemset(\u0026info, 0, sizeof info); /* otherwise uninitialized garbage */\n+\tlws_cmdline_option_handle_builtin(argc, argv, \u0026info);\n+\n+\tlwsl_user(\u0022LWS secure streams - AVS test [-d\u003cverb\u003e]\u005cn\u0022);\n+\n+\tinfo.options \u003d LWS_SERVER_OPTION_EXPLICIT_VHOSTS |\n+\t\t LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;\n+\tinfo.fd_limit_per_thread \u003d 1 + 6 + 1;\n+\tinfo.pss_policies_json \u003d default_ss_policy;\n+\tinfo.port \u003d CONTEXT_PORT_NO_LISTEN;\n+\n+#if defined(LWS_SS_USE_SSPC)\n+\t{\n+\t\tconst char *p;\n+\n+\t\t/* connect to ssproxy via UDS by default, else via\n+\t\t * tcp connection to this port */\n+\t\tif ((p \u003d lws_cmdline_option(argc, argv, \u0022-p\u0022)))\n+\t\t\tinfo.ss_proxy_port \u003d atoi(p);\n+\n+\t\t/* UDS \u0022proxy.ss.lws\u0022 in abstract namespace, else this socket\n+\t\t * path; when -p given this can specify the network interface\n+\t\t * to bind to */\n+\t\tif ((p \u003d lws_cmdline_option(argc, argv, \u0022-i\u0022)))\n+\t\t\tinfo.ss_proxy_bind \u003d p;\n+\n+\t\t/* if -p given, -a specifies the proxy address to connect to */\n+\t\tif ((p \u003d lws_cmdline_option(argc, argv, \u0022-a\u0022)))\n+\t\t\tinfo.ss_proxy_address \u003d p;\n+\t}\n+#endif\n+\n+#if defined(LWS_WITH_DETAILED_LATENCY)\n+\tinfo.detailed_latency_cb \u003d lws_det_lat_plot_cb;\n+\tinfo.detailed_latency_filepath \u003d \u0022/tmp/lws-latency-ssproxy\u0022;\n+#endif\n+\n+\t/* integrate us with lws system state management when context created */\n+\tnl.name \u003d \u0022app\u0022;\n+\tnl.notify_cb \u003d app_system_state_nf;\n+\tinfo.register_notifier_list \u003d app_notifier_list;\n+\n+\tcontext \u003d lws_create_context(\u0026info);\n+\tif (!context) {\n+\t\tlwsl_err(\u0022lws init failed\u005cn\u0022);\n+\t\treturn 1;\n+\t}\n+\n+\t/* the event loop */\n+\n+\twhile (n \u003e\u003d 0 \u0026\u0026 !interrupted)\n+\t\tn \u003d lws_service(context, 0);\n+\n+\tlws_context_destroy(context);\n+\tlwsl_user(\u0022Completed: %s\u005cn\u0022, bad ? \u0022failed\u0022 : \u0022OK\u0022);\n+\n+\treturn bad;\n+}\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-avs/year.wav b/minimal-examples/secure-streams/minimal-secure-streams-avs/year.wav\nnew file mode 100644\nindex 0000000..333b057\nBinary files /dev/null and b/minimal-examples/secure-streams/minimal-secure-streams-avs/year.wav differ\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-client-tx/CMakeLists.txt b/minimal-examples/secure-streams/minimal-secure-streams-client-tx/CMakeLists.txt\nnew file mode 100644\nindex 0000000..38dd300\n--- /dev/null\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-client-tx/CMakeLists.txt\n@@ -0,0 +1,81 @@\n+cmake_minimum_required(VERSION 2.8)\n+include(CheckCSourceCompiles)\n+\n+set(SAMP lws-minimal-secure-streams-client-tx)\n+set(SRCS minimal-secure-streams-client-tx.c)\n+\n+# If we are being built as part of lws, confirm current build config supports\n+# reqconfig, else skip building ourselves.\n+#\n+# If we are being built externally, confirm installed lws was configured to\n+# support reqconfig, else error out with a helpful message about the problem.\n+#\n+MACRO(require_lws_config reqconfig _val result)\n+\n+\tif (DEFINED ${reqconfig})\n+\tif (${reqconfig})\n+\t\tset (rq 1)\n+\telse()\n+\t\tset (rq 0)\n+\tendif()\n+\telse()\n+\t\tset(rq 0)\n+\tendif()\n+\n+\tif (${_val} EQUAL ${rq})\n+\t\tset(SAME 1)\n+\telse()\n+\t\tset(SAME 0)\n+\tendif()\n+\n+\tif (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})\n+\t\tif (${_val})\n+\t\t\tmessage(\u0022${SAMP}: skipping as lws being built without ${reqconfig}\u0022)\n+\t\telse()\n+\t\t\tmessage(\u0022${SAMP}: skipping as lws built with ${reqconfig}\u0022)\n+\t\tendif()\n+\t\tset(${result} 0)\n+\telse()\n+\t\tif (LWS_WITH_MINIMAL_EXAMPLES)\n+\t\t\tset(MET ${SAME})\n+\t\telse()\n+\t\t\tCHECK_C_SOURCE_COMPILES(\u0022#include \u003clibwebsockets.h\u003e\u005cnint main(void) {\u005cn#if defined(${reqconfig})\u005cn return 0;\u005cn#else\u005cn fail;\u005cn#endif\u005cn return 0;\u005cn}\u005cn\u0022 HAS_${reqconfig})\n+\t\t\tif (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})\n+\t\t\t\tset(HAS_${reqconfig} 0)\n+\t\t\telse()\n+\t\t\t\tset(HAS_${reqconfig} 1)\n+\t\t\tendif()\n+\t\t\tif ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))\n+\t\t\t\tset(MET 1)\n+\t\t\telse()\n+\t\t\t\tset(MET 0)\n+\t\t\tendif()\n+\t\tendif()\n+\t\tif (NOT MET)\n+\t\t\tif (${_val})\n+\t\t\t\tmessage(FATAL_ERROR \u0022This project requires lws must have been configured with ${reqconfig}\u0022)\n+\t\t\telse()\n+\t\t\t\tmessage(FATAL_ERROR \u0022Lws configuration of ${reqconfig} is incompatible with this project\u0022)\n+\t\t\tendif()\n+\t\tendif()\n+\tendif()\n+ENDMACRO()\n+\n+\n+set(requirements 1)\n+require_lws_config(LWS_ROLE_H1 1 requirements)\n+require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)\n+require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements)\n+require_lws_config(LWS_WITH_SECURE_STREAMS_PROXY_API 1 requirements)\n+\n+\n+if (requirements)\n+\tadd_executable(${SAMP} ${SRCS})\n+\n+\tif (websockets_shared)\n+\t\ttarget_link_libraries(${SAMP} websockets_shared)\n+\t\tadd_dependencies(${SAMP} websockets_shared)\n+\telse()\n+\t\ttarget_link_libraries(${SAMP} websockets)\n+\tendif()\n+endif()\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-client-tx/README.md b/minimal-examples/secure-streams/minimal-secure-streams-client-tx/README.md\nnew file mode 100644\nindex 0000000..80a1c69\n--- /dev/null\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-client-tx/README.md\n@@ -0,0 +1,64 @@\n+# lws minimal secure streams\n+\n+The application goes to https://warmcat.com and reads index.html there.\n+\n+It does it using Secure Streams... the main code in minimal-secure-streams.c\n+just sets up the context and opens a secure stream of type \u0022mintest\u0022.\n+\n+The handler for state changes and payloads for \u0022mintest\u0022 is in ss-myss.c\n+\n+The information about how a \u0022mintest\u0022 stream should connect and the\n+protocol it uses is kept separated in policy-database.c\n+\n+## build\n+\n+```\n+ $ cmake . \u0026\u0026 make\n+```\n+\n+## usage\n+\n+Commandline option|Meaning\n+---|---\n+-d \u003cloglevel\u003e|Debug verbosity in decimal, eg, -d15\n+-f| Force connecting to the wrong endpoint to check backoff retry flow\n+-p| Run as proxy server for clients to connect to over unix domain socket\n+\n+```\n+[2019/08/12 07:16:11:0045] USR: LWS minimal secure streams [-d\u003cverbosity\u003e] [-f]\n+[2019/08/12 07:16:12:6102] USR: myss_state: LWSSSCS_CREATING, ord 0x0\n+[2019/08/12 07:16:12:6107] USR: myss_state: LWSSSCS_POLL, ord 0x0\n+[2019/08/12 07:16:12:6117] N: lws_ss_client_connect: connecting h1get warmcat.com /\n+[2019/08/12 07:16:12:6118] USR: myss_state: LWSSSCS_CONNECTING, ord 0x0\n+[2019/08/12 07:16:13:4171] USR: myss_state: LWSSSCS_CONNECTED, ord 0x0\n+[2019/08/12 07:16:13:4222] USR: myss_rx: len 1024, flags: 1\n+[2019/08/12 07:16:13:4243] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4244] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4244] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4245] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4246] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4247] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4252] USR: myss_rx: len 1015, flags: 0\n+[2019/08/12 07:16:13:4264] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4265] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4266] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4267] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4268] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4268] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4269] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4270] USR: myss_rx: len 1015, flags: 0\n+[2019/08/12 07:16:13:4278] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4279] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4280] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4281] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4282] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4283] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4283] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4284] USR: myss_rx: len 1015, flags: 0\n+[2019/08/12 07:16:13:4287] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4288] USR: myss_rx: len 947, flags: 0\n+[2019/08/12 07:16:13:4293] USR: myss_rx: len 0, flags: 2\n+[2019/08/12 07:16:13:4399] USR: myss_state: LWSSSCS_DISCONNECTED, ord 0x0\n+[2019/08/12 07:16:13:4761] USR: myss_state: LWSSSCS_DESTROYING, ord 0x0\n+[2019/08/12 07:16:13:4781] USR: Completed: OK\n+```\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-client-tx/minimal-secure-streams-client-tx.c b/minimal-examples/secure-streams/minimal-secure-streams-client-tx/minimal-secure-streams-client-tx.c\nnew file mode 100644\nindex 0000000..0eee1e9\n--- /dev/null\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-client-tx/minimal-secure-streams-client-tx.c\n@@ -0,0 +1,198 @@\n+/*\n+ * lws-minimal-secure-streams-tx\n+ *\n+ * Written in 2010-2020 by Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * This file is made available under the Creative Commons CC0 1.0\n+ * Universal Public Domain Dedication.\n+ *\n+ *\n+ * This demonstrates tx from secure streams.\n+ *\n+ * It opens a stream and fires small 80-byte payloads on it at 50Hz (20ms)\n+ */\n+\n+#include \u003clibwebsockets.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003csignal.h\u003e\n+\n+#define PKT_SIZE 80\n+#define RATE_US 50000\n+\n+static int interrupted, bad \u003d 1;\n+\n+typedef struct myss {\n+\tstruct lws_sspc_handle \t*ss;\n+\tvoid\t\t\t*opaque_data;\n+\t/* ... application specific state ... */\n+\tlws_sorted_usec_list_t\tsul;\n+\n+\tint\t\t\tcount;\n+\tchar\t\t\tdue;\n+} myss_t;\n+\n+/* secure streams payload interface */\n+\n+static int\n+myss_rx(void *userobj, const uint8_t *buf, size_t len, int flags)\n+{\n+//\tmyss_t *m \u003d (myss_t *)userobj;\n+\n+\t//lwsl_user(\u0022%s: len %d, flags: %d\u005cn\u0022, __func__, (int)len, flags);\n+\t//lwsl_hexdump_info(buf, len);\n+\n+\treturn 0;\n+}\n+\n+static void\n+txcb(struct lws_sorted_usec_list *sul)\n+{\n+\tmyss_t *m \u003d lws_container_of(sul, myss_t, sul);\n+\n+\tif (m-\u003ecount \u003d\u003d 1000) {\n+\t\tinterrupted \u003d 1;\n+\t\treturn;\n+\t}\n+\n+\tm-\u003edue \u003d 1;\n+\tlws_sspc_request_tx(m-\u003ess);\n+\n+\tlws_sul_schedule(lws_sspc_get_context(m-\u003ess), 0, \u0026m-\u003esul, txcb, RATE_US);\n+\n+\n+}\n+\n+static int\n+myss_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len,\n+\tint *flags)\n+{\n+\tmyss_t *m \u003d (myss_t *)userobj;\n+\n+\tif (!m-\u003edue)\n+\t\treturn 0;\n+\n+\tm-\u003edue \u003d 0;\n+\n+\tif (lws_get_random(lws_sspc_get_context(m-\u003ess), buf, PKT_SIZE) !\u003d PKT_SIZE)\n+\t\treturn 1;\n+\n+\t*len \u003d PKT_SIZE;\n+\t*flags \u003d 0;\n+\tif (!m-\u003ecount)\n+\t\t*flags |\u003d LWSSS_FLAG_SOM;\n+\tif (m-\u003ecount \u003d\u003d 999) {\n+\t\t*flags |\u003d LWSSS_FLAG_EOM;\n+\t\tlwsl_user(\u0022%s: sent final packet\u005cn\u0022, __func__);\n+\t\tbad \u003d 0;\n+\t}\n+\n+\tm-\u003ecount++;\n+\n+\tlws_sul_schedule(lws_sspc_get_context(m-\u003ess), 0, \u0026m-\u003esul, txcb, RATE_US);\n+\n+\t// lwsl_user(\u0022%s: sending pkt %d\u005cn\u0022, __func__, m-\u003ecount);\n+\n+\treturn 0;\n+}\n+\n+static int\n+myss_state(void *userobj, void *sh, lws_ss_constate_t state,\n+\t\tlws_ss_tx_ordinal_t ack)\n+{\n+\tmyss_t *m \u003d (myss_t *)userobj;\n+\tstruct lws_context *context \u003d lws_sspc_get_context(m-\u003ess);\n+\n+\tlwsl_user(\u0022%s: %s, ord 0x%x\u005cn\u0022, __func__, lws_ss_state_name(state),\n+\t\t (unsigned int)ack);\n+\n+\tswitch (state) {\n+\tcase LWSSSCS_CREATING:\n+\t\tbreak;\n+\tcase LWSSSCS_CONNECTED:\n+\t\tlws_sul_schedule(context, 0, \u0026m-\u003esul, txcb, RATE_US);\n+\t\tbreak;\n+\tcase LWSSSCS_DISCONNECTED:\n+\t\tlws_sul_schedule(context, 0, \u0026m-\u003esul, txcb,\n+\t\t\t\t LWS_SET_TIMER_USEC_CANCEL);\n+\t\tbreak;\n+\tcase LWSSSCS_ALL_RETRIES_FAILED:\n+\t\t/* if we're out of retries, we want to close the app and FAIL */\n+\t\tinterrupted \u003d 1;\n+\t\tbreak;\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void\n+sigint_handler(int sig)\n+{\n+\tinterrupted \u003d 1;\n+}\n+\n+int main(int argc, const char **argv)\n+{\n+\tint n \u003d 0, logs \u003d LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;\n+\tstruct lws_context_creation_info info;\n+\tstruct lws_context *context;\n+\tlws_ss_info_t ssi;\n+\tconst char *p;\n+\n+\tsignal(SIGINT, sigint_handler);\n+\n+\tif ((p \u003d lws_cmdline_option(argc, argv, \u0022-d\u0022)))\n+\t\tlogs \u003d atoi(p);\n+\n+\tlws_set_log_level(logs, NULL);\n+\tlwsl_user(\u0022LWS secure streams client TX [-d\u003cverb\u003e]\u005cn\u0022);\n+\n+\tmemset(\u0026info, 0, sizeof info); /* otherwise uninitialized garbage */\n+\n+\tinfo.options \u003d //LWS_SERVER_OPTION_EXPLICIT_VHOSTS |\n+\t\t LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;\n+\tinfo.fd_limit_per_thread \u003d 1 + 6 + 1;\n+\tinfo.port \u003d CONTEXT_PORT_NO_LISTEN;\n+\tinfo.protocols \u003d lws_sspc_protocols;\n+#if defined(LWS_WITH_DETAILED_LATENCY)\n+\tinfo.detailed_latency_cb \u003d lws_det_lat_plot_cb;\n+\tinfo.detailed_latency_filepath \u003d \u0022/tmp/lws-latency-ssclient\u0022;\n+#endif\n+\n+\tcontext \u003d lws_create_context(\u0026info);\n+\tif (!context) {\n+\t\tlwsl_err(\u0022lws init failed\u005cn\u0022);\n+\t\treturn 1;\n+\t}\n+\n+\t/*\n+\t * We're requesting a secure stream via proxy... where and how this\n+\t * connects are details managed by the proxy policy\n+\t */\n+\n+\tmemset(\u0026ssi, 0, sizeof ssi);\n+\tssi.handle_offset\t\t\u003d offsetof(myss_t, ss);\n+\tssi.opaque_user_data_offset\t\u003d offsetof(myss_t, opaque_data);\n+\tssi.rx\t\t\t\t\u003d myss_rx;\n+\tssi.tx\t\t\t\t\u003d myss_tx;\n+\tssi.state\t\t\t\u003d myss_state;\n+\tssi.user_alloc\t\t\t\u003d sizeof(myss_t);\n+\tssi.streamtype\t\t\t\u003d \u0022spam\u0022;\n+\n+\tif (lws_sspc_create(context, 0, \u0026ssi, NULL, NULL, NULL, NULL)) {\n+\t\tlwsl_err(\u0022%s: create secure stream failed\u005cn\u0022, __func__);\n+\t\tgoto bail;\n+\t}\n+\n+\t/* the event loop */\n+\n+\twhile (n \u003e\u003d 0 \u0026\u0026 !interrupted)\n+\t\tn \u003d lws_service(context, 0);\n+\n+bail:\n+\tlws_context_destroy(context);\n+\tlwsl_user(\u0022Completed: %s\u005cn\u0022, bad ? \u0022failed\u0022 : \u0022OK\u0022);\n+\n+\treturn bad;\n+}\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-proxy/CMakeLists.txt b/minimal-examples/secure-streams/minimal-secure-streams-proxy/CMakeLists.txt\nnew file mode 100644\nindex 0000000..00c0c3d\n--- /dev/null\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-proxy/CMakeLists.txt\n@@ -0,0 +1,80 @@\n+cmake_minimum_required(VERSION 2.8)\n+include(CheckCSourceCompiles)\n+\n+set(SAMP lws-minimal-secure-streams-proxy)\n+set(SRCS main.c)\n+\n+# If we are being built as part of lws, confirm current build config supports\n+# reqconfig, else skip building ourselves.\n+#\n+# If we are being built externally, confirm installed lws was configured to\n+# support reqconfig, else error out with a helpful message about the problem.\n+#\n+MACRO(require_lws_config reqconfig _val result)\n+\n+\tif (DEFINED ${reqconfig})\n+\tif (${reqconfig})\n+\t\tset (rq 1)\n+\telse()\n+\t\tset (rq 0)\n+\tendif()\n+\telse()\n+\t\tset(rq 0)\n+\tendif()\n+\n+\tif (${_val} EQUAL ${rq})\n+\t\tset(SAME 1)\n+\telse()\n+\t\tset(SAME 0)\n+\tendif()\n+\n+\tif (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})\n+\t\tif (${_val})\n+\t\t\tmessage(\u0022${SAMP}: skipping as lws being built without ${reqconfig}\u0022)\n+\t\telse()\n+\t\t\tmessage(\u0022${SAMP}: skipping as lws built with ${reqconfig}\u0022)\n+\t\tendif()\n+\t\tset(${result} 0)\n+\telse()\n+\t\tif (LWS_WITH_MINIMAL_EXAMPLES)\n+\t\t\tset(MET ${SAME})\n+\t\telse()\n+\t\t\tCHECK_C_SOURCE_COMPILES(\u0022#include \u003clibwebsockets.h\u003e\u005cnint main(void) {\u005cn#if defined(${reqconfig})\u005cn return 0;\u005cn#else\u005cn fail;\u005cn#endif\u005cn return 0;\u005cn}\u005cn\u0022 HAS_${reqconfig})\n+\t\t\tif (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})\n+\t\t\t\tset(HAS_${reqconfig} 0)\n+\t\t\telse()\n+\t\t\t\tset(HAS_${reqconfig} 1)\n+\t\t\tendif()\n+\t\t\tif ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))\n+\t\t\t\tset(MET 1)\n+\t\t\telse()\n+\t\t\t\tset(MET 0)\n+\t\t\tendif()\n+\t\tendif()\n+\t\tif (NOT MET)\n+\t\t\tif (${_val})\n+\t\t\t\tmessage(FATAL_ERROR \u0022This project requires lws must have been configured with ${reqconfig}\u0022)\n+\t\t\telse()\n+\t\t\t\tmessage(FATAL_ERROR \u0022Lws configuration of ${reqconfig} is incompatible with this project\u0022)\n+\t\t\tendif()\n+\t\tendif()\n+\tendif()\n+ENDMACRO()\n+\n+\n+set(requirements 1)\n+require_lws_config(LWS_ROLE_H1 1 requirements)\n+require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)\n+require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements)\n+require_lws_config(LWS_WITH_SECURE_STREAMS_PROXY_API 1 requirements)\n+\n+if (requirements)\n+\tadd_executable(${SAMP} ${SRCS})\n+\n+\tif (websockets_shared)\n+\t\ttarget_link_libraries(${SAMP} websockets_shared)\n+\t\tadd_dependencies(${SAMP} websockets_shared)\n+\telse()\n+\t\ttarget_link_libraries(${SAMP} websockets)\n+\tendif()\n+endif()\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-proxy/README.md b/minimal-examples/secure-streams/minimal-secure-streams-proxy/README.md\nnew file mode 100644\nindex 0000000..ab5cbcb\n--- /dev/null\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-proxy/README.md\n@@ -0,0 +1,33 @@\n+# lws minimal secure streams proxy\n+\n+Operates as a secure streams proxy, by default on a listening unix domain socket\n+\u0022proxy.ss.lws\u0022 in the Linux abstract namespace.\n+\n+Give -p \u003cport\u003e to have it listen on a specific tcp port instead.\n+\n+## build\n+\n+```\n+ $ cmake . \u0026\u0026 make\n+```\n+\n+## usage\n+\n+Commandline option|Meaning\n+---|---\n+-d \u003cloglevel\u003e|Debug verbosity in decimal, eg, -d15\n+-f| Force connecting to the wrong endpoint to check backoff retry flow\n+-p \u003cport\u003e|If not given, proxy listens on a Unix Domain Socket, if given listen on specified tcp port\n+-i \u003ciface\u003e|Optionally specify the UDS path (no -p) or network interface to bind to (if -p also given)\n+\n+```\n+[2020/02/26 15:41:27:5768] U: LWS secure streams Proxy [-d\u003cverb\u003e]\n+[2020/02/26 15:41:27:5770] N: lws_ss_policy_set: 2.064KiB, pad 70%: hardcoded\n+[2020/02/26 15:41:27:5771] N: lws_tls_client_create_vhost_context: using mem client CA cert 1391\n+[2020/02/26 15:41:27:8681] N: lws_ss_policy_set: 4.512KiB, pad 15%: updated\n+[2020/02/26 15:41:27:8682] N: lws_tls_client_create_vhost_context: using mem client CA cert 837\n+[2020/02/26 15:41:27:8683] N: lws_tls_client_create_vhost_context: using mem client CA cert 1043\n+[2020/02/26 15:41:27:8684] N: lws_tls_client_create_vhost_context: using mem client CA cert 1167\n+[2020/02/26 15:41:27:8684] N: lws_tls_client_create_vhost_context: using mem client CA cert 1391\n+[2020/02/26 15:41:28:4226] N: ss_api_amazon_auth_rx: acquired 567-byte api.amazon.com auth token, exp 3600s\n+```\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-proxy/main.c b/minimal-examples/secure-streams/minimal-secure-streams-proxy/main.c\nnew file mode 100644\nindex 0000000..33e0d46\n--- /dev/null\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-proxy/main.c\n@@ -0,0 +1,285 @@\n+/*\n+ * lws-minimal-secure-streams-proxy\n+ *\n+ * Written in 2010-2020 by Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * This file is made available under the Creative Commons CC0 1.0\n+ * Universal Public Domain Dedication.\n+ *\n+ *\n+ * This is the proxy part for examples built to use it to connect to... it has\n+ * the policy and the core SS function, but it doesn't contain any of the user\n+ * code \u0022business logic\u0022... that's in the clients.\n+ *\n+ * The proxy side has the policy and performs the onward connection proxying\n+ * fulfilment. The clients state the streamtype name they want and ask for the\n+ * client to do the connection part.\n+ *\n+ * Rideshare information is being parsed out at the proxy side; the SSS RX part\n+ * also brings with it rideshare names.\n+ *\n+ * Metadata is passed back over SSS from the client in the TX messages for the\n+ * proxy to use per the policy.\n+ */\n+\n+#include \u003clibwebsockets.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003csignal.h\u003e\n+\n+static int interrupted, bad \u003d 1, port \u003d 0 /* unix domain socket */;\n+static const char *ibind \u003d NULL; /* default to unix domain skt \u0022proxy.ss.lws\u0022 */\n+static lws_state_notify_link_t nl;\n+\n+/*\n+ * We just define enough policy so it can fetch the latest one securely\n+ */\n+\n+static const char * const default_ss_policy \u003d\n+\t\u0022{\u0022\n+\t \u0022\u005c\u0022release\u005c\u0022:\u0022\t\t\t\u0022\u005c\u002201234567\u005c\u0022,\u0022\n+\t \u0022\u005c\u0022product\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022myproduct\u005c\u0022,\u0022\n+\t \u0022\u005c\u0022schema-version\u005c\u0022:\u0022\t\t\t\u00221,\u0022\n+\t \u0022\u005c\u0022retry\u005c\u0022: [\u0022\t/* named backoff / retry strategies */\n+\t\t\u0022{\u005c\u0022default\u005c\u0022: {\u0022\n+\t\t\t\u0022\u005c\u0022backoff\u005c\u0022: [\u0022\t \u00221000,\u0022\n+\t\t\t\t\t\t \u00222000,\u0022\n+\t\t\t\t\t\t \u00223000,\u0022\n+\t\t\t\t\t\t \u00225000,\u0022\n+\t\t\t\t\t\t\u002210000\u0022\n+\t\t\t\t\u0022],\u0022\n+\t\t\t\u0022\u005c\u0022conceal\u005c\u0022:\u0022\t\t\u00225,\u0022\n+\t\t\t\u0022\u005c\u0022jitterpc\u005c\u0022:\u0022\t\t\u002220,\u0022\n+\t\t\t\u0022\u005c\u0022svalidping\u005c\u0022:\u0022\t\u002230,\u0022\n+\t\t\t\u0022\u005c\u0022svalidhup\u005c\u0022:\u0022\t\u002235\u0022\n+\t\t\u0022}}\u0022\n+\t \u0022],\u0022\n+\t \u0022\u005c\u0022certs\u005c\u0022: [\u0022 /* named individual certificates in BASE64 DER */\n+\t\t/*\n+\t\t * Let's Encrypt certs for warmcat.com / libwebsockets.org\n+\t\t *\n+\t\t * We fetch the real policy from there using SS and switch to\n+\t\t * using that.\n+\t\t */\n+\t\t\u0022{\u005c\u0022isrg_root_x1\u005c\u0022: \u005c\u0022\u0022 /* ISRG ROOT X1 */\n+\t\u0022MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\u0022\n+\t\u0022TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\u0022\n+\t\u0022cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\u0022\n+\t\u0022WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\u0022\n+\t\u0022ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\u0022\n+\t\u0022MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\u0022\n+\t\u0022h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\u0022\n+\t\u00220TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\u0022\n+\t\u0022A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\u0022\n+\t\u0022T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\u0022\n+\t\u0022B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\u0022\n+\t\u0022B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\u0022\n+\t\u0022KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\u0022\n+\t\u0022OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\u0022\n+\t\u0022jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\u0022\n+\t\u0022qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\u0022\n+\t\u0022rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\u0022\n+\t\u0022HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\u0022\n+\t\u0022hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\u0022\n+\t\u0022ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\u0022\n+\t\u00223BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\u0022\n+\t\u0022NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\u0022\n+\t\u0022ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\u0022\n+\t\u0022TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\u0022\n+\t\u0022jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\u0022\n+\t\u0022oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\u0022\n+\t\u00224RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\u0022\n+\t\u0022mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\u0022\n+\t\u0022emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc\u003d\u0022\n+\t\t\u0022\u005c\u0022},\u0022\n+\t\t\u0022{\u005c\u0022LEX3_isrg_root_x1\u005c\u0022: \u005c\u0022\u0022 /* LE X3 signed by ISRG X1 root */\n+\t\u0022MIIFjTCCA3WgAwIBAgIRANOxciY0IzLc9AUoUSrsnGowDQYJKoZIhvcNAQELBQAw\u0022\n+\t\u0022TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\u0022\n+\t\u0022cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTYxMDA2MTU0MzU1\u0022\n+\t\u0022WhcNMjExMDA2MTU0MzU1WjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg\u0022\n+\t\u0022RW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDMwggEi\u0022\n+\t\u0022MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCc0wzwWuUuR7dyXTeDs2hjMOrX\u0022\n+\t\u0022NSYZJeG9vjXxcJIvt7hLQQWrqZ41CFjssSrEaIcLo+N15Obzp2JxunmBYB/XkZqf\u0022\n+\t\u002289B4Z3HIaQ6Vkc/+5pnpYDxIzH7KTXcSJJ1HG1rrueweNwAcnKx7pwXqzkrrvUHl\u0022\n+\t\u0022Npi5y/1tPJZo3yMqQpAMhnRnyH+lmrhSYRQTP2XpgofL2/oOVvaGifOFP5eGr7Dc\u0022\n+\t\u0022Gu9rDZUWfcQroGWymQQ2dYBrrErzG5BJeC+ilk8qICUpBMZ0wNAxzY8xOJUWuqgz\u0022\n+\t\u0022uEPxsR/DMH+ieTETPS02+OP88jNquTkxxa/EjQ0dZBYzqvqEKbbUC8DYfcOTAgMB\u0022\n+\t\u0022AAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBU\u0022\n+\t\u0022BgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIB\u0022\n+\t\u0022FiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBSo\u0022\n+\t\u0022SmpjBH3duubRObemRWXv86jsoTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3Js\u0022\n+\t\u0022LnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEF\u0022\n+\t\u0022BQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsG\u0022\n+\t\u0022AQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYD\u0022\n+\t\u0022VR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIB\u0022\n+\t\u0022ABnPdSA0LTqmRf/Q1eaM2jLonG4bQdEnqOJQ8nCqxOeTRrToEKtwT++36gTSlBGx\u0022\n+\t\u0022A/5dut82jJQ2jxN8RI8L9QFXrWi4xXnA2EqA10yjHiR6H9cj6MFiOnb5In1eWsRM\u0022\n+\t\u0022UM2v3e9tNsCAgBukPHAg1lQh07rvFKm/Bz9BCjaxorALINUfZ9DD64j2igLIxle2\u0022\n+\t\u0022DPxW8dI/F2loHMjXZjqG8RkqZUdoxtID5+90FgsGIfkMpqgRS05f4zPbCEHqCXl1\u0022\n+\t\u0022eO5HyELTgcVlLXXQDgAWnRzut1hFJeczY1tjQQno6f6s+nMydLN26WuU4s3UYvOu\u0022\n+\t\u0022OsUxRlJu7TSRHqDC3lSE5XggVkzdaPkuKGQbGpny+01/47hfXXNB7HntWNZ6N2Vw\u0022\n+\t\u0022p7G6OfY+YQrZwIaQmhrIqJZuigsrbe3W+gdn5ykE9+Ky0VgVUsfxo52mwFYs1JKY\u0022\n+\t\u00222PGDuWx8M6DlS6qQkvHaRUo0FMd8TsSlbF0/v965qGFKhSDeQoMpYnwcmQilRh/0\u0022\n+\t\u0022ayLThlHLN81gSkJjVrPI0Y8xCVPB4twb1PFUd2fPM3sA1tJ83sZ5v8vgFv2yofKR\u0022\n+\t\u0022PB0t6JzUA81mSqM3kxl5e+IZwhYAyO0OTg3/fs8HqGTNKd9BqoUwSRBzp06JMg5b\u0022\n+\t\u0022rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt\u0022\n+\t\t\u0022\u005c\u0022}\u0022\n+\t \u0022],\u0022\n+\t \u0022\u005c\u0022trust_stores\u005c\u0022: [\u0022 /* named cert chains */\n+\t\t\u0022{\u0022\n+\t\t\t\u0022\u005c\u0022name\u005c\u0022: \u005c\u0022le_via_isrg\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022stack\u005c\u0022: [\u0022\n+\t\t\t\t\u0022\u005c\u0022isrg_root_x1\u005c\u0022,\u0022\n+\t\t\t\t\u0022\u005c\u0022LEX3_isrg_root_x1\u005c\u0022\u0022\n+\t\t\t\u0022]\u0022\n+\t\t\u0022}\u0022\n+\t \u0022],\u0022\n+\t \u0022\u005c\u0022s\u005c\u0022: [\u0022\n+\t\t\u0022{\u005c\u0022fetch_policy\u005c\u0022: {\u0022\n+\t\t\t\u0022\u005c\u0022endpoint\u005c\u0022:\u0022\t\t\u0022\u005c\u0022warmcat.com\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022port\u005c\u0022:\u0022\t\t\u0022443,\u0022\n+\t\t\t\u0022\u005c\u0022protocol\u005c\u0022:\u0022\t\t\u0022\u005c\u0022h1\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_method\u005c\u0022:\u0022\t\u0022\u005c\u0022GET\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_url\u005c\u0022:\u0022\t\t\u0022\u005c\u0022policy/minimal-proxy.json\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022tls\u005c\u0022:\u0022\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022opportunistic\u005c\u0022:\u0022\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022retry\u005c\u0022:\u0022\t\t\u0022\u005c\u0022default\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022tls_trust_store\u005c\u0022:\u0022\t\u0022\u005c\u0022le_via_isrg\u005c\u0022\u0022\n+\t\t\u0022}}\u0022\n+\t\u0022}\u0022\n+;\n+\n+static const char *canned_root_token_payload \u003d\n+\t\u0022grant_type\u003drefresh_token\u0022\n+\t\u0022\u0026refresh_token\u003dAtzr|IwEBIJedGXjDqsU_vMxykqOMg\u0022\n+\t\u0022SHfYe3CPcedueWEMWSDMaDnEmiW8RlR1Kns7Cb4B-TOSnqp7ifVsY4BMY2B8tpHfO39XP\u0022\n+\t\u0022zfu9HapGjTR458IyHX44FE71pWJkGZ79uVBpljP4sazJuk8XS3Oe_yLnm_DIO6fU1nU3Y\u0022\n+\t\u00220flYmsOiOAQE_gRk_pdlmEtHnpMA-9rLw3mkY5L89Ty9kUygBsiFaYatouROhbsTn8-jW\u0022\n+\t\u0022k1zZLUDpT6ICtBXSnrCIg0pUbZevPFhTwdXd6eX-u4rq0W-XaDvPWFO7au-iPb4Zk5eZE\u0022\n+\t\u0022iX6sissYrtNmuEXc2uHu7MnQO1hHCaTdIO2CANVumf-PHSD8xseamyh04sLV5JgFzY45S\u0022\n+\t\u0022KvKMajiUZuLkMokOx86rjC2Hdkx5DO7G-dbG1ufBDG-N79pFMSs7Ck5pc283IdLoJkCQc\u0022\n+\t\u0022AGvTX8o8I29QqkcGou-9TKhOJmpX8As94T61ok0UqqEKPJ7RhfQHHYdCtsdwxgvfVr9qI\u0022\n+\t\u0022xL_hDCcTho8opCVX-6QhJHl6SQFlTw13\u0022\n+\t\u0022\u0026client_id\u003d\u0022\n+\t\t\u0022amzn1.application-oa2-client.4823334c434b4190a2b5a42c07938a2d\u0022;\n+\n+static int\n+app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link,\n+\t\t int current, int target)\n+{\n+\tstruct lws_context *context \u003d lws_system_context_from_system_mgr(mgr);\n+\tlws_system_blob_t *ab \u003d lws_system_get_blob(context,\n+\t\t\t\tLWS_SYSBLOB_TYPE_AUTH, 1 /* AUTH_IDX_ROOT */);\n+\tsize_t size;\n+\n+\t/*\n+\t * For the things we care about, let's notice if we are trying to get\n+\t * past them when we haven't solved them yet, and make the system\n+\t * state wait while we trigger the dependent action.\n+\t */\n+\tswitch (target) {\n+\tcase LWS_SYSTATE_REGISTERED:\n+\t\tsize \u003d lws_system_blob_get_size(ab);\n+\t\tif (size)\n+\t\t\tbreak;\n+\n+\t\t/* let's register our canned root token so auth can use it */\n+\t\tlws_system_blob_direct_set(ab,\n+\t\t\t\t(const uint8_t *)canned_root_token_payload,\n+\t\t\t\tstrlen(canned_root_token_payload));\n+\t\tbreak;\n+\tcase LWS_SYSTATE_OPERATIONAL:\n+\t\tif (current \u003d\u003d LWS_SYSTATE_OPERATIONAL)\n+\t\t\t/*\n+\t\t\t * At this point we have DHCP, ntp, system auth token\n+\t\t\t * and we can reasonably create the proxy\n+\t\t\t */\n+\t\t\tif (lws_ss_proxy_create(context, ibind, port)) {\n+\t\t\t\tlwsl_err(\u0022%s: failed to create ss proxy\u005cn\u0022,\n+\t\t\t\t\t\t__func__);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\tbreak;\n+\tcase LWS_SYSTATE_POLICY_INVALID:\n+\t\t/*\n+\t\t * This is a NOP since we used direct set... but in a real\n+\t\t * system this could easily change to be done on the heap, then\n+\t\t * this would be important\n+\t\t */\n+\t\tlws_system_blob_destroy(lws_system_get_blob(context,\n+\t\t\t\t\tLWS_SYSBLOB_TYPE_AUTH,\n+\t\t\t\t\t1 /* AUTH_IDX_ROOT */));\n+\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static lws_state_notify_link_t * const app_notifier_list[] \u003d {\n+\t\u0026nl, NULL\n+};\n+\n+static void\n+sigint_handler(int sig)\n+{\n+\tinterrupted \u003d 1;\n+}\n+\n+int main(int argc, const char **argv)\n+{\n+\tint n \u003d 0, logs \u003d LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;\n+\tstruct lws_context_creation_info info;\n+\tstruct lws_context *context;\n+\tconst char *p;\n+\n+\tsignal(SIGINT, sigint_handler);\n+\n+\tif ((p \u003d lws_cmdline_option(argc, argv, \u0022-d\u0022)))\n+\t\tlogs \u003d atoi(p);\n+\n+\t/* connect to ssproxy via UDS by default, else via tcp with this port */\n+\tif ((p \u003d lws_cmdline_option(argc, argv, \u0022-p\u0022)))\n+\t\tport \u003d atoi(p);\n+\n+\t/* UDS \u0022proxy.ss.lws\u0022 in abstract namespace, else this socket path;\n+\t * when -p given this can specify the network interface to bind to */\n+\tif ((p \u003d lws_cmdline_option(argc, argv, \u0022-i\u0022)))\n+\t\tibind \u003d p;\n+\n+\tlws_set_log_level(logs, NULL);\n+\tlwsl_user(\u0022LWS secure streams Proxy [-d\u003cverb\u003e]\u005cn\u0022);\n+\n+\tmemset(\u0026info, 0, sizeof info); /* otherwise uninitialized garbage */\n+\n+\tinfo.options \u003d LWS_SERVER_OPTION_EXPLICIT_VHOSTS |\n+\t\t LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;\n+\tinfo.fd_limit_per_thread \u003d 1 + 6 + 1;\n+\tinfo.pss_policies_json \u003d default_ss_policy;\n+\tinfo.port \u003d CONTEXT_PORT_NO_LISTEN;\n+#if defined(LWS_WITH_DETAILED_LATENCY)\n+\tinfo.detailed_latency_cb \u003d lws_det_lat_plot_cb;\n+\tinfo.detailed_latency_filepath \u003d \u0022/tmp/lws-latency-ssproxy\u0022;\n+#endif\n+\n+\t/* integrate us with lws system state management when context created */\n+\tnl.name \u003d \u0022app\u0022;\n+\tnl.notify_cb \u003d app_system_state_nf;\n+\tinfo.register_notifier_list \u003d app_notifier_list;\n+\n+\tcontext \u003d lws_create_context(\u0026info);\n+\tif (!context) {\n+\t\tlwsl_err(\u0022lws init failed\u005cn\u0022);\n+\t\treturn 1;\n+\t}\n+\n+\t/* the event loop */\n+\n+\twhile (n \u003e\u003d 0 \u0026\u0026 !interrupted)\n+\t\tn \u003d lws_service(context, 0);\n+\n+\tbad \u003d 0;\n+\n+\tlws_context_destroy(context);\n+\tlwsl_user(\u0022Completed: %s\u005cn\u0022, bad ? \u0022failed\u0022 : \u0022OK\u0022);\n+\n+\treturn bad;\n+}\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-seq/CMakeLists.txt b/minimal-examples/secure-streams/minimal-secure-streams-seq/CMakeLists.txt\nnew file mode 100644\nindex 0000000..b55a3a0\n--- /dev/null\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-seq/CMakeLists.txt\n@@ -0,0 +1,79 @@\n+cmake_minimum_required(VERSION 2.8)\n+include(CheckCSourceCompiles)\n+\n+set(SAMP lws-minimal-secure-streams-seq)\n+set(SRCS minimal-secure-streams.c)\n+\n+# If we are being built as part of lws, confirm current build config supports\n+# reqconfig, else skip building ourselves.\n+#\n+# If we are being built externally, confirm installed lws was configured to\n+# support reqconfig, else error out with a helpful message about the problem.\n+#\n+MACRO(require_lws_config reqconfig _val result)\n+\n+\tif (DEFINED ${reqconfig})\n+\tif (${reqconfig})\n+\t\tset (rq 1)\n+\telse()\n+\t\tset (rq 0)\n+\tendif()\n+\telse()\n+\t\tset(rq 0)\n+\tendif()\n+\n+\tif (${_val} EQUAL ${rq})\n+\t\tset(SAME 1)\n+\telse()\n+\t\tset(SAME 0)\n+\tendif()\n+\n+\tif (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})\n+\t\tif (${_val})\n+\t\t\tmessage(\u0022${SAMP}: skipping as lws being built without ${reqconfig}\u0022)\n+\t\telse()\n+\t\t\tmessage(\u0022${SAMP}: skipping as lws built with ${reqconfig}\u0022)\n+\t\tendif()\n+\t\tset(${result} 0)\n+\telse()\n+\t\tif (LWS_WITH_MINIMAL_EXAMPLES)\n+\t\t\tset(MET ${SAME})\n+\t\telse()\n+\t\t\tCHECK_C_SOURCE_COMPILES(\u0022#include \u003clibwebsockets.h\u003e\u005cnint main(void) {\u005cn#if defined(${reqconfig})\u005cn return 0;\u005cn#else\u005cn fail;\u005cn#endif\u005cn return 0;\u005cn}\u005cn\u0022 HAS_${reqconfig})\n+\t\t\tif (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})\n+\t\t\t\tset(HAS_${reqconfig} 0)\n+\t\t\telse()\n+\t\t\t\tset(HAS_${reqconfig} 1)\n+\t\t\tendif()\n+\t\t\tif ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))\n+\t\t\t\tset(MET 1)\n+\t\t\telse()\n+\t\t\t\tset(MET 0)\n+\t\t\tendif()\n+\t\tendif()\n+\t\tif (NOT MET)\n+\t\t\tif (${_val})\n+\t\t\t\tmessage(FATAL_ERROR \u0022This project requires lws must have been configured with ${reqconfig}\u0022)\n+\t\t\telse()\n+\t\t\t\tmessage(FATAL_ERROR \u0022Lws configuration of ${reqconfig} is incompatible with this project\u0022)\n+\t\t\tendif()\n+\t\tendif()\n+\tendif()\n+ENDMACRO()\n+\n+\n+set(requirements 1)\n+require_lws_config(LWS_ROLE_H1 1 requirements)\n+require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)\n+require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements)\n+\n+if (requirements)\n+\tadd_executable(${SAMP} ${SRCS})\n+\n+\tif (websockets_shared)\n+\t\ttarget_link_libraries(${SAMP} websockets_shared)\n+\t\tadd_dependencies(${SAMP} websockets_shared)\n+\telse()\n+\t\ttarget_link_libraries(${SAMP} websockets)\n+\tendif()\n+endif()\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-seq/README.md b/minimal-examples/secure-streams/minimal-secure-streams-seq/README.md\nnew file mode 100644\nindex 0000000..6af2e25\n--- /dev/null\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-seq/README.md\n@@ -0,0 +1,65 @@\n+# lws minimal sequre streams seq\n+\n+The application goes to https://warmcat.com and reads index.html there.\n+\n+It does it using Secure Streams... the main code in minimal-secure-streams.c\n+just sets up the context and opens a secure stream of type \u0022mintest\u0022.\n+\n+The handler for state changes and payloads for \u0022mintest\u0022 is in ss-myss.c\n+\n+The information about how a \u0022mintest\u0022 stream should connect and the\n+protocol it uses is kept separated in policy-database.c\n+\n+## build\n+\n+```\n+ $ cmake . \u0026\u0026 make\n+```\n+\n+## usage\n+\n+Commandline option|Meaning\n+---|---\n+-d \u003cloglevel\u003e|Debug verbosity in decimal, eg, -d15\n+-f| Force connecting to the wrong endpoint to check backoff retry flow\n+\n+```\n+ $ ./lws-minimal-secure-streams-seq\n+[2018/03/04 14:43:20:8562] USER: LWS minimal http client\n+[2018/03/04 14:43:20:8571] NOTICE: Creating Vhost 'default' port -1, 1 protocols, IPv6 on\n+[2018/03/04 14:43:20:8616] NOTICE: created client ssl context for default\n+[2018/03/04 14:43:20:8617] NOTICE: lws_client_connect_2: 0x1814dc0: address warmcat.com\n+[2018/03/04 14:43:21:1496] NOTICE: lws_client_connect_2: 0x1814dc0: address warmcat.com\n+[2018/03/04 14:43:22:0154] NOTICE: lws_client_interpret_server_handshake: incoming content length 26520\n+[2018/03/04 14:43:22:0154] NOTICE: lws_client_interpret_server_handshake: client connection up\n+[2018/03/04 14:43:22:0169] USER: RECEIVE_CLIENT_HTTP_READ: read 1024\n+[2018/03/04 14:43:22:0169] USER: RECEIVE_CLIENT_HTTP_READ: read 1024\n+[2018/03/04 14:43:22:0169] USER: RECEIVE_CLIENT_HTTP_READ: read 1024\n+[2018/03/04 14:43:22:0169] USER: RECEIVE_CLIENT_HTTP_READ: read 1015\n+[2018/03/04 14:43:22:0174] USER: RECEIVE_CLIENT_HTTP_READ: read 1024\n+[2018/03/04 14:43:22:0174] USER: RECEIVE_CLIENT_HTTP_READ: read 1024\n+[2018/03/04 14:43:22:0174] USER: RECEIVE_CLIENT_HTTP_READ: read 1024\n+[2018/03/04 14:43:22:0174] USER: RECEIVE_CLIENT_HTTP_READ: read 1015\n+[2018/03/04 14:43:22:0179] USER: RECEIVE_CLIENT_HTTP_READ: read 1024\n+[2018/03/04 14:43:22:0179] USER: RECEIVE_CLIENT_HTTP_READ: read 1024\n+[2018/03/04 14:43:22:0179] USER: RECEIVE_CLIENT_HTTP_READ: read 1024\n+[2018/03/04 14:43:22:0179] USER: RECEIVE_CLIENT_HTTP_READ: read 1015\n+[2018/03/04 14:43:22:3010] USER: RECEIVE_CLIENT_HTTP_READ: read 1024\n+[2018/03/04 14:43:22:3010] USER: RECEIVE_CLIENT_HTTP_READ: read 1024\n+[2018/03/04 14:43:22:3010] USER: RECEIVE_CLIENT_HTTP_READ: read 1024\n+[2018/03/04 14:43:22:3010] USER: RECEIVE_CLIENT_HTTP_READ: read 1015\n+[2018/03/04 14:43:22:3015] USER: RECEIVE_CLIENT_HTTP_READ: read 1024\n+[2018/03/04 14:43:22:3015] USER: RECEIVE_CLIENT_HTTP_READ: read 1024\n+[2018/03/04 14:43:22:3015] USER: RECEIVE_CLIENT_HTTP_READ: read 1024\n+[2018/03/04 14:43:22:3015] USER: RECEIVE_CLIENT_HTTP_READ: read 1015\n+[2018/03/04 14:43:22:3020] USER: RECEIVE_CLIENT_HTTP_READ: read 1024\n+[2018/03/04 14:43:22:3020] USER: RECEIVE_CLIENT_HTTP_READ: read 1024\n+[2018/03/04 14:43:22:3020] USER: RECEIVE_CLIENT_HTTP_READ: read 1024\n+[2018/03/04 14:43:22:3020] USER: RECEIVE_CLIENT_HTTP_READ: read 1015\n+[2018/03/04 14:43:22:3022] USER: RECEIVE_CLIENT_HTTP_READ: read 1024\n+[2018/03/04 14:43:22:3022] USER: RECEIVE_CLIENT_HTTP_READ: read 974\n+[2018/03/04 14:43:22:3022] NOTICE: lws_http_client_read: transaction completed says -1\n+[2018/03/04 14:43:23:3042] USER: Completed\n+```\n+\n+\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-seq/minimal-secure-streams.c b/minimal-examples/secure-streams/minimal-secure-streams-seq/minimal-secure-streams.c\nnew file mode 100644\nindex 0000000..f648f02\n--- /dev/null\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-seq/minimal-secure-streams.c\n@@ -0,0 +1,443 @@\n+/*\n+ * lws-minimal-secure-streams-seq\n+ *\n+ * Written in 2010-2020 by Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * This file is made available under the Creative Commons CC0 1.0\n+ * Universal Public Domain Dedication.\n+ *\n+ *\n+ * This demonstrates the a minimal http client using secure streams api.\n+ *\n+ * It visits https://warmcat.com/ and receives the html page there.\n+ *\n+ * This is the \u0022secure streams\u0022 api equivalent of minimal-http-client...\n+ * it shows how to use a sequencer to make it easy to build more complex\n+ * schemes on top of this example.\n+ *\n+ * The layering looks like this\n+ *\n+ * lifetime\n+ *\n+ * ------ app ------ process\n+ * ---- sequencer ---- process\n+ * --- secure stream --- process\n+ * ------- wsi ------- connection\n+ *\n+ * see minimal-secure-streams for a similar example without the sequencer.\n+ */\n+\n+#include \u003clibwebsockets.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003csignal.h\u003e\n+\n+static int interrupted, bad \u003d 1, flag_conn_fail, flag_h1post;\n+static const char * const default_ss_policy \u003d\n+\t\u0022{\u0022\n+\t \u0022\u005c\u0022release\u005c\u0022:\u0022\t\t\t\u0022\u005c\u002201234567\u005c\u0022,\u0022\n+\t \u0022\u005c\u0022product\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022myproduct\u005c\u0022,\u0022\n+\t \u0022\u005c\u0022schema-version\u005c\u0022:\u0022\t\t\t\u00221,\u0022\n+\t \u0022\u005c\u0022retry\u005c\u0022: [\u0022\t/* named backoff / retry strategies */\n+\t\t\u0022{\u005c\u0022default\u005c\u0022: {\u0022\n+\t\t\t\u0022\u005c\u0022backoff\u005c\u0022: [\u0022\t \u00221000,\u0022\n+\t\t\t\t\t\t \u00222000,\u0022\n+\t\t\t\t\t\t \u00223000,\u0022\n+\t\t\t\t\t\t \u00225000,\u0022\n+\t\t\t\t\t\t\u002210000\u0022\n+\t\t\t\t\u0022],\u0022\n+\t\t\t\u0022\u005c\u0022conceal\u005c\u0022:\u0022\t\t\u00225,\u0022\n+\t\t\t\u0022\u005c\u0022jitterpc\u005c\u0022:\u0022\t\t\u002220,\u0022\n+\t\t\t\u0022\u005c\u0022svalidping\u005c\u0022:\u0022\t\u0022300,\u0022\n+\t\t\t\u0022\u005c\u0022svalidhup\u005c\u0022:\u0022\t\u0022310\u0022\n+\t\t\u0022}}\u0022\n+\t \u0022],\u0022\n+\t \u0022\u005c\u0022certs\u005c\u0022: [\u0022 /* named individual certificates in BASE64 DER */\n+\t\t/*\n+\t\t * Need to be in order from root cert... notice sometimes as\n+\t\t * with Let's Encrypt there are multiple possible validation\n+\t\t * paths, all the pieces for one validation path must be\n+\t\t * given, excluding the server cert itself. Let's Encrypt\n+\t\t * intermediate is signed by their ISRG Root CA but also is\n+\t\t * cross-signed by an IdenTrust intermediate that's widely\n+\t\t * deployed in browsers. We use the ISRG path because that\n+\t\t * way we can skip the extra IdenTrust root cert.\n+\t\t */\n+\t\t\u0022{\u005c\u0022isrg_root_x1\u005c\u0022: \u005c\u0022\u0022 /* ISRG ROOT X1 */\n+\t\u0022MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\u0022\n+\t\u0022TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\u0022\n+\t\u0022cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\u0022\n+\t\u0022WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\u0022\n+\t\u0022ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\u0022\n+\t\u0022MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\u0022\n+\t\u0022h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\u0022\n+\t\u00220TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\u0022\n+\t\u0022A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\u0022\n+\t\u0022T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\u0022\n+\t\u0022B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\u0022\n+\t\u0022B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\u0022\n+\t\u0022KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\u0022\n+\t\u0022OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\u0022\n+\t\u0022jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\u0022\n+\t\u0022qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\u0022\n+\t\u0022rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\u0022\n+\t\u0022HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\u0022\n+\t\u0022hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\u0022\n+\t\u0022ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\u0022\n+\t\u00223BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\u0022\n+\t\u0022NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\u0022\n+\t\u0022ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\u0022\n+\t\u0022TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\u0022\n+\t\u0022jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\u0022\n+\t\u0022oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\u0022\n+\t\u00224RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\u0022\n+\t\u0022mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\u0022\n+\t\u0022emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc\u003d\u0022\n+\t\t\u0022\u005c\u0022},\u0022\n+\t\t\u0022{\u005c\u0022LEX3_isrg_root_x1\u005c\u0022: \u005c\u0022\u0022 /* LE X3 signed by ISRG X1 root */\n+\t\u0022MIIFjTCCA3WgAwIBAgIRANOxciY0IzLc9AUoUSrsnGowDQYJKoZIhvcNAQELBQAw\u0022\n+\t\u0022TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\u0022\n+\t\u0022cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTYxMDA2MTU0MzU1\u0022\n+\t\u0022WhcNMjExMDA2MTU0MzU1WjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg\u0022\n+\t\u0022RW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDMwggEi\u0022\n+\t\u0022MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCc0wzwWuUuR7dyXTeDs2hjMOrX\u0022\n+\t\u0022NSYZJeG9vjXxcJIvt7hLQQWrqZ41CFjssSrEaIcLo+N15Obzp2JxunmBYB/XkZqf\u0022\n+\t\u002289B4Z3HIaQ6Vkc/+5pnpYDxIzH7KTXcSJJ1HG1rrueweNwAcnKx7pwXqzkrrvUHl\u0022\n+\t\u0022Npi5y/1tPJZo3yMqQpAMhnRnyH+lmrhSYRQTP2XpgofL2/oOVvaGifOFP5eGr7Dc\u0022\n+\t\u0022Gu9rDZUWfcQroGWymQQ2dYBrrErzG5BJeC+ilk8qICUpBMZ0wNAxzY8xOJUWuqgz\u0022\n+\t\u0022uEPxsR/DMH+ieTETPS02+OP88jNquTkxxa/EjQ0dZBYzqvqEKbbUC8DYfcOTAgMB\u0022\n+\t\u0022AAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBU\u0022\n+\t\u0022BgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIB\u0022\n+\t\u0022FiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBSo\u0022\n+\t\u0022SmpjBH3duubRObemRWXv86jsoTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3Js\u0022\n+\t\u0022LnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEF\u0022\n+\t\u0022BQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsG\u0022\n+\t\u0022AQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYD\u0022\n+\t\u0022VR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIB\u0022\n+\t\u0022ABnPdSA0LTqmRf/Q1eaM2jLonG4bQdEnqOJQ8nCqxOeTRrToEKtwT++36gTSlBGx\u0022\n+\t\u0022A/5dut82jJQ2jxN8RI8L9QFXrWi4xXnA2EqA10yjHiR6H9cj6MFiOnb5In1eWsRM\u0022\n+\t\u0022UM2v3e9tNsCAgBukPHAg1lQh07rvFKm/Bz9BCjaxorALINUfZ9DD64j2igLIxle2\u0022\n+\t\u0022DPxW8dI/F2loHMjXZjqG8RkqZUdoxtID5+90FgsGIfkMpqgRS05f4zPbCEHqCXl1\u0022\n+\t\u0022eO5HyELTgcVlLXXQDgAWnRzut1hFJeczY1tjQQno6f6s+nMydLN26WuU4s3UYvOu\u0022\n+\t\u0022OsUxRlJu7TSRHqDC3lSE5XggVkzdaPkuKGQbGpny+01/47hfXXNB7HntWNZ6N2Vw\u0022\n+\t\u0022p7G6OfY+YQrZwIaQmhrIqJZuigsrbe3W+gdn5ykE9+Ky0VgVUsfxo52mwFYs1JKY\u0022\n+\t\u00222PGDuWx8M6DlS6qQkvHaRUo0FMd8TsSlbF0/v965qGFKhSDeQoMpYnwcmQilRh/0\u0022\n+\t\u0022ayLThlHLN81gSkJjVrPI0Y8xCVPB4twb1PFUd2fPM3sA1tJ83sZ5v8vgFv2yofKR\u0022\n+\t\u0022PB0t6JzUA81mSqM3kxl5e+IZwhYAyO0OTg3/fs8HqGTNKd9BqoUwSRBzp06JMg5b\u0022\n+\t\u0022rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt\u0022\n+\t\t\u0022\u005c\u0022}\u0022\n+\t \u0022],\u0022\n+\t \u0022\u005c\u0022trust_stores\u005c\u0022: [\u0022 /* named cert chains */\n+\t\t\u0022{\u0022\n+\t\t\t\u0022\u005c\u0022name\u005c\u0022: \u005c\u0022le_via_isrg\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022stack\u005c\u0022: [\u0022\n+\t\t\t\t\u0022\u005c\u0022isrg_root_x1\u005c\u0022,\u0022\n+\t\t\t\t\u0022\u005c\u0022LEX3_isrg_root_x1\u005c\u0022\u0022\n+\t\t\t\u0022]\u0022\n+\t\t\u0022}\u0022\n+\t \u0022],\u0022\n+\t \u0022\u005c\u0022s\u005c\u0022: [\u0022 /* the supported stream types */\n+\t\t\u0022{\u005c\u0022mintest\u005c\u0022: {\u0022\n+\t\t\t\u0022\u005c\u0022endpoint\u005c\u0022:\u0022\t\t\u0022\u005c\u0022warmcat.com\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022port\u005c\u0022:\u0022\t\t\u0022443,\u0022\n+\t\t\t\u0022\u005c\u0022protocol\u005c\u0022:\u0022\t\t\u0022\u005c\u0022h1\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_method\u005c\u0022:\u0022\t\u0022\u005c\u0022GET\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_url\u005c\u0022:\u0022\t\t\u0022\u005c\u0022index.html\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022plugins\u005c\u0022:\u0022\t\t\u0022[],\u0022\n+\t\t\t\u0022\u005c\u0022tls\u005c\u0022:\u0022\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022opportunistic\u005c\u0022:\u0022\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022retry\u005c\u0022:\u0022\t\t\u0022\u005c\u0022default\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022tls_trust_store\u005c\u0022:\u0022\t\u0022\u005c\u0022le_via_isrg\u005c\u0022\u0022\n+\t\t\u0022}},\u0022\n+\t\t\u0022{\u005c\u0022mintest-fail\u005c\u0022: {\u0022\n+\t\t\t\u0022\u005c\u0022endpoint\u005c\u0022:\u0022\t\t\u0022\u005c\u0022warmcat.com\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022port\u005c\u0022:\u0022\t\t\u002222,\u0022\n+\t\t\t\u0022\u005c\u0022protocol\u005c\u0022:\u0022\t\t\u0022\u005c\u0022h1\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_method\u005c\u0022:\u0022\t\u0022\u005c\u0022GET\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_url\u005c\u0022:\u0022\t\t\u0022\u005c\u0022index.html\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022plugins\u005c\u0022:\u0022\t\t\u0022[],\u0022\n+\t\t\t\u0022\u005c\u0022tls\u005c\u0022:\u0022\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022opportunistic\u005c\u0022:\u0022\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022retry\u005c\u0022:\u0022\t\t\u0022\u005c\u0022default\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022tls_trust_store\u005c\u0022:\u0022\t\u0022\u005c\u0022le_via_isrg\u005c\u0022\u0022\n+\t\t\u0022}},\u0022\n+\t\t\u0022{\u005c\u0022minpost\u005c\u0022: {\u0022\n+\t\t\t\u0022\u005c\u0022endpoint\u005c\u0022:\u0022\t\t\u0022\u005c\u0022warmcat.com\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022port\u005c\u0022:\u0022\t\t\u0022443,\u0022\n+\t\t\t\u0022\u005c\u0022protocol\u005c\u0022:\u0022\t\t\u0022\u005c\u0022h1\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_method\u005c\u0022:\u0022\t\u0022\u005c\u0022POST\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_url\u005c\u0022:\u0022\t\t\u0022\u005c\u0022testserver/formtest\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022plugins\u005c\u0022:\u0022\t\t\u0022[],\u0022\n+\t\t\t\u0022\u005c\u0022tls\u005c\u0022:\u0022\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022opportunistic\u005c\u0022:\u0022\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022retry\u005c\u0022:\u0022\t\t\u0022\u005c\u0022default\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022tls_trust_store\u005c\u0022:\u0022\t\u0022\u005c\u0022le_via_isrg\u005c\u0022\u0022\n+\t\t\u0022}}\u0022\n+\t \u0022]\u0022\n+\t\u0022}\u0022\n+;\n+\n+typedef struct myss {\n+\tstruct lws_ss_handle \t*ss;\n+\tvoid\t\t\t*opaque_data;\n+\t/* ... application specific state ... */\n+} myss_t;\n+\n+/* secure streams payload interface */\n+\n+static int\n+myss_rx(void *userobj, const uint8_t *buf, size_t len, int flags)\n+{\n+//\tmyss_t *m \u003d (myss_t *)userobj;\n+\n+\tlwsl_user(\u0022%s: len %d, flags: %d\u005cn\u0022, __func__, (int)len, flags);\n+\tlwsl_hexdump_info(buf, len);\n+\n+\t/*\n+\t * If we received the whole message, we let the sequencer know it\n+\t * was a success\n+\t */\n+\tif (flags \u0026 LWSSS_FLAG_EOM) {\n+\t\tbad \u003d 0;\n+\t\tinterrupted \u003d 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int\n+myss_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len,\n+\tint *flags)\n+{\n+\t// myss_t *m \u003d (myss_t *)userobj;\n+\n+\t/* in this example, we don't send any payload */\n+\n+\treturn 0;\n+}\n+\n+static int\n+myss_state(void *userobj, void *sh, lws_ss_constate_t state,\n+\t\tlws_ss_tx_ordinal_t ack)\n+{\n+\tmyss_t *m \u003d (myss_t *)userobj;\n+\n+\tlwsl_user(\u0022%s: %s, ord 0x%x\u005cn\u0022, __func__, lws_ss_state_name(state),\n+\t\t (unsigned int)ack);\n+\n+\tswitch (state) {\n+\tcase LWSSSCS_CREATING:\n+\t\tlws_ss_request_tx(m-\u003ess);\n+\t\tbreak;\n+\tcase LWSSSCS_ALL_RETRIES_FAILED:\n+\t\t/* if we're out of retries, we want to close the app and FAIL */\n+\t\tinterrupted \u003d 1;\n+\t\tbreak;\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+typedef enum {\n+\tSEQ_IDLE,\n+\tSEQ_TRY_CONNECT,\n+\tSEQ_RECONNECT_WAIT,\n+\tSEQ_CONNECTED,\n+} myseq_state_t;\n+\n+typedef struct myseq {\n+\tstruct lws_ss_handle\t*ss;\n+\n+\tmyseq_state_t\t\tstate;\n+\tint\t\t\thttp_resp;\n+\n+\tuint16_t\t\ttry;\n+} myseq_t;\n+\n+/*\n+ * This defines the sequence of things the test app does.\n+ */\n+\n+static lws_seq_cb_return_t\n+min_sec_str_sequencer_cb(struct lws_sequencer *seq, void *user, int event,\n+\t\t\t void *v, void *a)\n+{\n+\tstruct myseq *s \u003d (struct myseq *)user;\n+\tlws_ss_info_t ssi;\n+\n+\tswitch ((int)event) {\n+\n+\t/* these messages are created just by virtue of being a sequencer */\n+\n+\tcase LWSSEQ_CREATED: /* our sequencer just got started */\n+\t\ts-\u003estate \u003d SEQ_IDLE;\n+\t\tlwsl_notice(\u0022%s: LWSSEQ_CREATED\u005cn\u0022, __func__);\n+\n+\t\t/* We're making an outgoing secure stream ourselves */\n+\n+\t\tmemset(\u0026ssi, 0, sizeof(ssi));\n+\t\tssi.handle_offset \u003d offsetof(myss_t, ss);\n+\t\tssi.opaque_user_data_offset \u003d offsetof(myss_t, opaque_data);\n+\t\tssi.rx \u003d myss_rx;\n+\t\tssi.tx \u003d myss_tx;\n+\t\tssi.state \u003d myss_state;\n+\t\tssi.user_alloc \u003d sizeof(myss_t);\n+\n+\t\t/* requested to fail (to check backoff)? */\n+\t\tif (flag_conn_fail)\n+\t\t\tssi.streamtype \u003d \u0022mintest-fail\u0022;\n+\t\telse\n+\t\t\t/* request to check h1 POST */\n+\t\t\tif (flag_h1post)\n+\t\t\t\tssi.streamtype \u003d \u0022minpost\u0022;\n+\t\t\telse\n+\t\t\t\t/* default to h1 GET */\n+\t\t\t\tssi.streamtype \u003d \u0022mintest\u0022;\n+\n+\t\tif (lws_ss_create(lws_seq_get_context(seq), 0, \u0026ssi, NULL,\n+\t\t\t\t \u0026s-\u003ess, seq, NULL)) {\n+\t\t\tlwsl_err(\u0022%s: failed to create secure stream\u005cn\u0022,\n+\t\t\t\t __func__);\n+\n+\t\t\treturn LWSSEQ_RET_DESTROY;\n+\t\t}\n+\t\tbreak;\n+\n+\tcase LWSSEQ_DESTROYED:\n+\t\tlwsl_notice(\u0022%s: LWSSEQ_DESTROYED\u005cn\u0022, __func__);\n+\t\tbreak;\n+\n+\tcase LWSSEQ_TIMED_OUT: /* current step timed out */\n+\t\tif (s-\u003estate \u003d\u003d SEQ_RECONNECT_WAIT)\n+\t\t\tlws_ss_request_tx(s-\u003ess);\n+\t\tbreak;\n+\n+\t/*\n+\t * These messages are created because we have a secure stream that was\n+\t * bound to this sequencer at creation time. It copies its state\n+\t * events to us as its sequencer parent. v is the lws_ss_handle_t *\n+\t */\n+\n+\tcase LWSSEQ_SS_STATE_BASE + LWSSSCS_CREATING:\n+\t\tlwsl_info(\u0022%s: seq LWSSSCS_CREATING\u005cn\u0022, __func__);\n+\t\tlws_ss_request_tx(s-\u003ess);\n+\t\tbreak;\n+\tcase LWSSEQ_SS_STATE_BASE + LWSSSCS_DISCONNECTED:\n+\t\tlwsl_info(\u0022%s: seq LWSSSCS_DISCONNECTED\u005cn\u0022, __func__);\n+\t\tbreak;\n+\tcase LWSSEQ_SS_STATE_BASE + LWSSSCS_UNREACHABLE:\n+\t\tlwsl_info(\u0022%s: seq LWSSSCS_UNREACHABLE\u005cn\u0022, __func__);\n+\t\tbreak;\n+\tcase LWSSEQ_SS_STATE_BASE + LWSSSCS_AUTH_FAILED:\n+\t\tlwsl_info(\u0022%s: seq LWSSSCS_AUTH_FAILED\u005cn\u0022, __func__);\n+\t\tbreak;\n+\tcase LWSSEQ_SS_STATE_BASE + LWSSSCS_CONNECTED:\n+\t\tlwsl_info(\u0022%s: seq LWSSSCS_CONNECTED\u005cn\u0022, __func__);\n+\t\tbreak;\n+\tcase LWSSEQ_SS_STATE_BASE + LWSSSCS_CONNECTING:\n+\t\tlwsl_info(\u0022%s: seq LWSSSCS_CONNECTING\u005cn\u0022, __func__);\n+\t\tbreak;\n+\tcase LWSSEQ_SS_STATE_BASE + LWSSSCS_DESTROYING:\n+\t\tlwsl_info(\u0022%s: seq LWSSSCS_DESTROYING\u005cn\u0022, __func__);\n+\t\tbreak;\n+\tcase LWSSEQ_SS_STATE_BASE + LWSSSCS_POLL:\n+\t\t/* somebody called lws_ss_poll() on the stream */\n+\t\tlwsl_info(\u0022%s: seq LWSSSCS_POLL\u005cn\u0022, __func__);\n+\t\tbreak;\n+\tcase LWSSEQ_SS_STATE_BASE + LWSSSCS_ALL_RETRIES_FAILED:\n+\t\tlwsl_info(\u0022%s: seq LWSSSCS_ALL_RETRIES_FAILED\u005cn\u0022, __func__);\n+\t\tinterrupted \u003d 1;\n+\t\tbreak;\n+\tcase LWSSEQ_SS_STATE_BASE + LWSSSCS_QOS_ACK_REMOTE:\n+\t\tlwsl_info(\u0022%s: seq LWSSSCS_QOS_ACK_REMOTE\u005cn\u0022, __func__);\n+\t\tbreak;\n+\tcase LWSSEQ_SS_STATE_BASE + LWSSSCS_QOS_ACK_LOCAL:\n+\t\tlwsl_info(\u0022%s: seq LWSSSCS_QOS_ACK_LOCAL\u005cn\u0022, __func__);\n+\t\tbreak;\n+\n+\t/*\n+\t * This is the message we send from the ss handler to inform the\n+\t * sequencer we had the payload properly\n+\t */\n+\n+\tcase LWSSEQ_USER_BASE:\n+\t\tbad \u003d 0;\n+\t\tinterrupted \u003d 1;\n+\t\tbreak;\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn LWSSEQ_RET_CONTINUE;\n+}\n+\n+static void\n+sigint_handler(int sig)\n+{\n+\tinterrupted \u003d 1;\n+}\n+\n+int main(int argc, const char **argv)\n+{\n+\tint n \u003d 0, logs \u003d LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;\n+\tstruct lws_context_creation_info info;\n+\tstruct lws_context *context;\n+\tlws_seq_info_t i;\n+\tconst char *p;\n+\tmyseq_t *ms;\n+\n+\tsignal(SIGINT, sigint_handler);\n+\n+\tif ((p \u003d lws_cmdline_option(argc, argv, \u0022-d\u0022)))\n+\t\tlogs \u003d atoi(p);\n+\n+\tlws_set_log_level(logs, NULL);\n+\tlwsl_user(\u0022LWS minimal secure streams [-d\u003cverbosity\u003e][-f][--h1post]\u005cn\u0022);\n+\n+\tflag_conn_fail \u003d !!lws_cmdline_option(argc, argv, \u0022-f\u0022);\n+\tflag_h1post \u003d !!lws_cmdline_option(argc, argv, \u0022--h1post\u0022);\n+\n+\tmemset(\u0026info, 0, sizeof info); /* otherwise uninitialized garbage */\n+\n+\tinfo.options \u003d LWS_SERVER_OPTION_EXPLICIT_VHOSTS |\n+\t\t LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;\n+\tinfo.fd_limit_per_thread \u003d 1 + 1 + 1;\n+\tinfo.pss_policies_json \u003d default_ss_policy;\n+\tinfo.port \u003d CONTEXT_PORT_NO_LISTEN;\n+\n+\tcontext \u003d lws_create_context(\u0026info);\n+\tif (!context) {\n+\t\tlwsl_err(\u0022lws init failed\u005cn\u0022);\n+\t\treturn 1;\n+\t}\n+\n+\t/*\n+\t * Create the sequencer that performs the steps of the test action\n+\t * from inside the event loop.\n+\t */\n+\n+\tmemset(\u0026i, 0, sizeof(i));\n+\ti.context\t\u003d context;\n+\ti.user_size\t\u003d sizeof(myseq_t);\n+\ti.puser\t\t\u003d (void **)\u0026ms;\n+\ti.cb\t\t\u003d min_sec_str_sequencer_cb;\n+\ti.name\t\t\u003d \u0022min-sec-stream-seq\u0022;\n+\n+\tif (!lws_seq_create(\u0026i)) {\n+\t\tlwsl_err(\u0022%s: failed to create sequencer\u005cn\u0022, __func__);\n+\t\tgoto bail;\n+\t}\n+\n+\t/* the event loop */\n+\n+\twhile (n \u003e\u003d 0 \u0026\u0026 !interrupted)\n+\t\tn \u003d lws_service(context, 0);\n+\n+bail:\n+\tlws_context_destroy(context);\n+\tlwsl_user(\u0022Completed: %s\u005cn\u0022, bad ? \u0022failed\u0022 : \u0022OK\u0022);\n+\n+\treturn bad;\n+}\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-sink/CMakeLists.txt b/minimal-examples/secure-streams/minimal-secure-streams-sink/CMakeLists.txt\nnew file mode 100644\nindex 0000000..080ddea\n--- /dev/null\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-sink/CMakeLists.txt\n@@ -0,0 +1,79 @@\n+cmake_minimum_required(VERSION 2.8)\n+include(CheckCSourceCompiles)\n+\n+set(SAMP lws-minimal-secure-streams-sink)\n+set(SRCS main.c)\n+\n+# If we are being built as part of lws, confirm current build config supports\n+# reqconfig, else skip building ourselves.\n+#\n+# If we are being built externally, confirm installed lws was configured to\n+# support reqconfig, else error out with a helpful message about the problem.\n+#\n+MACRO(require_lws_config reqconfig _val result)\n+\n+\tif (DEFINED ${reqconfig})\n+\tif (${reqconfig})\n+\t\tset (rq 1)\n+\telse()\n+\t\tset (rq 0)\n+\tendif()\n+\telse()\n+\t\tset(rq 0)\n+\tendif()\n+\n+\tif (${_val} EQUAL ${rq})\n+\t\tset(SAME 1)\n+\telse()\n+\t\tset(SAME 0)\n+\tendif()\n+\n+\tif (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})\n+\t\tif (${_val})\n+\t\t\tmessage(\u0022${SAMP}: skipping as lws being built without ${reqconfig}\u0022)\n+\t\telse()\n+\t\t\tmessage(\u0022${SAMP}: skipping as lws built with ${reqconfig}\u0022)\n+\t\tendif()\n+\t\tset(${result} 0)\n+\telse()\n+\t\tif (LWS_WITH_MINIMAL_EXAMPLES)\n+\t\t\tset(MET ${SAME})\n+\t\telse()\n+\t\t\tCHECK_C_SOURCE_COMPILES(\u0022#include \u003clibwebsockets.h\u003e\u005cnint main(void) {\u005cn#if defined(${reqconfig})\u005cn return 0;\u005cn#else\u005cn fail;\u005cn#endif\u005cn return 0;\u005cn}\u005cn\u0022 HAS_${reqconfig})\n+\t\t\tif (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})\n+\t\t\t\tset(HAS_${reqconfig} 0)\n+\t\t\telse()\n+\t\t\t\tset(HAS_${reqconfig} 1)\n+\t\t\tendif()\n+\t\t\tif ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))\n+\t\t\t\tset(MET 1)\n+\t\t\telse()\n+\t\t\t\tset(MET 0)\n+\t\t\tendif()\n+\t\tendif()\n+\t\tif (NOT MET)\n+\t\t\tif (${_val})\n+\t\t\t\tmessage(FATAL_ERROR \u0022This project requires lws must have been configured with ${reqconfig}\u0022)\n+\t\t\telse()\n+\t\t\t\tmessage(FATAL_ERROR \u0022Lws configuration of ${reqconfig} is incompatible with this project\u0022)\n+\t\t\tendif()\n+\t\tendif()\n+\tendif()\n+ENDMACRO()\n+\n+\n+set(requirements 1)\n+require_lws_config(LWS_ROLE_H1 1 requirements)\n+require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)\n+require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements)\n+\n+if (requirements)\n+\tadd_executable(${SAMP} ${SRCS})\n+\n+\tif (websockets_shared)\n+\t\ttarget_link_libraries(${SAMP} websockets_shared)\n+\t\tadd_dependencies(${SAMP} websockets_shared)\n+\telse()\n+\t\ttarget_link_libraries(${SAMP} websockets)\n+\tendif()\n+endif()\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-sink/README.md b/minimal-examples/secure-streams/minimal-secure-streams-sink/README.md\nnew file mode 100644\nindex 0000000..80a1c69\n--- /dev/null\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-sink/README.md\n@@ -0,0 +1,64 @@\n+# lws minimal secure streams\n+\n+The application goes to https://warmcat.com and reads index.html there.\n+\n+It does it using Secure Streams... the main code in minimal-secure-streams.c\n+just sets up the context and opens a secure stream of type \u0022mintest\u0022.\n+\n+The handler for state changes and payloads for \u0022mintest\u0022 is in ss-myss.c\n+\n+The information about how a \u0022mintest\u0022 stream should connect and the\n+protocol it uses is kept separated in policy-database.c\n+\n+## build\n+\n+```\n+ $ cmake . \u0026\u0026 make\n+```\n+\n+## usage\n+\n+Commandline option|Meaning\n+---|---\n+-d \u003cloglevel\u003e|Debug verbosity in decimal, eg, -d15\n+-f| Force connecting to the wrong endpoint to check backoff retry flow\n+-p| Run as proxy server for clients to connect to over unix domain socket\n+\n+```\n+[2019/08/12 07:16:11:0045] USR: LWS minimal secure streams [-d\u003cverbosity\u003e] [-f]\n+[2019/08/12 07:16:12:6102] USR: myss_state: LWSSSCS_CREATING, ord 0x0\n+[2019/08/12 07:16:12:6107] USR: myss_state: LWSSSCS_POLL, ord 0x0\n+[2019/08/12 07:16:12:6117] N: lws_ss_client_connect: connecting h1get warmcat.com /\n+[2019/08/12 07:16:12:6118] USR: myss_state: LWSSSCS_CONNECTING, ord 0x0\n+[2019/08/12 07:16:13:4171] USR: myss_state: LWSSSCS_CONNECTED, ord 0x0\n+[2019/08/12 07:16:13:4222] USR: myss_rx: len 1024, flags: 1\n+[2019/08/12 07:16:13:4243] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4244] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4244] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4245] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4246] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4247] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4252] USR: myss_rx: len 1015, flags: 0\n+[2019/08/12 07:16:13:4264] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4265] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4266] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4267] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4268] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4268] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4269] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4270] USR: myss_rx: len 1015, flags: 0\n+[2019/08/12 07:16:13:4278] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4279] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4280] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4281] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4282] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4283] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4283] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4284] USR: myss_rx: len 1015, flags: 0\n+[2019/08/12 07:16:13:4287] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4288] USR: myss_rx: len 947, flags: 0\n+[2019/08/12 07:16:13:4293] USR: myss_rx: len 0, flags: 2\n+[2019/08/12 07:16:13:4399] USR: myss_state: LWSSSCS_DISCONNECTED, ord 0x0\n+[2019/08/12 07:16:13:4761] USR: myss_state: LWSSSCS_DESTROYING, ord 0x0\n+[2019/08/12 07:16:13:4781] USR: Completed: OK\n+```\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams-sink/main.c b/minimal-examples/secure-streams/minimal-secure-streams-sink/main.c\nnew file mode 100644\nindex 0000000..792cad1\n--- /dev/null\n+++ b/minimal-examples/secure-streams/minimal-secure-streams-sink/main.c\n@@ -0,0 +1,288 @@\n+/*\n+ * lws-minimal-secure-streams-sink\n+ *\n+ * Written in 2010-2020 by Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * This file is made available under the Creative Commons CC0 1.0\n+ * Universal Public Domain Dedication.\n+ */\n+\n+#include \u003clibwebsockets.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003csignal.h\u003e\n+\n+static int interrupted, bad \u003d 1;\n+static const char * const default_ss_policy \u003d\n+\t\u0022{\u0022\n+\t \u0022\u005c\u0022release\u005c\u0022:\u0022\t\t\t\u0022\u005c\u002201234567\u005c\u0022,\u0022\n+\t \u0022\u005c\u0022product\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022myproduct\u005c\u0022,\u0022\n+\t \u0022\u005c\u0022schema-version\u005c\u0022:\u0022\t\t\t\u00221,\u0022\n+\t \u0022\u005c\u0022retry\u005c\u0022: [\u0022\t/* named backoff / retry strategies */\n+\t\t\u0022{\u005c\u0022default\u005c\u0022: {\u0022\n+\t\t\t\u0022\u005c\u0022backoff\u005c\u0022: [\u0022\t \u00221000,\u0022\n+\t\t\t\t\t\t \u00222000,\u0022\n+\t\t\t\t\t\t \u00223000,\u0022\n+\t\t\t\t\t\t \u00225000,\u0022\n+\t\t\t\t\t\t\u002210000\u0022\n+\t\t\t\t\u0022],\u0022\n+\t\t\t\u0022\u005c\u0022conceal\u005c\u0022:\u0022\t\t\u00225,\u0022\n+\t\t\t\u0022\u005c\u0022jitterpc\u005c\u0022:\u0022\t\t\u002220,\u0022\n+\t\t\t\u0022\u005c\u0022svalidping\u005c\u0022:\u0022\t\u0022300,\u0022\n+\t\t\t\u0022\u005c\u0022svalidhup\u005c\u0022:\u0022\t\u0022310\u0022\n+\t\t\u0022}}\u0022\n+\t \u0022],\u0022\n+\t \u0022\u005c\u0022certs\u005c\u0022: [\u0022 /* named individual certificates in BASE64 DER */\n+\t\t/*\n+\t\t * Need to be in order from root cert... notice sometimes as\n+\t\t * with Let's Encrypt there are multiple possible validation\n+\t\t * paths, all the pieces for one validation path must be\n+\t\t * given, excluding the server cert itself. Let's Encrypt\n+\t\t * intermediate is signed by their ISRG Root CA but also is\n+\t\t * cross-signed by an IdenTrust intermediate that's widely\n+\t\t * deployed in browsers. We use the ISRG path because that\n+\t\t * way we can skip the extra IdenTrust root cert.\n+\t\t */\n+\t\t\u0022{\u005c\u0022isrg_root_x1\u005c\u0022: \u005c\u0022\u0022 /* ISRG ROOT X1 */\n+\t\u0022MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\u0022\n+\t\u0022TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\u0022\n+\t\u0022cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\u0022\n+\t\u0022WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\u0022\n+\t\u0022ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\u0022\n+\t\u0022MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\u0022\n+\t\u0022h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\u0022\n+\t\u00220TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\u0022\n+\t\u0022A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\u0022\n+\t\u0022T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\u0022\n+\t\u0022B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\u0022\n+\t\u0022B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\u0022\n+\t\u0022KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\u0022\n+\t\u0022OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\u0022\n+\t\u0022jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\u0022\n+\t\u0022qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\u0022\n+\t\u0022rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\u0022\n+\t\u0022HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\u0022\n+\t\u0022hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\u0022\n+\t\u0022ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\u0022\n+\t\u00223BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\u0022\n+\t\u0022NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\u0022\n+\t\u0022ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\u0022\n+\t\u0022TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\u0022\n+\t\u0022jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\u0022\n+\t\u0022oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\u0022\n+\t\u00224RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\u0022\n+\t\u0022mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\u0022\n+\t\u0022emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc\u003d\u0022\n+\t\t\u0022\u005c\u0022},\u0022\n+\t\t\u0022{\u005c\u0022LEX3_isrg_root_x1\u005c\u0022: \u005c\u0022\u0022 /* LE X3 signed by ISRG X1 root */\n+\t\u0022MIIFjTCCA3WgAwIBAgIRANOxciY0IzLc9AUoUSrsnGowDQYJKoZIhvcNAQELBQAw\u0022\n+\t\u0022TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\u0022\n+\t\u0022cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTYxMDA2MTU0MzU1\u0022\n+\t\u0022WhcNMjExMDA2MTU0MzU1WjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg\u0022\n+\t\u0022RW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDMwggEi\u0022\n+\t\u0022MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCc0wzwWuUuR7dyXTeDs2hjMOrX\u0022\n+\t\u0022NSYZJeG9vjXxcJIvt7hLQQWrqZ41CFjssSrEaIcLo+N15Obzp2JxunmBYB/XkZqf\u0022\n+\t\u002289B4Z3HIaQ6Vkc/+5pnpYDxIzH7KTXcSJJ1HG1rrueweNwAcnKx7pwXqzkrrvUHl\u0022\n+\t\u0022Npi5y/1tPJZo3yMqQpAMhnRnyH+lmrhSYRQTP2XpgofL2/oOVvaGifOFP5eGr7Dc\u0022\n+\t\u0022Gu9rDZUWfcQroGWymQQ2dYBrrErzG5BJeC+ilk8qICUpBMZ0wNAxzY8xOJUWuqgz\u0022\n+\t\u0022uEPxsR/DMH+ieTETPS02+OP88jNquTkxxa/EjQ0dZBYzqvqEKbbUC8DYfcOTAgMB\u0022\n+\t\u0022AAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBU\u0022\n+\t\u0022BgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIB\u0022\n+\t\u0022FiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBSo\u0022\n+\t\u0022SmpjBH3duubRObemRWXv86jsoTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3Js\u0022\n+\t\u0022LnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEF\u0022\n+\t\u0022BQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsG\u0022\n+\t\u0022AQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYD\u0022\n+\t\u0022VR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIB\u0022\n+\t\u0022ABnPdSA0LTqmRf/Q1eaM2jLonG4bQdEnqOJQ8nCqxOeTRrToEKtwT++36gTSlBGx\u0022\n+\t\u0022A/5dut82jJQ2jxN8RI8L9QFXrWi4xXnA2EqA10yjHiR6H9cj6MFiOnb5In1eWsRM\u0022\n+\t\u0022UM2v3e9tNsCAgBukPHAg1lQh07rvFKm/Bz9BCjaxorALINUfZ9DD64j2igLIxle2\u0022\n+\t\u0022DPxW8dI/F2loHMjXZjqG8RkqZUdoxtID5+90FgsGIfkMpqgRS05f4zPbCEHqCXl1\u0022\n+\t\u0022eO5HyELTgcVlLXXQDgAWnRzut1hFJeczY1tjQQno6f6s+nMydLN26WuU4s3UYvOu\u0022\n+\t\u0022OsUxRlJu7TSRHqDC3lSE5XggVkzdaPkuKGQbGpny+01/47hfXXNB7HntWNZ6N2Vw\u0022\n+\t\u0022p7G6OfY+YQrZwIaQmhrIqJZuigsrbe3W+gdn5ykE9+Ky0VgVUsfxo52mwFYs1JKY\u0022\n+\t\u00222PGDuWx8M6DlS6qQkvHaRUo0FMd8TsSlbF0/v965qGFKhSDeQoMpYnwcmQilRh/0\u0022\n+\t\u0022ayLThlHLN81gSkJjVrPI0Y8xCVPB4twb1PFUd2fPM3sA1tJ83sZ5v8vgFv2yofKR\u0022\n+\t\u0022PB0t6JzUA81mSqM3kxl5e+IZwhYAyO0OTg3/fs8HqGTNKd9BqoUwSRBzp06JMg5b\u0022\n+\t\u0022rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt\u0022\n+\t\t\u0022\u005c\u0022}\u0022\n+\t \u0022],\u0022\n+\t \u0022\u005c\u0022trust_stores\u005c\u0022: [\u0022 /* named cert chains */\n+\t\t\u0022{\u0022\n+\t\t\t\u0022\u005c\u0022name\u005c\u0022: \u005c\u0022le_via_isrg\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022stack\u005c\u0022: [\u0022\n+\t\t\t\t\u0022\u005c\u0022isrg_root_x1\u005c\u0022,\u0022\n+\t\t\t\t\u0022\u005c\u0022LEX3_isrg_root_x1\u005c\u0022\u0022\n+\t\t\t\u0022]\u0022\n+\t\t\u0022}\u0022\n+\t \u0022],\u0022\n+\t \u0022\u005c\u0022s\u005c\u0022: [\u0022 /* the supported stream types */\n+\t\t\u0022{\u005c\u0022\u005c\u0022: {\u0022\n+\t\t\t\u0022\u005c\u0022endpoint\u005c\u0022:\u0022\t\t\u0022\u005c\u0022warmcat.com\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022port\u005c\u0022:\u0022\t\t\u0022443,\u0022\n+\t\t\t\u0022\u005c\u0022protocol\u005c\u0022:\u0022\t\t\u0022\u005c\u0022h2\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_method\u005c\u0022:\u0022\t\u0022\u005c\u0022GET\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_url\u005c\u0022:\u0022\t\t\u0022\u005c\u0022index.html\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022plugins\u005c\u0022:\u0022\t\t\u0022[],\u0022\n+\t\t\t\u0022\u005c\u0022tls\u005c\u0022:\u0022\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022nailed_up\u005c\u0022:\u0022\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022long_poll\u005c\u0022:\u0022\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022retry\u005c\u0022:\u0022\t\t\u0022\u005c\u0022default\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022tls_trust_store\u005c\u0022:\u0022\t\u0022\u005c\u0022le_via_isrg\u005c\u0022\u0022\n+\t\t\u0022}}\u0022\n+\t \u0022]\u0022\n+\t\u0022}\u0022\n+;\n+\n+typedef struct myss {\n+\tstruct lws_ss_handle \t*ss;\n+\tvoid\t\t\t*opaque_data;\n+\t/* ... application specific state ... */\n+\tlws_sorted_usec_list_t\tsul;\n+\n+\tint\t\t\tcount;\n+} myss_t;\n+\n+/* secure streams payload interface */\n+\n+static int\n+myss_rx(void *userobj, const uint8_t *buf, size_t len, int flags)\n+{\n+//\tmyss_t *m \u003d (myss_t *)userobj;\n+\n+\tlwsl_user(\u0022%s: len %d, flags: %d\u005cn\u0022, __func__, (int)len, flags);\n+\tlwsl_hexdump_info(buf, len);\n+\n+\t/*\n+\t * If we received the whole message, for our example it means\n+\t * we are done.\n+\t */\n+\tif (flags \u0026 LWSSS_FLAG_EOM) {\n+\t\tbad \u003d 0;\n+\t\tinterrupted \u003d 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int\n+myss_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len,\n+\tint *flags)\n+{\n+\t//myss_t *m \u003d (myss_t *)userobj;\n+\n+\treturn 0;\n+}\n+\n+static int\n+myss_state(void *userobj, void *sh, lws_ss_constate_t state,\n+\t lws_ss_tx_ordinal_t ack)\n+{\n+\tmyss_t *m \u003d (myss_t *)userobj;\n+\n+\tlwsl_user(\u0022%s: %s, ord 0x%x\u005cn\u0022, __func__, lws_ss_state_name(state),\n+\t\t (unsigned int)ack);\n+\n+\tswitch (state) {\n+\tcase LWSSSCS_CREATING:\n+\t\tlws_ss_request_tx(m-\u003ess);\n+\t\tbreak;\n+\tcase LWSSSCS_ALL_RETRIES_FAILED:\n+\t\t/* if we're out of retries, we want to close the app and FAIL */\n+\t\tinterrupted \u003d 1;\n+\t\tbreak;\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void\n+sigint_handler(int sig)\n+{\n+\tinterrupted \u003d 1;\n+}\n+\n+int main(int argc, const char **argv)\n+{\n+\tstruct lws_context_creation_info info;\n+\tstruct lws_context *context;\n+\tint n \u003d 0;\n+\n+\tsignal(SIGINT, sigint_handler);\n+\n+\tmemset(\u0026info, 0, sizeof info); /* otherwise uninitialized garbage */\n+\tlws_cmdline_option_handle_builtin(argc, argv, \u0026info);\n+\tlwsl_user(\u0022LWS secure streams [-d\u003cverb\u003e] [-f] [-p] [--h1post]\u005cn\u0022);\n+\n+\tinfo.options \u003d LWS_SERVER_OPTION_EXPLICIT_VHOSTS |\n+\t\t LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;\n+\tinfo.fd_limit_per_thread \u003d 1 + 6 + 1;\n+\tinfo.pss_policies_json \u003d default_ss_policy;\n+\tinfo.port \u003d CONTEXT_PORT_NO_LISTEN;\n+\n+\tcontext \u003d lws_create_context(\u0026info);\n+\tif (!context) {\n+\t\tlwsl_err(\u0022lws init failed\u005cn\u0022);\n+\t\treturn 1;\n+\t}\n+\n+\t// puts(default_ss_policy);\n+\n+\tif (lws_cmdline_option(argc, argv, \u0022-p\u0022)) {\n+\n+\t\t/* we are being the proxy */\n+\n+#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API)\n+\t\tif (lws_ss_proxy_create(context, NULL, 0)) {\n+\t\t\tlwsl_err(\u0022%s: failed to create ss proxy\u005cn\u0022, __func__);\n+\t\t\tgoto bail;\n+\t\t}\n+\t\tlwsl_notice(\u0022%s: secure streams proxy mode\u005cn\u0022, __func__);\n+#else\n+\t\tlwsl_err(\u0022%s: needs cmake LWS_WITH_SECURE_STREAMS_PROXY_API\u005cn\u0022,\n+\t\t\t __func__);\n+#endif\n+\t} else {\n+\t\tlws_ss_info_t ssi;\n+\n+\t\t/* We're making an outgoing secure stream ourselves */\n+\n+\t\tmemset(\u0026ssi, 0, sizeof(ssi));\n+\t\tssi.handle_offset \u003d offsetof(myss_t, ss);\n+\t\tssi.opaque_user_data_offset \u003d offsetof(myss_t, opaque_data);\n+\t\tssi.rx \u003d myss_rx;\n+\t\tssi.tx \u003d myss_tx;\n+\t\tssi.state \u003d myss_state;\n+\t\tssi.user_alloc \u003d sizeof(myss_t);\n+\n+\t\t/* requested to fail (to check backoff)? */\n+\t\tif (lws_cmdline_option(argc, argv, \u0022-f\u0022))\n+\t\t\tssi.streamtype \u003d \u0022mintest-fail\u0022;\n+\t\telse\n+\t\t\t/* request to check h1 POST */\n+\t\t\tif (lws_cmdline_option(argc, argv, \u0022--h1post\u0022))\n+\t\t\t\tssi.streamtype \u003d \u0022minpost\u0022;\n+\t\t\telse\n+\t\t\t\t/* default to h1 GET */\n+\t\t\t\tssi.streamtype \u003d \u0022mintest\u0022;\n+\n+\t\tif (lws_ss_create(context, 0, \u0026ssi, NULL, NULL, NULL, NULL)) {\n+\t\t\tlwsl_err(\u0022%s: failed to create secure stream\u005cn\u0022,\n+\t\t\t\t __func__);\n+\t\t\tgoto bail;\n+\t\t}\n+\t}\n+\n+\t/* the event loop */\n+\n+\twhile (n \u003e\u003d 0 \u0026\u0026 !interrupted)\n+\t\tn \u003d lws_service(context, 0);\n+\n+bail:\n+\n+\tlws_context_destroy(context);\n+\tlwsl_user(\u0022Completed: %s\u005cn\u0022, bad ? \u0022failed\u0022 : \u0022OK\u0022);\n+\n+\treturn bad;\n+}\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams/CMakeLists.txt b/minimal-examples/secure-streams/minimal-secure-streams/CMakeLists.txt\nnew file mode 100644\nindex 0000000..675e523\n--- /dev/null\n+++ b/minimal-examples/secure-streams/minimal-secure-streams/CMakeLists.txt\n@@ -0,0 +1,91 @@\n+cmake_minimum_required(VERSION 2.8)\n+include(CheckCSourceCompiles)\n+\n+set(SAMP lws-minimal-secure-streams)\n+\n+# If we are being built as part of lws, confirm current build config supports\n+# reqconfig, else skip building ourselves.\n+#\n+# If we are being built externally, confirm installed lws was configured to\n+# support reqconfig, else error out with a helpful message about the problem.\n+#\n+MACRO(require_lws_config reqconfig _val result)\n+\n+\tif (DEFINED ${reqconfig})\n+\tif (${reqconfig})\n+\t\tset (rq 1)\n+\telse()\n+\t\tset (rq 0)\n+\tendif()\n+\telse()\n+\t\tset(rq 0)\n+\tendif()\n+\n+\tif (${_val} EQUAL ${rq})\n+\t\tset(SAME 1)\n+\telse()\n+\t\tset(SAME 0)\n+\tendif()\n+\n+\tif (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})\n+\t\tif (${_val})\n+\t\t\tmessage(\u0022${SAMP}: skipping as lws being built without ${reqconfig}\u0022)\n+\t\telse()\n+\t\t\tmessage(\u0022${SAMP}: skipping as lws built with ${reqconfig}\u0022)\n+\t\tendif()\n+\t\tset(${result} 0)\n+\telse()\n+\t\tif (LWS_WITH_MINIMAL_EXAMPLES)\n+\t\t\tset(MET ${SAME})\n+\t\telse()\n+\t\t\tCHECK_C_SOURCE_COMPILES(\u0022#include \u003clibwebsockets.h\u003e\u005cnint main(void) {\u005cn#if defined(${reqconfig})\u005cn return 0;\u005cn#else\u005cn fail;\u005cn#endif\u005cn return 0;\u005cn}\u005cn\u0022 HAS_${reqconfig})\n+\t\t\tif (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})\n+\t\t\t\tset(HAS_${reqconfig} 0)\n+\t\t\telse()\n+\t\t\t\tset(HAS_${reqconfig} 1)\n+\t\t\tendif()\n+\t\t\tif ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))\n+\t\t\t\tset(MET 1)\n+\t\t\telse()\n+\t\t\t\tset(MET 0)\n+\t\t\tendif()\n+\t\tendif()\n+\t\tif (NOT MET)\n+\t\t\tif (${_val})\n+\t\t\t\tmessage(FATAL_ERROR \u0022This project requires lws must have been configured with ${reqconfig}\u0022)\n+\t\t\telse()\n+\t\t\t\tmessage(FATAL_ERROR \u0022Lws configuration of ${reqconfig} is incompatible with this project\u0022)\n+\t\t\tendif()\n+\t\tendif()\n+\tendif()\n+ENDMACRO()\n+\n+\n+set(requirements 1)\n+require_lws_config(LWS_ROLE_H1 1 requirements)\n+require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)\n+require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements)\n+\n+if (requirements)\n+\tadd_executable(${SAMP} minimal-secure-streams.c)\n+\n+\tif (websockets_shared)\n+\t\ttarget_link_libraries(${SAMP} websockets_shared)\n+\t\tadd_dependencies(${SAMP} websockets_shared)\n+\telse()\n+\t\ttarget_link_libraries(${SAMP} websockets)\n+\tendif()\n+\n+\tif (LWS_WITH_SECURE_STREAMS_PROXY_API)\n+\t\tadd_compile_options(-DLWS_SS_USE_SSPC)\n+\n+\t\tadd_executable(${SAMP}-client minimal-secure-streams.c)\n+\t\tif (websockets_shared)\n+\t\t\ttarget_link_libraries(${SAMP}-client websockets_shared)\n+\t\t\tadd_dependencies(${SAMP}-client websockets_shared)\n+\t\telse()\n+\t\t\ttarget_link_libraries(${SAMP}-client websockets)\n+\t\tendif()\n+\tendif()\n+\n+endif()\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams/README.md b/minimal-examples/secure-streams/minimal-secure-streams/README.md\nnew file mode 100644\nindex 0000000..80a1c69\n--- /dev/null\n+++ b/minimal-examples/secure-streams/minimal-secure-streams/README.md\n@@ -0,0 +1,64 @@\n+# lws minimal secure streams\n+\n+The application goes to https://warmcat.com and reads index.html there.\n+\n+It does it using Secure Streams... the main code in minimal-secure-streams.c\n+just sets up the context and opens a secure stream of type \u0022mintest\u0022.\n+\n+The handler for state changes and payloads for \u0022mintest\u0022 is in ss-myss.c\n+\n+The information about how a \u0022mintest\u0022 stream should connect and the\n+protocol it uses is kept separated in policy-database.c\n+\n+## build\n+\n+```\n+ $ cmake . \u0026\u0026 make\n+```\n+\n+## usage\n+\n+Commandline option|Meaning\n+---|---\n+-d \u003cloglevel\u003e|Debug verbosity in decimal, eg, -d15\n+-f| Force connecting to the wrong endpoint to check backoff retry flow\n+-p| Run as proxy server for clients to connect to over unix domain socket\n+\n+```\n+[2019/08/12 07:16:11:0045] USR: LWS minimal secure streams [-d\u003cverbosity\u003e] [-f]\n+[2019/08/12 07:16:12:6102] USR: myss_state: LWSSSCS_CREATING, ord 0x0\n+[2019/08/12 07:16:12:6107] USR: myss_state: LWSSSCS_POLL, ord 0x0\n+[2019/08/12 07:16:12:6117] N: lws_ss_client_connect: connecting h1get warmcat.com /\n+[2019/08/12 07:16:12:6118] USR: myss_state: LWSSSCS_CONNECTING, ord 0x0\n+[2019/08/12 07:16:13:4171] USR: myss_state: LWSSSCS_CONNECTED, ord 0x0\n+[2019/08/12 07:16:13:4222] USR: myss_rx: len 1024, flags: 1\n+[2019/08/12 07:16:13:4243] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4244] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4244] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4245] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4246] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4247] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4252] USR: myss_rx: len 1015, flags: 0\n+[2019/08/12 07:16:13:4264] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4265] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4266] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4267] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4268] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4268] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4269] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4270] USR: myss_rx: len 1015, flags: 0\n+[2019/08/12 07:16:13:4278] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4279] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4280] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4281] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4282] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4283] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4283] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4284] USR: myss_rx: len 1015, flags: 0\n+[2019/08/12 07:16:13:4287] USR: myss_rx: len 1024, flags: 0\n+[2019/08/12 07:16:13:4288] USR: myss_rx: len 947, flags: 0\n+[2019/08/12 07:16:13:4293] USR: myss_rx: len 0, flags: 2\n+[2019/08/12 07:16:13:4399] USR: myss_state: LWSSSCS_DISCONNECTED, ord 0x0\n+[2019/08/12 07:16:13:4761] USR: myss_state: LWSSSCS_DESTROYING, ord 0x0\n+[2019/08/12 07:16:13:4781] USR: Completed: OK\n+```\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c b/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c\nnew file mode 100644\nindex 0000000..fe44a43\n--- /dev/null\n+++ b/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c\n@@ -0,0 +1,421 @@\n+/*\n+ * lws-minimal-secure-streams\n+ *\n+ * Written in 2010-2020 by Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * This file is made available under the Creative Commons CC0 1.0\n+ * Universal Public Domain Dedication.\n+ *\n+ *\n+ * This demonstrates a minimal http client using secure streams api.\n+ *\n+ * It visits https://warmcat.com/ and receives the html page there.\n+ *\n+ * This example is built two different ways from the same source... one includes\n+ * the policy everything needed to fulfil the stream directly. The other -client\n+ * variant has no policy itself and some other minor init changes, and connects\n+ * to the -proxy example to actually get the connection done.\n+ *\n+ * In the -client build case, the example does not even init the tls libraries\n+ * since the proxy part will take care of all that.\n+ */\n+\n+#include \u003clibwebsockets.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003csignal.h\u003e\n+\n+/*\n+ * uncomment to force network traffic through 127.0.0.1:1080\n+ *\n+ * On your local machine, you can run a SOCKS5 proxy like this\n+ *\n+ * $ ssh -N -D 0.0.0.0:1080 localhost -v\n+ *\n+ * If enabled, this also fetches a remote policy that also\n+ * specifies that all traffic should go through the remote\n+ * proxy.\n+ */\n+// #define VIA_LOCALHOST_SOCKS\n+\n+static int interrupted, bad \u003d 1;\n+static lws_state_notify_link_t nl;\n+\n+/*\n+ * If the -proxy app is fulfilling our connection, then we don't need to have\n+ * the policy in the client.\n+ *\n+ * When we build with LWS_SS_USE_SSPC, the apis hook up to a proxy process over\n+ * a Unix Domain Socket. To test that, you need to separately run the\n+ * ./lws-minimal-secure-streams-proxy test app on the same machine.\n+ */\n+\n+#if !defined(LWS_SS_USE_SSPC)\n+static const char * const default_ss_policy \u003d\n+\t\u0022{\u0022\n+\t \u0022\u005c\u0022release\u005c\u0022:\u0022\t\t\t\u0022\u005c\u002201234567\u005c\u0022,\u0022\n+\t \u0022\u005c\u0022product\u005c\u0022:\u0022\t\t\t\u0022\u005c\u0022myproduct\u005c\u0022,\u0022\n+\t \u0022\u005c\u0022schema-version\u005c\u0022:\u0022\t\t\t\u00221,\u0022\n+#if defined(VIA_LOCALHOST_SOCKS)\n+\t \u0022\u005c\u0022via-socks5\u005c\u0022:\u0022 \u0022\u005c\u0022127.0.0.1:1080\u005c\u0022,\u0022\n+#endif\n+\n+\t \u0022\u005c\u0022retry\u005c\u0022: [\u0022\t/* named backoff / retry strategies */\n+\t\t\u0022{\u005c\u0022default\u005c\u0022: {\u0022\n+\t\t\t\u0022\u005c\u0022backoff\u005c\u0022: [\u0022\t \u00221000,\u0022\n+\t\t\t\t\t\t \u00222000,\u0022\n+\t\t\t\t\t\t \u00223000,\u0022\n+\t\t\t\t\t\t \u00225000,\u0022\n+\t\t\t\t\t\t\u002210000\u0022\n+\t\t\t\t\u0022],\u0022\n+\t\t\t\u0022\u005c\u0022conceal\u005c\u0022:\u0022\t\t\u00225,\u0022\n+\t\t\t\u0022\u005c\u0022jitterpc\u005c\u0022:\u0022\t\t\u002220,\u0022\n+\t\t\t\u0022\u005c\u0022svalidping\u005c\u0022:\u0022\t\u002230,\u0022\n+\t\t\t\u0022\u005c\u0022svalidhup\u005c\u0022:\u0022\t\u002235\u0022\n+\t\t\u0022}}\u0022\n+\t \u0022],\u0022\n+\t \u0022\u005c\u0022certs\u005c\u0022: [\u0022 /* named individual certificates in BASE64 DER */\n+\t\t/*\n+\t\t * Let's Encrypt certs for warmcat.com / libwebsockets.org\n+\t\t *\n+\t\t * We fetch the real policy from there using SS and switch to\n+\t\t * using that.\n+\t\t */\n+\t\t\u0022{\u005c\u0022isrg_root_x1\u005c\u0022: \u005c\u0022\u0022 /* ISRG ROOT X1 */\n+\t\u0022MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\u0022\n+\t\u0022TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\u0022\n+\t\u0022cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\u0022\n+\t\u0022WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\u0022\n+\t\u0022ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\u0022\n+\t\u0022MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\u0022\n+\t\u0022h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\u0022\n+\t\u00220TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\u0022\n+\t\u0022A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\u0022\n+\t\u0022T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\u0022\n+\t\u0022B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\u0022\n+\t\u0022B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\u0022\n+\t\u0022KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\u0022\n+\t\u0022OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\u0022\n+\t\u0022jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\u0022\n+\t\u0022qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\u0022\n+\t\u0022rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\u0022\n+\t\u0022HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\u0022\n+\t\u0022hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\u0022\n+\t\u0022ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\u0022\n+\t\u00223BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\u0022\n+\t\u0022NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\u0022\n+\t\u0022ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\u0022\n+\t\u0022TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\u0022\n+\t\u0022jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\u0022\n+\t\u0022oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\u0022\n+\t\u00224RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\u0022\n+\t\u0022mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\u0022\n+\t\u0022emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc\u003d\u0022\n+\t\t\u0022\u005c\u0022},\u0022\n+\t\t\u0022{\u005c\u0022LEX3_isrg_root_x1\u005c\u0022: \u005c\u0022\u0022 /* LE X3 signed by ISRG X1 root */\n+\t\u0022MIIFjTCCA3WgAwIBAgIRANOxciY0IzLc9AUoUSrsnGowDQYJKoZIhvcNAQELBQAw\u0022\n+\t\u0022TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\u0022\n+\t\u0022cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTYxMDA2MTU0MzU1\u0022\n+\t\u0022WhcNMjExMDA2MTU0MzU1WjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg\u0022\n+\t\u0022RW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDMwggEi\u0022\n+\t\u0022MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCc0wzwWuUuR7dyXTeDs2hjMOrX\u0022\n+\t\u0022NSYZJeG9vjXxcJIvt7hLQQWrqZ41CFjssSrEaIcLo+N15Obzp2JxunmBYB/XkZqf\u0022\n+\t\u002289B4Z3HIaQ6Vkc/+5pnpYDxIzH7KTXcSJJ1HG1rrueweNwAcnKx7pwXqzkrrvUHl\u0022\n+\t\u0022Npi5y/1tPJZo3yMqQpAMhnRnyH+lmrhSYRQTP2XpgofL2/oOVvaGifOFP5eGr7Dc\u0022\n+\t\u0022Gu9rDZUWfcQroGWymQQ2dYBrrErzG5BJeC+ilk8qICUpBMZ0wNAxzY8xOJUWuqgz\u0022\n+\t\u0022uEPxsR/DMH+ieTETPS02+OP88jNquTkxxa/EjQ0dZBYzqvqEKbbUC8DYfcOTAgMB\u0022\n+\t\u0022AAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBU\u0022\n+\t\u0022BgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIB\u0022\n+\t\u0022FiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBSo\u0022\n+\t\u0022SmpjBH3duubRObemRWXv86jsoTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3Js\u0022\n+\t\u0022LnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEF\u0022\n+\t\u0022BQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsG\u0022\n+\t\u0022AQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYD\u0022\n+\t\u0022VR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIB\u0022\n+\t\u0022ABnPdSA0LTqmRf/Q1eaM2jLonG4bQdEnqOJQ8nCqxOeTRrToEKtwT++36gTSlBGx\u0022\n+\t\u0022A/5dut82jJQ2jxN8RI8L9QFXrWi4xXnA2EqA10yjHiR6H9cj6MFiOnb5In1eWsRM\u0022\n+\t\u0022UM2v3e9tNsCAgBukPHAg1lQh07rvFKm/Bz9BCjaxorALINUfZ9DD64j2igLIxle2\u0022\n+\t\u0022DPxW8dI/F2loHMjXZjqG8RkqZUdoxtID5+90FgsGIfkMpqgRS05f4zPbCEHqCXl1\u0022\n+\t\u0022eO5HyELTgcVlLXXQDgAWnRzut1hFJeczY1tjQQno6f6s+nMydLN26WuU4s3UYvOu\u0022\n+\t\u0022OsUxRlJu7TSRHqDC3lSE5XggVkzdaPkuKGQbGpny+01/47hfXXNB7HntWNZ6N2Vw\u0022\n+\t\u0022p7G6OfY+YQrZwIaQmhrIqJZuigsrbe3W+gdn5ykE9+Ky0VgVUsfxo52mwFYs1JKY\u0022\n+\t\u00222PGDuWx8M6DlS6qQkvHaRUo0FMd8TsSlbF0/v965qGFKhSDeQoMpYnwcmQilRh/0\u0022\n+\t\u0022ayLThlHLN81gSkJjVrPI0Y8xCVPB4twb1PFUd2fPM3sA1tJ83sZ5v8vgFv2yofKR\u0022\n+\t\u0022PB0t6JzUA81mSqM3kxl5e+IZwhYAyO0OTg3/fs8HqGTNKd9BqoUwSRBzp06JMg5b\u0022\n+\t\u0022rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt\u0022\n+\t\t\u0022\u005c\u0022}\u0022\n+\t \u0022],\u0022\n+\t \u0022\u005c\u0022trust_stores\u005c\u0022: [\u0022 /* named cert chains */\n+\t\t\u0022{\u0022\n+\t\t\t\u0022\u005c\u0022name\u005c\u0022: \u005c\u0022le_via_isrg\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022stack\u005c\u0022: [\u0022\n+\t\t\t\t\u0022\u005c\u0022isrg_root_x1\u005c\u0022,\u0022\n+\t\t\t\t\u0022\u005c\u0022LEX3_isrg_root_x1\u005c\u0022\u0022\n+\t\t\t\u0022]\u0022\n+\t\t\u0022}\u0022\n+\t \u0022],\u0022\n+\t \u0022\u005c\u0022s\u005c\u0022: [\u0022\n+\t\t\u0022{\u005c\u0022fetch_policy\u005c\u0022: {\u0022\n+\t\t\t\u0022\u005c\u0022endpoint\u005c\u0022:\u0022\t\t\u0022\u005c\u0022warmcat.com\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022port\u005c\u0022:\u0022\t\t\u0022443,\u0022\n+\t\t\t\u0022\u005c\u0022protocol\u005c\u0022:\u0022\t\t\u0022\u005c\u0022h1\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_method\u005c\u0022:\u0022\t\u0022\u005c\u0022GET\u005c\u0022,\u0022\n+#if defined(VIA_LOCALHOST_SOCKS)\n+\t\t\t\u0022\u005c\u0022http_url\u005c\u0022:\u0022\t\t\u0022\u005c\u0022policy/minimal-proxy-socks.json\u005c\u0022,\u0022\n+#else\n+\t\t\t\u0022\u005c\u0022http_url\u005c\u0022:\u0022\t\t\u0022\u005c\u0022policy/minimal-proxy.json\u005c\u0022,\u0022\n+#endif\n+\t\t\t\u0022\u005c\u0022tls\u005c\u0022:\u0022\t\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022opportunistic\u005c\u0022:\u0022\t\u0022true,\u0022\n+\t\t\t\u0022\u005c\u0022retry\u005c\u0022:\u0022\t\t\u0022\u005c\u0022default\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022tls_trust_store\u005c\u0022:\u0022\t\u0022\u005c\u0022le_via_isrg\u005c\u0022\u0022\n+\t\t\u0022}}\u0022\n+\t\u0022}\u0022\n+;\n+\n+#endif\n+\n+typedef struct myss {\n+\tstruct lws_ss_handle \t\t*ss;\n+\tvoid\t\t\t\t*opaque_data;\n+\t/* ... application specific state ... */\n+\tlws_sorted_usec_list_t\t\tsul;\n+} myss_t;\n+\n+static const char *canned_root_token_payload \u003d\n+\t\u0022grant_type\u003drefresh_token\u0022\n+\t\u0022\u0026refresh_token\u003dAtzr|IwEBIJedGXjDqsU_vMxykqOMg\u0022\n+\t\u0022SHfYe3CPcedueWEMWSDMaDnEmiW8RlR1Kns7Cb4B-TOSnqp7ifVsY4BMY2B8tpHfO39XP\u0022\n+\t\u0022zfu9HapGjTR458IyHX44FE71pWJkGZ79uVBpljP4sazJuk8XS3Oe_yLnm_DIO6fU1nU3Y\u0022\n+\t\u00220flYmsOiOAQE_gRk_pdlmEtHnpMA-9rLw3mkY5L89Ty9kUygBsiFaYatouROhbsTn8-jW\u0022\n+\t\u0022k1zZLUDpT6ICtBXSnrCIg0pUbZevPFhTwdXd6eX-u4rq0W-XaDvPWFO7au-iPb4Zk5eZE\u0022\n+\t\u0022iX6sissYrtNmuEXc2uHu7MnQO1hHCaTdIO2CANVumf-PHSD8xseamyh04sLV5JgFzY45S\u0022\n+\t\u0022KvKMajiUZuLkMokOx86rjC2Hdkx5DO7G-dbG1ufBDG-N79pFMSs7Ck5pc283IdLoJkCQc\u0022\n+\t\u0022AGvTX8o8I29QqkcGou-9TKhOJmpX8As94T61ok0UqqEKPJ7RhfQHHYdCtsdwxgvfVr9qI\u0022\n+\t\u0022xL_hDCcTho8opCVX-6QhJHl6SQFlTw13\u0022\n+\t\u0022\u0026client_id\u003d\u0022\n+\t\t\u0022amzn1.application-oa2-client.4823334c434b4190a2b5a42c07938a2d\u0022;\n+\n+/* secure streams payload interface */\n+\n+static int\n+myss_rx(void *userobj, const uint8_t *buf, size_t len, int flags)\n+{\n+//\tmyss_t *m \u003d (myss_t *)userobj;\n+\n+\tlwsl_user(\u0022%s: len %d, flags: %d\u005cn\u0022, __func__, (int)len, flags);\n+\tlwsl_hexdump_info(buf, len);\n+\n+\t/*\n+\t * If we received the whole message, for our example it means\n+\t * we are done.\n+\t */\n+\tif (flags \u0026 LWSSS_FLAG_EOM) {\n+\t\tbad \u003d 0;\n+\t\tinterrupted \u003d 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int\n+myss_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len,\n+\tint *flags)\n+{\n+\t//myss_t *m \u003d (myss_t *)userobj;\n+\n+\treturn 0;\n+}\n+\n+static int\n+myss_state(void *userobj, void *sh, lws_ss_constate_t state,\n+\t lws_ss_tx_ordinal_t ack)\n+{\n+\tmyss_t *m \u003d (myss_t *)userobj;\n+\n+\tlwsl_user(\u0022%s: %s, ord 0x%x\u005cn\u0022, __func__, lws_ss_state_name(state),\n+\t\t (unsigned int)ack);\n+\n+\tswitch (state) {\n+\tcase LWSSSCS_CREATING:\n+\t\tlws_ss_set_metadata(m-\u003ess, \u0022uptag\u0022, \u0022myuptag123\u0022, 10);\n+\t\tlws_ss_set_metadata(m-\u003ess, \u0022ctype\u0022, \u0022myctype\u0022, 7);\n+\t\tlws_ss_client_connect(m-\u003ess);\n+\t\tbreak;\n+\tcase LWSSSCS_ALL_RETRIES_FAILED:\n+\t\t/* if we're out of retries, we want to close the app and FAIL */\n+\t\tinterrupted \u003d 1;\n+\t\tbreak;\n+\tcase LWSSSCS_QOS_ACK_REMOTE:\n+\t\tlwsl_notice(\u0022%s: LWSSSCS_QOS_ACK_REMOTE\u005cn\u0022, __func__);\n+\t\tbreak;\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int\n+app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link,\n+\t\t int current, int target)\n+{\n+\tstruct lws_context *context \u003d lws_system_context_from_system_mgr(mgr);\n+\tlws_system_blob_t *ab \u003d lws_system_get_blob(context,\n+\t\t\t\tLWS_SYSBLOB_TYPE_AUTH, 1 /* AUTH_IDX_ROOT */);\n+\tsize_t size;\n+\n+\t/*\n+\t * For the things we care about, let's notice if we are trying to get\n+\t * past them when we haven't solved them yet, and make the system\n+\t * state wait while we trigger the dependent action.\n+\t */\n+\tswitch (target) {\n+\tcase LWS_SYSTATE_REGISTERED:\n+\t\tsize \u003d lws_system_blob_get_size(ab);\n+\t\tif (size)\n+\t\t\tbreak;\n+\n+\t\t/* let's register our canned root token so auth can use it */\n+\t\tlws_system_blob_direct_set(ab,\n+\t\t\t\t(const uint8_t *)canned_root_token_payload,\n+\t\t\t\tstrlen(canned_root_token_payload));\n+\t\tbreak;\n+\n+\tcase LWS_SYSTATE_OPERATIONAL:\n+\t\tif (current \u003d\u003d LWS_SYSTATE_OPERATIONAL) {\n+\t\t\tlws_ss_info_t ssi;\n+\n+\t\t\t/* We're making an outgoing secure stream ourselves */\n+\n+\t\t\tmemset(\u0026ssi, 0, sizeof(ssi));\n+\t\t\tssi.handle_offset \u003d offsetof(myss_t, ss);\n+\t\t\tssi.opaque_user_data_offset \u003d offsetof(myss_t,\n+\t\t\t\t\t\t\t opaque_data);\n+\t\t\tssi.rx \u003d myss_rx;\n+\t\t\tssi.tx \u003d myss_tx;\n+\t\t\tssi.state \u003d myss_state;\n+\t\t\tssi.user_alloc \u003d sizeof(myss_t);\n+\t\t\tssi.streamtype \u003d \u0022mintest\u0022;\n+\n+\t\t\tif (lws_ss_create(context, 0, \u0026ssi, NULL, NULL,\n+\t\t\t\t\t NULL, NULL)) {\n+\t\t\t\tlwsl_err(\u0022%s: failed to create secure stream\u005cn\u0022,\n+\t\t\t\t\t __func__);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t}\n+\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static lws_state_notify_link_t * const app_notifier_list[] \u003d {\n+\t\u0026nl, NULL\n+};\n+\n+static void\n+sigint_handler(int sig)\n+{\n+\tinterrupted \u003d 1;\n+}\n+\n+int main(int argc, const char **argv)\n+{\n+\tstruct lws_context_creation_info info;\n+\tstruct lws_context *context;\n+\tint n \u003d 0;\n+\n+\tsignal(SIGINT, sigint_handler);\n+\n+\tmemset(\u0026info, 0, sizeof info);\n+\tlws_cmdline_option_handle_builtin(argc, argv, \u0026info);\n+\n+\tlwsl_user(\u0022LWS secure streams test client [-d\u003cverb\u003e]\u005cn\u0022);\n+\n+\tinfo.fd_limit_per_thread \u003d 1 + 6 + 1;\n+\tinfo.port \u003d CONTEXT_PORT_NO_LISTEN;\n+#if defined(LWS_SS_USE_SSPC)\n+\tinfo.protocols \u003d lws_sspc_protocols;\n+\t{\n+\t\tconst char *p;\n+\n+\t\t/* connect to ssproxy via UDS by default, else via\n+\t\t * tcp connection to this port */\n+\t\tif ((p \u003d lws_cmdline_option(argc, argv, \u0022-p\u0022)))\n+\t\t\tinfo.ss_proxy_port \u003d atoi(p);\n+\n+\t\t/* UDS \u0022proxy.ss.lws\u0022 in abstract namespace, else this socket\n+\t\t * path; when -p given this can specify the network interface\n+\t\t * to bind to */\n+\t\tif ((p \u003d lws_cmdline_option(argc, argv, \u0022-i\u0022)))\n+\t\t\tinfo.ss_proxy_bind \u003d p;\n+\n+\t\t/* if -p given, -a specifies the proxy address to connect to */\n+\t\tif ((p \u003d lws_cmdline_option(argc, argv, \u0022-a\u0022)))\n+\t\t\tinfo.ss_proxy_address \u003d p;\n+\t}\n+#else\n+\tinfo.pss_policies_json \u003d default_ss_policy;\n+\tinfo.options \u003d LWS_SERVER_OPTION_EXPLICIT_VHOSTS |\n+\t\t LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;\n+#endif\n+#if defined(LWS_WITH_DETAILED_LATENCY)\n+\tinfo.detailed_latency_cb \u003d lws_det_lat_plot_cb;\n+\tinfo.detailed_latency_filepath \u003d \u0022/tmp/lws-latency-ssproxy\u0022;\n+#endif\n+\n+\t/* integrate us with lws system state management when context created */\n+\n+\tnl.name \u003d \u0022app\u0022;\n+\tnl.notify_cb \u003d app_system_state_nf;\n+\tinfo.register_notifier_list \u003d app_notifier_list;\n+\n+\t/* create the context */\n+\n+\tcontext \u003d lws_create_context(\u0026info);\n+\tif (!context) {\n+\t\tlwsl_err(\u0022lws init failed\u005cn\u0022);\n+\t\treturn 1;\n+\t}\n+\n+\t/*\n+\t * Set the related lws_system blobs\n+\t *\n+\t * ...direct_set() sets a pointer, so the thing pointed to has to have\n+\t * a suitable lifetime, eg, something that already exists on the heap or\n+\t * a const string in .rodata like this\n+\t */\n+\n+\tlws_system_blob_direct_set(lws_system_get_blob(context,\n+\t\t\t\t LWS_SYSBLOB_TYPE_DEVICE_SERIAL, 0),\n+\t\t\t\t (const uint8_t *)\u0022SN12345678\u0022, 10);\n+\tlws_system_blob_direct_set(lws_system_get_blob(context,\n+\t\t\t\t LWS_SYSBLOB_TYPE_DEVICE_FW_VERSION, 0),\n+\t\t\t\t (const uint8_t *)\u0022v0.01\u0022, 5);\n+\n+\t/*\n+\t * ..._heap_append() appends to a buflist kind of arrangement on heap,\n+\t * just one block is fine, otherwise it will concatenate the fragments\n+\t * in the order they were appended (and take care of freeing them at\n+\t * context destroy time). ..._heap_empty() is also available to remove\n+\t * everything that was already allocated.\n+\t *\n+\t * Here we use _heap_append() just so it's tested as well as direct set.\n+\t */\n+\n+\tlws_system_blob_heap_append(lws_system_get_blob(context,\n+\t\t\t\t LWS_SYSBLOB_TYPE_DEVICE_TYPE, 0),\n+\t\t\t\t (const uint8_t *)\u0022spacerocket\u0022, 11);\n+\n+\t/* the event loop */\n+\n+\twhile (n \u003e\u003d 0 \u0026\u0026 !interrupted)\n+\t\tn \u003d lws_service(context, 0);\n+\n+\tlws_context_destroy(context);\n+\n+\tlwsl_user(\u0022Completed: %s\u005cn\u0022, bad ? \u0022failed\u0022 : \u0022OK\u0022);\n+\n+\treturn bad;\n+}\ndiff --git a/scripts/travis_control.sh b/scripts/travis_control.sh\nindex e608f18..04a66cd 100755\n--- a/scripts/travis_control.sh\n+++ b/scripts/travis_control.sh\n@@ -1,7 +1,7 @@\n #!/bin/bash\n \n if [ \u0022$COVERITY_SCAN_BRANCH\u0022 !\u003d 1 -a \u0022$TRAVIS_OS_NAME\u0022 \u003d \u0022osx\u0022 ]; then\n-\tif [ \u0022$LWS_METHOD\u0022 !\u003d \u0022mbedtls\u0022 ] ; then\n+\tif [ \u0022$LWS_METHOD\u0022 !\u003d \u0022mbedtls\u0022 -a \u0022$LWS_METHOD\u0022 !\u003d \u0022ss+mbedtls\u0022 ] ; then\n \t\tmkdir build \u0026\u0026 cd build \u0026\u0026\n \t\tcmake -DOPENSSL_ROOT_DIR\u003d\u0022/usr/local/opt/openssl\u0022 $CMAKE_ARGS .. \u0026\u0026\n \t\tcmake --build .\n@@ -34,7 +34,7 @@ else\n \t\t\t\t\tcmake --build . \u0026\u0026\n \t\t\t\t\t../scripts/h2load-smp.sh\n \t\t\t\telse\n-\t\t\t\t\tif [ \u0022$LWS_METHOD\u0022 \u003d \u0022mbedtls\u0022 ] ; then\n+\t\t\t\t\tif [ \u0022$LWS_METHOD\u0022 \u003d \u0022mbedtls\u0022 -o \u0022$LWS_METHOD\u0022 \u003d \u0022ss+mbedtls\u0022 ] ; then\n \t\t\t\t\t\tcmake $CMAKE_ARGS .. \u0026\u0026\n \t\t\t\t\t\tcmake --build . \u0026\u0026\n \t\t\t\t\t\tsudo make install \u0026\u0026\ndiff --git a/scripts/travis_install.sh b/scripts/travis_install.sh\nindex dffe915..9b84cb9 100755\n--- a/scripts/travis_install.sh\n+++ b/scripts/travis_install.sh\n@@ -21,7 +21,7 @@ then\n \t\tsudo update-ca-certificates\n \tfi\n \n-\tif [ \u0022$LWS_METHOD\u0022 \u003d\u003d \u0022mbedtls\u0022 ];\n+\tif [ \u0022$LWS_METHOD\u0022 \u003d\u003d \u0022mbedtls\u0022 -o \u0022$LWS_METHOD\u0022 \u003d\u003d \u0022ss+mbedtls\u0022 ];\n \tthen\n \t\tsudo apt-get install -y -qq realpath libjemalloc1 libev4 libuv-dev valgrind\n \t\twget https://libwebsockets.org/openssl-1.1.0-trusty.tar.bz2 -O/tmp/openssl.tar.bz2\n","s":{"c":1756842600,"u": 72042}}
],"g": 278035,"chitpc": 0,"ehitpc": 0,"indexed":0
,
"ab": 0, "si": 0, "db":0, "di":0, "sat":0, "lfc": "0000"}