diff --git a/.sai.json b/.sai.json index f8fdfc87b0..029ddc7ba5 100644 --- a/.sai.json +++ b/.sai.json @@ -12,7 +12,7 @@ }, "netbsd-OSX-bigsur/x86_64-intel-i3/llvm": { "build": [ - "mkdir -p build destdir; cd build; CCACHE_DISABLE=1 LD_LIBRARY_PATH=../destdir/usr/local/share/libwebsockets-test-server/plugins:../destdir/usr/local/lib MACOSX_DEPLOYMENT_TARGET=15.7 cmake .. -DCMAKE_MAKE_PROGRAM=/usr/bin/make ${cmake}", + "mkdir -p build destdir; cd build; CCACHE_DISABLE=1 LD_LIBRARY_PATH=../destdir/usr/local/share/libwebsockets-test-server/plugins:../destdir/usr/local/lib MACOSX_DEPLOYMENT_TARGET=15.7 cmake .. -DCMAKE_MAKE_PROGRAM=/usr/bin/make -DLWS_GNUTLS_INCLUDE_DIRS=/usr/local/include -DLWS_GNUTLS_LIBRARIES=/usr/local/lib/libgnutls.dylib ${cmake}", "cd build && make -j$SAI_PARALLEL && rm -rf ../destdir && make -j$SAI_PARALLEL DESTDIR=../destdir install", "cd build ; export LD_LIBRARY_PATH=\"$HOME/jobs/$SAI_VN/src/build/lib\" ; ctest -j$SAI_PARALLEL --output-on-failure --repeat until-pass:3", "cd build && SAI_CPACK=\"-G ZIP\" ${cpack}" @@ -20,7 +20,7 @@ }, "netbsd-OSX-tahoe/aarch64-apple-m1/llvm": { "build": [ - "mkdir -p build destdir; cd build; CCACHE_DISABLE=1 LD_LIBRARY_PATH=../destdir/usr/local/share/libwebsockets-test-server/plugins:../destdir/usr/local/lib MACOSX_DEPLOYMENT_TARGET=26 cmake .. -DCMAKE_MAKE_PROGRAM=/usr/bin/make ${cmake}", + "mkdir -p build destdir; cd build; CCACHE_DISABLE=1 LD_LIBRARY_PATH=../destdir/usr/local/share/libwebsockets-test-server/plugins:../destdir/usr/local/lib MACOSX_DEPLOYMENT_TARGET=26 cmake .. -DCMAKE_MAKE_PROGRAM=/usr/bin/make -DLWS_GNUTLS_INCLUDE_DIRS=/System/Volumes/Data/opt/homebrew/Cellar/gnutls/3.8.13_2/include -DLWS_GNUTLS_LIBRARIES=/System/Volumes/Data/opt/homebrew/Cellar/gnutls/3.8.13_2/lib/libgnutls.dylib ${cmake}", "cd build && make -j$SAI_PARALLEL && rm -rf ../destdir && make -j$SAI_PARALLEL DESTDIR=../destdir install", "cd build ; export LD_LIBRARY_PATH=\"$HOME/jobs/$SAI_VN/src/build/lib\" ; ctest -j$SAI_PARALLEL --output-on-failure --repeat until-pass:3", "cd build && SAI_CPACK=\"-G ZIP\" ${cpack}" @@ -69,7 +69,7 @@ "w11/x86_64-amd/msvc": { "default": false, "build": [ - "\"C:\\Program Files\\CMake\\bin\\cmake.exe\" -S . -B build -DOPENSSL_ROOT_DIR=\"C:\\Users\\andy\\vcpkg\\packages\\openssl_x64-windows\" -DLWS_EXT_PTHREAD_INCLUDE_DIR=\"C:\\Program Files (x86)\\pthreads\\include\" -DLWS_EXT_PTHREAD_LIBRARIES=\"C:\\Program Files (x86)\\pthreads\\lib\\x64\\libpthreadGC2.a\" ${cmake}", + "\"C:\\Program Files\\CMake\\bin\\cmake.exe\" -S . -B build -DLWS_WITH_SCHANNEL=0 -DOPENSSL_ROOT_DIR=\"C:\\Users\\andy\\vcpkg\\packages\\openssl_x64-windows\" -DLWS_EXT_PTHREAD_INCLUDE_DIR=\"C:\\Program Files (x86)\\pthreads\\include\" -DLWS_EXT_PTHREAD_LIBRARIES=\"C:\\Program Files (x86)\\pthreads\\lib\\x64\\libpthreadGC2.a\" ${cmake}", "\"C:\\Program Files\\CMake\\bin\\cmake.exe\" --build build --config DEBUG --parallel %SAI_PARALLEL%", "\"C:\\Program Files\\CMake\\bin\\ctest.exe\" --test-dir build -C DEBUG -j%SAI_PARALLEL% --output-on-failure --repeat until-pass:3" ] @@ -120,10 +120,14 @@ "cmake": "-DLWS_WITH_SYS_FAULT_INJECTION=1 -DLWS_WITH_MINIMAL_EXAMPLES=1 -DLWS_WITH_CBOR=1" }, "esp32-heltec": { - "cmake": "-DLWS_IPV6=0", + "cmake": "-DLWS_IPV6=1", "cpack": "esp-heltec-wb32", "platforms": "none, freertos-espidf/xl6-esp32/gcc" }, + "mbedtls4": { + "cmake": "-DLWS_WITH_MBEDTLS=1 -DCMAKE_PREFIX_PATH=/opt/mbedtls -DLWS_WITH_MINIMAL_EXAMPLES=1", + "platforms": "none, rocky9/aarch64-a72a55-rk3588/gcc" + }, "gnutls": { "cmake": "-DLWS_WITH_GNUTLS=1 -DLWS_GNUTLS_INCLUDE_DIRS=\"/opt/gnutls/lib/includes\" -DLWS_GNUTLS_LIBRARIES=\"/opt/gnutls/lib/.libs/libgnutls.so\" -DLWS_ROLE_QUIC=1", "platforms": "none, rocky9/aarch64-a72a55-rk3588/gcc" @@ -156,10 +160,10 @@ "cmake": "-DLWS_WITH_MINIMAL_EXAMPLES=1 -DLWS_WITH_TLS_SESSIONS=1" }, "h1only-examples": { - "cmake": "-DLWS_WITH_HTTP2=0 -DLWS_WITH_MINIMAL_EXAMPLES=1" + "cmake": "-DLWS_WITH_HTTP3=0 -DLWS_WITH_HTTP2=0 -DLWS_WITH_MINIMAL_EXAMPLES=1" }, "h1only-notls": { - "cmake": "-DLWS_WITH_HTTP2=0 -DLWS_WITH_SSL=OFF" + "cmake": "-DLWS_WITH_HTTP3=0 -DLWS_WITH_HTTP2=0 -DLWS_WITH_SSL=OFF" }, "unix-domain": { "cmake": "-DUNIX_SOCK=1" @@ -203,7 +207,7 @@ "cmake": "-DLWS_WITH_MBEDTLS=1 -DLWS_WITHOUT_TESTAPPS=1" }, "mbedtls": { - "cmake": "-DLWS_WITH_MBEDTLS=1 -DLWS_WITH_QUIC=1 -DLWS_WITH_HTTP2=1 -DLWS_WITH_LWSWS=1 -DLWS_WITH_MINIMAL_EXAMPLES=1 -DLWS_WITH_JOSE=1 -DCMAKE_BUILD_TYPE=DEBUG" + "cmake": "-DLWS_WITH_MBEDTLS=1 -DLWS_WITH_HTTP3=1 -DLWS_WITH_HTTP2=1 -DLWS_WITH_LWSWS=1 -DLWS_WITH_MINIMAL_EXAMPLES=1 -DLWS_WITH_JOSE=1 -DCMAKE_BUILD_TYPE=DEBUG" }, "mbedtls-metrics": { "cmake": "-DLWS_WITH_MBEDTLS=1 -DLWS_WITH_HTTP2=1 -DLWS_WITH_LWSWS=1 -DLWS_WITH_MINIMAL_EXAMPLES=1 -DLWS_WITH_JOSE=1 -DCMAKE_BUILD_TYPE=DEBUG -DLWS_WITH_SYS_METRICS=1" diff --git a/CMakeLists-implied-options.txt b/CMakeLists-implied-options.txt index b87026ee44..0981925fc2 100644 --- a/CMakeLists-implied-options.txt +++ b/CMakeLists-implied-options.txt @@ -281,6 +281,7 @@ if (LWS_WITH_HTTP2) endif() if (LWS_WITH_HTTP3) set(LWS_ROLE_H3 1) + set(LWS_ROLE_WT 1) endif() if (LWS_WITH_CGI) set(LWS_ROLE_CGI 1) @@ -355,6 +356,7 @@ if (LWS_PLAT_FREERTOS) set(LWS_HAVE_REALLOC 1) set(LWS_HAVE_GETIFADDRS 1) set(LWS_WITH_CUSTOM_HEADERS 0) + set(LWS_WITH_STUB 0) endif() if (LWS_WITHOUT_TESTAPPS) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a76aed6d1..893ac79efd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,12 +148,17 @@ option(LWS_WITH_NETWORK "Compile with network-related code" ON) option(LWS_ROLE_H1 "Compile with support for http/1 (needed for ws)" ON) option(LWS_ROLE_WS "Compile with support for websockets" ON) option(LWS_ROLE_MQTT "Build with support for MQTT client" OFF) -option(LWS_ROLE_QUIC "Build with support for QUIC transport" OFF) +if (DEFINED LWS_WITH_UDP AND NOT LWS_WITH_UDP) + set(LWS_QUIC_DEFAULT OFF) +else() + set(LWS_QUIC_DEFAULT ON) +endif() +option(LWS_ROLE_QUIC "Build with support for QUIC transport" ${LWS_QUIC_DEFAULT}) option(LWS_ROLE_DBUS "Compile with support for DBUS" OFF) option(LWS_ROLE_RAW_PROXY "Raw packet proxy" OFF) option(LWS_ROLE_RAW_FILE "Compile with support for raw files" ON) option(LWS_WITH_HTTP2 "Compile with server support for HTTP/2" ON) -option(LWS_WITH_HTTP3 "Compile with support for HTTP/3" OFF) +option(LWS_WITH_HTTP3 "Compile with support for HTTP/3" ${LWS_QUIC_DEFAULT}) option(LWS_WITH_LS_QPACK "Compile tests against ls-qpack for correctness testing" OFF) option(LWS_WITH_LWSWS "Libwebsockets Webserver" OFF) option(LWS_WITH_CGI "Include CGI (spawn process with network-connected stdin/out/err) APIs" OFF) @@ -179,6 +184,9 @@ option(LWS_WITH_ALEXA "Enable Alexa example" OFF) option(LWS_WITH_GTK "Enable gtk example" OFF) option(LWS_WITH_FTS "Full Text Search support" OFF) option(LWS_WITH_SYS_ASYNC_DNS "Nonblocking internal IPv4 + IPv6 DNS resolver" OFF) +if (LWS_WITH_HTTP3) + set(LWS_WITH_SYS_ASYNC_DNS 1) +endif() option(LWS_WITH_SYS_ASYNC_DNS_DNSSEC "Include DNSSEC parsing/validation in async-dns (requires crypto)" OFF) option(LWS_WITH_AUTHORITATIVE_DNS "Authoritative DNS zone signer / server" OFF) option(LWS_WITH_SYS_NTPCLIENT "Build in tiny ntpclient good for tls date validation and run via lws_system" OFF) @@ -238,13 +246,35 @@ option(LWS_WITH_SSL "Include SSL support (defaults to OpenSSL or similar, mbedTL option(LWS_WITH_MBEDTLS "Use mbedTLS (>=2.0) replacement for OpenSSL. When setting this, you also may need to specify LWS_MBEDTLS_LIBRARIES and LWS_MBEDTLS_INCLUDE_DIRS" OFF) option(LWS_WITH_BEARSSL "Use BearSSL replacement for OpenSSL. When setting this, you also may need to specify LWS_BEARSSL_LIBRARIES and LWS_BEARSSL_INCLUDE_DIRS" OFF) set(LWS_BEARSSL_PROFILE "full" CACHE STRING "BearSSL profile to use (e.g. full, client, minimal)") -if (WIN32) -option(LWS_WITH_SCHANNEL "Use Windows SChannel for SSL" OFF) -endif() option(LWS_WITH_BORINGSSL "Use BoringSSL replacement for OpenSSL" OFF) -option(LWS_WITH_GNUTLS "Use GnuTLS for SSL" OFF) +option(LWS_WITH_AWSLC "Use AWSLC replacement for OpenSSL" OFF) option(LWS_WITH_CYASSL "Use CyaSSL replacement for OpenSSL. When setting this, you also need to specify LWS_CYASSL_LIBRARIES and LWS_CYASSL_INCLUDE_DIRS" OFF) option(LWS_WITH_WOLFSSL "Use wolfSSL replacement for OpenSSL. When setting this, you also may need to specify LWS_WOLFSSL_LIBRARIES and LWS_WOLFSSL_INCLUDE_DIRS" OFF) + +if (LWS_WITH_BEARSSL) + set(LWS_ROLE_QUIC 0) + set(LWS_WITH_HTTP3 0) +endif() + +if (LWS_WITH_SSL AND NOT (LWS_WITH_BORINGSSL OR LWS_WITH_AWSLC OR LWS_WITH_MBEDTLS OR LWS_WITH_WOLFSSL OR LWS_WITH_CYASSL OR LWS_WITH_GNUTLS OR LWS_WITH_BEARSSL OR LWS_WITH_SCHANNEL)) + set(LWS_ROLE_QUIC 0) + set(LWS_WITH_HTTP3 0) +endif() + +if (WIN32 AND NOT (LWS_WITH_BORINGSSL OR LWS_WITH_AWSLC OR LWS_WITH_MBEDTLS OR LWS_WITH_WOLFSSL OR LWS_WITH_CYASSL OR LWS_WITH_BEARSSL OR LWS_WITH_GNUTLS)) + set(LWS_SCHANNEL_DEFAULT ON) +else() + set(LWS_SCHANNEL_DEFAULT OFF) +endif() +if (WIN32) + option(LWS_WITH_SCHANNEL "Use Windows SChannel for SSL" ${LWS_SCHANNEL_DEFAULT}) +endif() + +if (LWS_ROLE_QUIC AND NOT (LWS_WITH_BORINGSSL OR LWS_WITH_AWSLC OR LWS_WITH_MBEDTLS OR LWS_WITH_WOLFSSL OR LWS_WITH_CYASSL OR LWS_WITH_BEARSSL OR LWS_WITH_SCHANNEL OR ESP_PLATFORM OR LWS_WITH_ESP32)) + option(LWS_WITH_GNUTLS "Use GnuTLS for SSL" ON) +else() + set(LWS_WITH_GNUTLS OFF CACHE BOOL "Use GnuTLS for SSL" FORCE) +endif() option(LWS_SSL_CLIENT_USE_OS_CA_CERTS "SSL support should make use of the OS-installed CA root certs" ON) option(LWS_TLS_LOG_PLAINTEXT_RX "For debugging log the received plaintext as soon as decrypted" OFF) option(LWS_TLS_LOG_PLAINTEXT_TX "For debugging log the transmitted plaintext just before encryption" OFF) @@ -392,6 +422,12 @@ else() set(LWS_WITH_BINDTODEVICE 0) set(LWS_WITH_LIBCAP OFF) endif() + +if (LWS_WITH_NETLINK OR LWS_WITH_ESP32 OR ESP_PLATFORM) + set(LWS_WITH_ROUTING 1) +else() + set(LWS_WITH_ROUTING 0) +endif() option(LWS_WITH_MCUFONT_ENCODER "Build the ttf to mcufont encoder" OFF) option(LWS_WITH_WAKE_LOGGING "Log each wake reason" OFF) option(LWS_WITH_EXTIP "Include ExtIP IP tracking support" OFF) @@ -599,7 +635,8 @@ endif() if (WIN32) message(STATUS "LWS_BUILTIN_PLUGIN_NAMES: ${LWS_BUILTIN_PLUGIN_NAMES}") message(STATUS "CONFIG_SCOPE: ${LWS_BUILTIN_PLUGIN_NAMES}") - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/win32port/version.rc.in ${CMAKE_CURRENT_BINARY_DIR}/win32port/version.rc @ONLY) + message("LWS_HAVE_SSL_set_tlsext_host_name: ${LWS_HAVE_SSL_set_tlsext_host_name}") +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/win32port/version.rc.in ${CMAKE_CURRENT_BINARY_DIR}/win32port/version.rc @ONLY) set(RESOURCES ${CMAKE_CURRENT_BINARY_DIR}/win32port/version.rc) include_directories("${CMAKE_CURRENT_SOURCE_DIR}/win32port/win32helpers") endif() @@ -1257,12 +1294,14 @@ endif() message(STATUS "LWS_BUILTIN_PLUGIN_NAMES: ${LWS_BUILTIN_PLUGIN_NAMES}") message(STATUS "CONFIG_SCOPE: ${LWS_BUILTIN_PLUGIN_NAMES}") +message("LWS_HAVE_SSL_set_tlsext_host_name: ${LWS_HAVE_SSL_set_tlsext_host_name}") configure_file(${PROJECT_SOURCE_DIR}/cmake/LwsCheckRequirements.cmake ${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/LwsCheckRequirements.cmake @ONLY) message(STATUS "LWS_BUILTIN_PLUGIN_NAMES: ${LWS_BUILTIN_PLUGIN_NAMES}") message(STATUS "CONFIG_SCOPE: ${LWS_BUILTIN_PLUGIN_NAMES}") +message("LWS_HAVE_SSL_set_tlsext_host_name: ${LWS_HAVE_SSL_set_tlsext_host_name}") configure_file(${PROJECT_SOURCE_DIR}/cmake/LwsCheckRequirements.cmake ${PROJECT_BINARY_DIR}/LwsCheckRequirements.cmake @ONLY) @@ -1270,6 +1309,7 @@ configure_file(${PROJECT_SOURCE_DIR}/cmake/LwsCheckRequirements.cmake # Generate version info for both build-tree and install-tree. message(STATUS "LWS_BUILTIN_PLUGIN_NAMES: ${LWS_BUILTIN_PLUGIN_NAMES}") message(STATUS "CONFIG_SCOPE: ${LWS_BUILTIN_PLUGIN_NAMES}") +message("LWS_HAVE_SSL_set_tlsext_host_name: ${LWS_HAVE_SSL_set_tlsext_host_name}") configure_file(${PROJECT_SOURCE_DIR}/cmake/libwebsockets-config-version.cmake.in ${PROJECT_BINARY_DIR}/libwebsockets-config-version.cmake @ONLY) @@ -1281,6 +1321,7 @@ set(LWS__INCLUDE_DIRS set(LIBWEBSOCKETS_INCLUDE_DIRS ${LWS__INCLUDE_DIRS} CACHE PATH "Libwebsockets include directories") message(STATUS "LWS_BUILTIN_PLUGIN_NAMES: ${LWS_BUILTIN_PLUGIN_NAMES}") message(STATUS "CONFIG_SCOPE: ${LWS_BUILTIN_PLUGIN_NAMES}") +message("LWS_HAVE_SSL_set_tlsext_host_name: ${LWS_HAVE_SSL_set_tlsext_host_name}") configure_file(${PROJECT_SOURCE_DIR}/cmake/libwebsockets-config.cmake.in ${PROJECT_BINARY_DIR}/libwebsockets-config.cmake @ONLY) @@ -1324,6 +1365,7 @@ endif() # Generate the lws_config.h that includes all the public compilation settings. message(STATUS "LWS_BUILTIN_PLUGIN_NAMES: ${LWS_BUILTIN_PLUGIN_NAMES}") message(STATUS "CONFIG_SCOPE: ${LWS_BUILTIN_PLUGIN_NAMES}") +message("LWS_HAVE_SSL_set_tlsext_host_name: ${LWS_HAVE_SSL_set_tlsext_host_name}") configure_file( "${PROJECT_SOURCE_DIR}/cmake/lws_config.h.in" "${PROJECT_BINARY_DIR}/lws_config.h") @@ -1410,6 +1452,7 @@ set(LIB_LIST_AT_END ${STRIPPED_LIB_LIST_AT_END}) message(STATUS "LWS_BUILTIN_PLUGIN_NAMES: ${LWS_BUILTIN_PLUGIN_NAMES}") message(STATUS "CONFIG_SCOPE: ${LWS_BUILTIN_PLUGIN_NAMES}") +message("LWS_HAVE_SSL_set_tlsext_host_name: ${LWS_HAVE_SSL_set_tlsext_host_name}") configure_file(${PROJECT_SOURCE_DIR}/cmake/libwebsockets-config.cmake.in ${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/libwebsockets-config.cmake @ONLY) diff --git a/README.md b/README.md index 06b02b94ab..0f7e0409f7 100644 --- a/README.md +++ b/README.md @@ -5,21 +5,26 @@ ** NEW features available on main ** - Support for SChannel (windows native TLS), GnuTLS, and BearSSL added - - QUIC transport protocol implementation, using openssl, aws-lc, wolfssl, boringssl, libressl, mbedtls, gnutls, and sc + - QUIC + H3 + Webtranspoer implementation, using aws-lc, wolfssl, boringssl, libressl, gnutls, and schannel (OpenSSL and mbedtls are not compatible, but are fine for h1/h2) + + - On Windows, Schannel is the default, else GnuTLS is the default if you are want h3, otherwise OpenSSL | TLS Library | Server TLS | Client TLS | QUIC Transport (TLS 1.3) | WSS / HTTPS | MQTT over TLS | ALPN (HTTP/2) | DTLS (WebRTC) | Session Cache | JIT Trust | GenCrypto | | :--- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | -| **OpenSSL** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | +| **GnuTLS** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **No** | **Yes** | +| **OpenSSL** | **Yes** | **Yes** | **No*** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | | **LibreSSL** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **No** | **Yes** | | **AWS-LC** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **No** | **Yes** | | **BoringSSL** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **No** | **Yes** | | **wolfSSL** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **No** | **Yes** | -| **mbedTLS** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | -| **GnuTLS** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **No** | **Yes** | +| **mbedTLS** | **Yes** | **Yes** | **No** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | | **SChannel** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **No** | **Yes** | | **BearSSL** | **Yes** | **Yes** | **No** | **Yes** | **Yes** | **Yes** | **No** | **Yes** | **Yes** | **Yes** | +\* *Note: 1) Upstream OpenSSL does not provide the necessary QUIC TLS API (`SSL_set_quic_method`) to act as a cryptographic engine for LWS's QUIC transport. If you need QUIC/HTTP3 support, we recommend using BoringSSL, GnuTLS, WolfSSL, or the `quictls` fork of OpenSSL.* + + - DHT support built-in: `-DLWS_WITH_DHT=1` ** v4.5 is released, you can follow it on v4.5-stable ** @@ -99,7 +104,7 @@ and am starting to port more cases from there into SS-based examples. |Loop support, sul scheduler|default, event libs|same| |Supports comms mode|Client, Server, Raw|same| |Supports protocols|h1, h2, ws, mqtt (client)|same| -|TLS support|mbedtls (including v3), openssl (including v3), wolfssl, boringssl, aws-lc, libressl|same| +|TLS support|mbedtls (including v3 and v4, no QUIC), openssl (including v3), wolfssl, boringssl, aws-lc, libressl|same| |Serializable, proxiable, muxable, transportable|No|Yes| |Auto-allocated per-connection user object|pss specified in lws_protocols|Specified in ss info struct| |Connection User API|Protocol-specific lws_protocols cbs (> 100)|SS API (rx, tx, state callbacks only)| diff --git a/READMEs/README.quic-0rtt.md b/READMEs/README.quic-0rtt.md new file mode 100644 index 0000000000..d38839b512 --- /dev/null +++ b/READMEs/README.quic-0rtt.md @@ -0,0 +1,104 @@ +# QUIC 0-RTT / Early Data + +libwebsockets supports QUIC 0-RTT (Early Data) to allow clients to send data before the TLS 1.3 handshake fully completes, reducing latency for resuming connections. + +Because 0-RTT data is susceptible to replay attacks, the implementation uses an explicit opt-in model. Existing applications using QUIC or HTTP/3 will ignore 0-RTT by default and continue operating with the standard `LWS_CALLBACK_CLIENT_ESTABLISHED`. + +## How it works + +When a client connection initiates a handshake with a server it has previously connected to, it can attempt to send 0-RTT data using early TLS secrets. +- If the server accepts it, the client's 0-RTT data is processed immediately. +- If the server rejects it, the connection falls back to the standard 1-RTT handshake. + +## Enabling 0-RTT + +To enable 0-RTT capabilities on a connection, both the client and server must explicitly allow it using flags and options: + +### Client + +When creating a client connection, set the `LCCSCF_ALLOW_EARLY_DATA` flag in the `ssl_connection` member of your `struct lws_client_connect_info`: + +```c +struct lws_client_connect_info i; +memset(&i, 0, sizeof(i)); +// ... +i.ssl_connection = LCCSCF_USE_SSL | LCCSCF_ALLOW_EARLY_DATA; +// ... +lws_client_connect_via_info(&i); +``` + +### Server + +When creating the server vhost, add the `LWS_SERVER_OPTION_ALLOW_EARLY_DATA` flag to the vhost `options`: + +```c +struct lws_context_creation_info info; +memset(&info, 0, sizeof(info)); +// ... +info.options |= LWS_SERVER_OPTION_ALLOW_EARLY_DATA; +// ... +lws_create_context(&info); +``` + +## Opting a Stream into 0-RTT + +When early data is possible on a connection, the protocol callback will receive a new reason: `LWS_CALLBACK_CLIENT_ESTABLISHED_EARLY`. + +To opt a specific stream into sending 0-RTT data, your callback **must return `1`** when handling this reason: + +```c +static int +callback_example(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + switch (reason) { + case LWS_CALLBACK_CLIENT_ESTABLISHED_EARLY: + /* We have an opportunity to send 0-RTT data. + * Return 1 to opt-in and become writable immediately. + * Return 0 (default) to ignore 0-RTT. + */ + return 1; + + case LWS_CALLBACK_CLIENT_ESTABLISHED: + /* The traditional handshake has completed. */ + break; + + // ... + } + return 0; +} +``` + +If you return `1`, the stream opts into 0-RTT, and LWS will immediately call `lws_callback_on_writable(wsi)` for that stream so you can send your early data payload. + +> [!NOTE] +> Opting into 0-RTT does not skip the normal `LWS_CALLBACK_CLIENT_ESTABLISHED`. You will still receive `LWS_CALLBACK_CLIENT_ESTABLISHED` when the QUIC handshake actually completes. + +## Handling Rejection and Idempotency + +### Client-side Rejection Status + +Since 0-RTT can be rejected by the server (e.g. if the server lost its session ticket keys), the client needs to know if the early data it sent was actually accepted. +You can query the status of 0-RTT using the `lws_tls_0rtt_status(wsi)` API: + +```c +enum lws_0rtt_status status = lws_tls_0rtt_status(wsi); + +if (status == LWS_0RTT_STATUS_REJECTED) { + /* 0-RTT was rejected by the server. Any early data sent must be re-sent. */ +} +``` + +### Server-side Idempotency + +Because 0-RTT data can be intercepted and replayed by attackers, servers MUST ensure that any actions taken based on 0-RTT data are strictly idempotent (e.g., HTTP GET requests without side effects). + +Servers can check if incoming data was received during the 0-RTT phase by calling `lws_rx_is_early_data(wsi)`: + +```c +if (lws_rx_is_early_data(wsi)) { + /* Data was received via 0-RTT. Enforce idempotency! + * Do not process state-changing requests like POST or DELETE here. + */ +} +``` diff --git a/READMEs/README.quic.md b/READMEs/README.quic.md index 2cced1db4d..fbedb48618 100644 --- a/READMEs/README.quic.md +++ b/READMEs/README.quic.md @@ -196,3 +196,65 @@ SChannel is native to Windows, so no third-party TLS library compilation is requ cmake .. -DLWS_WITH_SCHANNEL=ON -DLWS_ROLE_QUIC=ON cmake --build . --config Release ``` + +--- + +## Testing QUIC and HTTP/3 Compliance + +lws uses `h3spec` to validate its QUIC and HTTP/3 implementation against the RFCs. The `ctest` infrastructure automatically discovers and runs the `h3spec` test suite against the `lws-minimal-quic-client-server` test application if the `h3spec` executable is found in your system's `PATH`. + +### Enabling `h3spec` tests in CI or locally + +To enable `h3spec` testing, simply download the pre-compiled static binary for your platform from the [h3spec GitHub releases](https://github.com/kazu-yamamoto/h3spec/releases) and place it somewhere in your `PATH` (e.g., `/usr/local/bin`). + +**Example for Linux x86_64:** +```bash +wget https://github.com/kazu-yamamoto/h3spec/releases/download/v0.1.13/h3spec-linux-x86_64 +chmod +x h3spec-linux-x86_64 +sudo cp h3spec-linux-x86_64 /usr/local/bin/h3spec +``` + +Once installed, re-run `cmake` on your lws build directory so it can discover the `h3spec` executable. Then, simply run `ctest` (or `make test`) as usual. The `h3spec` test will spawn a temporary test server in the background, run the compliance suite, and tear down the server automatically. + +--- + +## Congestion Control + +Libwebsockets features a pluggable QUIC Congestion Control architecture. By default, it uses a New Reno algorithm, but we also provide an implementation of CUBIC. + +### Selecting a Congestion Control Algorithm + +You can select the congestion control algorithm used for the context by configuring `quic_cc_ops` in `struct lws_context_creation_info`. We export two built-in implementations natively in `lws-quic.h`: + +- `lws_cc_ops_newreno` +- `lws_cc_ops_cubic` + +Example of selecting CUBIC: +```c +struct lws_context_creation_info info; +memset(&info, 0, sizeof(info)); +/* ... other config ... */ +info.quic_cc_ops = &lws_cc_ops_cubic; + +struct lws_context *context = lws_create_context(&info); +``` + +### Writing Your Own Congestion Control Algorithm + +If you need a specialized algorithm (like BBR), you can easily plug it in by implementing the `struct lws_cc_ops` interface defined in `lws-quic.h`: + +```c +struct lws_cc_ops { + void (*init)(struct lws *nwsi); + void (*on_sent)(struct lws *nwsi, size_t bytes); + void (*on_ack)(struct lws *nwsi, size_t bytes_acked, lws_usec_t rtt); + void (*on_loss)(struct lws *nwsi, size_t bytes_lost); + int (*can_send)(struct lws *nwsi, size_t bytes); + lws_usec_t (*get_pacing_delay)(struct lws *nwsi, size_t bytes_to_send); +}; +``` + +1. **State Management**: Inside `init()`, allocate your custom state structure and assign it to `nwsi->quic.qn->cc_state`. +2. **Implement Hooks**: Fill out the remaining hooks to track `bytes_in_flight`, adjust `cwnd`, manage `ssthresh`, and handle loss/ack events. +3. **Pacing**: `get_pacing_delay()` should return `0` if it's safe to send immediately, or the number of microseconds to delay the send. +4. **Use It**: Assign a pointer to your custom `lws_cc_ops` struct to `info.quic_cc_ops` during context creation. diff --git a/READMEs/README.webtransport.md b/READMEs/README.webtransport.md new file mode 100644 index 0000000000..75ee381e27 --- /dev/null +++ b/READMEs/README.webtransport.md @@ -0,0 +1,102 @@ +# Using WebTransport and QUIC Datagrams in libwebsockets + +`libwebsockets` supports WebTransport (RFC 9297) and QUIC Datagrams (RFC 9221) natively over its HTTP/3 and QUIC transport layers. WebTransport is an API that provides low-latency, bidirectional, multiplexed, and secure client-server messaging. + +## WebTransport vs. WebSocket + +WebTransport offers several architectural advantages over WebSocket, particularly in high-performance or lossy networking environments: + +| Feature | WebSocket (`ws`) | WebTransport (`wt`) | +| --- | --- | --- | +| **Transport Protocol** | TCP (HTTP/1.1 or HTTP/2) | UDP (QUIC via HTTP/3) | +| **Multiplexing** | Built-in for H2, none for H1. | Native QUIC streams (multiple independent streams without head-of-line blocking). | +| **Delivery Guarantees** | Reliable, strictly ordered. | Offers both reliable streams and unreliable **Datagrams**. | +| **Connection Setup** | Requires 1-3 RTTs (TCP + TLS + HTTP). | 0-RTT or 1-RTT (QUIC handshake). | +| **Security** | TLS 1.2 or 1.3 | Always TLS 1.3 (embedded in QUIC). | + +Use **WebSocket** when you need maximum backward compatibility across older clients, infrastructure, and proxies. +Use **WebTransport** when you require low-latency media streaming, gaming, or parallel data transfers where head-of-line blocking is unacceptable. + +## Architecture in `libwebsockets` + +WebTransport in `libwebsockets` maps elegantly to the `wsi` (WebSocket Instance) abstraction. WebTransport requires an HTTP/3 virtual host. + +### The Session `wsi` + +A WebTransport connection starts with an HTTP/3 `CONNECT` request specifying the `:protocol: webtransport` pseudo-header. If accepted, `libwebsockets` transitions this `wsi` to the `wt` role (`&role_ops_wt`). + +- **Datagrams**: The session `wsi` handles WebTransport Datagrams. Any payload written directly to the session `wsi` is encapsulated in a QUIC `DATAGRAM` frame, prefixed with the WebTransport Quarter Session ID. + +### Child Streams + +Within the WebTransport session, you can spawn multiple independent QUIC streams. + +- **Creation**: Call `lws_wt_create_stream(session_wsi, is_unidi)` to create a new child stream. `is_unidi` determines whether it is a unidirectional or bidirectional stream. +- **Handling**: Each child stream gets its own `wsi` running the `wt` role. It will trigger its own `LWS_CALLBACK_ESTABLISHED`, `LWS_CALLBACK_RECEIVE`, and `LWS_CALLBACK_CLOSED` events. +- **Writing**: Data written to a child stream using `lws_write` is framed directly as a QUIC `STREAM` payload, avoiding multiplexing overhead. + +## Quick Start Example + +### Server-side Initialization + +Ensure your context and vhost are configured with HTTP/3 support and TLS: + +```c +struct lws_context_creation_info info; +memset(&info, 0, sizeof info); + +info.port = 443; +info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; +info.alpn = "h3"; /* Required for WebTransport */ +info.ssl_cert_filepath = "server.cert"; +info.ssl_private_key_filepath = "server.key"; +``` + +### Handling the `wt` Protocol + +Implement the protocol callback. Distinguish between the session and its streams using `lws_wt_is_session(wsi)`: + +```c +#include + +static int +callback_webtransport(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + switch (reason) { + case LWS_CALLBACK_ESTABLISHED: + if (lws_wt_is_session(wsi)) { + /* New WebTransport session established. + * You can create streams here, or wait for the client to initiate them. */ + struct lws *stream_wsi = lws_wt_create_stream(wsi, 0 /* bidi */); + } else { + /* A child stream was established. */ + } + break; + + case LWS_CALLBACK_RECEIVE: + if (lws_wt_is_session(wsi)) { + /* Received a WebTransport Datagram */ + } else { + /* Received data on a WebTransport Stream */ + } + break; + + /* ... other callbacks ... */ + } + return 0; +} +``` + +## Security and Browser Clients + +Major web browsers (Chrome, Firefox, Safari) support the `WebTransport` JavaScript API. However, browsers strictly enforce TLS certificates for WebTransport. + +If you are developing locally with self-signed certificates, the browser will instantly reject the connection. You can bypass this during development in Chromium-based browsers using: + +```bash +# Ignore certificate errors for local WebTransport development +google-chrome --ignore-certificate-errors --origin-to-force-quic-on=localhost:443 +``` + +Alternatively, you can provide the SHA-256 hash of your self-signed certificate in the JavaScript `WebTransport` constructor's `serverCertificateHashes` option. diff --git a/cmake/lws_config.h.in b/cmake/lws_config.h.in index 7032bbdae8..2d19f54ef5 100644 --- a/cmake/lws_config.h.in +++ b/cmake/lws_config.h.in @@ -81,6 +81,8 @@ #cmakedefine LWS_HAVE_mbedtls_ssl_set_hs_own_cert #cmakedefine LWS_HAVE_mbedtls_ssl_set_hs_authmode #cmakedefine LWS_HAVE_mbedtls_ssl_set_verify +#cmakedefine LWS_HAVE_mbedtls_ssl_set_export_keys_cb +#cmakedefine LWS_HAVE_mbedtls_ssl_conf_export_keys_cb #cmakedefine LWS_HAVE_mbedtls_x509_crt_parse_file #cmakedefine LWS_HAVE_MBEDTLS_NET_SOCKETS #cmakedefine LWS_HAVE_MBEDTLS_SSL_NEW_SESSION_TICKET @@ -147,6 +149,7 @@ #cmakedefine LWS_ROLE_WS #cmakedefine LWS_ROLE_MQTT #cmakedefine LWS_ROLE_QUIC +#cmakedefine LWS_ROLE_WT #cmakedefine LWS_SHA1_USE_OPENSSL_NAME #cmakedefine LWS_SSL_CLIENT_USE_OS_CA_CERTS #cmakedefine LWS_SSL_SERVER_WITH_ECDH_CERT @@ -213,10 +216,12 @@ #cmakedefine LWS_WITH_LWSAC #cmakedefine LWS_LOGS_TIMESTAMP #cmakedefine LWS_WITH_MBEDTLS +#cmakedefine LWS_HAVE_MBEDTLS_V4 #cmakedefine LWS_WITH_BEARSSL #cmakedefine LWS_WITH_SCHANNEL #cmakedefine LWS_WITH_GNUTLS #cmakedefine LWS_WITH_MINIZ +#cmakedefine LWS_WITH_ROUTING #cmakedefine LWS_WITH_NETLINK #cmakedefine LWS_WITH_BINDTODEVICE #cmakedefine LWS_WITH_NETWORK diff --git a/include/libwebsockets.h b/include/libwebsockets.h index 6b021a6ac4..450d934fd1 100644 --- a/include/libwebsockets.h +++ b/include/libwebsockets.h @@ -411,8 +411,10 @@ typedef void X509_VERIFY_PARAM; #if defined(LWS_WITH_TLS) #include +#if !defined(LWS_HAVE_MBEDTLS_V4) #include #include +#endif #include #if !defined(MBEDTLS_PRIVATE) @@ -907,10 +909,14 @@ lws_fx_string(const lws_fx_t *a, char *buf, size_t size); #include #if defined(LWS_WITH_MBEDTLS) +#if !defined(LWS_HAVE_MBEDTLS_V4) #include #include #include #include +#else +#include +#endif #endif #if defined(LWS_WITH_BEARSSL) #include diff --git a/include/libwebsockets/lws-async-dns.h b/include/libwebsockets/lws-async-dns.h index cd5028c4ff..c694aedd23 100644 --- a/include/libwebsockets/lws-async-dns.h +++ b/include/libwebsockets/lws-async-dns.h @@ -36,6 +36,7 @@ typedef enum dns_query_type { LWS_ADNS_RECORD_NSEC = 0x2f, LWS_ADNS_RECORD_DNSKEY = 0x30, LWS_ADNS_RECORD_NSEC3 = 0x32, + LWS_ADNS_RECORD_HTTPS = 0x41, } adns_query_type_t; typedef enum { @@ -128,6 +129,19 @@ LWS_VISIBLE LWS_EXTERN const uint8_t * lws_async_dns_get_rr_cache(struct lws_context *context, const char *name, adns_query_type_t qtype, uint16_t *paylen); +/** + * lws_async_dns_get_alpn() - check if an ALPN protocol is in the cached HTTPS record + * + * \param context: the lws_context + * \param name: the DNS name + * \param alpn: the ALPN protocol string to check (e.g. "h3") + * + * Returns 1 if the ALPN protocol is found in the cached HTTPS record for the name, + * or 0 otherwise. + */ +LWS_VISIBLE LWS_EXTERN int +lws_async_dns_get_alpn(struct lws_context *context, const char *name, const char *alpn); + /** * lws_async_dns_server_add() - add a DNS server to the lws async DNS list * diff --git a/include/libwebsockets/lws-callbacks.h b/include/libwebsockets/lws-callbacks.h index fb70b9a02f..1eb50f5b60 100644 --- a/include/libwebsockets/lws-callbacks.h +++ b/include/libwebsockets/lws-callbacks.h @@ -916,6 +916,11 @@ enum lws_callback_reasons { LWS_CALLBACK_QT_CLIENT_RECEIVE = 218, /**< QUIC transport payload received on client side */ + LWS_CALLBACK_CLIENT_ESTABLISHED_EARLY = 219, + /**< 0-RTT/Early data callback. If the client stream opts into 0-RTT, + * it can return 1 from this callback to enable early WRITEABLE callbacks + * before the handshake completes. Return 0 to ignore 0-RTT. */ + /****** add new things just above ---^ ******/ LWS_CALLBACK_USER = 1000, diff --git a/include/libwebsockets/lws-client.h b/include/libwebsockets/lws-client.h index dd75cc956a..83b57be8d5 100644 --- a/include/libwebsockets/lws-client.h +++ b/include/libwebsockets/lws-client.h @@ -111,6 +111,8 @@ enum lws_client_connect_ssl_connection_flags { LCCSCF_CACHE_COOKIES = (1 << 30), /**< If built with -DLWS_WITH_CACHE_NSCOOKIEJAR, store and reapply * http cookies in a Netscape Cookie Jar on this connection */ + LCCSCF_ALLOW_EARLY_DATA = (int)(1u << 31), + /**< Allow 0-RTT early data for QUIC/TLS 1.3 connections */ }; /* diff --git a/include/libwebsockets/lws-context-vhost.h b/include/libwebsockets/lws-context-vhost.h index 6c7b06ad27..11138280d4 100644 --- a/include/libwebsockets/lws-context-vhost.h +++ b/include/libwebsockets/lws-context-vhost.h @@ -262,11 +262,23 @@ #define LWS_SERVER_OPTION_VH_INSTANTIATE_ALL_PROTOCOLS (1ll << 42) /**< (VH) force instantiation of all protocols for this vhost */ -#define LWS_SERVER_OPTION_VH_SKIP_PRIV_DROP (1ll << 43) - /**< Cause create vhost api to skip priv drop, requires caller - * to manage it themselves */ +#define LWS_SERVER_OPTION_VH_SKIP_PRIV_DROP (1ll << 43) + /**< Cause create vhost api to skip priv drop, requires caller + * to manage it themselves */ - /****** add new things just above ---^ ******/ +#define LWS_SERVER_OPTION_CMDLINE_FORCE_H1 (1ll << 44) + /**< (CTX) Set by core built-in options to force ALPN to HTTP/1.1 */ + +#define LWS_SERVER_OPTION_CMDLINE_FORCE_H2 (1ll << 45) + /**< (CTX) Set by core built-in options to force ALPN to H2 */ + +#define LWS_SERVER_OPTION_CMDLINE_FORCE_H3 (1ll << 46) + /**< (CTX) Set by core built-in options to force ALPN to H3 */ + +#define LWS_SERVER_OPTION_ALLOW_EARLY_DATA (1ll << 47) + /**< (VH) Accept 0-RTT early data for QUIC/TLS 1.3 connections */ + + /****** add new things just above ---^ ******/ #define lws_check_opt(c, f) ((((uint64_t)c) & ((uint64_t)f)) == ((uint64_t)f)) @@ -285,6 +297,20 @@ typedef int (*lws_peer_limits_notify_t)(struct lws_context *ctx, lws_sockaddr46 *sa46); #endif +/** + * lws_quic_tx_credit_cb_t - Callback for dynamic QUIC TX credit window scaling + * + * \param wsi: The stream wsi, or the network wsi for connection-level flow control + * \param current_window: The current window size (rx_window_size) + * \param consumed_bytes: The number of bytes the application just consumed + * \param time_since_last_update_us: Microseconds since the window was last scaled or initialized + * + * Return: The new window size (in bytes). If you return 0 or a value less than the + * current ungranted window, the window will not be scaled. + */ +typedef uint64_t (*lws_quic_tx_credit_cb_t)(struct lws *wsi, uint64_t current_window, + uint64_t consumed_bytes, uint64_t time_since_last_update_us); + /** struct lws_context_creation_info - parameters to create context and /or vhost with * * This is also used to create vhosts.... if LWS_SERVER_OPTION_EXPLICIT_VHOSTS @@ -1057,6 +1083,12 @@ struct lws_context_creation_info { uint32_t quic_mtu; /**< VHOST: 0 for default (1280), or the desired QUIC MTU for the vhost */ + lws_quic_tx_credit_cb_t quic_tx_credit_cb; + /**< VHOST: Callback for dynamic QUIC TX credit window scaling. If NULL, defaults to 50% refill batched logic. */ + + const struct lws_cc_ops *quic_cc_ops; + /**< CONTEXT: QUIC congestion control algorithm ops to use. If NULL, defaults to &lws_cc_ops_newreno. */ + void *_unused[1]; /**< dummy */ }; diff --git a/include/libwebsockets/lws-genaes.h b/include/libwebsockets/lws-genaes.h index 2573898a16..ea53e24fd1 100644 --- a/include/libwebsockets/lws-genaes.h +++ b/include/libwebsockets/lws-genaes.h @@ -34,8 +34,12 @@ ///@{ #if defined(LWS_WITH_MBEDTLS) +#if !defined(LWS_HAVE_MBEDTLS_V4) #include #include +#else +#include +#endif #endif enum enum_aes_modes { @@ -67,6 +71,7 @@ enum enum_aes_padding { struct lws_genaes_ctx { #if defined(LWS_WITH_MBEDTLS) +#if !defined(LWS_HAVE_MBEDTLS_V4) union { mbedtls_aes_context ctx; #if defined(MBEDTLS_CIPHER_MODE_XTS) @@ -74,6 +79,12 @@ struct lws_genaes_ctx { #endif mbedtls_gcm_context ctx_gcm; } u; +#else + psa_key_id_t key_id; + psa_algorithm_t alg; + psa_cipher_operation_t cipher_ctx; + psa_aead_operation_t aead_ctx; +#endif #elif defined(LWS_WITH_SCHANNEL) struct { void *hAlg; diff --git a/include/libwebsockets/lws-genchacha.h b/include/libwebsockets/lws-genchacha.h index abddaa39ee..84864f4e64 100644 --- a/include/libwebsockets/lws-genchacha.h +++ b/include/libwebsockets/lws-genchacha.h @@ -28,7 +28,11 @@ #include #if defined(LWS_WITH_MBEDTLS) +#if !defined(LWS_HAVE_MBEDTLS_V4) #include +#else +#include +#endif #endif /*! \defgroup generic chacha diff --git a/include/libwebsockets/lws-gendtls.h b/include/libwebsockets/lws-gendtls.h index 2489ec840e..1153e1c987 100644 --- a/include/libwebsockets/lws-gendtls.h +++ b/include/libwebsockets/lws-gendtls.h @@ -37,8 +37,10 @@ #if defined(LWS_WITH_MBEDTLS) #include +#if !defined(LWS_HAVE_MBEDTLS_V4) #include #include +#endif #include #elif defined(LWS_WITH_GNUTLS) #include diff --git a/include/libwebsockets/lws-genec.h b/include/libwebsockets/lws-genec.h index 5743c1cd91..a1fe2178c5 100644 --- a/include/libwebsockets/lws-genec.h +++ b/include/libwebsockets/lws-genec.h @@ -32,10 +32,17 @@ enum enum_genec_alg { struct lws_genec_ctx { #if defined(LWS_WITH_MBEDTLS) +#if !defined(LWS_HAVE_MBEDTLS_V4) union { mbedtls_ecdh_context *ctx_ecdh; mbedtls_ecdsa_context *ctx_ecdsa; } u; +#else + psa_key_id_t key_id; + psa_algorithm_t alg; + uint8_t *peer_key; + size_t peer_key_len; +#endif #elif defined(LWS_WITH_SCHANNEL) struct { void *hAlg; @@ -60,7 +67,7 @@ struct lws_genec_ctx { char has_private; }; -#if defined(LWS_WITH_MBEDTLS) +#if defined(LWS_WITH_MBEDTLS) && !defined(LWS_HAVE_MBEDTLS_V4) enum enum_lws_dh_side { LDHS_OURS = MBEDTLS_ECDH_OURS, LDHS_THEIRS = MBEDTLS_ECDH_THEIRS diff --git a/include/libwebsockets/lws-genhash.h b/include/libwebsockets/lws-genhash.h index 000bbbe56a..0173d2a1b9 100644 --- a/include/libwebsockets/lws-genhash.h +++ b/include/libwebsockets/lws-genhash.h @@ -40,6 +40,10 @@ #include #endif +#if defined(LWS_HAVE_MBEDTLS_V4) +#include +#endif + enum lws_genhash_types { LWS_GENHASH_TYPE_UNKNOWN, @@ -65,6 +69,9 @@ enum lws_genhmac_types { struct lws_genhash_ctx { uint8_t type; #if defined(LWS_WITH_MBEDTLS) +#if defined(LWS_HAVE_MBEDTLS_V4) + psa_hash_operation_t hash_ctx; +#else union { mbedtls_md5_context md5; mbedtls_sha1_context sha1; @@ -72,6 +79,7 @@ struct lws_genhash_ctx { mbedtls_sha512_context sha512; /* 384 also uses this */ const mbedtls_md_info_t *hmac; } u; +#endif #elif defined(LWS_WITH_SCHANNEL) struct { void *hAlg; @@ -98,8 +106,13 @@ struct lws_genhash_ctx { struct lws_genhmac_ctx { uint8_t type; #if defined(LWS_WITH_MBEDTLS) +#if defined(LWS_HAVE_MBEDTLS_V4) + psa_mac_operation_t mac_ctx; + psa_key_id_t key_id; +#else const mbedtls_md_info_t *hmac; mbedtls_md_context_t ctx; +#endif #elif defined(LWS_WITH_SCHANNEL) struct { void *hAlg; @@ -119,7 +132,7 @@ struct lws_genhmac_ctx { EVP_MD_CTX *ctx; EVP_PKEY *key; #else -#if defined(LWS_HAVE_HMAC_CTX_new) +#if defined(LWS_HAVE_HMAC_CTX_new) || defined(LWS_WITH_BORINGSSL) || defined(LWS_WITH_AWSLC) HMAC_CTX *ctx; #else HMAC_CTX ctx; diff --git a/include/libwebsockets/lws-genrsa.h b/include/libwebsockets/lws-genrsa.h index c9f227196e..1b4879b425 100644 --- a/include/libwebsockets/lws-genrsa.h +++ b/include/libwebsockets/lws-genrsa.h @@ -44,7 +44,11 @@ enum enum_genrsa_mode { struct lws_genrsa_ctx { #if defined(LWS_WITH_MBEDTLS) +#if !defined(LWS_HAVE_MBEDTLS_V4) mbedtls_rsa_context *ctx; +#else + psa_key_id_t key_id; +#endif #elif defined(LWS_WITH_SCHANNEL) struct { void *hAlg; diff --git a/include/libwebsockets/lws-qpack.h b/include/libwebsockets/lws-qpack.h index 613326d740..aca95263f9 100644 --- a/include/libwebsockets/lws-qpack.h +++ b/include/libwebsockets/lws-qpack.h @@ -42,6 +42,7 @@ struct lws_qpack_dynamic_table { struct lws_qpack_dynamic_table_entry *entries; uint32_t virtual_payload_usage; uint32_t virtual_payload_max; + uint32_t virtual_payload_limit; uint32_t insert_count; uint32_t known_received_count; uint16_t num_entries; @@ -69,9 +70,8 @@ struct lws_qpack_tx_encoder { uint16_t used_entries; uint16_t pos; /* Ring buffer head */ - /* Temporary buffer for encoder stream output during header generation */ - unsigned char enc_buf[65536]; - size_t enc_ptr; + struct lws_buflist *tx_bl; + struct lws *wsi_qpack_enc; }; struct lws_qpack_context { diff --git a/include/libwebsockets/lws-quic.h b/include/libwebsockets/lws-quic.h index dbe594702a..717ba108f8 100644 --- a/include/libwebsockets/lws-quic.h +++ b/include/libwebsockets/lws-quic.h @@ -38,6 +38,13 @@ enum lws_tls_quic_secret_type { LWS_TLS_QUIC_SECRET_SERVER_APPLICATION, }; +enum lws_0rtt_status { + LWS_0RTT_STATUS_NONE, /**< No 0-RTT attempted */ + LWS_0RTT_STATUS_ATTEMPTED, /**< Client sent 0-RTT, awaiting server decision */ + LWS_0RTT_STATUS_ACCEPTED, /**< Server accepted 0-RTT data */ + LWS_0RTT_STATUS_REJECTED, /**< Server rejected 0-RTT data, client must resend */ +}; + /** * lws_tls_quic_secret_cb() - Callback for QUIC traffic secret derivation * @@ -101,10 +108,62 @@ lws_tls_quic_set_transport_parameters(struct lws *wsi, const uint8_t *tp, size_t LWS_VISIBLE LWS_EXTERN int lws_tls_quic_get_transport_parameters(struct lws *wsi, const uint8_t **tp, size_t *tp_len); +/** + * lws_tls_0rtt_status() - Get the status of 0-RTT early data + * + * \param wsi: the wsi + * + * Returns the status of 0-RTT early data for the connection. + */ +LWS_VISIBLE LWS_EXTERN enum lws_0rtt_status +lws_tls_0rtt_status(struct lws *wsi); + +/** + * lws_rx_is_early_data() - Determine if received data is 0-RTT early data + * + * \param wsi: the wsi + * + * Returns 1 if the current RX data was received as 0-RTT early data, 0 otherwise. + */ +LWS_VISIBLE LWS_EXTERN int +lws_rx_is_early_data(struct lws *wsi); + /** * lws_tls_quic_api_test() - Internal API test for QUIC TLS 1.3 memory BIOs */ LWS_VISIBLE LWS_EXTERN int lws_tls_quic_api_test(void); +/** + * lws_tls_quic_migrate_wsi() - Migrate QUIC TLS context from old to new wsi + * + * \param old_wsi: the old logical stream wsi + * \param new_wsi: the new parent network connection wsi + */ +LWS_VISIBLE LWS_EXTERN int +lws_tls_quic_migrate_wsi(struct lws *old_wsi, struct lws *new_wsi); + +/** + * lws_quic_initiate_key_update() - Manually trigger a QUIC Key Update + * + * \param wsi: any QUIC wsi on the connection (stream or network) + * + * Initiates a Key Update on the QUIC connection as per RFC 9001. + * Returns 0 if successfully initiated, or nonzero on failure. + */ +LWS_VISIBLE LWS_EXTERN int +lws_quic_initiate_key_update(struct lws *wsi); + +struct lws_cc_ops { + void (*init)(struct lws *nwsi); + void (*on_sent)(struct lws *nwsi, size_t bytes); + void (*on_ack)(struct lws *nwsi, size_t bytes_acked, lws_usec_t rtt); + void (*on_loss)(struct lws *nwsi, size_t bytes_lost); + int (*can_send)(struct lws *nwsi, size_t bytes); + lws_usec_t (*get_pacing_delay)(struct lws *nwsi, size_t bytes_to_send); +}; + +LWS_VISIBLE LWS_EXTERN_FOR_DATA const struct lws_cc_ops lws_cc_ops_newreno; +LWS_VISIBLE LWS_EXTERN_FOR_DATA const struct lws_cc_ops lws_cc_ops_cubic; + ///@} diff --git a/include/libwebsockets/lws-secure-streams-policy.h b/include/libwebsockets/lws-secure-streams-policy.h index 1e9668f777..6e2427290e 100644 --- a/include/libwebsockets/lws-secure-streams-policy.h +++ b/include/libwebsockets/lws-secure-streams-policy.h @@ -152,6 +152,7 @@ typedef struct lws_ss_trust_store { enum { LWSSSP_H1, LWSSSP_H2, + LWSSSP_H3, LWSSSP_WS, LWSSSP_MQTT, LWSSSP_RAW, @@ -267,6 +268,8 @@ typedef struct lws_ss_policy { // } h1; // struct { /* LWSSSP_H2 */ // } h2; +// struct { /* LWSSSP_H3 */ +// } h3; struct { /* LWSSSP_WS */ const char *subprotocol; uint8_t binary; diff --git a/include/libwebsockets/lws-stub.h b/include/libwebsockets/lws-stub.h index 29efda83d2..4b03625f55 100644 --- a/include/libwebsockets/lws-stub.h +++ b/include/libwebsockets/lws-stub.h @@ -30,6 +30,8 @@ #if defined(LWS_WITH_STUB) +struct lws_stub_manager; + struct lws_stub_config { struct lws_context *cx; struct lws_vhost *vh; @@ -38,6 +40,8 @@ struct lws_stub_config { const struct lws_protocols *protocols; /* Protocol array for the UDS server vhost */ const void *extra_payload; /* Optional extra data to write to child stdin */ size_t extra_payload_len; + void *user; /* Opaque user pointer passed to vhost */ + void (*connected_cb)(struct lws_stub_manager *mgr); /* Called when UDS connects */ }; struct lws_stub_manager; diff --git a/include/libwebsockets/lws-webtransport.h b/include/libwebsockets/lws-webtransport.h new file mode 100644 index 0000000000..e593fd68b7 --- /dev/null +++ b/include/libwebsockets/lws-webtransport.h @@ -0,0 +1,45 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2026 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef _LWS_WEBTRANSPORT_H +#define _LWS_WEBTRANSPORT_H + +#if defined(LWS_ROLE_WT) + +/* WebTransport Stream Types (RFC 9297) */ +#define LWS_WT_STREAM_TYPE_BIDI 0x41 +#define LWS_WT_STREAM_TYPE_UNIDI 0x54 + +/* + * WebTransport API + */ + +LWS_VISIBLE LWS_EXTERN struct lws * +lws_wt_create_stream(struct lws *wsi_session, int unidi); + +LWS_VISIBLE LWS_EXTERN int +lws_wt_is_session(struct lws *wsi); + +#endif /* LWS_ROLE_WT */ +#endif /* _LWS_WEBTRANSPORT_H */ diff --git a/include/libwebsockets/lws-write.h b/include/libwebsockets/lws-write.h index 5ecdf390a2..af011a8f4e 100644 --- a/include/libwebsockets/lws-write.h +++ b/include/libwebsockets/lws-write.h @@ -68,6 +68,10 @@ enum lws_write_protocol { LWS_WRITE_HTTP_HEADERS_CONTINUATION = 9, /**< Continuation of http/2 headers */ + LWS_WRITE_QUIC_DATAGRAM = 10, + /**< Send a QUIC datagram (RFC 9221). The payload is sent as a single QUIC + * DATAGRAM frame. + */ /****** add new things just above ---^ ******/ diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 577dcc19e3..5b9319e03c 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -483,6 +483,7 @@ if (DEFINED LWS_PLAT_UNIX) endif() endif() set(LWS_HAVE_MBEDTLS_NET_SOCKETS ${LWS_HAVE_MBEDTLS_NET_SOCKETS} PARENT_SCOPE) +set(LWS_HAVE_SSL_set_tlsext_host_name ${LWS_HAVE_SSL_set_tlsext_host_name} PARENT_SCOPE) set(LWS_HAVE_MBEDTLS_SSL_NEW_SESSION_TICKET ${LWS_HAVE_MBEDTLS_SSL_NEW_SESSION_TICKET} PARENT_SCOPE) set(LWS_HAVE_mbedtls_ssl_conf_alpn_protocols ${LWS_HAVE_mbedtls_ssl_conf_alpn_protocols} PARENT_SCOPE) set(TEST_SERVER_SSL_KEY "${TEST_SERVER_SSL_KEY}" PARENT_SCOPE) @@ -505,3 +506,7 @@ endif() set(USE_WOLFSSL ${USE_WOLFSSL} PARENT_SCOPE) set(LWS_DEPS_LIB_PATHS ${LWS_DEPS_LIB_PATHS} PARENT_SCOPE) +set(LWS_ROLE_QUIC ${LWS_ROLE_QUIC} PARENT_SCOPE) +set(LWS_WITH_HTTP3 ${LWS_WITH_HTTP3} PARENT_SCOPE) +set(LWS_ROLE_H3 ${LWS_ROLE_H3} PARENT_SCOPE) +set(LWS_ROLE_WT ${LWS_ROLE_WT} PARENT_SCOPE) diff --git a/lib/core-net/CMakeLists.txt b/lib/core-net/CMakeLists.txt index f97168a611..d83e014e37 100644 --- a/lib/core-net/CMakeLists.txt +++ b/lib/core-net/CMakeLists.txt @@ -72,7 +72,7 @@ if (LWS_WITH_SYS_STATE) ) endif() -if (LWS_WITH_NETLINK) +if (LWS_WITH_ROUTING) list(APPEND SOURCES core-net/route.c ) diff --git a/lib/core-net/adopt.c b/lib/core-net/adopt.c index efea1882af..ef468ce88d 100644 --- a/lib/core-net/adopt.c +++ b/lib/core-net/adopt.c @@ -170,9 +170,13 @@ __lws_adopt_descriptor_vhost1(struct lws_vhost *vh, lws_adoption_type type, new_wsi->a.protocol = lws_vhost_name_to_protocol(new_wsi->a.vhost, vh_prot_name); if (!new_wsi->a.protocol) { - lwsl_vhost_err(new_wsi->a.vhost, "Protocol %s not enabled", - vh_prot_name); - goto bail; + if (!strcmp(vh_prot_name, "quic")) { + new_wsi->a.protocol = &new_wsi->a.vhost->protocols[0]; + } else { + lwsl_vhost_err(new_wsi->a.vhost, "Protocol %s not enabled", + vh_prot_name); + goto bail; + } } if (lws_ensure_user_space(new_wsi)) { lwsl_wsi_notice(new_wsi, "OOM"); @@ -703,13 +707,13 @@ adopt_socket_readbuf(struct lws *wsi, const char *readbuf, size_t len) } #if defined(LWS_WITH_UDP) -#if defined(LWS_WITH_CLIENT) /* * This is the ASYNC_DNS callback target for udp client, it's analogous to * connect3() */ +#if defined(LWS_WITH_CLIENT) static struct lws * lws_create_adopt_udp2(struct lws *wsi, const char *ads, const struct addrinfo *r, int n, void *opaque) @@ -797,9 +801,24 @@ lws_create_adopt_udp2(struct lws *wsi, const char *ads, if (sock.sockfd == LWS_SOCK_INVALID) goto resume; + lws_plat_apply_FD_CLOEXEC((int)sock.sockfd); + lws_plat_set_nonblocking(sock.sockfd); + +#if defined(LWS_WITH_IPV6) && defined(IPV6_V6ONLY) + if (s->dest.sa4.sin_family == AF_INET6 && + (!(wsi->a.vhost->options & LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY) || + (wsi->a.vhost->options & LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE))) { + int opt = 1; + if (setsockopt(sock.sockfd, IPPROTO_IPV6, IPV6_V6ONLY, + (const void *)&opt, sizeof(opt)) < 0) { + lwsl_vhost_notice(wsi->a.vhost, "set IPV6_V6ONLY fail"); + } + } +#endif + /* ipv6 udp!!! */ - if (s->af == AF_INET) + if (s->dest.sa4.sin_family == AF_INET) s->dest.sa4.sin_port = htons(wsi->c_port); #if defined(LWS_WITH_IPV6) else @@ -891,6 +910,134 @@ lws_create_adopt_udp2(struct lws *wsi, const char *ads, return NULL; } +#else +static struct lws * +lws_create_adopt_udp2(struct lws *wsi, const char *ads, + const struct addrinfo *r, int n, void *opaque) +{ + lws_sock_file_fd_type sock; + int bc = 1; + lws_sockaddr46 dest; + + assert(wsi); + + if (ads && (n < 0 || !r)) { + lwsl_notice("%s: bad: n %d, r %p\n", __func__, n, r); + goto bail; + } + + memset(&dest, 0, sizeof(dest)); + + if (r) { + if (r->ai_family == AF_INET) { + dest.sa4.sin_family = AF_INET; + memcpy(&dest.sa4.sin_addr, &((struct sockaddr_in *)r->ai_addr)->sin_addr, sizeof(struct in_addr)); + } +#if defined(LWS_WITH_IPV6) + else if (r->ai_family == AF_INET6) { + dest.sa6.sin6_family = AF_INET6; + memcpy(&dest.sa6.sin6_addr, &((struct sockaddr_in6 *)r->ai_addr)->sin6_addr, sizeof(struct in6_addr)); + } +#endif + } else { +#if defined(LWS_WITH_IPV6) + if (!lws_check_opt(wsi->a.context->options, + LWS_SERVER_OPTION_DISABLE_IPV6)) { + dest.sa6.sin6_family = AF_INET6; + } else +#endif + { + dest.sa4.sin_family = AF_INET; + dest.sa4.sin_addr.s_addr = INADDR_ANY; + } + } + +#if !defined(__linux__) + sock.sockfd = socket(dest.sa4.sin_family, SOCK_DGRAM, IPPROTO_UDP); +#else + sock.sockfd = socket(wsi->pf_packet ? PF_PACKET : dest.sa4.sin_family, + SOCK_DGRAM, wsi->pf_packet ? htons(0x800) : IPPROTO_UDP); +#endif + if (sock.sockfd == LWS_SOCK_INVALID) + goto bail; + + lws_plat_apply_FD_CLOEXEC((int)sock.sockfd); + lws_plat_set_nonblocking(sock.sockfd); + +#if defined(LWS_WITH_IPV6) && defined(IPV6_V6ONLY) + if (dest.sa4.sin_family == AF_INET6 && + (!(wsi->a.vhost->options & LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY) || + (wsi->a.vhost->options & LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE))) { + int opt = 1; + if (setsockopt(sock.sockfd, IPPROTO_IPV6, IPV6_V6ONLY, + (const void *)&opt, sizeof(opt)) < 0) { + lwsl_vhost_notice(wsi->a.vhost, "set IPV6_V6ONLY fail"); + } + } +#endif + + if (dest.sa4.sin_family == AF_INET) + dest.sa4.sin_port = htons(wsi->c_port); +#if defined(LWS_WITH_IPV6) + else + dest.sa6.sin6_port = htons(wsi->c_port); +#endif + + if (setsockopt(sock.sockfd, SOL_SOCKET, SO_REUSEADDR, + (const char *)&bc, sizeof(bc)) < 0) + lwsl_err("%s: failed to set reuse\n", __func__); + + if (wsi->do_broadcast && + setsockopt(sock.sockfd, SOL_SOCKET, SO_BROADCAST, + (const char *)&bc, sizeof(bc)) < 0) + lwsl_err("%s: failed to set broadcast\n", __func__); + + if (opaque && lws_plat_BINDTODEVICE(sock.sockfd, (const char *)opaque)) + goto resume; + + if (wsi->do_bind && + bind(sock.sockfd, sa46_sockaddr(&dest), +#if defined(_WIN32) + (int) +#endif + sa46_socklen(&dest)) == -1) { + lwsl_err("%s: bind failed\n", __func__); + goto resume; + } + + if (!wsi->do_bind && !wsi->pf_packet) { +#if !defined(__APPLE__) + if (connect(sock.sockfd, sa46_sockaddr(&dest), + sa46_socklen(&dest)) == -1 && + errno != EADDRNOTAVAIL) { + lwsl_err("%s: conn failed\n", __func__); + goto resume; + } +#endif + } + + if (wsi->udp) + wsi->udp->sa46 = dest; + wsi->sa46_peer = dest; + +#if defined(LWS_WITH_SYS_ASYNC_DNS) + { + lws_async_dns_server_t *asds = + __lws_async_dns_server_find_wsi( + &wsi->a.context->async_dns, wsi); + if (asds) + asds->dns_server_connected = 1; + } +#endif + + return lws_adopt_descriptor_vhost2(wsi, LWS_ADOPT_RAW_SOCKET_UDP, sock); + +resume: + compatible_close(sock.sockfd); +bail: + return NULL; +} +#endif struct lws * lws_create_adopt_udp(struct lws_vhost *vhost, const char *ads, int port, @@ -1022,7 +1169,6 @@ lws_create_adopt_udp(struct lws_vhost *vhost, const char *ads, int port, #endif } #endif -#endif struct lws * lws_adopt_socket_readbuf(struct lws_context *context, lws_sockfd_type accept_fd, diff --git a/lib/core-net/client/connect.c b/lib/core-net/client/connect.c index 065da71dd6..8ec3ca5747 100644 --- a/lib/core-net/client/connect.c +++ b/lib/core-net/client/connect.c @@ -48,9 +48,10 @@ lws_http_client_connect_via_info2(struct lws *wsi) wsi->a.opaque_user_data = wsi->stash->opaque_user_data; - if (stash->cis[CIS_METHOD] && (!strcmp(stash->cis[CIS_METHOD], "RAW") || + if ((stash->cis[CIS_METHOD] && (!strcmp(stash->cis[CIS_METHOD], "RAW") || !strcmp(stash->cis[CIS_METHOD], "MQTT") || - !strcmp(stash->cis[CIS_METHOD], "QUIC"))) + !strcmp(stash->cis[CIS_METHOD], "QUIC"))) || + (stash->cis[CIS_ALPN] && !strcmp(stash->cis[CIS_ALPN], "h3"))) goto no_ah; /* @@ -134,8 +135,16 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i) const char *cisin[CIS_COUNT]; char buf_localport[8]; struct lws_vhost *vh; + const char *alpn = i->alpn; int tsi; + if (i->context->options & LWS_SERVER_OPTION_CMDLINE_FORCE_H3) + alpn = "h3"; + else if (i->context->options & LWS_SERVER_OPTION_CMDLINE_FORCE_H2) + alpn = "h2"; + else if (i->context->options & LWS_SERVER_OPTION_CMDLINE_FORCE_H1) + alpn = "http/1.1"; + if (i->context->requested_stop_internal_loops) return NULL; @@ -361,7 +370,7 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i) cisin[CIS_IFACE] = i->iface; lws_snprintf(buf_localport, sizeof(buf_localport), "%u", i->local_port); cisin[CIS_LOCALPORT] = buf_localport; - cisin[CIS_ALPN] = i->alpn; + cisin[CIS_ALPN] = alpn; cisin[CIS_USERNAME] = i->auth_username; cisin[CIS_PASSWORD] = i->auth_password; @@ -383,8 +392,8 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i) goto bail; #if defined(LWS_WITH_TLS) - if (i->alpn) - lws_strncpy(wsi->alpn, i->alpn, sizeof(wsi->alpn)); + if (alpn) + lws_strncpy(wsi->alpn, alpn, sizeof(wsi->alpn)); #endif wsi->a.opaque_user_data = wsi->stash->opaque_user_data = @@ -500,10 +509,9 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i) i->uri_replace_to); #endif - if (i->method && (!strcmp(i->method, "RAW") || - !strcmp(i->method, "QUIC") // || -// !strcmp(i->method, "MQTT") - )) { + if ((i->method && (!strcmp(i->method, "RAW") || + !strcmp(i->method, "QUIC"))) || + (alpn && !strcmp(alpn, "h3"))) { /* * Not for MQTT here, since we don't know if we will diff --git a/lib/core-net/client/connect2.c b/lib/core-net/client/connect2.c index 245d5fd348..7585000f94 100644 --- a/lib/core-net/client/connect2.c +++ b/lib/core-net/client/connect2.c @@ -135,6 +135,35 @@ lws_getaddrinfo46(struct lws *wsi, const char *ads, struct addrinfo **result) static const char * const dns_nxdomain = "DNS NXDOMAIN"; #endif +#if defined(LWS_WITH_TLS) +#if 0 +static struct lws * +lws_client_connect_dns_https_cb(struct lws *wsi, const char *ads, + const struct addrinfo *result, int n, void *opaque) +{ + if (!wsi || !wsi->a.context->alpn_cache || !wsi->c_port || !ads) + return wsi; + + if (n == LADNS_RET_FOUND) { + char key[256]; + void *p; + const char *c_alpn = "h3"; + + /* We check if h3 is supported by the HTTPS record */ + if (lws_async_dns_get_alpn(wsi->a.context, ads, c_alpn)) { + lws_snprintf(key, sizeof(key), "alpn_%s_%u", ads, wsi->c_port); + lws_cache_write_through(wsi->a.context->alpn_cache, key, + (const uint8_t *)c_alpn, strlen(c_alpn) + 1, + lws_now_usecs() + (lws_usec_t)(3600ULL * 1000000ULL), &p); + lwsl_wsi_notice(wsi, "HTTPS DNS record cached ALPN h3 for %s", key); + } + } + + return wsi; +} +#endif +#endif + struct lws * lws_client_connect_2_dnsreq_MAY_CLOSE_WSI(struct lws *wsi) { @@ -212,7 +241,7 @@ lws_client_connect_2_dnsreq_MAY_CLOSE_WSI(struct lws *wsi) break; case ACTIVE_CONNS_MUXED: lwsl_wsi_info(wsi, "ACTIVE_CONNS_MUXED"); - if (lwsi_role_h2(wsi)) { + if (lwsi_role_h2(wsi) || lwsi_role_h3(wsi)) { if (wsi->a.protocol->callback(wsi, LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP, @@ -230,13 +259,16 @@ lws_client_connect_2_dnsreq_MAY_CLOSE_WSI(struct lws *wsi) lwsi_state(wsi)); if (lwsi_state(wsi) == LRS_UNCONNECTED) { - if (lwsi_role_h2(w)) + if (lwsi_role_h2(w) || lwsi_role_h3(w)) lwsi_set_state(wsi, LRS_H2_WAITING_TO_SEND_HEADERS); else lwsi_set_state(wsi, LRS_H1C_ISSUE_HANDSHAKE2); } + lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND, + (int)wsi->a.context->timeout_secs); + return lws_client_connect_4_established(wsi, w, 0); } @@ -271,6 +303,66 @@ lws_client_connect_2_dnsreq_MAY_CLOSE_WSI(struct lws *wsi) if (!adsin) return NULL; +#if defined(LWS_WITH_TLS) + if (wsi->a.context->alpn_cache && wsi->tls.use_ssl && wsi->c_port) { + char key[256]; + const char *cached_alpn; + size_t clen; + + lws_snprintf(key, sizeof(key), "alpn_%s_%u", adsin, wsi->c_port); + if (!lws_cache_item_get(wsi->a.context->alpn_cache, key, (const void **)&cached_alpn, &clen)) { + lws_strncpy(wsi->alpn_discovered, cached_alpn, sizeof(wsi->alpn_discovered)); + lwsl_wsi_notice(wsi, "ALPN cache hit for %s: %s", key, wsi->alpn_discovered); + } else { + wsi->alpn_discovered[0] = '\0'; + } + } +#endif + +#if defined(LWS_ROLE_H3) || defined(LWS_ROLE_QUIC) + if (wsi->tls.use_ssl && !wsi->tried_quic) { + const char *requested_alpn = NULL; + int try_quic = 0; + + if (wsi->stash) + requested_alpn = wsi->stash->cis[CIS_ALPN]; + else + requested_alpn = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ALPN); + + if (wsi->alpn_discovered[0]) { + /* Cache hit */ + if (strstr(wsi->alpn_discovered, "h3")) + try_quic = 1; + } else if (requested_alpn && strstr(requested_alpn, "h3")) { + /* Cache miss, but h3 is allowed */ + try_quic = 1; + } + + if (try_quic) { + const struct lws_role_ops *r = lws_role_by_name("quic"); + if (r) { + lwsl_wsi_notice(wsi, "Attempting QUIC connection first"); + if (!wsi->udp) { + wsi->udp = lws_malloc(sizeof(*wsi->udp), "udp struct"); + if (wsi->udp) + memset(wsi->udp, 0, sizeof(*wsi->udp)); + } + if (wsi->udp) { + struct lws_client_connect_info i; + wsi->tried_quic = 1; + memset(&i, 0, sizeof(i)); + i.method = "QUIC"; + i.alpn = "h3"; + lws_role_transition(wsi, LWSIFR_CLIENT, LRS_UNCONNECTED, r); + if (lws_role_call_client_bind(wsi, &i)) { + /* failed */ + } + } + } + } + } +#endif + #if defined(LWS_WITH_UNIX_SOCK) /* * unix socket destination? @@ -367,10 +459,16 @@ lws_client_connect_2_dnsreq_MAY_CLOSE_WSI(struct lws *wsi) if (lws_fi(&wsi->fic, "dnsfail")) return lws_client_connect_3_connect(wsi, NULL, NULL, -4, NULL); - else + else { + + +#if defined(LWS_WITH_TLS) + /* we will skip HTTPS DNS query on this wsi to prevent double binding */ +#endif n = lws_async_dns_query(wsi->a.context, wsi->tsi, adsin, LWS_ADNS_RECORD_A, lws_client_connect_3_connect, wsi, NULL, NULL); + } if (n == LADNS_RET_FAILED_WSI_CLOSED) return NULL; diff --git a/lib/core-net/client/connect3.c b/lib/core-net/client/connect3.c index 0c1d4990f1..3b73226776 100644 --- a/lib/core-net/client/connect3.c +++ b/lib/core-net/client/connect3.c @@ -266,9 +266,8 @@ lws_client_connect_3_connect(struct lws *wsi, const char *ads, if (lwsi_state(wsi) == LRS_WAITING_CONNECT && lws_socket_is_valid(wsi->desc.sockfd)) { - if (!wsi->dns_sorted_list.count && - !wsi->sul_connect_timeout.list.owner) - /* no dns results and no ongoing timeout for one */ + if (!wsi->sul_connect_timeout.list.owner) + /* no ongoing timeout for one */ goto connect_to; /* @@ -364,7 +363,7 @@ lws_client_connect_3_connect(struct lws *wsi, const char *ads, if (wsi->udp) wsi->udp->sa46 = curr->dest; #endif -#if defined(LWS_WITH_NETLINK) +#if defined(LWS_WITH_ROUTING) wsi->peer_route_uidx = curr->uidx; lwsl_wsi_info(wsi, "peer_route_uidx %d", wsi->peer_route_uidx); #endif @@ -746,6 +745,22 @@ lws_client_connect_3_connect(struct lws *wsi, const char *ads, &salen) == -1) { en = LWS_ERRNO; lwsl_info("getsockname: %s\n", lws_errno_describe(en, t16, sizeof(t16))); + } else { +#if defined(LWS_WITH_IPV6) + if (wsi->sa46_peer.sa4.sin_family == AF_INET6 && + wsi->sa46_local.sa4.sin_family == AF_INET6) { + const uint8_t *pb = (const uint8_t *)&wsi->sa46_peer.sa6.sin6_addr; + const uint8_t *lb = (const uint8_t *)&wsi->sa46_local.sa6.sin6_addr; + int peer_is_ll = (pb[0] == 0xfe && (pb[1] & 0xc0) == 0x80); + int local_is_ll = (lb[0] == 0xfe && (lb[1] & 0xc0) == 0x80); + + if (local_is_ll && !peer_is_ll) { + lwsl_wsi_notice(wsi, "rejecting global v6 peer with link-local src"); + cce = "incompatible v6 scopes"; + goto try_next_dns_result_fds; + } + } +#endif } #if defined(_DEBUG) #if defined(LWS_WITH_UNIX_SOCK) @@ -765,7 +780,10 @@ lws_client_connect_3_connect(struct lws *wsi, const char *ads, #endif lws_metrics_caliper_report(wsi->cal_conn, METRES_GO); - lws_addrinfo_clean(wsi); +#if defined(LWS_ROLE_QUIC) + if (strcmp(wsi->role_ops->name, "quic") != 0) +#endif + lws_addrinfo_clean(wsi); if (wsi->a.protocol) wsi->a.protocol->callback(wsi, LWS_CALLBACK_WSI_CREATE, diff --git a/lib/core-net/client/connect4.c b/lib/core-net/client/connect4.c index 7bd9b80278..14912b3c1f 100644 --- a/lib/core-net/client/connect4.c +++ b/lib/core-net/client/connect4.c @@ -39,10 +39,11 @@ lws_client_connect_4_established(struct lws *wsi, struct lws *wsi_piggyback, meth = lws_wsi_client_stash_item(wsi, CIS_METHOD, _WSI_TOKEN_CLIENT_METHOD); - if (meth && (!strcmp(meth, "RAW") || + if ((meth && (!strcmp(meth, "RAW") || !strcmp(meth, "MQTT") || !strcmp(meth, "QUIC") || - !strcmp(meth, "UDP"))) + !strcmp(meth, "UDP"))) || + !strcmp(wsi->role_ops->name, "quic")) rawish = 1; if (wsi_piggyback) @@ -239,10 +240,10 @@ lws_client_connect_4_established(struct lws *wsi, struct lws *wsi_piggyback, #endif #if defined(LWS_ROLE_QUIC) - if (meth && !strcmp(meth, "QUIC")) { + if ((meth && !strcmp(meth, "QUIC")) || + !strcmp(wsi->role_ops->name, "quic")) { lwsi_set_state(wsi, LRS_WAITING_SSL); wsi->hdr_parsing_completed = 1; - lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); lws_callback_on_writable(wsi); return wsi; } diff --git a/lib/core-net/client/sort-dns.c b/lib/core-net/client/sort-dns.c index eba642a1fe..5ea45dc516 100644 --- a/lib/core-net/client/sort-dns.c +++ b/lib/core-net/client/sort-dns.c @@ -45,7 +45,17 @@ #include #endif -#if defined(LWS_WITH_IPV6) && defined(LWS_WITH_NETLINK) +#if !defined(IFA_F_DEPRECATED) +#define IFA_F_DEPRECATED 0 +#endif +#if !defined(IFA_F_HOMEADDRESS) +#define IFA_F_HOMEADDRESS 0 +#endif +#if !defined(IFA_F_TEMPORARY) +#define IFA_F_TEMPORARY 0 +#endif + +#if defined(LWS_WITH_IPV6) && defined(LWS_WITH_ROUTING) /* * RFC6724 default policy table @@ -136,10 +146,9 @@ lws_ipv6_prefix_match_len(const struct sockaddr_in6 *a, static int lws_ipv6_unicast_scope(const struct sockaddr_in6 *sa) { - uint64_t *u; + const uint8_t *b = (const uint8_t *)&sa->sin6_addr; - u = (uint64_t *)&sa->sin6_addr; - if (*u == 0xfe80000000000000ull) + if (b[0] == 0xfe && (b[1] & 0xc0) == 0x80) return 2; /* link-local */ return 0xe; @@ -407,8 +416,8 @@ lws_sort_dns_dcomp(const lws_dns_sort_t *da, const lws_dns_sort_t *db) scopea = lws_ipv6_unicast_scope(to_v6_sa(&da->dest)); scopeb = lws_ipv6_unicast_scope(to_v6_sa(&db->dest)); - scope_srca = lws_ipv6_unicast_scope(to_v6_sa(&da->source)); - scope_srcb = lws_ipv6_unicast_scope(to_v6_sa(&db->source)); + scope_srca = da->source ? lws_ipv6_unicast_scope(to_v6_sa(&da->source->src)) : 0; + scope_srcb = db->source ? lws_ipv6_unicast_scope(to_v6_sa(&db->source->src)) : 0; if (scopea == scope_srca && scopeb != scope_srcb) return SAS_PREFER_A; @@ -470,8 +479,10 @@ lws_sort_dns_dcomp(const lws_dns_sort_t *da, const lws_dns_sort_t *db) if (!db->source) return SAS_PREFER_A; - lws_sort_dns_classify(&da->source->dest, &score_srca); - lws_sort_dns_classify(&db->source->dest, &score_srcb); + if (da->source) + lws_sort_dns_classify(&da->source->src, &score_srca); + if (db->source) + lws_sort_dns_classify(&db->source->src, &score_srcb); if (score_srca.label == da->score.label && score_srcb.label != db->score.label) @@ -527,8 +538,8 @@ lws_sort_dns_dcomp(const lws_dns_sort_t *da, const lws_dns_sort_t *db) * then prefer DB. */ - cpla = lws_ipv6_prefix_match_len(&da->source->dest.sa6, &da->dest.sa6); - cplb = lws_ipv6_prefix_match_len(&db->source->dest.sa6, &db->dest.sa6); + cpla = da->source ? lws_ipv6_prefix_match_len(&da->source->src.sa6, &da->dest.sa6) : 0; + cplb = db->source ? lws_ipv6_prefix_match_len(&db->source->src.sa6, &db->dest.sa6) : 0; if (cpla > cplb) return SAS_PREFER_A; @@ -588,7 +599,7 @@ lws_sort_dns_dump(struct lws *wsi) int lws_sort_dns(struct lws *wsi, const struct addrinfo *result) { -#if defined(LWS_WITH_NETLINK) +#if defined(LWS_WITH_ROUTING) struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi]; #endif const struct addrinfo *ai = result; @@ -605,7 +616,7 @@ lws_sort_dns(struct lws *wsi, const struct addrinfo *result) */ while (ai) { -#if defined(LWS_WITH_NETLINK) +#if defined(LWS_WITH_ROUTING) lws_route_t *estr = NULL #if defined(LWS_WITH_IPV6) , *bestsrc = NULL @@ -634,7 +645,7 @@ lws_sort_dns(struct lws *wsi, const struct addrinfo *result) lwsl_wsi_info(wsi, "unsorted entry (af %d) %s", ds->dest.sa4.sin_family, afip); -#if defined(LWS_WITH_NETLINK) +#if defined(LWS_WITH_ROUTING) /* * Let's assess this DNS result in terms of route @@ -642,7 +653,7 @@ lws_sort_dns(struct lws *wsi, const struct addrinfo *result) * we don't have a way to use it if we listed it */ - if (pt->context->routing_table.count) { + if (pt->context->routing_table.count && !wsi->do_bind) { estr = _lws_route_est_outgoing(pt, &ds->dest); if (!estr) { @@ -665,7 +676,7 @@ lws_sort_dns(struct lws *wsi, const struct addrinfo *result) } #endif -#if defined(LWS_WITH_NETLINK) && defined(LWS_WITH_IPV6) +#if defined(LWS_WITH_ROUTING) && defined(LWS_WITH_IPV6) /* * These sorting rules only apply to ipv6. If we have ipv4 @@ -733,13 +744,12 @@ lws_sort_dns(struct lws *wsi, const struct addrinfo *result) } just_add: - if (!bestsrc) { + ds->source = bestsrc ? bestsrc : estr; + if (!ds->source) { lws_dll2_add_tail(&ds->list, &wsi->dns_sorted_list); goto next; } - ds->source = bestsrc; - /* * RFC6724 Section 6: Destination Address Selection * diff --git a/lib/core-net/close.c b/lib/core-net/close.c index 429723f4ea..88b6727453 100644 --- a/lib/core-net/close.c +++ b/lib/core-net/close.c @@ -197,13 +197,23 @@ __lws_reset_wsi(struct lws *wsi) wsi->close_when_buffered_out_drained = wsi->could_have_pending = 0; #endif +#if defined(LWS_ROLE_QUIC) + lws_quic_stream_cleanup(wsi); + memset(&wsi->quic, 0, sizeof(wsi->quic)); +#endif + +#if defined(LWS_ROLE_H3) + memset(&wsi->h3, 0, sizeof(wsi->h3)); +#endif + #if defined(LWS_WITH_CLIENT) wsi->do_ws = wsi->chunked = wsi->client_rx_avail = wsi->client_http_body_pending = wsi->transaction_from_pipeline_queue = wsi->keepalive_active = wsi->keepalive_rejected = wsi->redirected_to_get = wsi->client_pipeline = wsi->client_h2_alpn = wsi->client_mux_substream = wsi->client_mux_migrated = - wsi->tls_session_reused = wsi->perf_done = 0; + wsi->tls_session_reused = wsi->perf_done = + wsi->tried_quic = 0; wsi->immortal_substream_count = 0; #endif @@ -319,11 +329,51 @@ lws_remove_child_from_any_parent(struct lws *wsi) void lws_inform_client_conn_fail(struct lws *wsi, void *arg, size_t len) { - lws_addrinfo_clean(wsi); - if (wsi->already_did_cce) return; +#if defined(LWS_ROLE_H3) || defined(LWS_ROLE_QUIC) + if (wsi->udp && wsi->tried_quic && wsi->role_ops && !strcmp(wsi->role_ops->name, "quic")) { + const char *path = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI); + const char *host = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST); + const char *ads = wsi->cli_hostname_copy; + + if (!ads && wsi->stash) + ads = wsi->stash->cis[CIS_ADDRESS]; + + if (wsi->dns_sorted_list.count) { + struct lws_dll2 *d = lws_dll2_get_head(&wsi->dns_sorted_list); + lws_dns_sort_t *ds = lws_container_of(d, lws_dns_sort_t, list); + char ads_fallback[48]; + + lws_sa46_write_numeric_address(&ds->dest, ads_fallback, sizeof(ads_fallback)); + lws_dll2_remove(d); + lws_free(ds); + + if (ads_fallback[0] && host && path) { + lwsl_wsi_notice(wsi, "QUIC fail, trying next DNS result %s", ads_fallback); + lws_addrinfo_clean(wsi); + if (lws_client_reset(&wsi, (int)wsi->tls.use_ssl, ads_fallback, wsi->c_port, path, host, 1)) { + return; + } + } + } + + if (ads && host && path) { + wsi->tried_quic = 0; + lwsl_wsi_notice(wsi, "QUIC connection failed, falling back to TCP"); + lws_free_set_NULL(wsi->udp); + lws_addrinfo_clean(wsi); + if (lws_client_reset(&wsi, (int)wsi->tls.use_ssl, ads, wsi->c_port, path, host, 1)) { + /* Successfully scheduled fallback */ + return; + } + } + } +#endif + + lws_addrinfo_clean(wsi); + wsi->already_did_cce = 1; if (!wsi->a.protocol || (wsi->a.context && wsi->a.context->being_destroyed)) @@ -358,6 +408,9 @@ lws_addrinfo_clean(struct lws *wsi) static void lws_async_worker_wait_and_reap(struct lws *wsi) { + if (!wsi->async_worker_job) + return; + while (1) { pthread_mutex_lock(&wsi->a.context->async_worker_mutex); if (!wsi->async_worker_job) { @@ -1016,7 +1069,7 @@ __lws_close_free_wsi_final(struct lws *wsi) if (wsi->client_mux_substream_was) wsi->h2.END_STREAM = wsi->h2.END_HEADERS = 0; #endif -#if defined(LWS_ROLE_H2) || defined(LWS_ROLE_MQTT) +#if defined(LWS_ROLE_H2) || defined(LWS_ROLE_MQTT) || defined(LWS_ROLE_H3) if (wsi->mux.parent_wsi) { lws_wsi_mux_sibling_disconnect(wsi); wsi->mux.parent_wsi = NULL; diff --git a/lib/core-net/network.c b/lib/core-net/network.c index 4b3365a807..6b5f91c0e2 100644 --- a/lib/core-net/network.c +++ b/lib/core-net/network.c @@ -131,6 +131,7 @@ lws_get_addresses(struct lws_vhost *vh, void *ads, char *name, memmove(rip, rip + 7, strlen(rip) - 6); if (name) { +#if !defined(LWS_PLAT_FREERTOS) && !defined(LWS_PLAT_OPTEE) getnameinfo((struct sockaddr *)ads, sizeof(struct sockaddr_in6), name, #if defined(__ANDROID__) @@ -139,6 +140,12 @@ lws_get_addresses(struct lws_vhost *vh, void *ads, char *name, (socklen_t)name_len, #endif NULL, 0, 0); +#else + if (!lws_plat_inet_ntop(AF_INET6, + &((struct sockaddr_in6 *)ads)->sin6_addr, + name, (socklen_t)name_len)) + name[0] = '\0'; +#endif } return 0; @@ -148,8 +155,8 @@ lws_get_addresses(struct lws_vhost *vh, void *ads, char *name, addr4.sin_family = AF_INET; addr4.sin_addr = ((struct sockaddr_in *)ads)->sin_addr; -#if !defined(LWS_PLAT_FREERTOS) if (name) { +#if !defined(LWS_PLAT_FREERTOS) && !defined(LWS_PLAT_OPTEE) getnameinfo((struct sockaddr *)ads, sizeof(struct sockaddr_in), name, @@ -159,8 +166,13 @@ lws_get_addresses(struct lws_vhost *vh, void *ads, char *name, (socklen_t)name_len, #endif NULL, 0, 0); - } +#else + if (!lws_plat_inet_ntop(AF_INET, + &((struct sockaddr_in *)ads)->sin_addr, + name, (socklen_t)name_len)) + name[0] = '\0'; #endif + } } if (addr4.sin_family == AF_UNSPEC) @@ -196,6 +208,12 @@ const char * lws_get_peer_simple(struct lws *wsi, char *name, size_t namelen) { wsi = lws_get_network_wsi(wsi); +#if defined(LWS_WITH_UDP) + if (wsi->udp) { + lws_sa46_write_numeric_address(&wsi->udp->sa46, name, namelen); + return name; + } +#endif return lws_get_peer_simple_fd(wsi->desc.sockfd, name, namelen); } #endif @@ -272,7 +290,7 @@ lws_socket_bind(struct lws_vhost *vhost, struct lws *wsi, #ifdef LWS_WITH_UNIX_SOCK struct sockaddr_un serv_unix; #endif -#ifdef LWS_WITH_IPV6 +#if defined(LWS_WITH_IPV6) && !defined(LWS_PLAT_FREERTOS) && !defined(LWS_PLAT_OPTEE) struct sockaddr_in6 serv_addr6; #endif struct sockaddr_in serv_addr4; @@ -494,7 +512,6 @@ lws_socket_bind(struct lws_vhost *vhost, struct lws *wsi, return port; } -#if defined(LWS_WITH_CLIENT) unsigned int lws_retry_get_delay_ms(struct lws_context *context, @@ -578,29 +595,32 @@ lws_retry_sul_schedule_retry_wsi(struct lws *wsi, lws_sorted_usec_list_t *sul, wsi->role_ops == &role_ops_h2 #endif ) +#if defined(LWS_WITH_CLIENT) /* * Since we're doing it by wsi, we're in a position to check for * http retry-after, it will increase us accordingly if found */ lws_http_check_retry_after(wsi, &us); +#endif #endif lws_sul_schedule(wsi->a.context, wsi->tsi, sul, cb, us); return 0; } -#endif #if defined(LWS_WITH_IPV6) unsigned long lws_get_addr_scope(struct lws *wsi, const char *ifname_or_ipaddr) { unsigned long scope; - char ip[NI_MAXHOST]; +#if !defined(LWS_PLAT_FREERTOS) && !defined(LWS_PLAT_OPTEE) + char ip[256]; unsigned int i; -#if !defined(WIN32) +#endif +#if !defined(WIN32) && !defined(LWS_PLAT_FREERTOS) && !defined(LWS_PLAT_OPTEE) struct ifaddrs *addrs, *addr; -#else +#elif defined(WIN32) PIP_ADAPTER_ADDRESSES adapter, addrs = NULL; PIP_ADAPTER_UNICAST_ADDRESS addr; struct sockaddr_in6 *sockaddr; @@ -625,7 +645,7 @@ lws_get_addr_scope(struct lws *wsi, const char *ifname_or_ipaddr) scope = 0; -#if !defined(WIN32) +#if !defined(WIN32) && !defined(LWS_PLAT_FREERTOS) && !defined(LWS_PLAT_OPTEE) getifaddrs(&addrs); for (addr = addrs; addr; addr = addr->ifa_next) { @@ -650,7 +670,28 @@ lws_get_addr_scope(struct lws *wsi, const char *ifname_or_ipaddr) } } freeifaddrs(addrs); -#else +#elif defined(LWS_PLAT_FREERTOS) +#if defined(LWS_AMAZON_RTOS) || defined(LWS_ESP_PLATFORM) +#include +#include + { + ip6_addr_t ip6; + struct netif *netif; + int j; + + if (ip6addr_aton(ifname_or_ipaddr, &ip6)) { + for (netif = netif_list; netif != NULL; netif = netif->next) { + for (j = 0; j < LWIP_IPV6_NUM_ADDRESSES; j++) { + if (ip6_addr_isvalid(netif_ip6_addr_state(netif, j)) && + ip6_addr_cmp(&ip6, netif_ip6_addr(netif, j))) { + return netif_get_index(netif); + } + } + } + } + } +#endif +#elif defined(WIN32) for (i = 0; i < 5; i++) { ret = GetAdaptersAddresses(AF_INET6, GAA_FLAG_INCLUDE_PREFIX, diff --git a/lib/core-net/output.c b/lib/core-net/output.c index d5ec1eaf4e..cfc5880570 100644 --- a/lib/core-net/output.c +++ b/lib/core-net/output.c @@ -355,6 +355,13 @@ lws_ssl_capable_write_no_ssl(struct lws *wsi, unsigned char *buf, size_t len) #endif len, 0, sa46_sockaddr(&wsi->udp->sa46), sa46_socklen(&wsi->udp->sa46)); + + if (n < 0 && LWS_ERRNO == LWS_EISCONN) + n = (int)sendto(wsi->desc.sockfd, (const char *)buf, +#if defined(WIN32) + (int) +#endif + len, 0, NULL, 0); } else #endif if (wsi->role_ops->file_handle) diff --git a/lib/core-net/pollfd.c b/lib/core-net/pollfd.c index 120e29857b..cf451956b5 100644 --- a/lib/core-net/pollfd.c +++ b/lib/core-net/pollfd.c @@ -302,7 +302,7 @@ __insert_wsi_socket_into_fds(struct lws_context *context, struct lws *wsi) assert(wsi); -#if defined(LWS_WITH_NETLINK) +#if defined(LWS_WITH_ROUTING) assert(wsi->event_pipe || wsi->a.vhost || wsi == pt->context->netlink); #else assert(wsi->event_pipe || wsi->a.vhost); diff --git a/lib/core-net/private-lib-core-net.h b/lib/core-net/private-lib-core-net.h index ccfdbbcc1b..1397a6967c 100644 --- a/lib/core-net/private-lib-core-net.h +++ b/lib/core-net/private-lib-core-net.h @@ -47,6 +47,9 @@ struct lws_muxable { }; #include "private-lib-roles.h" +#if defined(LWS_ROLE_WT) +#include "wt/private-lib-roles-wt.h" +#endif #ifdef __cplusplus extern "C" { @@ -758,7 +761,10 @@ struct lws { struct _lws_quic_related quic; #endif #if defined(LWS_ROLE_H3) - struct lws_qpack_tx_encoder *qpack_tx_encoder; + struct _lws_h3_related h3; +#endif +#if defined(LWS_ROLE_WT) + struct _lws_wt_related wt; #endif #if defined(LWS_ROLE_H2) || defined(LWS_ROLE_MQTT) || defined(LWS_ROLE_QUIC) @@ -854,6 +860,8 @@ struct lws { #if defined(LWS_WITH_CLIENT) struct client_info_stash *stash; char *cli_hostname_copy; + char alpn_discovered[8]; + #if defined(LWS_WITH_CONMON) struct lws_conmon conmon; @@ -892,7 +900,7 @@ struct lws { unsigned int mux_substream:1; unsigned int upgraded_to_http2:1; unsigned int mux_stream_immortal:1; - unsigned int h2_stream_carries_ws:1; /* immortal set as well */ + unsigned int h23_stream_carries_ws:1; /* immortal set as well */ unsigned int h2_stream_carries_sse:1; /* immortal set as well */ unsigned int h2_acked_settings:1; unsigned int seen_nonpseudoheader:1; @@ -947,6 +955,7 @@ struct lws { unsigned int tls_borrowed:1; unsigned int tls_borrowed_hs:1; unsigned int tls_read_wanted_write:1; + unsigned int tried_quic:1; #ifdef LWS_WITH_ACCESS_LOG unsigned int access_log_pending:1; @@ -1009,7 +1018,7 @@ struct lws { #if defined(LWS_WITH_CGI) || defined(LWS_WITH_CLIENT) char reason_bf; /* internal writeable callback reason bitfield */ #endif -#if defined(LWS_WITH_NETLINK) +#if defined(LWS_WITH_ROUTING) lws_route_uidx_t peer_route_uidx; /**< unique index of the route the connection is estimated to take */ #endif diff --git a/lib/core-net/route.c b/lib/core-net/route.c index 8999f54226..502cbe6b57 100644 --- a/lib/core-net/route.c +++ b/lib/core-net/route.c @@ -405,7 +405,7 @@ _lws_route_pt_close_route_users(struct lws_context_per_thread *pt, return 0; } -#if defined(LWS_WITH_NETWORK) && defined(LWS_WITH_NETLINK) +#if defined(LWS_WITH_NETWORK) && defined(LWS_WITH_ROUTING) struct lws_dll2_owner * lws_routing_table_get(struct lws_context *cx) { diff --git a/lib/core-net/service.c b/lib/core-net/service.c index ba04b0339f..84244233df 100644 --- a/lib/core-net/service.c +++ b/lib/core-net/service.c @@ -128,6 +128,7 @@ lws_callback_as_writeable(struct lws *wsi) int n, m; n = wsi->role_ops->writeable_cb[lwsi_role_server(wsi)]; + // lwsl_wsi_notice(wsi, "lws_callback_as_writeable: cb enum = %d", n); m = user_callback_handle_rxflow(wsi->a.protocol->callback, wsi, (enum lws_callback_reasons) n, wsi->user_space, NULL, 0); diff --git a/lib/core-net/stub.c b/lib/core-net/stub.c index d61bba5923..374d568f90 100644 --- a/lib/core-net/stub.c +++ b/lib/core-net/stub.c @@ -33,6 +33,10 @@ #include #endif +#if defined(__APPLE__) +#include +#endif + #if defined(LWS_WITH_CLIENT) struct lws_stub_req { struct lws_dll2 list; @@ -51,6 +55,7 @@ struct lws_stub_manager { char uds_path[256]; char secret[129]; struct lws_spawn_piped *lsp; + struct lws_stub_config config; const struct lws_protocols *protocols; @@ -65,6 +70,9 @@ struct lws_stub_manager { char exe_path[256]; }; +static int +lws_stub_client_connect(struct lws_stub_manager *mgr); + struct lws_stub_manager * lws_stub_spawn(const struct lws_stub_config *config) { @@ -79,6 +87,7 @@ lws_stub_spawn(const struct lws_stub_config *config) mgr->cx = config->cx; mgr->vh = config->vh; + memcpy(&mgr->config, config, sizeof(mgr->config)); lws_strncpy(mgr->uds_path, config->uds_path, sizeof(mgr->uds_path)); mgr->protocols = config->protocols; @@ -87,34 +96,42 @@ lws_stub_spawn(const struct lws_stub_config *config) lws_hex_from_byte_array(rand, sizeof(rand), mgr->secret, sizeof(mgr->secret)); memset(&spawn_info, 0, sizeof(spawn_info)); - const char *argv0 = lws_cmdline_option_cx_argv0(mgr->cx); const char *exe_path = "/usr/local/bin/lwsws"; - if (argv0) { +#if defined(__APPLE__) + { + uint32_t size = sizeof(mgr->exe_path); + if (_NSGetExecutablePath(mgr->exe_path, &size) == 0) + exe_path = mgr->exe_path; + } +#elif defined(__linux__) + { + int m = (int)readlink("/proc/self/exe", mgr->exe_path, sizeof(mgr->exe_path) - 1); + if (m > 0) { + mgr->exe_path[m] = '\0'; + exe_path = mgr->exe_path; + } + } +#else + { + const char *argv0 = lws_cmdline_option_cx_argv0(mgr->cx); + if (argv0) { if (argv0[0] == '/') { lws_strncpy(mgr->exe_path, argv0, sizeof(mgr->exe_path)); exe_path = mgr->exe_path; } else { -#if defined(__linux__) - int m = (int)readlink("/proc/self/exe", mgr->exe_path, sizeof(mgr->exe_path) - 1); - if (m > 0) { - mgr->exe_path[m] = '\0'; - exe_path = mgr->exe_path; - } else -#endif #if !defined(WIN32) - { - /* Fallback to realpath of argv0 if possible */ - if (realpath(argv0, mgr->exe_path)) - exe_path = mgr->exe_path; - else - exe_path = argv0; - } + if (realpath(argv0, mgr->exe_path)) + exe_path = mgr->exe_path; + else + exe_path = argv0; #else exe_path = argv0; #endif } + } } +#endif mgr->exec_array[n++] = exe_path; lwsl_notice("%s: Spawning stub with exe: %s\n", __func__, exe_path); @@ -173,6 +190,9 @@ lws_stub_spawn(const struct lws_stub_config *config) lwsl_vhost_notice(mgr->vh, "%s: Spawned stub %s\n", __func__, config->stub_name); + if (!mgr->sul.list.owner && !mgr->wsi_client) + lws_stub_client_connect(mgr); + return mgr; spawn_fail: @@ -198,7 +218,7 @@ lws_stub_server_init(const struct lws_stub_config *config, char *secret_out, voi /* 1. Read secret from stdin */ while (rx < 128) { - ssize_t n = read(0, secret_out + rx, 128 - (unsigned int)rx); + ssize_t n = read(0, (void *)(secret_out + rx), 128 - (unsigned int)rx); if (n <= 0) break; rx += (size_t)n; @@ -214,7 +234,7 @@ lws_stub_server_init(const struct lws_stub_config *config, char *secret_out, voi if (extra_out && extra_len > 0) { /* We only do a single read here because the payload size is variable * and unknown to the child, and the pipe remains open for future IPC. */ - ssize_t n = read(0, extra_out, (unsigned int)extra_len); + ssize_t n = read(0, (void *)extra_out, (unsigned int)extra_len); if (n < 0) { lwsl_err("%s: Failed to read extra payload\n", __func__); /* Non-fatal */ @@ -227,6 +247,7 @@ lws_stub_server_init(const struct lws_stub_config *config, char *secret_out, voi info.iface = config->uds_path; info.protocols = config->protocols; info.vhost_name = config->stub_name; + info.user = config->user; unlink(info.iface); vh_uds = lws_create_vhost(config->cx, &info); @@ -236,7 +257,9 @@ lws_stub_server_init(const struct lws_stub_config *config, char *secret_out, voi } /* 3. Secure permissions: Only root (and unprivileged clients dropping privs) */ +#if !defined(WIN32) chmod(info.iface, 0600); +#endif /* Signal ready */ lwsl_notice("STUB-READY (%s)\n", config->stub_name); @@ -292,8 +315,7 @@ lws_stub_client_connect(struct lws_stub_manager *mgr) if (mgr->ctry > 1) lwsl_notice("%s: Synchronous connect failed (errno %d), retrying in %u ms (attempt %d)\n", __func__, LWS_ERRNO, (unsigned int)ms, mgr->ctry); lws_sul_schedule(mgr->cx, 0, &mgr->sul, stub_retry_cb, ms * 1000); - } else - lwsl_err("%s: Max retries reached\n", __func__); + } return -1; } @@ -326,6 +348,8 @@ lws_callback_stub_client(struct lws *wsi, enum lws_callback_reasons reason, case LWS_CALLBACK_RAW_CONNECTED: lwsl_notice("%s: UDS connected to stub\n", __func__); mgr->ctry = 0; /* Reset retry counter on success */ + if (mgr->config.connected_cb) + mgr->config.connected_cb(mgr); lws_callback_on_writable(wsi); break; @@ -343,8 +367,18 @@ lws_callback_stub_client(struct lws *wsi, enum lws_callback_reasons reason, req->tx_pos += (size_t)n; } - if (req->tx_pos < req->tx_len) + if (req->tx_pos < req->tx_len) { lws_callback_on_writable(wsi); + } else if (!req->rx_cb && !req->raw_cb) { + /* No response expected, so we can complete and free the request immediately */ + lws_dll2_remove(&req->list); + lws_free(req->tx_buf); + lws_free(req); + + /* If there are more requests queued, ask for writable again */ + if (lws_dll2_get_head(&mgr->reqs)) + lws_callback_on_writable(wsi); + } break; } @@ -415,9 +449,15 @@ lws_stub_request(struct lws_stub_manager *mgr, lws_dll2_add_tail(&req->list, &mgr->reqs); - if (!mgr->wsi_client) - lws_stub_client_connect(mgr); - else + if (!mgr->wsi_client) { + if (!mgr->sul.list.owner) { + if (mgr->ctry >= 10) { + /* If we hit max retries, reset and try again if new requests come in */ + mgr->ctry = 0; + } + lws_stub_client_connect(mgr); + } + } else lws_callback_on_writable(mgr->wsi_client); return 0; diff --git a/lib/core-net/vhost.c b/lib/core-net/vhost.c index e6a954e127..56344f91ab 100644 --- a/lib/core-net/vhost.c +++ b/lib/core-net/vhost.c @@ -34,6 +34,12 @@ const struct lws_role_ops *available_roles[] = { #if defined(LWS_ROLE_QUIC) &role_ops_quic, #endif +#if defined(LWS_ROLE_H3) + &role_ops_h3, +#endif +#if defined(LWS_ROLE_WT) + &role_ops_wt, +#endif #if defined(LWS_ROLE_H1) &role_ops_h1, #endif @@ -123,9 +129,22 @@ lws_role_call_alpn_negotiated(struct lws *wsi, const char *alpn) lwsl_wsi_info(wsi, "'%s'", alpn); #endif + /* First try the WSI's current role if it matches the ALPN or if it's QUIC */ + if (wsi->role_ops && lws_rops_fidx(wsi->role_ops, LWS_ROPS_alpn_negotiated) && + ((wsi->role_ops->alpn && !strcmp(wsi->role_ops->alpn, alpn)) || + (!strcmp(wsi->role_ops->name, "quic") && !strcmp(alpn, "lws-quic")))) { + lwsl_wsi_notice(wsi, "lws_role_call_alpn_negotiated: Matched WSI current role: %s", wsi->role_ops->name); +#if defined(LWS_WITH_SERVER) + lws_metrics_tag_wsi_add(wsi, "upg", wsi->role_ops->name); +#endif + return (lws_rops_func_fidx(wsi->role_ops, LWS_ROPS_alpn_negotiated)). + alpn_negotiated(wsi, alpn); + } + LWS_FOR_EVERY_AVAILABLE_ROLE_START(ar) if (ar->alpn && !strcmp(ar->alpn, alpn) && lws_rops_fidx(ar, LWS_ROPS_alpn_negotiated)) { + // lwsl_wsi_notice(wsi, "lws_role_call_alpn_negotiated: Matched fallback role: %s", ar->name); #if defined(LWS_WITH_SERVER) lws_metrics_tag_wsi_add(wsi, "upg", ar->name); #endif @@ -697,6 +716,9 @@ lws_create_vhost(struct lws_context *context, #endif #if defined(LWS_WITH_CLIENT) extern const struct lws_protocols lws_async_ipc_protocol; +#endif +#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API) + extern const struct lws_protocols lws_sspc_protocols[]; #endif int n; @@ -934,6 +956,9 @@ lws_create_vhost(struct lws_context *context, (unsigned int)dht_count + #if defined(LWS_WITH_CLIENT) 1 + +#endif +#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API) + 1 + #endif (unsigned int)context->plugin_protocol_count + (unsigned int)fx + 1), "vh plugin table"); @@ -1005,6 +1030,12 @@ lws_create_vhost(struct lws_context *context, vh->count_protocols++; #endif +#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API) + memcpy(&lwsp[m], &lws_sspc_protocols[0], sizeof(*lwsp)); + m++; + vh->count_protocols++; +#endif + /* * 3: For compatibility, all protocols enabled on vhost if only @@ -2080,6 +2111,34 @@ lws_vhost_active_conns(struct lws *wsi, struct lws **nwsi, const char *adsin) } #endif +#if defined(LWS_ROLE_H3) + /* + * h3: if in usable state already: just use it without + * going through the queue + */ + if (lwsi_role_h3(w) && w->client_h2_alpn && w->client_mux_migrated && + (lwsi_state(w) == LRS_H2_WAITING_TO_SEND_HEADERS || + lwsi_state(w) == LRS_ESTABLISHED || + lwsi_state(w) == LRS_IDLING)) { + + lwsl_wsi_info(w, "just join h3 directly 0x%x", + lwsi_state(w)); + + if (lwsi_state(w) == LRS_IDLING) + _lws_generic_transaction_completed_active_conn(&w, 0); + + wsi->client_h2_alpn = 1; + if (lws_wsi_h3_adopt(w, wsi)) { + lws_vhost_unlock(wsi->a.vhost); /* } ---------- */ + lws_context_unlock(wsi->a.context); /* -------------- cx { */ + + *nwsi = w; + + return ACTIVE_CONNS_MUXED; + } + } +#endif + #if defined(LWS_ROLE_MQTT) /* * MQTT: if in usable state already: just use it without diff --git a/lib/core-net/wsi.c b/lib/core-net/wsi.c index 6ad3ef774c..8367481319 100644 --- a/lib/core-net/wsi.c +++ b/lib/core-net/wsi.c @@ -580,15 +580,48 @@ lws_tls_conn *lws_get_ssl(struct lws *wsi) { return wsi->tls.ssl; } #endif int lws_has_buffered_out(struct lws *wsi) { - if (wsi->buflist_out) + if (wsi->buflist_out) { + lwsl_notice("lws_has_buffered_out: %s has buflist_out\n", lws_wsi_tag(wsi)); return 1; + } #if defined(LWS_ROLE_H2) { struct lws *nwsi = lws_get_network_wsi(wsi); - if (nwsi->buflist_out) + if (nwsi && nwsi->buflist_out) { + lwsl_notice("lws_has_buffered_out: network wsi %s has buflist_out\n", lws_wsi_tag(nwsi)); return 1; + } + } +#endif + +#if defined(LWS_ROLE_QUIC) + if (wsi->quic.qs) { + struct lws *nwsi = lws_get_network_wsi(wsi); + struct lws_quic_netconn *qn = nwsi ? nwsi->quic.qn : NULL; + if (qn) { + uint64_t sid = wsi->quic.qs->stream_id; + int i; + + for (i = 0; i < LWS_QUIC_LEVEL_COUNT; i++) { + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, qn->pending_tx[i].head) { + struct lws_quic_tx_frame *f = lws_container_of(d, struct lws_quic_tx_frame, list); + if ((f->type & 0xf8) == LWS_QUIC_FT_STREAM && f->stream_id == sid) { + lwsl_notice("lws_has_buffered_out: %s has pending_tx stream frame on sid %llu\n", lws_wsi_tag(wsi), (unsigned long long)sid); + return 1; + } + } lws_end_foreach_dll_safe(d, d1); + + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, qn->in_flight[i].head) { + struct lws_quic_tx_frame *f = lws_container_of(d, struct lws_quic_tx_frame, list); + if ((f->type & 0xf8) == LWS_QUIC_FT_STREAM && f->stream_id == sid) { + lwsl_notice("lws_has_buffered_out: %s has in_flight stream frame on sid %llu\n", lws_wsi_tag(wsi), (unsigned long long)sid); + return 1; + } + } lws_end_foreach_dll_safe(d, d1); + } + } } #endif @@ -1357,7 +1390,7 @@ int lws_wsi_keepalive_timeout_eff(struct lws *wsi) { return ds; } -#if defined(LWS_ROLE_H2) || defined(LWS_ROLE_MQTT) +#if defined(LWS_ROLE_H2) || defined(LWS_ROLE_MQTT) || defined(LWS_ROLE_QUIC) void lws_wsi_mux_insert(struct lws *wsi, struct lws *parent_wsi, unsigned int sid) { @@ -1369,7 +1402,8 @@ void lws_wsi_mux_insert(struct lws *wsi, struct lws *parent_wsi, wsi->mux.my_sid = sid; wsi->mux.parent_wsi = parent_wsi; - wsi->role_ops = parent_wsi->role_ops; + if (!wsi->role_ops) + wsi->role_ops = parent_wsi->role_ops; /* new guy's sibling is whoever was the first child before */ wsi->mux.sibling_list = parent_wsi->mux.child_list; @@ -1532,7 +1566,7 @@ int lws_wsi_mux_action_pending_writeable_reqs(struct lws *wsi) { } int lws_wsi_txc_check_skint(struct lws_tx_credit *txc, int32_t tx_cr) { - if (txc->tx_cr <= 0) { + if (tx_cr <= 0) { /* * If other side is not able to cope with us sending any DATA * so no matter if we have POLLOUT on our side if it's DATA we @@ -1540,7 +1574,7 @@ int lws_wsi_txc_check_skint(struct lws_tx_credit *txc, int32_t tx_cr) { */ if (!txc->skint) - lwsl_info("%s: %p: skint (%d)\n", __func__, txc, (int)txc->tx_cr); + lwsl_info("%s: %p: skint (%d)\n", __func__, txc, (int)tx_cr); txc->skint = 1; @@ -1548,7 +1582,7 @@ int lws_wsi_txc_check_skint(struct lws_tx_credit *txc, int32_t tx_cr) { } if (txc->skint) - lwsl_info("%s: %p: unskint (%d)\n", __func__, txc, (int)txc->tx_cr); + lwsl_info("%s: %p: unskint (%d)\n", __func__, txc, (int)tx_cr); txc->skint = 0; @@ -1600,9 +1634,9 @@ int lws_wsi_mux_apply_queue(struct lws *wsi) { lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, wsi->dll2_cli_txn_queue_owner.head) { +#if defined(LWS_ROLE_H2) struct lws *w = lws_container_of(d, struct lws, dll2_cli_txn_queue); -#if defined(LWS_ROLE_H2) if (lwsi_role_http(wsi) && lwsi_state(w) == LRS_H2_WAITING_TO_SEND_HEADERS) { lwsl_wsi_info(w, "cli pipeq to be h2"); diff --git a/lib/core/context.c b/lib/core/context.c index 26931acb51..09dc13b88f 100644 --- a/lib/core/context.c +++ b/lib/core/context.c @@ -152,7 +152,7 @@ lws_state_notify_protocol_init(struct lws_state_manager *mgr, } #endif -#if defined(LWS_WITH_NETLINK) +#if defined(LWS_WITH_ROUTING) /* * If we're going to use netlink routing data for DNS, we have to * wait to collect it asynchronously from the platform first. Netlink @@ -337,6 +337,9 @@ static const char * const opts_str = #if defined(LWS_ROLE_H2) "H2 " #endif +#if defined(LWS_ROLE_H3) + "H3 " +#endif #if defined(LWS_ROLE_WS) "WS " #endif @@ -415,15 +418,14 @@ lws_create_context(const struct lws_context_creation_info *info) #if defined(__ANDROID__) struct rlimit rt; #endif - size_t #if defined(LWS_PLAT_FREERTOS) /* smaller default, can set in info->pt_serv_buf_size */ - s1 = 2048, + size_t s1 = 2048; #else - s1 = 4096, + size_t s1 = 4096; #endif - size = sizeof(struct lws_context); #endif + size_t size = sizeof(struct lws_context); #if !defined(LWS_PLAT_BAREMETAL) && defined(LWS_WITH_NETWORK) int n; #endif @@ -646,6 +648,7 @@ lws_create_context(const struct lws_context_creation_info *info) if (!plev || lws_fi(&info->fic, "ctx_createfail_evlib_sel")) goto fail_event_libs; +#endif /* LWS_WITH_NETWORK */ #if defined(LWS_WITH_NETWORK) size += (size_t)plev->ops->evlib_size_ctx /* the ctx evlib priv */ + @@ -661,6 +664,15 @@ lws_create_context(const struct lws_context_creation_info *info) goto early_bail; } + context->name = info->vhost_name; + if (info->log_cx) + context->log_cx = info->log_cx; + else + context->log_cx = &log_cx; + lwsl_refcount_cx(context->log_cx, 1); + +#if defined(LWS_WITH_NETWORK) + #if defined(LWS_WITH_SYS_STATE) // NOTE: we need to init this fields because they may be used in logger when context destroying context->mgr_system.state_names = system_state_names; @@ -704,14 +716,12 @@ lws_create_context(const struct lws_context_creation_info *info) context->argc = info->argc; context->argv = info->argv; #endif - context->name = info->vhost_name; - if (info->log_cx) - context->log_cx = info->log_cx; - else - context->log_cx = &log_cx; - lwsl_refcount_cx(context->log_cx, 1); context->system_ops = info->system_ops; + + context->quic_tx_credit_cb = info->quic_tx_credit_cb; + context->quic_cc_ops = info->quic_cc_ops; + context->pt_serv_buf_size = (unsigned int)s1; context->protocols_copy = info->protocols; #if defined(LWS_WITH_TLS_JIT_TRUST) @@ -851,7 +861,11 @@ lws_create_context(const struct lws_context_creation_info *info) char mbedtls_version[32]; #if defined(MBEDTLS_VERSION_C) +#if defined(LWS_HAVE_MBEDTLS_V4) + lws_snprintf(mbedtls_version, sizeof(mbedtls_version), "%s", mbedtls_version_get_string()); +#else mbedtls_version_get_string(mbedtls_version); +#endif #else lws_snprintf(mbedtls_version, sizeof(mbedtls_version), "%s", MBEDTLS_VERSION_STRING); #endif @@ -1575,6 +1589,27 @@ lws_create_context(const struct lws_context_creation_info *info) } #endif +#if defined(LWS_WITH_CLIENT) + { + struct lws_cache_creation_info cci; + memset(&cci, 0, sizeof(cci)); + + cci.cx = context; + cci.ops = &lws_cache_ops_heap; + cci.name = "ALPN"; + cci.max_footprint = 4096; + cci.max_items = 256; + cci.max_payload = 16; + + context->alpn_cache = lws_cache_create(&cci); + if (!context->alpn_cache) { + lwsl_cx_err(context, "Failed to init ALPN cache"); + goto bail; + } + } +#endif + + #if defined(LWS_WITH_SYS_ASYNC_DNS) if (info->async_dns_servers) { const char **dsrv = info->async_dns_servers; @@ -2402,6 +2437,11 @@ lws_context_destroy(struct lws_context *context) lws_cache_destroy(&context->l1); #endif +#if defined(LWS_WITH_CLIENT) + lws_cache_destroy(&context->alpn_cache); +#endif + + #if defined(LWS_WITH_SYS_SMD) _lws_smd_destroy(context); #endif diff --git a/lib/core/libwebsockets.c b/lib/core/libwebsockets.c index 66a9fd7f34..f32be739cf 100644 --- a/lib/core/libwebsockets.c +++ b/lib/core/libwebsockets.c @@ -585,6 +585,7 @@ lws_json_purify(char *escaped, const char *string, int len, int *in_used) p++; *q++ = '\\'; *q++ = 't'; + len--; continue; } @@ -592,6 +593,7 @@ lws_json_purify(char *escaped, const char *string, int len, int *in_used) p++; *q++ = '\\'; *q++ = 'n'; + len--; continue; } @@ -599,6 +601,7 @@ lws_json_purify(char *escaped, const char *string, int len, int *in_used) p++; *q++ = '\\'; *q++ = 'r'; + len--; continue; } @@ -606,6 +609,7 @@ lws_json_purify(char *escaped, const char *string, int len, int *in_used) p++; *q++ = '\\'; *q++ = '\\'; + len--; continue; } @@ -972,7 +976,6 @@ lws_tokenize(struct lws_tokenize *ts) ts->dry = 0; if (ts->reset_token) { - ts->effline = ts->line; ts->state = LWS_TOKZS_LEADING_WHITESPACE; ts->token_len = 0; ts->reset_token = 0; @@ -1057,6 +1060,7 @@ lws_tokenize(struct lws_tokenize *ts) } ts->state = LWS_TOKZS_QUOTED_STRING; + ts->effline = ts->line; ts->token = ts->collect; ts->token_len = 0; @@ -1138,6 +1142,7 @@ lws_tokenize(struct lws_tokenize *ts) ts->delim = LWSTZ_DT_NEED_NEXT_CONTENT; } + ts->effline = ts->line; ts->token = ts->start - 1; ts->token_len = 1; ts->reset_token = 1; @@ -1182,6 +1187,7 @@ lws_tokenize(struct lws_tokenize *ts) ts->delim = LWSTZ_DT_NEED_DELIM; } + ts->effline = ts->line; ts->state = LWS_TOKZS_TOKEN; ts->reset_token = 1; @@ -1576,18 +1582,61 @@ lws_mutex_refcount_assert_held(struct lws_mutex_refcount *mr) #if !defined(LWS_PLAT_FREERTOS) && !defined(LWS_PLAT_BAREMETAL) +#if !defined(LWS_PLAT_ANDROID) && defined(LWS_WITH_NETWORK) +static const struct lws_switches builtins[] = { + { "-d", "Log level (eg, -d15)" }, + { "--fault-injection", "Inject fault at named point" }, + { "--fault-seed", "Random seed for fault injection" }, + { "--ignore-sigterm", "Ignore sigterm" }, + { "--ssproxy-port", "SSProxy port" }, + { "--ssproxy-iface", "SSProxy interface" }, + { "--ssproxy-ads", "SSProxy address" }, + { "--lws-stub", "LWS Stub" }, + { "--h1", "Force HTTP/1.1 (client)" }, + { "--h2", "Force HTTP/2 (client)" }, + { "--h3", "Force HTTP/3 (client)" }, + { "-h", "Print this help" }, + { "--help", "Print this help" }, +}; + +enum opts { + OPT_DEBUGLEVEL, + OPT_FAULTINJECTION, + OPT_FAULT_SEED, + OPT_IGNORE_SIGTERM, + OPT_SSPROXY_PORT, + OPT_SSPROXY_IFACE, + OPT_SSPROXY_ADS, + OPT_LWS_STUB, + OPT_H1, + OPT_H2, + OPT_H3, + OPT_HELP1, + OPT_HELP2, +}; +#endif + + void lws_switches_print_help(const char *prog, const struct lws_switches *switches, size_t count) { size_t i; + lws_set_log_level(LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE, NULL); + lwsl_user("\nUsage: %s [options]\n", prog); lwsl_user("\n"); for (i = 0; i < count; i++) lwsl_user(" %-20s %s\n", switches[i].sw, switches[i].doc); +#if !defined(LWS_PLAT_FREERTOS) && !defined(LWS_PLAT_BAREMETAL) && !defined(LWS_PLAT_ANDROID) && defined(LWS_WITH_NETWORK) + lwsl_user("\n (Also supports LWS built-in options)\n\n"); + for (i = 0; i < LWS_ARRAY_SIZE(builtins); i++) + lwsl_user(" %-20s %s\n", builtins[i].sw, builtins[i].doc); +#endif + lwsl_user("\n"); } @@ -1684,28 +1733,6 @@ lws_cmdline_option(int argc, const char **argv, const char *val) #endif #if !defined(LWS_PLAT_FREERTOS) && !defined(LWS_PLAT_BAREMETAL) && !defined(LWS_PLAT_ANDROID) && defined(LWS_WITH_NETWORK) -static const char * const builtins[] = { - "-d", - "--fault-injection", - "--fault-seed", - "--ignore-sigterm", - "--ssproxy-port", - "--ssproxy-iface", - "--ssproxy-ads", - "--lws-stub", -}; - -enum opts { - OPT_DEBUGLEVEL, - OPT_FAULTINJECTION, - OPT_FAULT_SEED, - OPT_IGNORE_SIGTERM, - OPT_SSPROXY_PORT, - OPT_SSPROXY_IFACE, - OPT_SSPROXY_ADS, - OPT_LWS_STUB, -}; - static void lws_sigterm_catch(int sig) { @@ -1725,8 +1752,8 @@ _lws_context_info_defaults(struct lws_context_creation_info *info, info->pss_policies_json = sspol; #endif #if defined(LWS_WITH_SECURE_STREAMS_PROXY_API) - if (!sspol) - info->protocols = lws_sspc_protocols; + // We no longer override info->protocols here. + // Instead, the ssproxy protocol is appended in lws_create_vhost. #endif info->options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | LWS_SERVER_OPTION_H2_JUST_FIX_WINDOW_UPDATE_OVERFLOW | @@ -1793,7 +1820,7 @@ lws_cmdline_option_handle_builtin(int argc, const char **argv, info->argv = argv; for (n = 0; n < (int)LWS_ARRAY_SIZE(builtins); n++) { - p = lws_cmdline_option(argc, argv, builtins[n]); + p = lws_cmdline_option(argc, argv, builtins[n].sw); if (!p) continue; @@ -1845,6 +1872,22 @@ lws_cmdline_option_handle_builtin(int argc, const char **argv, case OPT_LWS_STUB: info->lws_stub = p; break; + + case OPT_H1: + info->options |= LWS_SERVER_OPTION_CMDLINE_FORCE_H1; + break; + case OPT_H2: + info->options |= LWS_SERVER_OPTION_CMDLINE_FORCE_H2; + break; + case OPT_H3: + info->options |= LWS_SERVER_OPTION_CMDLINE_FORCE_H3; + break; + + case OPT_HELP1: + case OPT_HELP2: + lws_set_log_level(logs, NULL); + lws_switches_print_help(argv[0], NULL, 0); + exit(0); } } diff --git a/lib/core/private-lib-core.h b/lib/core/private-lib-core.h index 63f2050356..88c94aab43 100644 --- a/lib/core/private-lib-core.h +++ b/lib/core/private-lib-core.h @@ -371,7 +371,7 @@ enum { LWSLCG_WSI_SERVER, /* server wsi */ -#if defined(LWS_ROLE_H2) || defined(LWS_ROLE_MQTT) +#if defined(LWS_ROLE_H2) || defined(LWS_ROLE_MQTT) || defined(LWS_ROLE_QUIC) LWSLCG_WSI_MUX, /* a mux child wsi */ #endif @@ -490,7 +490,7 @@ struct lws_context { const struct lws_extension *extensions; #endif -#if defined(LWS_WITH_NETLINK) +#if defined(LWS_WITH_ROUTING) lws_sorted_usec_list_t sul_nl_coldplug; /* process can only have one netlink socket, have to do it in ctx */ lws_dll2_owner_t routing_table; @@ -586,6 +586,10 @@ struct lws_context { struct lws_cache_ttl_lru *l1, *nsc; #endif +#if defined(LWS_WITH_CLIENT) + struct lws_cache_ttl_lru *alpn_cache; +#endif + #if defined(LWS_WITH_SYS_NTPCLIENT) void *ntpclient_priv; #endif @@ -689,7 +693,7 @@ struct lws_context { const char *username, *groupname; #endif -#if defined(LWS_WITH_MBEDTLS) +#if defined(LWS_WITH_MBEDTLS) && !defined(LWS_HAVE_MBEDTLS_V4) mbedtls_entropy_context mec; mbedtls_ctr_drbg_context mcdc; #endif @@ -803,7 +807,7 @@ struct lws_context { uint16_t smd_queue_depth; #endif -#if defined(LWS_WITH_NETLINK) && defined(LWS_WITH_NETWORK) +#if defined(LWS_WITH_ROUTING) && defined(LWS_WITH_NETWORK) lws_route_uidx_t route_uidx; #endif @@ -822,7 +826,7 @@ struct lws_context { unsigned int evlib_finalize_destroy_after_int_loops_stop:1; unsigned int max_fds_unrelated_to_ulimit:1; unsigned int policy_updated:1; -#if defined(LWS_WITH_NETLINK) +#if defined(LWS_WITH_ROUTING) unsigned int nl_initial_done:1; #endif @@ -843,6 +847,9 @@ struct lws_context { uint8_t captive_portal_detect_type; uint8_t destroy_state; /* enum lws_context_destroy */ + + lws_quic_tx_credit_cb_t quic_tx_credit_cb; + const struct lws_cc_ops *quic_cc_ops; }; #define lws_get_context_protocol(ctx, x) ctx->vhost_list->protocols[x] @@ -1115,6 +1122,13 @@ lws_find_mount(struct lws *wsi, const char *uri_ptr, int uri_len); #ifdef LWS_WITH_HTTP2 int lws_wsi_is_h2(struct lws *wsi); +#else +#define lws_wsi_is_h2(wsi) (0) +#endif +#ifdef LWS_ROLE_H3 +int lws_wsi_is_h3(struct lws *wsi); +#else +#define lws_wsi_is_h3(wsi) (0) #endif /* * custom allocator diff --git a/lib/event-libs/CMakeLists.txt b/lib/event-libs/CMakeLists.txt index 4c1dbc3c6b..1e7d6a62f0 100644 --- a/lib/event-libs/CMakeLists.txt +++ b/lib/event-libs/CMakeLists.txt @@ -163,5 +163,11 @@ add_subdir_include_directories_local_end() # export_to_parent_intermediate() +if (LIBUV_INCLUDE_DIRS) + set(LWS_PUBLIC_INCLUDES ${LWS_PUBLIC_INCLUDES} "${LIBUV_INCLUDE_DIRS}") +endif() +if (LWS_PUBLIC_INCLUDES) + set(LWS_PUBLIC_INCLUDES ${LWS_PUBLIC_INCLUDES} PARENT_SCOPE) +endif() set(EVLIB_PLUGINS_LIST ${EVLIB_PLUGINS_LIST} PARENT_SCOPE) diff --git a/lib/event-libs/glib/glib.c b/lib/event-libs/glib/glib.c index ea413c065b..1e17aa35ba 100644 --- a/lib/event-libs/glib/glib.c +++ b/lib/event-libs/glib/glib.c @@ -265,7 +265,8 @@ elops_accept_glib(struct lws *wsi) struct lws_wsi_eventlibs_glib *wsipr = wsi_to_priv_glib(wsi); int fd; - assert(!wsi_to_subclass(wsi)); + if (wsi_to_subclass(wsi)) + return 0; wsi_to_subclass(wsi) = (struct lws_io_watcher_glib_subclass *) g_source_new((GSourceFuncs *)&lws_glib_source_ops, diff --git a/lib/event-libs/libuv/libuv.c b/lib/event-libs/libuv/libuv.c index db87806adc..79473a4df6 100644 --- a/lib/event-libs/libuv/libuv.c +++ b/lib/event-libs/libuv/libuv.c @@ -379,7 +379,7 @@ elops_destroy_context1_uv(struct lws_context *context) /* only for internal loops... */ - if (!pt->event_loop_foreign) { + if (!pt->event_loop_foreign && pt_to_priv_uv(pt)->io_loop) { while (budget-- && (m = uv_run(pt_to_priv_uv(pt)->io_loop, UV_RUN_NOWAIT))) diff --git a/lib/jose/jwe/enc/aescbc.c b/lib/jose/jwe/enc/aescbc.c index d1243a1777..c8bb19c23e 100755 --- a/lib/jose/jwe/enc/aescbc.c +++ b/lib/jose/jwe/enc/aescbc.c @@ -236,6 +236,12 @@ lws_jwe_auth_and_decrypt_cbc_hs(struct lws_jwe *jwe, uint8_t *enc_cek, el.buf = enc_cek + (hlen / 2); el.len = (unsigned int)hlen / 2; + if (jwe->jws.map.len[LJWE_CTXT] % LWS_AES_CBC_BLOCKLEN) { + lwsl_notice("%s: ciphertext len %d not multiple of block size\n", + __func__, (int)jwe->jws.map.len[LJWE_CTXT]); + return -1; + } + if (lws_genaes_create(&aesctx, LWS_GAESO_DEC, LWS_GAESM_CBC, &el, LWS_GAESP_NO_PADDING, NULL)) { lwsl_err("%s: lws_genaes_create failed\n", __func__); diff --git a/lib/misc/lws-struct-lejp.c b/lib/misc/lws-struct-lejp.c index e8d522450f..6cc01dd795 100644 --- a/lib/misc/lws-struct-lejp.c +++ b/lib/misc/lws-struct-lejp.c @@ -588,7 +588,7 @@ lws_struct_json_serialize(lws_struct_serialize_t *js, uint8_t *buf, break; } - if (j->subsequent) { + if (j->subsequent && !js->offset) { *buf++ = ','; len--; lws_struct_pretty(js, &buf, &len); @@ -803,8 +803,10 @@ lws_struct_json_serialize(lws_struct_serialize_t *js, uint8_t *buf, switch (map->type) { case LSMT_STRING_CHAR_ARRAY: case LSMT_STRING_PTR: - *buf++ = '\"'; - len--; + if (!js->remaining) { + *buf++ = '\"'; + len--; + } break; case LSMT_SCHEMA: continue; diff --git a/lib/plat/freertos/esp32/drivers/netdev/wifi-esp32.c b/lib/plat/freertos/esp32/drivers/netdev/wifi-esp32.c index 89d7cc975d..3c394e2801 100644 --- a/lib/plat/freertos/esp32/drivers/netdev/wifi-esp32.c +++ b/lib/plat/freertos/esp32/drivers/netdev/wifi-esp32.c @@ -361,24 +361,32 @@ _event_handler_ip(void *arg, esp_event_base_t event_base, int32_t event_id, if (event_id == IP_EVENT_STA_GOT_IP) { ip_event_got_ip_t *e = (ip_event_got_ip_t *)event_data; char ip[16]; -#if 0 - tcpip_adapter_dns_info_t e32ip; - - /* - * Since atm we get this via DHCP, presumably we can get ahold - * of related info set by the router - */ - - if (tcpip_adapter_get_dns_info(TCPIP_ADAPTER_IF_STA, - TCPIP_ADAPTER_DNS_MAIN, - /* also _BACKUP, _FALLBACK */ - &e32ip)) { - lwsl_err("%s: there's no dns server set\n", __func__); - e32ip.ip.u_addr.ipv4 = 0x08080808; - e32ip.ip.type = ESP_IPADDR_TYPE_V4; +#if defined(LWS_WITH_ROUTING) + struct lws_context_per_thread *pt = &ctx->pt[0]; + lws_route_t *rou; + + lws_pt_lock(pt, __func__); + _lws_route_table_ifdown(pt, 0); + + rou = lws_zalloc(sizeof(*rou), __func__); + if (rou) { + rou->if_idx = 0; + rou->priority = 0; + rou->dest_len = 0; + rou->dest.sa4.sin_family = AF_INET; + + rou->src_len = 32; + rou->src.sa4.sin_family = AF_INET; + memcpy(&rou->src.sa4.sin_addr, &e->ip_info.ip, 4); + + rou->gateway.sa4.sin_family = AF_INET; + memcpy(&rou->gateway.sa4.sin_addr, &e->ip_info.gw, 4); + + rou->uidx = _lws_route_get_uidx(ctx); + + lws_dll2_add_tail(&rou->list, &ctx->routing_table); } - - netdevs->sa46_dns_resolver. + lws_pt_unlock(pt); #endif lws_write_numeric_address((void *)&e->ip_info.ip, 4, ip, diff --git a/lib/plat/freertos/freertos-resolv.c b/lib/plat/freertos/freertos-resolv.c index bf7ac47a4e..dd7ac86fea 100644 --- a/lib/plat/freertos/freertos-resolv.c +++ b/lib/plat/freertos/freertos-resolv.c @@ -25,13 +25,48 @@ #include "private-lib-core.h" #include "private-lib-async-dns.h" +#if defined(LWS_WITH_ESP32) +#include +#endif + #if defined(LWS_WITH_SYS_ASYNC_DNS) int lws_plat_asyncdns_get_server(struct lws_context *context, int index, lws_sockaddr46 *sa46) { +#if defined(LWS_WITH_ESP32) + const ip_addr_t *dns; +#else uint32_t ipv4; +#endif + +#if defined(LWS_WITH_ESP32) + if (index > 2) + return -1; + + dns = dns_getserver(index); + if (!dns || ip_addr_isany(dns)) + return -1; + memset(sa46, 0, sizeof(*sa46)); +#if defined(LWIP_IPV6) && LWIP_IPV6 +#if defined(LWS_WITH_IPV6) + if (IP_IS_V6(dns)) { + sa46->sa4.sin_family = AF_INET6; + memcpy(&sa46->sa6.sin6_addr, ip_2_ip6(dns)->addr, 16); + } else +#endif + { + sa46->sa4.sin_family = AF_INET; + sa46->sa4.sin_addr.s_addr = ip_2_ip4(dns)->addr; + } +#else + sa46->sa4.sin_family = AF_INET; + sa46->sa4.sin_addr.s_addr = dns->addr; +#endif + + return 0; +#else if (index > 0) return -1; @@ -42,9 +77,9 @@ lws_plat_asyncdns_get_server(struct lws_context *context, int index, sa46->sa4.sin_addr.s_addr = ipv4; return 0; +#endif } - #endif int diff --git a/lib/plat/unix/unix-init.c b/lib/plat/unix/unix-init.c index bb6e75425b..0266d7fb31 100644 --- a/lib/plat/unix/unix-init.c +++ b/lib/plat/unix/unix-init.c @@ -141,6 +141,7 @@ lws_plat_init(struct lws_context *context, { int n; +#if !defined(LWS_HAVE_MBEDTLS_V4) /* initialize platform random through mbedtls */ mbedtls_entropy_init(&context->mec); mbedtls_ctr_drbg_init(&context->mcdc); @@ -150,6 +151,12 @@ lws_plat_init(struct lws_context *context, if (n) lwsl_err("%s: mbedtls_ctr_drbg_seed() returned 0x%x\n", __func__, n); +#else + n = psa_crypto_init(); + if (n != 0) + lwsl_err("%s: psa_crypto_init() returned 0x%x\n", + __func__, n); +#endif #if 0 else { uint8_t rtest[16]; @@ -269,7 +276,7 @@ lws_plat_context_late_destroy(struct lws_context *context) if (context->fd_random != LWS_INVALID_FILE) close(context->fd_random); -#if defined(LWS_WITH_MBEDTLS) +#if defined(LWS_WITH_MBEDTLS) && !defined(LWS_HAVE_MBEDTLS_V4) mbedtls_entropy_free(&context->mec); mbedtls_ctr_drbg_free(&context->mcdc); #endif diff --git a/lib/roles/CMakeLists.txt b/lib/roles/CMakeLists.txt index 9b3cb7ea30..de9f33fa3b 100644 --- a/lib/roles/CMakeLists.txt +++ b/lib/roles/CMakeLists.txt @@ -59,6 +59,10 @@ if (LWS_ROLE_H3) add_subdir_include_directories(h3) endif() +if (LWS_ROLE_WT) + add_subdir_include_directories(wt) +endif() + if (LWS_ROLE_WS) add_subdir_include_directories(ws) endif() diff --git a/lib/roles/h2/http2.c b/lib/roles/h2/http2.c index 0729530d7f..2d46de7436 100644 --- a/lib/roles/h2/http2.c +++ b/lib/roles/h2/http2.c @@ -479,7 +479,7 @@ lws_h2_rst_stream(struct lws *wsi, uint32_t err, const char *reason) if (!h2n) return 0; - if (!wsi->h2_stream_carries_ws && h2n->type == LWS_H2_FRAME_TYPE_COUNT) + if (!wsi->h23_stream_carries_ws && h2n->type == LWS_H2_FRAME_TYPE_COUNT) return 0; pps = lws_h2_new_pps(LWS_H2_PPS_RST_STREAM); @@ -667,7 +667,7 @@ int lws_h2_frame_write(struct lws *wsi, int type, int flags, unsigned char *p = &buf[-LWS_H2_FRAME_HEADER_LENGTH]; int n; - //if (wsi->h2_stream_carries_ws) + //if (wsi->h23_stream_carries_ws) // lwsl_hexdump_level(LLL_NOTICE, buf, len); *p++ = (uint8_t)(len >> 16); @@ -2583,7 +2583,9 @@ lws_h2_client_handshake(struct lws *wsi) /* it's time for us to send our client stream headers */ - if (!meth) + if (wsi->do_ws) + meth = "CONNECT"; + else if (!meth) meth = "GET"; /* h2 pseudoheaders must be in a bunch at the start */ @@ -2594,6 +2596,14 @@ lws_h2_client_handshake(struct lws *wsi) (int)strlen(meth), &p, end)) goto fail_length; + if (wsi->do_ws) { + if (lws_add_http_header_by_token(wsi, + WSI_TOKEN_COLON_PROTOCOL, + (unsigned char *)"websocket", 9, + &p, end)) + goto fail_length; + } + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_COLON_SCHEME, (unsigned char *)"https", 5, @@ -2623,6 +2633,27 @@ lws_h2_client_handshake(struct lws *wsi) (unsigned char *)path, n, &p, end)) goto fail_length; +#if defined(LWS_ROLE_WS) + if (wsi->do_ws) { + const char *prot = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN); + + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_VERSION, + (unsigned char *)"13", 2, &p, end)) + goto fail_length; + + if (!prot && wsi->stash && wsi->stash->cis[CIS_PROTOCOL]) + prot = wsi->stash->cis[CIS_PROTOCOL]; + + if (prot) { + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_PROTOCOL, + (unsigned char *)prot, (int)strlen(prot), &p, end)) + goto fail_length; + } + + wsi->h23_stream_carries_ws = 1; + } +#endif + n = lws_hdr_total_length(wsi, _WSI_TOKEN_CLIENT_HOST); simp = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST); if (!n && wsi->stash && wsi->stash->cis[CIS_ADDRESS]) { diff --git a/lib/roles/h2/ops-h2.c b/lib/roles/h2/ops-h2.c index 8e4dfd1f44..434a938dee 100644 --- a/lib/roles/h2/ops-h2.c +++ b/lib/roles/h2/ops-h2.c @@ -603,7 +603,7 @@ rops_check_upgrades_h2(struct lws *wsi) lwsl_info("Upgrade h2 to ws\n"); lws_mux_mark_immortal(wsi); - wsi->h2_stream_carries_ws = 1; + wsi->h23_stream_carries_ws = 1; lws_metrics_tag_wsi_add(wsi, "upg", "ws_over_h2"); @@ -752,7 +752,7 @@ rops_close_kill_connection_h2(struct lws *wsi, enum lws_close_status reason) } #endif - if (wsi->mux_substream && wsi->h2_stream_carries_ws) + if (wsi->mux_substream && wsi->h23_stream_carries_ws) lws_h2_rst_stream(wsi, 0, "none"); /* else if (wsi->mux_substream) @@ -965,7 +965,8 @@ lws_h2_bind_for_post_before_action(struct lws *wsi) wsi->http.rx_content_length) && (blen = lws_buflist_next_segment_len(&wsi->buflist, &buffered))) { - if ((size_t)wsi->http.rx_content_length < blen) + if (wsi->http.content_length_given && + (size_t)wsi->http.rx_content_length < blen) blen = (size_t)wsi->http.rx_content_length; if (wsi->a.protocol->callback(wsi, LWS_CALLBACK_HTTP_BODY, @@ -1031,9 +1032,15 @@ rops_perform_user_POLLOUT_h2(struct lws *wsi) if (!*wsi2) return 0; + int sanity = 1000; do { struct lws *w, **wa; + if (!sanity--) { + lwsl_wsi_warn(wsi, "POLLOUT multiplexer loop sanity limit reached, closing"); + return LWS_HP_RET_BAIL_DIE; + } + wa = &(*wsi2)->mux.sibling_list; if (!(*wsi2)->mux.requested_POLLOUT) goto next_child; @@ -1051,6 +1058,7 @@ rops_perform_user_POLLOUT_h2(struct lws *wsi) wa = &wsi->mux.child_list; goto next_child; } + wa = wsi2; /* wsi2 is updated to point to the next element by move_child_to_tail */ lwsl_info("%s: child %s, sid %d, (wsistate 0x%x)\n", __func__, lws_wsi_tag(w), w->mux.my_sid, diff --git a/lib/roles/h3/CMakeLists.txt b/lib/roles/h3/CMakeLists.txt index 1f67453629..aa55bf769a 100644 --- a/lib/roles/h3/CMakeLists.txt +++ b/lib/roles/h3/CMakeLists.txt @@ -33,6 +33,7 @@ include_directories(.) list(APPEND SOURCES roles/h3/qpack.c + roles/h3/ops-h3.c ) # diff --git a/lib/roles/h3/ops-h3.c b/lib/roles/h3/ops-h3.c new file mode 100644 index 0000000000..cde31c06e9 --- /dev/null +++ b/lib/roles/h3/ops-h3.c @@ -0,0 +1,1507 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2026 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "private-lib-core.h" +#include "private-lib-roles-h3.h" + +struct lws * +lws_get_quic_network_wsi(struct lws *wsi); + +static int +rops_write_role_protocol_h3(struct lws *wsi, unsigned char *buf, size_t len, + enum lws_write_protocol *wp); + +static lws_handling_result_t +rops_handle_POLLIN_h3(struct lws_context_per_thread *pt, struct lws *wsi, + struct lws_pollfd *pollfd) +{ + /* h3 is an encapsulation role... it doesn't do POLLIN itself */ + return LWS_HPI_RET_HANDLED; +} + +#if defined(LWS_WITH_CLIENT) +static int +lws_h3_client_handshake(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi]; + uint8_t *buf, *start, *p, *end; + char *meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD), + *uri = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI), *simp; + const char *path = "/"; + int m, n; + + lwsl_wsi_debug(wsi, "%s", __func__); + + p = start = buf = pt->serv_buf + LWS_PRE; + end = start + (wsi->a.context->pt_serv_buf_size / 2) - LWS_PRE - 1; + + if (wsi->do_ws) + meth = "CONNECT"; + else if (!meth) + meth = "GET"; + + /* Reserve space for the 2-byte QPACK prefix at the beginning of the header block */ + p += 2; + wsi->http.h3_prefix_ptr = start; + + + if (lws_add_http3_header_by_token(wsi, WSI_TOKEN_HTTP_COLON_METHOD, + (unsigned char *)meth, (int)strlen(meth), &p, end)) + return -1; + + if (wsi->do_ws) { + if (lws_add_http3_header_by_token(wsi, WSI_TOKEN_COLON_PROTOCOL, + (unsigned char *)"websocket", 9, &p, end)) + return -1; + } + + if (lws_add_http3_header_by_token(wsi, WSI_TOKEN_HTTP_COLON_SCHEME, + (unsigned char *)"https", 5, &p, end)) + return -1; + + n = lws_hdr_total_length(wsi, _WSI_TOKEN_CLIENT_HOST); + simp = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST); + if (!n && wsi->stash && wsi->stash->cis[CIS_ADDRESS]) { + n = (int)strlen(wsi->stash->cis[CIS_ADDRESS]); + simp = wsi->stash->cis[CIS_ADDRESS]; + } + if (n && simp && lws_add_http3_header_by_token(wsi, WSI_TOKEN_HTTP_COLON_AUTHORITY, + (unsigned char *)simp, n, &p, end)) + return -1; + + n = lws_hdr_total_length(wsi, _WSI_TOKEN_CLIENT_URI); + if (n) + path = uri; + else if (wsi->stash && wsi->stash->cis[CIS_PATH]) { + path = wsi->stash->cis[CIS_PATH]; + n = (int)strlen(path); + } else + n = 1; + + if (n > 1 && path[0] == '/' && path[1] == '/') { + path++; + n--; + } + + if (n && lws_add_http3_header_by_token(wsi, WSI_TOKEN_HTTP_COLON_PATH, + (unsigned char *)path, n, &p, end)) + return -1; + +#if defined(LWS_ROLE_WS) + if (wsi->do_ws) { + const char *prot = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN); + + if (lws_add_http3_header_by_token(wsi, WSI_TOKEN_VERSION, + (unsigned char *)"13", 2, &p, end)) + return -1; + + if (!prot && wsi->stash && wsi->stash->cis[CIS_PROTOCOL]) + prot = wsi->stash->cis[CIS_PROTOCOL]; + + if (prot) { + if (lws_add_http3_header_by_token(wsi, WSI_TOKEN_PROTOCOL, + (unsigned char *)prot, (int)strlen(prot), &p, end)) + return -1; + } + + wsi->h23_stream_carries_ws = 1; + } +#endif + + if (wsi->flags & LCCSCF_HTTP_MULTIPART_MIME) { + uint8_t *p1 = lws_http_multipart_headers(wsi, p); + if (!p1) + return -1; + p = p1; + } + + /* Let the user append additional headers via callback */ + if (wsi->a.protocol->callback(wsi, LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER, + wsi->user_space, &p, lws_ptr_diff_size_t(end, p) - 12)) + return -1; + + if (lws_finalize_http_header(wsi, &p, end)) + return -1; + m = LWS_WRITE_HTTP_HEADERS; +#if defined(LWS_WITH_CLIENT) + if (!(wsi->client_http_body_pending || lws_has_buffered_out(wsi))) + m |= LWS_WRITE_H2_STREAM_END; +#endif + + lwsl_notice("%s: calling lws_write with m=0x%x (body_pending=%d, buffered_out=%d)\n", + __func__, m, wsi->client_http_body_pending, lws_has_buffered_out(wsi)); + + n = lws_write(wsi, start, lws_ptr_diff_size_t(p, start), (enum lws_write_protocol)m); + if (n != lws_ptr_diff(p, start)) + return -1; + + /* Update WSI state */ + lws_role_transition(wsi, LWSIFR_CLIENT, LRS_ESTABLISHED, &role_ops_h3); + + return 0; +} +#endif + + +static int +rops_perform_user_POLLOUT_h3(struct lws *wsi) +{ + lwsl_wsi_info(wsi, "rops_perform_user_POLLOUT_h3: entry, state=%d", lwsi_state(wsi)); + + if (wsi->http.deferred_transaction_completed) { + if (!lws_has_buffered_out(wsi)) { + wsi->http.deferred_transaction_completed = 0; + if (lws_http_transaction_completed(wsi)) { + wsi->socket_is_permanently_unusable = 1; + return -1; + } + } + return 0; + } + + if (lwsi_state(wsi) == LRS_FLUSHING_BEFORE_CLOSE) { + if (!lws_has_buffered_out(wsi)) { + wsi->socket_is_permanently_unusable = 1; + return -1; + } + return 0; + } + +#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION) + if (wsi->http.comp_ctx.buflist_comp || + wsi->http.comp_ctx.may_have_more) { + enum lws_write_protocol wp = LWS_WRITE_HTTP; + + lwsl_wsi_info(wsi, "completing comp partial (buflist %p, may %d)", + wsi->http.comp_ctx.buflist_comp, + wsi->http.comp_ctx.may_have_more); + + if (rops_write_role_protocol_h3(wsi, NULL, 0, &wp) < 0) { + lwsl_wsi_info(wsi, "signalling to close due to comp write fail"); + return -1; + } + lws_callback_on_writable(wsi); + return 0; + } +#endif + + if (wsi->h3.h3n && wsi == wsi->h3.h3n->cwsi_qpack_enc) { + struct lws_qpack_tx_encoder *enc = &wsi->h3.h3n->qpack_tx_encoder; + uint8_t *p; + size_t len = (size_t)lws_buflist_next_segment_len(&enc->tx_bl, &p); + + if (len) { + uint8_t tx_buf[LWS_PRE + 512]; + if (len > 512) len = 512; + memcpy(tx_buf + LWS_PRE, p, len); + + int n = lws_write(wsi, tx_buf + LWS_PRE, len, LWS_WRITE_BINARY); + if (n < 0) return -1; + + if (n > 0) { + lws_buflist_use_segment(&enc->tx_bl, (size_t)n); + if (lws_buflist_next_segment_len(&enc->tx_bl, &p)) + lws_callback_on_writable(wsi); + } + } + return 0; + } + + if (wsi->h3.h3n && (wsi == wsi->h3.h3n->cwsi_control || + wsi == wsi->h3.h3n->cwsi_qpack_dec)) + return 0; + +#if defined(LWS_WITH_FILE_OPS) + if (lwsi_state(wsi) == LRS_ISSUING_FILE) { + int n; + int32_t usable_credit = wsi->txc.tx_cr; + if (lws_rops_fidx(wsi->role_ops, LWS_ROPS_tx_credit)) { + usable_credit = lws_rops_func_fidx(wsi->role_ops, LWS_ROPS_tx_credit). + tx_credit(wsi, LWSTXCR_US_TO_PEER, 0); + } + if (lws_wsi_txc_check_skint(&wsi->txc, usable_credit)) { + return 0; + } + + ((volatile struct lws *)wsi)->leave_pollout_active = 0; + + n = lws_serve_http_file_fragment(wsi); + lwsl_wsi_info(wsi, "lws_serve_http_file_fragment says %d", n); + + if (n < 0) { + lwsl_wsi_notice(wsi, "Closing POLLOUT child"); + return -1; + } + if (n > 0) { + if (lws_http_transaction_completed(wsi)) + return -1; + } + if (!n) { + int32_t usable_credit2 = wsi->txc.tx_cr; + if (lws_rops_fidx(wsi->role_ops, LWS_ROPS_tx_credit)) { + usable_credit2 = lws_rops_func_fidx(wsi->role_ops, LWS_ROPS_tx_credit). + tx_credit(wsi, LWSTXCR_US_TO_PEER, 0); + } + if (usable_credit2 > 0) { + lws_callback_on_writable(wsi); + wsi->mux.requested_POLLOUT = 1; + } + } + + return 0; + } +#endif + + if (lwsi_state(wsi) == LRS_H2_AWAIT_PREFACE) { + lwsi_set_state(wsi, LRS_H2_WAITING_TO_SEND_HEADERS); + lwsl_wsi_info(wsi, "rops_perform_user_POLLOUT_h3: advanced to LRS_H2_WAITING_TO_SEND_HEADERS"); + /* fall through to let it send headers now */ + } + + if (lwsi_state(wsi) == LRS_H2_WAITING_TO_SEND_HEADERS) { +#if defined(LWS_WITH_CLIENT) + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + if (nwsi && nwsi->h3.h3n && nwsi->h3.h3n->peer_control && !nwsi->h3.h3n->peer_control->h3.seen_settings) { + /* Wait for peer SETTINGS frame */ + return 0; + } + + if (wsi->do_ws && nwsi && nwsi->h3.h3n && !nwsi->h3.h3n->peer_supports_ws) { + if (wsi->cli_hostname_copy && wsi->a.context->alpn_cache && wsi->c_port) { + char key[256]; + void *p; + lws_snprintf(key, sizeof(key), "alpn_%s_%u", wsi->cli_hostname_copy, wsi->c_port); + /* Overwrite h3 entry with h2 */ + lws_cache_write_through(wsi->a.context->alpn_cache, key, + (const uint8_t *)"h2", 3, + lws_now_usecs() + (lws_usec_t)(3600ULL * 1000000ULL), &p); + lwsl_wsi_notice(wsi, "H3 WS not supported by peer, downgrading ALPN cache to h2 for %s", key); + } + return -1; + } + if (lws_h3_client_handshake(wsi)) { + lwsl_wsi_err(wsi, "lws_h3_client_handshake failed!"); + return -1; + } +#endif + return 0; + } + +#if defined(LWS_WITH_SERVER) + if (lwsi_state(wsi) == LRS_DEFERRING_ACTION) { + int n; + + lwsi_set_state(wsi, LRS_ESTABLISHED); + + lwsl_debug("H3_TRACE: wsi %p entering lws_http_action from rops_perform_user_POLLOUT_h3\n", wsi); + n = lws_http_action(wsi); + if (n < 0) { + lwsl_debug("H3_TRACE: wsi %p lws_http_action failed, returning %d\n", wsi, n); + return -1; + } + if (n > 0) { + lwsl_wsi_notice(wsi, "closing stream after h3 action completed (%d)", n); + return -1; + } + lwsl_debug("H3_TRACE: wsi %p lws_http_action returned 0 (success)\n", wsi); + return 0; + } +#endif + + if (lwsi_state(wsi) == LRS_ESTABLISHED) { + int m = lws_callback_as_writeable(wsi); + lwsl_wsi_info(wsi, "rops_perform_user_POLLOUT_h3: LRS_ESTABLISHED lws_callback_as_writeable returned %d", m); + return m; + } + + lwsl_wsi_info(wsi, "rops_perform_user_POLLOUT_h3: falling through to lws_callback_as_writeable with state=%d", lwsi_state(wsi)); + return lws_callback_as_writeable(wsi); +} + +static int +lws_h3_parse_path(struct lws *wsi, const char *value, size_t value_len) +{ + struct allocated_headers *ah = wsi->http.ah; + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + size_t i; + + if (!ah) + return -1; + + /* Start fragment for WSI_TOKEN_HTTP_COLON_PATH */ + ah->nfrag++; + if (ah->nfrag >= LWS_ARRAY_SIZE(ah->frag_index)) { + lwsl_wsi_err(wsi, "frag index too big"); + return -1; + } + + ah->frags[ah->nfrag].offset = ah->pos; + ah->frags[ah->nfrag].len = 0; + ah->frags[ah->nfrag].nfrag = 0; + ah->frags[ah->nfrag].flags = 2; /* we had reason to set it */ + + ah->hdr_token_idx = WSI_TOKEN_HTTP_COLON_PATH; + ah->frag_index[WSI_TOKEN_HTTP_COLON_PATH] = ah->nfrag; + + ah->ues = URIES_IDLE; + ah->ups = URIPS_IDLE; + ah->post_literal_equal = 0; + + for (i = 0; i < value_len; i++) { + uint8_t c = (uint8_t)value[i]; + + switch (lws_parse_urldecode(wsi, &c)) { + case LPUR_CONTINUE: + break; + case LPUR_SWALLOW: + continue; + case LPUR_EXCESSIVE: + case LPUR_FORBID: + lwsl_wsi_notice(wsi, "Evil or excessive URI in H3 path"); + lws_quic_enter_closing_state(nwsi, LWS_H3_MESSAGE_ERROR, 0, 1); + return -1; + default: + return -1; + } + + /* Append character */ + if ((int)ah->pos >= (int)wsi->a.context->max_http_header_data - 1) { + lwsl_wsi_err(wsi, "Header data overflow"); + return -1; + } + ah->data[ah->pos++] = (char)c; + ah->frags[ah->nfrag].len++; + } + + /* Seal fragment */ + if ((int)ah->pos >= (int)wsi->a.context->max_http_header_data - 1) { + lwsl_wsi_err(wsi, "Header data overflow"); + return -1; + } + ah->data[ah->pos++] = '\0'; + + return 0; +} + +static int +lws_h3_qpack_header_cb(void *user, int name_idx, const char *name, size_t name_len, const char *value, size_t value_len) +{ + struct lws *wsi = (struct lws *)user; + int tok = name_idx; + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + int is_pseudo = 0; + + /* If we haven't attached an ah, do it now */ + if (!wsi->http.ah) { + if (lws_header_table_attach(wsi, 0)) { + lwsl_wsi_err(wsi, "Failed to attach ah"); + return -1; + } + } + + if (name) { + lwsl_wsi_notice(wsi, "QPACK decoded header: name=%.*s, value=%.*s", (int)name_len, name, (int)value_len, value); + /* It's an unknown header, or string-based. We need to match it. */ + tok = lws_http_string_to_known_header(name, name_len); + if (name_len > 0 && name[0] == ':') is_pseudo = 1; + } else { + lwsl_wsi_notice(wsi, "QPACK decoded header: tok=%d (%s), value=%.*s", tok, (const char *)lws_token_to_string((enum lws_token_indexes)tok), (int)value_len, value); + if (tok == WSI_TOKEN_HTTP_COLON_AUTHORITY || + tok == WSI_TOKEN_HTTP_COLON_METHOD || + tok == WSI_TOKEN_HTTP_COLON_PATH || + tok == WSI_TOKEN_HTTP_COLON_SCHEME || + tok == WSI_TOKEN_HTTP_COLON_STATUS || + tok == WSI_TOKEN_COLON_PROTOCOL) { + is_pseudo = 1; + } + } + + if (is_pseudo) { + /* HTTP/3 4.1.1: MUST send H3_MESSAGE_ERROR if pseudo-header fields exist after regular fields */ + if (wsi->h3.seen_regular_header) { + lws_quic_enter_closing_state(nwsi, LWS_H3_MESSAGE_ERROR, 0, 1); + return -1; + } + + /* HTTP/3 4.1.1: MUST send H3_MESSAGE_ERROR if a pseudo-header is duplicated */ + if (tok == WSI_TOKEN_HTTP_COLON_METHOD) { + if (wsi->h3.seen_pseudo_method) goto duplicate; + wsi->h3.seen_pseudo_method = 1; + } else if (tok == WSI_TOKEN_HTTP_COLON_SCHEME) { + if (wsi->h3.seen_pseudo_scheme) goto duplicate; + wsi->h3.seen_pseudo_scheme = 1; + } else if (tok == WSI_TOKEN_HTTP_COLON_AUTHORITY) { + if (wsi->h3.seen_pseudo_authority) goto duplicate; + wsi->h3.seen_pseudo_authority = 1; + } else if (tok == WSI_TOKEN_HTTP_COLON_PATH) { + if (wsi->h3.seen_pseudo_path) goto duplicate; + wsi->h3.seen_pseudo_path = 1; + } else if (tok == WSI_TOKEN_HTTP_COLON_STATUS) { + if (wsi->h3.seen_pseudo_status) goto duplicate; + wsi->h3.seen_pseudo_status = 1; + } else if (tok == WSI_TOKEN_COLON_PROTOCOL) { + if (wsi->h3.seen_pseudo_protocol) goto duplicate; + wsi->h3.seen_pseudo_protocol = 1; + } else { + /* Prohibited pseudo-header */ + lws_quic_enter_closing_state(nwsi, LWS_H3_MESSAGE_ERROR, 0, 1); + return -1; + } + } else { + wsi->h3.seen_regular_header = 1; + } + + if (tok >= 0 && tok < WSI_TOKEN_COUNT) { + /* Known token */ + if (tok == WSI_TOKEN_HTTP_COLON_STATUS) { + wsi->http.ah->http_response = (uint32_t)atoi(value); + } + if (tok == WSI_TOKEN_HTTP_COLON_PATH) { + if (lws_h3_parse_path(wsi, value, value_len)) + return -1; + } else { + if (tok == WSI_TOKEN_HTTP_COLON_AUTHORITY) { + if (lws_hdr_simple_create(wsi, WSI_TOKEN_HOST, value)) + return -1; + } + if (lws_hdr_simple_create(wsi, (enum lws_token_indexes)tok, value)) + return -1; + } + } else { + lwsl_wsi_debug(wsi, "Ignoring unknown header: %s", name ? name : "unknown"); + } + + return 0; + +duplicate: + lws_quic_enter_closing_state(nwsi, LWS_H3_MESSAGE_ERROR, 0, 1); + return -1; +} + +static int +lws_h3_parse_varint_accum(struct lws *wsi, const uint8_t **pbuf, size_t *plen, uint64_t *val) +{ + const uint8_t *buf = *pbuf; + size_t len = *plen; + size_t needed = 0; + + if (wsi->h3.rx_varint_len == 0) { + if (len == 0) return 0; + uint8_t type = buf[0] >> 6; + if (type == 0) needed = 1; + else if (type == 1) needed = 2; + else if (type == 2) needed = 4; + else needed = 8; + + if (len >= needed) { + size_t consumed = lws_quic_parse_varint(buf, len, val); + *pbuf += consumed; + *plen -= consumed; + return 1; + } + } else { + uint8_t type = wsi->h3.rx_varint_buf[0] >> 6; + if (type == 0) needed = 1; + else if (type == 1) needed = 2; + else if (type == 2) needed = 4; + else needed = 8; + } + + size_t to_copy = needed - wsi->h3.rx_varint_len; + if (to_copy > len) to_copy = len; + + memcpy(&wsi->h3.rx_varint_buf[wsi->h3.rx_varint_len], buf, to_copy); + wsi->h3.rx_varint_len += (uint8_t)to_copy; + *pbuf += to_copy; + *plen -= to_copy; + + if (wsi->h3.rx_varint_len == needed) { + lws_quic_parse_varint(wsi->h3.rx_varint_buf, needed, val); + wsi->h3.rx_varint_len = 0; + return 1; + } + + return 0; +} + + +#if defined(LWS_WITH_CLIENT) +static int +rops_client_bind_h3(struct lws *wsi, const struct lws_client_connect_info *i) +{ + if (!i) + return 0; + + /* + * If alpn was specified as h3, we want to start as quic, + * and when ALPN confirms h3, the streams transition to h3. + */ + return 0; +} +#endif + +static int +rops_adoption_bind_h3(struct lws *wsi, int type, const char *vh_prot_name) +{ + /* + * If we are adopting a QUIC stream that negotiated h3, + * we transition it to h3 role here. + */ + return 0; +} + +static struct lws * +lws_h3_create_unidi_stream(struct lws *nwsi, uint8_t type) +{ + struct lws_quic_netconn *qn = nwsi->quic.qn; + struct lws *cwsi; + + cwsi = lws_create_new_server_wsi(nwsi->a.vhost, nwsi->tsi, 0, "h3_unidi"); + if (!cwsi) + return NULL; + + lws_role_transition(cwsi, LWSIFR_CLIENT, LRS_ESTABLISHED, &role_ops_h3); + cwsi->mux_substream = 1; +#if defined(LWS_WITH_CLIENT) + cwsi->client_mux_substream = 1; +#endif + + cwsi->quic.qs = lws_zalloc(sizeof(*cwsi->quic.qs), "quic stream"); + if (!cwsi->quic.qs) { + lws_close_free_wsi(cwsi, LWS_CLOSE_STATUS_NOSTATUS, "oom"); + return NULL; + } + + if (qn->peer_initial_max_stream_data_uni) { + cwsi->txc.tx_cr = (int32_t)qn->peer_initial_max_stream_data_uni; + cwsi->txc.peer_tx_cr_est = (int32_t)qn->peer_initial_max_stream_data_uni; + } else { + cwsi->txc.tx_cr = 65535; + cwsi->txc.peer_tx_cr_est = 65535; + } + cwsi->quic.qs->stream_id = qn->next_stream_id_unidi_local; + + /* We're doing client unidi streams */ + lws_wsi_mux_insert(cwsi, nwsi, (unsigned int)qn->next_stream_id_unidi_local); + qn->next_stream_id_unidi_local += 4; + + cwsi->h3.h3n = nwsi->h3.h3n; + + { + uint8_t pre[LWS_PRE + 16]; +#if (_LWS_ENABLED_LOGS & LLL_NOTICE) + int n; +#endif + size_t send_len = 1; + pre[LWS_PRE] = type; + if (type == 0x00) { + /* HTTP/3 Control Stream MUST send a SETTINGS frame (Type 0x04) immediately */ +#if defined(LWS_PLAT_FREERTOS) +#define LWS_QPACK_CAP_VARINT 0x50 /* 4096 */ +#else +#define LWS_QPACK_CAP_VARINT 0x60 /* 8192 */ +#endif + + if (nwsi->a.vhost->h2.set.s[H2SET_ENABLE_CONNECT_PROTOCOL]) { + pre[LWS_PRE + 1] = 0x04; /* SETTINGS */ + pre[LWS_PRE + 2] = 0x0c; /* Length 12 */ + pre[LWS_PRE + 3] = 0x01; /* SETTINGS_QPACK_MAX_TABLE_CAPACITY */ + pre[LWS_PRE + 4] = LWS_QPACK_CAP_VARINT; + pre[LWS_PRE + 5] = 0x00; + pre[LWS_PRE + 6] = 0x08; /* SETTINGS_ENABLE_CONNECT_PROTOCOL */ + pre[LWS_PRE + 7] = 0x01; /* 1 */ + pre[LWS_PRE + 8] = 0x33; /* SETTINGS_H3_DATAGRAM */ + pre[LWS_PRE + 9] = 0x01; /* 1 */ + pre[LWS_PRE + 10] = 0xab; pre[LWS_PRE + 11] = 0x60; pre[LWS_PRE + 12] = 0x37; pre[LWS_PRE + 13] = 0x42; /* SETTINGS_ENABLE_WEBTRANSPORT */ + pre[LWS_PRE + 14] = 0x01; /* 1 */ + send_len = 15; + } else { + pre[LWS_PRE + 1] = 0x04; /* SETTINGS */ + pre[LWS_PRE + 2] = 0x03; /* Length 3 */ + pre[LWS_PRE + 3] = 0x01; /* SETTINGS_QPACK_MAX_TABLE_CAPACITY */ + pre[LWS_PRE + 4] = LWS_QPACK_CAP_VARINT; + pre[LWS_PRE + 5] = 0x00; + send_len = 6; + } + +#if (_LWS_ENABLED_LOGS & LLL_NOTICE) + n = lws_write(cwsi, &pre[LWS_PRE], send_len, LWS_WRITE_BINARY | LWS_WRITE_NO_FIN); + lwsl_notice("lws_h3_create_unidi_stream: lws_write control ret %d\n", n); +#else + lws_write(cwsi, &pre[LWS_PRE], send_len, LWS_WRITE_BINARY | LWS_WRITE_NO_FIN); +#endif + } else { +#if (_LWS_ENABLED_LOGS & LLL_NOTICE) + n = lws_write(cwsi, &pre[LWS_PRE], 1, LWS_WRITE_BINARY | LWS_WRITE_NO_FIN); + lwsl_notice("lws_h3_create_unidi_stream: lws_write %d ret %d\n", type, n); +#else + lws_write(cwsi, &pre[LWS_PRE], 1, LWS_WRITE_BINARY | LWS_WRITE_NO_FIN); +#endif + } + } + + lws_callback_on_writable(cwsi); + + return cwsi; +} + +static size_t +lws_quic_parse_varint_prefix(const uint8_t *buf, size_t len, int prefix_len, uint64_t *val) +{ + if (!len) return 0; + uint8_t mask = (uint8_t)((1 << prefix_len) - 1); + uint8_t first = buf[0] & mask; + if (first < mask) { + *val = first; + return 1; + } + /* It's mask + a variable length integer... */ + /* We need to parse an integer that is encoded 7 bits per byte until MSB is 0 */ + size_t i = 1; + uint64_t v = mask; + int shift = 0; + while (i < len) { + uint8_t b = buf[i++]; + v += (uint64_t)(b & 0x7f) << shift; + if (!(b & 0x80)) { + *val = v; + return i; + } + shift += 7; + } + return 0; /* Need more data */ +} + +int +lws_h3_rx_stream_data(struct lws *wsi, const uint8_t *buf, size_t len) +{ + // lwsl_notice("H3 RX: %d bytes\n", (int)len); + // lwsl_hexdump_notice(buf, len); + + /* If it's unidirectional and we don't know the type yet */ + if (wsi->quic.qs && wsi->quic.qs->is_unidirectional && !wsi->h3.type_set) { + uint64_t type; + size_t consumed = lws_quic_parse_varint(buf, len, &type); + + if (!consumed) return 0; /* Need more data */ + + wsi->h3.stream_type = (uint8_t)type; + wsi->h3.type_set = 1; + if (type == 0x02) { + wsi->h3.qpack_dec_state.state = LQP_DEC_INSTRUCTION; + } + buf += consumed; + len -= consumed; + + lwsl_wsi_info(wsi, "H3 RX: Unidi stream type %llu", (unsigned long long)type); + + /* Link it to peer's control streams if applicable */ + if (wsi->h3.h3n) { + if (type == 0x00) wsi->h3.h3n->peer_control = wsi; + else if (type == 0x02) wsi->h3.h3n->peer_qpack_enc = wsi; + else if (type == 0x03) wsi->h3.h3n->peer_qpack_dec = wsi; + } + } + + if (!len) + return 0; + + while (len > 0) { + if (wsi->h3.stream_type == 0x00) { + lwsl_wsi_debug(wsi, "H3 RX: Control Stream data len %d", (int)len); + } else if (wsi->h3.stream_type == 0x02) { + struct lws_qpack_context *ctx = wsi->h3.h3n ? &wsi->h3.h3n->qpack_dec_ctx : NULL; + lwsl_wsi_info(wsi, "LWS_H3_RX_STREAM_DATA: Encoder Stream payload received, len=%d", (int)len); + + if (lws_qpack_decode_encoder_stream(&wsi->h3.qpack_dec_state, ctx, buf, len)) { + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + lwsl_err("ERROR: QPACK_ENCODER_STREAM_ERROR!!!!\n"); +lws_quic_enter_closing_state(nwsi, LWS_QPACK_ENCODER_STREAM_ERROR, 0, 1); + return 1; + } + buf += len; len = 0; + return 0; + } else if (wsi->h3.stream_type == 0x03) { + lwsl_wsi_debug(wsi, "H3 RX: Decoder Stream data len %d", (int)len); + size_t i = 0; + while (i < len) { + uint8_t b = buf[i]; + if ((b & 0x80) == 0x80) { /* Header Acknowledgement */ + uint64_t stream_id; + size_t consumed = lws_quic_parse_varint_prefix(&buf[i], len - i, 7, &stream_id); + if (consumed) { + lwsl_wsi_info(wsi, "QPACK Header Ack stream_id=%llu", (unsigned long long)stream_id); + i += consumed; + continue; + } + break; + } else if ((b & 0xc0) == 0x40) { /* Stream Cancellation */ + uint64_t stream_id; + size_t consumed = lws_quic_parse_varint_prefix(&buf[i], len - i, 6, &stream_id); + if (consumed) { + lwsl_wsi_info(wsi, "QPACK Stream Cancel stream_id=%llu", (unsigned long long)stream_id); + i += consumed; + continue; + } + break; + } else if ((b & 0xc0) == 0x00) { /* Insert Count Increment */ + uint64_t inc; + size_t consumed = lws_quic_parse_varint_prefix(&buf[i], len - i, 6, &inc); + if (consumed) { + if (inc == 0) { + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + lws_quic_enter_closing_state(nwsi, LWS_QPACK_DECODER_STREAM_ERROR, 0, 1); + return 1; + } + if (wsi->h3.h3n) + wsi->h3.h3n->qpack_tx_encoder.known_received_count += (uint32_t)inc; + lwsl_wsi_info(wsi, "QPACK Insert Count Increment inc=%llu known=%u", (unsigned long long)inc, (unsigned int)(wsi->h3.h3n ? wsi->h3.h3n->qpack_tx_encoder.known_received_count : 0)); + i += consumed; + continue; + } + break; + } + i++; + } + buf += len; len = 0; + return 0; + } + if (wsi->h3.rx_frame_state == 0) { + if (lws_h3_parse_varint_accum(wsi, &buf, &len, &wsi->h3.rx_frame_type)) { + wsi->h3.rx_frame_state = 1; + } else break; + } else if (wsi->h3.rx_frame_state == 1) { + if (lws_h3_parse_varint_accum(wsi, &buf, &len, &wsi->h3.rx_frame_len)) { + wsi->h3.rx_frame_state = 2; + wsi->h3.rx_frame_payload_read = 0; + lwsl_wsi_info(wsi, "H3 RX: Frame Type %llu, Len %llu on stream type %d (unidi=%d)", + (unsigned long long)wsi->h3.rx_frame_type, (unsigned long long)wsi->h3.rx_frame_len, + wsi->h3.stream_type, wsi->quic.qs ? wsi->quic.qs->is_unidirectional : 0); + + /* Validation: Control Streams */ + if (wsi->quic.qs && wsi->quic.qs->is_unidirectional && wsi->h3.stream_type == 0x00) { + /* HTTP/3 6.2.1: MUST send H3_MISSING_SETTINGS if the first control frame is not SETTINGS */ + if (!wsi->h3.seen_settings && wsi->h3.rx_frame_type != 0x04) { + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + lws_quic_enter_closing_state(nwsi, LWS_H3_MISSING_SETTINGS, 0, 1); + return 1; + } + /* HTTP/3 7.2.1/7.2.2: MUST send H3_FRAME_UNEXPECTED if DATA or HEADERS is received on a control stream */ + if (wsi->h3.rx_frame_type == 0x00 || wsi->h3.rx_frame_type == 0x01) { + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + lws_quic_enter_closing_state(nwsi, LWS_H3_FRAME_UNEXPECTED, 0, 1); + return 1; + } + /* HTTP/3 7.2.4: MUST send H3_FRAME_UNEXPECTED if a second SETTINGS frame is received */ + if (wsi->h3.seen_settings && wsi->h3.rx_frame_type == 0x04) { + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + lws_quic_enter_closing_state(nwsi, LWS_H3_FRAME_UNEXPECTED, 0, 1); + return 1; + } + if (wsi->h3.rx_frame_type == 0x04) { + wsi->h3.seen_settings = 1; + } + } + + /* Validation: Request Streams */ + if (!wsi->quic.qs || !wsi->quic.qs->is_unidirectional) { + lwsl_wsi_warn(wsi, "H3 Validation: rx_frame_type=%d, hdr_parsing_completed=%d", + (int)wsi->h3.rx_frame_type, (int)wsi->hdr_parsing_completed); + if (wsi->h3.rx_frame_type == 0x00 && !wsi->hdr_parsing_completed) { + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + lws_quic_enter_closing_state(nwsi, LWS_H3_FRAME_UNEXPECTED, 0, 1); + return 1; + } + if (wsi->h3.rx_frame_type == 0x03) { /* CANCEL_PUSH */ + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + lws_quic_enter_closing_state(nwsi, LWS_H3_FRAME_UNEXPECTED, 0, 1); + return 1; + } + } + } else break; + } else if (wsi->h3.rx_frame_state == 2) { + size_t chunk = (size_t)(wsi->h3.rx_frame_len - wsi->h3.rx_frame_payload_read); + if (chunk > len) chunk = len; + + if (!wsi->quic.qs || !wsi->quic.qs->is_unidirectional) { + if (wsi->h3.rx_frame_type == 0x01) { /* HEADERS */ + struct lws_qpack_context *ctx = wsi->h3.h3n ? &wsi->h3.h3n->qpack_dec_ctx : NULL; + if (lws_qpack_decode_header_block(&wsi->h3.qpack_dec_state, ctx, buf, chunk, lws_h3_qpack_header_cb, wsi)) { + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + lws_quic_enter_closing_state(nwsi, LWS_QPACK_DECOMPRESSION_FAILED, 0, 1); + return 1; + } + } else if (wsi->h3.rx_frame_type == 0x00) { /* DATA */ + /* Deliver data to application */ +#if defined(LWS_WITH_CLIENT) + int m = 0; + if (wsi->client_mux_substream) { + if (!wsi->a.protocol) { + lwsl_wsi_err(wsi, "doesn't have protocol"); + } else { + m = user_callback_handle_rxflow( + wsi->a.protocol->callback, + wsi, + LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ, + wsi->user_space, + (void *)buf, (unsigned int)chunk); + } + if (m) { + lwsl_wsi_info(wsi, "RECEIVE_CLIENT_HTTP closed it"); + return 1; + } + if (wsi->http.rx_content_length > 0) + wsi->http.rx_content_remain -= chunk; + + if (wsi->http.content_length_given && !wsi->http.rx_content_remain) { + lwsl_wsi_info(wsi, "H3 client transaction completed via content-length"); + if (lws_http_transaction_completed_client(wsi)) + return 1; + } + } else +#endif + { + /* Server-side receive */ + int n; + + if (lwsi_state(wsi) == LRS_DEFERRING_ACTION) { + n = lws_buflist_append_segment(&wsi->buflist, buf, chunk); + if (n < 0) + return 1; + lwsl_debug("H3_TRACE: deferred %d bytes in buflist\n", (int)chunk); + } else { + wsi->outer_will_close = 1; + n = lws_read_h1(wsi, (unsigned char *)buf, chunk); + wsi->outer_will_close = 0; + + if (n < 0) { + lwsl_wsi_info(wsi, "server side read failed"); + return 1; + } + } + } + } + } else if (wsi->h3.stream_type == 0x00 && wsi->h3.rx_frame_type == 0x04) { + /* Parse SETTINGS frame */ + size_t i = 0; + while (i < chunk) { + /* Need to parse identifier (varint) and value (varint) */ + /* For now, just check if it's an HTTP/2 setting! (HTTP/3 7.2.4.1) */ + /* Proper parsing requires state, but if we just check bytes: */ + uint64_t id, val; + const uint8_t *p = buf + i; + size_t clen = chunk - i; + size_t consumed = lws_quic_parse_varint(p, clen, &id); + if (consumed) { + p += consumed; clen -= consumed; i += consumed; + consumed = lws_quic_parse_varint(p, clen, &val); + if (consumed) { + i += consumed; + if (id == 0x00 || id == 0x02 || id == 0x03 || id == 0x04 || id == 0x05) { + /* Prohibited HTTP/2 setting */ + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + lws_quic_enter_closing_state(nwsi, LWS_H3_SETTINGS_ERROR, 0, 1); + return 1; + } else if (id == 0x01) { /* SETTINGS_QPACK_MAX_TABLE_CAPACITY */ + if (wsi->h3.h3n) { + wsi->h3.h3n->qpack_dec_ctx.dyn_table.virtual_payload_limit = (uint32_t)val; + } + } else if (id == 0x08) { + /* SETTINGS_ENABLE_WEB_SOCKETS */ + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + if (nwsi && nwsi->h3.h3n) + nwsi->h3.h3n->peer_supports_ws = 1; + } else if (id == LWS_H3_SETTINGS_H3_DATAGRAM) { + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + if (nwsi && nwsi->h3.h3n) + nwsi->h3.h3n->peer_supports_h3_datagram = 1; + } else if (id == LWS_H3_SETTINGS_ENABLE_WEBTRANSPORT) { + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + if (nwsi && nwsi->h3.h3n) + nwsi->h3.h3n->peer_supports_webtransport = 1; + } + } else { + i++; /* Malformed but skip to consume */ + } + } else { + i++; + } + } + } + + wsi->h3.rx_frame_payload_read += chunk; + buf += chunk; + len -= chunk; + + /* Replenish flow control window */ + lws_wsi_tx_credit(wsi, LWSTXCR_PEER_TO_US, (int)chunk); + + if (wsi->h3.rx_frame_payload_read == wsi->h3.rx_frame_len) { + if (wsi->h3.stream_type == 0x00 && wsi->h3.rx_frame_type == 0x04) { + /* SETTINGS frame fully received, wake up children */ + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + if (nwsi) { + struct lws *child = nwsi->mux.child_list; + while (child) { + lws_callback_on_writable(child); + child = child->mux.sibling_list; + } + } + } + + if ((!wsi->quic.qs || !wsi->quic.qs->is_unidirectional) && wsi->h3.rx_frame_type == 0x01) { + /* HEADERS frame complete, validate and notify application! */ + + /* HTTP/3 4.1.3: MUST send H3_MESSAGE_ERROR if mandatory pseudo-header fields are absent */ + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + lwsl_wsi_info(wsi, "H3 HEADERS frame completed. is_server=%d, seen_method=%d, seen_scheme=%d, seen_path=%d", + (nwsi && nwsi->quic.qn) ? nwsi->quic.qn->is_server : -1, + wsi->h3.seen_pseudo_method, wsi->h3.seen_pseudo_scheme, wsi->h3.seen_pseudo_path); + if (nwsi && nwsi->quic.qn && nwsi->quic.qn->is_server) { + /* Request headers must have :method, :scheme, :path, and :authority */ + if (!wsi->h3.seen_pseudo_method || !wsi->h3.seen_pseudo_scheme || !wsi->h3.seen_pseudo_path || !wsi->h3.seen_pseudo_authority) { + lwsl_wsi_notice(wsi, "H3 MESSAGE_ERROR: Missing mandatory pseudo-headers!"); + lws_quic_enter_closing_state(nwsi, LWS_H3_MESSAGE_ERROR, 0, 1); + return 1; + } + /* Prohibited in requests */ + if (wsi->h3.seen_pseudo_status) { + lws_quic_enter_closing_state(nwsi, LWS_H3_MESSAGE_ERROR, 0, 1); + return 1; + } + } + + /* duplicate :path into the individual method uri header index */ + const char *p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_METHOD); +#if (_LWS_ENABLED_LOGS & LLL_INFO) + const char *path_val = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_PATH); + lwsl_wsi_info(wsi, "Decoded method: %s, Decoded path: %s", p ? p : "NULL", path_val ? path_val : "NULL"); +#endif + + static const char * const method_names[] = { + "GET", "POST", + #if defined(LWS_WITH_HTTP_UNCOMMON_HEADERS) + "OPTIONS", "PUT", "PATCH", "DELETE", + #endif + "CONNECT", "HEAD" + }; + static const unsigned char method_index[] = { + WSI_TOKEN_GET_URI, + WSI_TOKEN_POST_URI, + #if defined(LWS_WITH_HTTP_UNCOMMON_HEADERS) + WSI_TOKEN_OPTIONS_URI, + WSI_TOKEN_PUT_URI, + WSI_TOKEN_PATCH_URI, + WSI_TOKEN_DELETE_URI, + #endif + WSI_TOKEN_CONNECT, + WSI_TOKEN_HEAD_URI, + }; + for (int n = 0; n < (int)LWS_ARRAY_SIZE(method_names); n++) { + if (p && !strcasecmp(p, method_names[n])) { + wsi->http.ah->frag_index[method_index[n]] = + wsi->http.ah->frag_index[WSI_TOKEN_HTTP_COLON_PATH]; + break; + } + } + + lwsl_debug("H3_TRACE: HEADERS frame complete for wsi %p. :authority len=%d (%s), HOST len=%d (%s), vhost=%s\n", + wsi, + lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_AUTHORITY), + lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_AUTHORITY) ? lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_AUTHORITY) : "null", + lws_hdr_total_length(wsi, WSI_TOKEN_HOST), + lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST) ? lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST) : "null", + wsi->a.vhost ? wsi->a.vhost->name : "null"); + + wsi->hdr_parsing_completed = 1; + + /* Extract Content-Length if present */ + if (lws_hdr_extant(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { + const char *simp = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH); + if (simp) { + long long cl_val = atoll(simp); + if (cl_val >= 0) { + wsi->http.rx_content_length = (unsigned long long)cl_val; + wsi->http.rx_content_remain = wsi->http.rx_content_length; + wsi->http.content_length_given = 1; + if (wsi->http.rx_content_length == 0) + wsi->http.content_length_explicitly_zero = 1; + } + } + } + +#if defined(LWS_WITH_CLIENT) + if (wsi->client_mux_substream) { + if (lws_client_interpret_server_handshake(wsi)) { + lwsl_info("cli int serv hs closed, or redir\n"); + return 1; + } + } else +#endif + { + lwsl_err("H3_VHOST_SELECT: headers parsed. vhost listen_port: %d, auth len: %d, authority: %s\n", + wsi->a.vhost->listen_port, + lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_AUTHORITY), + lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_AUTHORITY) ? lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_AUTHORITY) : "NULL"); + + /* select vhost based on authority */ + if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_AUTHORITY)) { + int port = wsi->a.vhost->listen_port; + struct lws_vhost *vhost = lws_select_vhost( + wsi->a.context, port, + lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_AUTHORITY)); + + if (!vhost && port != 443) { + vhost = lws_select_vhost(wsi->a.context, 443, + lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_AUTHORITY)); + } + if (!vhost) { + vhost = lws_select_vhost(wsi->a.context, 0, + lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_AUTHORITY)); + } + + lwsl_debug("H3_TRACE: lws_select_vhost returned %p (%s). Original port: %d\n", + vhost, vhost ? vhost->name : "none", port); + if (vhost) { + lws_vhost_bind_wsi(vhost, wsi); + lwsl_debug("H3_TRACE: bound wsi %p to vhost %s\n", wsi, vhost->name); + } + } + + lwsi_set_state(wsi, LRS_DEFERRING_ACTION); + lws_callback_on_writable(wsi); + lwsl_debug("H3_TRACE: wsi %p transitioned to LRS_DEFERRING_ACTION and called callback_on_writable\n", wsi); + } + } + wsi->h3.rx_frame_state = 0; /* Next frame */ + } + } + } + + return 0; +} + +static int +rops_alpn_negotiated_h3(struct lws *wsi, const char *alpn) +{ + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + struct lws_h3_netconn *h3n; + + lwsl_wsi_notice(wsi, "H3 ALPN Negotiated: %s", alpn); + + if (!nwsi || !nwsi->quic.qn) + return 1; + + /* Only the first H3 stream (which is the application request) initializes the connection */ + if (!nwsi->h3.h3n) { + h3n = lws_zalloc(sizeof(*h3n), "h3n"); + if (!h3n) + return 1; + nwsi->h3.h3n = h3n; + h3n->nwsi = nwsi; + + /* Initialize QPACK encoder context */ + h3n->qpack_tx_encoder.entries = h3n->tx_entries; + h3n->qpack_tx_encoder.num_entries = LWS_ARRAY_SIZE(h3n->tx_entries); + h3n->qpack_tx_encoder.virtual_payload_max = 4096; + nwsi->h3.qpack_tx_encoder = &h3n->qpack_tx_encoder; + + /* Create the 3 local control streams */ + /* Client unidi start at 2 */ + if (nwsi->quic.qn->next_stream_id_unidi_local == 0) + nwsi->quic.qn->next_stream_id_unidi_local = lwsi_role_server(nwsi) ? 3 : 2; + + h3n->cwsi_control = lws_h3_create_unidi_stream(nwsi, 0x00); + h3n->cwsi_qpack_enc = lws_h3_create_unidi_stream(nwsi, 0x02); + h3n->cwsi_qpack_dec = lws_h3_create_unidi_stream(nwsi, 0x03); + + if (!h3n->cwsi_control || !h3n->cwsi_qpack_enc || !h3n->cwsi_qpack_dec) + return 1; + + h3n->qpack_tx_encoder.wsi_qpack_enc = h3n->cwsi_qpack_enc; + } + + wsi->h3.h3n = nwsi->h3.h3n; + wsi->h3.qpack_tx_encoder = nwsi->h3.qpack_tx_encoder; + + /* If we are the network wsi, we must notify our children! */ + if (wsi == nwsi) { + struct lws *child = nwsi->mux.child_list; + lwsl_wsi_info(wsi, "H3 ALPN Negotiated, child_list=%p", child); + while (child) { + lwsl_wsi_info(child, "H3 ALPN child state=%d", lwsi_state(child)); + if (lwsi_state(child) == LRS_UNCONNECTED || lwsi_state(child) == LRS_WAITING_CONNECT) { + lwsl_wsi_info(child, "H3 ALPN Negotiated, transitioning child"); + lws_role_transition(child, lwsi_role_client(nwsi) ? LWSIFR_CLIENT : LWSIFR_SERVER, LRS_H2_WAITING_TO_SEND_HEADERS, &role_ops_h3); + child->h3.h3n = nwsi->h3.h3n; + child->h3.qpack_tx_encoder = nwsi->h3.qpack_tx_encoder; + lws_callback_on_writable(child); + } + child = child->mux.sibling_list; + } + } + + return 0; +} + +static int +rops_close_kill_connection_h3(struct lws *wsi, enum lws_close_status reason) +{ + if (wsi->mux.parent_wsi) + lws_wsi_mux_sibling_disconnect(wsi); + + lws_quic_stream_cleanup(wsi); + + return 0; +} + +static int +rops_write_role_protocol_h3(struct lws *wsi, unsigned char *buf, size_t len, + enum lws_write_protocol *wp) +{ + unsigned char *pre = buf - LWS_PRE; + int base = (*wp & 0x1f); + int is_http = base == LWS_WRITE_HTTP || base == LWS_WRITE_HTTP_FINAL; + int is_headers = base == LWS_WRITE_HTTP_HEADERS; + size_t olen = len; + int n; +#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION) + unsigned char mtubuf[4096 + LWS_PRE]; + int32_t max_out = sizeof(mtubuf) - LWS_PRE; + + if (is_http && wsi->http.lcs) { + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + int32_t cr = wsi->txc.tx_cr; + if (nwsi && nwsi->txc.tx_cr < cr) + cr = nwsi->txc.tx_cr; + + /* If there's no tx credit, or it's too small to hold even a tiny frame, return 0 now so + * the application buffers the *uncompressed* data and retries later, instead of + * us consuming it and QUIC rejecting it. */ + if (cr <= 16) { + lwsl_info("%s: delaying compression due to tx_cr %d\n", __func__, cr); + return 0; + } + + cr -= 16; /* Leave room for H3 DATA frame overhead */ + if (cr > 0 && cr < max_out) + max_out = cr; + } +#endif + + if (is_http && wsi->http.tx_content_length) { + wsi->http.tx_content_remain -= len; + lwsl_info("%s: %s: tx_content_rem = %llu\n", __func__, + lws_wsi_tag(wsi), + (unsigned long long)wsi->http.tx_content_remain); + if (!wsi->http.tx_content_remain) { + lwsl_info("%s: selecting final write mode\n", __func__); + base = LWS_WRITE_HTTP_FINAL; + *wp = (enum lws_write_protocol)(((unsigned int)*wp & ~0x1fu) | LWS_WRITE_HTTP_FINAL); + } + } + + if (base == LWS_WRITE_HTTP_FINAL) { + *wp = (enum lws_write_protocol)((unsigned int)*wp | LWS_WRITE_H2_STREAM_END); + } + +#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION) + if (is_http && wsi->http.lcs) { + unsigned char *out = mtubuf + LWS_PRE; + size_t o = (size_t)max_out; + + n = lws_http_compression_transform(wsi, buf, len, wp, &out, &o); + if (n) + return n; + + buf = out; + len = o; + base = (*wp) & 0x1f; + + if (base == LWS_WRITE_HTTP_FINAL) { + *wp = (enum lws_write_protocol)((unsigned int)*wp | LWS_WRITE_H2_STREAM_END); + } else { + *wp = (enum lws_write_protocol)((unsigned int)*wp & ~(unsigned int)LWS_WRITE_H2_STREAM_END); + } + + if (!len && base != LWS_WRITE_HTTP_FINAL) + return (int)olen; + } +#endif + + if (is_http) { + /* It's HTTP payload, we need to frame it in an H3 DATA frame (type 0x00) */ + /* We assume the caller reserved LWS_PRE bytes before buf. */ + uint8_t len_buf[8]; + int len_bytes = (int)lws_quic_write_varint(len_buf, sizeof(len_buf), len); + + pre = buf - len_bytes - 1; + pre[0] = 0x00; /* DATA frame type */ + memcpy(&pre[1], len_buf, (size_t)len_bytes); + + len += (size_t)(len_bytes + 1); + } else if (is_headers) { + /* It's HTTP headers, we need to frame it in an H3 HEADERS frame (type 0x01) */ + /* We assume the caller reserved LWS_PRE bytes before buf. */ + uint8_t len_buf[8]; + int len_bytes = (int)lws_quic_write_varint(len_buf, sizeof(len_buf), len); + + pre = buf - len_bytes - 1; + pre[0] = 0x01; /* HEADERS frame type */ + memcpy(&pre[1], len_buf, (size_t)len_bytes); + + len += (size_t)(len_bytes + 1); + // lwsl_notice("%s: HTTP/3 HEADERS frame: unframed len=%d, framed len=%d\n", __func__, (int)olen, (int)len); + // lwsl_hexdump_notice(pre, len); + } + + { + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + if (nwsi && lws_rops_fidx(nwsi->role_ops, LWS_ROPS_write_role_protocol)) { + n = lws_rops_func_fidx(nwsi->role_ops, LWS_ROPS_write_role_protocol). + write_role_protocol(wsi, (is_http || is_headers) ? pre : buf, len, wp); + if (n <= 0) + return n; + return (int)olen; + } + } + + return -1; +} + +static int +rops_callback_on_writable_h3(struct lws *wsi) +{ + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + + if (wsi->mux.requested_POLLOUT) { + lwsl_debug("already pending writable\n"); + } + + lws_wsi_mux_mark_parents_needing_writeable(wsi); + + if (nwsi && nwsi != wsi) + return lws_callback_on_writable(nwsi); + + return 0; +} + +extern int rops_tx_credit_quic(struct lws *wsi, char peer_to_us, int add); + +#if defined(LWS_ROLE_WS) && defined(LWS_WITH_SERVER) +static int +rops_check_upgrades_h3(struct lws *wsi) +{ + const char *p; + + /* + * with H3 there's also a way to upgrade a stream to something + * else... :method is CONNECT and :protocol says the name of + * the new protocol we want to carry. We have to have sent a + * SETTINGS saying that we support it though. + */ + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_METHOD); + if (!wsi->a.vhost->h2.set.s[H2SET_ENABLE_CONNECT_PROTOCOL] || + !wsi->mux_substream || !p || strcmp(p, "CONNECT")) + return LWS_UPG_RET_CONTINUE; + + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_COLON_PROTOCOL); + if (!p) + return LWS_UPG_RET_CONTINUE; + + if (!strcmp(p, "websocket")) { + lwsl_info("Upgrade h3 to ws\n"); + lws_mux_mark_immortal(wsi); + wsi->h23_stream_carries_ws = 1; + + lws_metrics_tag_wsi_add(wsi, "upg", "ws_over_h3"); + + if (lws_process_ws_upgrade(wsi)) + return LWS_UPG_RET_BAIL; + + lwsl_info("Upgraded h3 to ws OK\n"); + + return LWS_UPG_RET_DONE; + } else if (!strcmp(p, "webtransport")) { +#if defined(LWS_ROLE_WT) + lwsl_info("Upgrade h3 to wt\n"); + extern const struct lws_role_ops role_ops_wt; + lws_mux_mark_immortal(wsi); + lws_metrics_tag_wsi_add(wsi, "upg", "wt_over_h3"); + + /* Switch role to WebTransport */ + lws_role_transition(wsi, LWSIFR_SERVER, LRS_ESTABLISHED, &role_ops_wt); + + if (wsi->a.protocol && wsi->a.protocol->callback) { + if (wsi->a.protocol->callback(wsi, LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED, wsi->user_space, NULL, 0)) + return LWS_UPG_RET_BAIL; + } + + lwsl_info("Upgraded h3 to wt OK\n"); + + return LWS_UPG_RET_DONE; +#else + return LWS_UPG_RET_CONTINUE; +#endif + } + + return LWS_UPG_RET_CONTINUE; +} +#endif + +static const lws_rops_t rops_table_h3[] = { + /* 1 */ { .handle_POLLIN = rops_handle_POLLIN_h3 }, + /* 2 */ { .perform_user_POLLOUT = rops_perform_user_POLLOUT_h3 }, + /* 3 */ { .adoption_bind = rops_adoption_bind_h3 }, +#if defined(LWS_WITH_CLIENT) + /* 4 */ { .client_bind = rops_client_bind_h3 }, +#endif + /* 5 */ { .alpn_negotiated = rops_alpn_negotiated_h3 }, + /* 6 */ { .close_kill_connection = rops_close_kill_connection_h3 }, + /* 7 */ { .write_role_protocol = rops_write_role_protocol_h3 }, + /* 8 */ { .callback_on_writable = rops_callback_on_writable_h3 }, + /* 9 */ { .tx_credit = rops_tx_credit_quic }, +#if defined(LWS_ROLE_WS) && defined(LWS_WITH_SERVER) + /* 10 */ { .check_upgrades = rops_check_upgrades_h3 }, +#endif +}; + +const struct lws_role_ops role_ops_h3 = { + /* role name */ "h3", + /* alpn id */ "h3", + + /* rops_table */ rops_table_h3, + /* rops_idx */ { +#if defined(LWS_ROLE_WS) && defined(LWS_WITH_SERVER) + /* LWS_ROPS_check_upgrades */ + /* LWS_ROPS_pt_init_destroy */ 0xA0, +#else + /* LWS_ROPS_check_upgrades */ + /* LWS_ROPS_pt_init_destroy */ 0x00, +#endif + /* LWS_ROPS_init_vhost */ + /* LWS_ROPS_destroy_vhost */ 0x00, + /* LWS_ROPS_service_flag_pending */ + /* LWS_ROPS_handle_POLLIN */ 0x01, + /* LWS_ROPS_handle_POLLOUT */ + /* LWS_ROPS_perform_user_POLLOUT */ 0x02, + /* LWS_ROPS_callback_on_writable */ + /* LWS_ROPS_tx_credit */ 0x89, + /* LWS_ROPS_write_role_protocol */ + /* LWS_ROPS_encapsulation_parent */ 0x70, + /* LWS_ROPS_alpn_negotiated */ + /* LWS_ROPS_close_via_role_protocol */ 0x50, + /* LWS_ROPS_close_role */ + /* LWS_ROPS_close_kill_connection */ 0x06, + /* LWS_ROPS_destroy_role */ + /* LWS_ROPS_adoption_bind */ 0x03, +#if defined(LWS_WITH_CLIENT) + /* LWS_ROPS_client_bind */ + /* LWS_ROPS_issue_keepalive */ 0x40, +#else + /* LWS_ROPS_client_bind */ + /* LWS_ROPS_issue_keepalive */ 0x00, +#endif + }, + + /* adoption_cb clnt, srv */ { LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED, + LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED }, + /* rx_cb clnt, srv */ { LWS_CALLBACK_RECEIVE_CLIENT_HTTP, + LWS_CALLBACK_HTTP }, + /* writeable cb clnt, srv */ { LWS_CALLBACK_CLIENT_HTTP_WRITEABLE, + LWS_CALLBACK_HTTP_WRITEABLE }, + /* close cb clnt, srv */ { LWS_CALLBACK_CLOSED_CLIENT_HTTP, + LWS_CALLBACK_CLOSED_HTTP }, + /* protocol_bind cb c, srv */ { LWS_CALLBACK_CLIENT_HTTP_BIND_PROTOCOL, + LWS_CALLBACK_HTTP_BIND_PROTOCOL }, + /* protocol_unbind cb c, srv */ { LWS_CALLBACK_CLIENT_HTTP_DROP_PROTOCOL, + LWS_CALLBACK_HTTP_DROP_PROTOCOL }, + /* file_handle */ 0, +}; + +struct lws * +lws_wsi_h3_adopt(struct lws *parent_wsi, struct lws *wsi) +{ + struct lws *nwsi = lws_get_network_wsi(parent_wsi); + struct lws_quic_netconn *qn = nwsi->quic.qn; + uint64_t sid; + + if (!qn) { + lwsl_err("%s: no quic netconn\n", __func__); + return NULL; + } + + wsi->seen_nonpseudoheader = 0; + wsi->hdr_parsing_completed = 0; +#if defined(LWS_WITH_CLIENT) + wsi->client_mux_substream = 1; +#endif + + wsi->quic.qs = lws_zalloc(sizeof(*wsi->quic.qs), "quic stream"); + if (!wsi->quic.qs) + return NULL; + + wsi->quic.qs->wsi = wsi; + + /* If next_stream_id_bidi_local is 0, initialize it to 4 because stream 0 is already used */ + if (qn->next_stream_id_bidi_local == 0) + qn->next_stream_id_bidi_local = 4; + + sid = qn->next_stream_id_bidi_local; + wsi->quic.qs->stream_id = sid; + qn->next_stream_id_bidi_local += 4; + + wsi->mux_substream = 1; +#if defined(LWS_WITH_CLIENT) + wsi->client_h2_alpn = 1; +#endif + + lws_wsi_mux_insert(wsi, nwsi, (unsigned int)sid); + + /* Initialize flow control credits */ + int32_t init_cr = nwsi->txc.manual_initial_tx_credit; + if (!init_cr) { + if (qn->peer_initial_max_stream_data_bidi_local) + init_cr = (int32_t)qn->peer_initial_max_stream_data_bidi_local; + else + init_cr = 65535; + } + wsi->txc.peer_tx_cr_est = init_cr; + wsi->txc.tx_cr = init_cr; + + wsi->h3.h3n = nwsi->h3.h3n; + + if (lws_ensure_user_space(wsi)) { + lws_free_set_NULL(wsi->quic.qs); + return NULL; + } + + lws_role_transition(wsi, LWSIFR_CLIENT, LRS_H2_WAITING_TO_SEND_HEADERS, + &role_ops_h3); + + lws_callback_on_writable(wsi); + + return wsi; +} + diff --git a/lib/roles/h3/private-lib-roles-h3.h b/lib/roles/h3/private-lib-roles-h3.h index 64d878a440..df0096d852 100644 --- a/lib/roles/h3/private-lib-roles-h3.h +++ b/lib/roles/h3/private-lib-roles-h3.h @@ -25,10 +25,93 @@ #ifndef _PRIVATE_LIB_ROLES_H3_H_ #define _PRIVATE_LIB_ROLES_H3_H_ +/* HTTP/3 Error Codes (RFC 9114, Section 8.1) */ +#define LWS_H3_NO_ERROR 0x0100 +#define LWS_H3_GENERAL_PROTOCOL_ERROR 0x0101 +#define LWS_H3_INTERNAL_ERROR 0x0102 +#define LWS_H3_STREAM_CREATION_ERROR 0x0103 +#define LWS_H3_CLOSED_CRITICAL_STREAM 0x0104 +#define LWS_H3_FRAME_UNEXPECTED 0x0105 +#define LWS_H3_FRAME_ERROR 0x0106 +#define LWS_H3_EXCESSIVE_LOAD 0x0107 +#define LWS_H3_ID_ERROR 0x0108 +#define LWS_H3_SETTINGS_ERROR 0x0109 +#define LWS_H3_MISSING_SETTINGS 0x010a +#define LWS_H3_REQUEST_REJECTED 0x010b +#define LWS_H3_REQUEST_CANCELLED 0x010c +#define LWS_H3_REQUEST_INCOMPLETE 0x010d +#define LWS_H3_MESSAGE_ERROR 0x010e +#define LWS_H3_VERSION_FALLBACK 0x010f + +#define LWS_H3_SETTINGS_ENABLE_WEBTRANSPORT 0x2b603742 +#define LWS_H3_SETTINGS_H3_DATAGRAM 0x33 + +/* QPACK Error Codes (RFC 9204, Section 8.2) */ +#define LWS_QPACK_DECOMPRESSION_FAILED 0x0200 +#define LWS_QPACK_ENCODER_STREAM_ERROR 0x0201 +#define LWS_QPACK_DECODER_STREAM_ERROR 0x0202 + +int +lws_h3_rx_stream_data(struct lws *wsi, const uint8_t *buf, size_t len); + extern const struct lws_role_ops role_ops_h3; #define lwsi_role_h3(wsi) (wsi->role_ops == &role_ops_h3) +struct lws * +lws_wsi_h3_adopt(struct lws *parent_wsi, struct lws *wsi); + + + +struct lws_h3_netconn { + struct lws *nwsi; + + /* Local control streams we send to the peer */ + struct lws *cwsi_control; + struct lws *cwsi_qpack_enc; + struct lws *cwsi_qpack_dec; + + /* Peer control streams we receive from the peer */ + struct lws *peer_control; + struct lws *peer_qpack_enc; + struct lws *peer_qpack_dec; + + struct lws_qpack_tx_encoder qpack_tx_encoder; + struct lws_qpack_tx_table_entry tx_entries[32]; + + struct lws_qpack_context qpack_dec_ctx; + + uint8_t peer_supports_ws:1; + uint8_t peer_supports_webtransport:1; + uint8_t peer_supports_h3_datagram:1; +}; + +struct _lws_h3_related { + struct lws_h3_netconn *h3n; /* malloc'd for root net conn */ + struct lws_qpack_tx_encoder *qpack_tx_encoder; + struct lws_qpack_stream_state qpack_dec_state; + uint8_t h3_state; + uint8_t stream_type; + uint8_t type_set:1; + uint8_t seen_settings:1; + + /* Pseudo-header tracking for HTTP/3 4.1.3 validations */ + uint8_t seen_regular_header:1; + uint8_t seen_pseudo_method:1; + uint8_t seen_pseudo_scheme:1; + uint8_t seen_pseudo_authority:1; + uint8_t seen_pseudo_path:1; + uint8_t seen_pseudo_status:1; + uint8_t seen_pseudo_protocol:1; + /* H3 Frame parsing state */ + uint8_t rx_frame_state; /* 0: type, 1: length, 2: payload */ + uint64_t rx_frame_type; + uint64_t rx_frame_len; + uint64_t rx_frame_payload_read; + + uint8_t rx_varint_buf[8]; + uint8_t rx_varint_len; +}; /* Internal QPACK API */ int diff --git a/lib/roles/h3/qpack.c b/lib/roles/h3/qpack.c index ffcb1d2bb4..0412afdd93 100644 --- a/lib/roles/h3/qpack.c +++ b/lib/roles/h3/qpack.c @@ -125,7 +125,7 @@ static const unsigned char qpack_static_token[99] = { LWS_QPACK_IGNORE_ENTRY, /* purpose */ WSI_TOKEN_HTTP_SERVER, LWS_QPACK_IGNORE_ENTRY, /* timing-allow-origin */ - WSI_TOKEN_UPGRADE, + LWS_QPACK_IGNORE_ENTRY, /* upgrade-insecure-requests */ WSI_TOKEN_HTTP_USER_AGENT, WSI_TOKEN_X_FORWARDED_FOR, LWS_QPACK_IGNORE_ENTRY, /* x-frame-options */ @@ -133,7 +133,7 @@ static const unsigned char qpack_static_token[99] = { }; static const char * const qpack_canned[] = { - "", "", "0", "", "0", "", "", "", "", "", + "", "/", "0", "", "0", "", "", "", "", "", "", "", "", "", "", "CONNECT", "DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT", "http", "https", "103", "200", "304", "404", "503", "*/*", "application/dns-message", "gzip, deflate, br", "bytes", "cache-control", "content-type", "*", "max-age=0", "max-age=2592000", "max-age=604800", "no-cache", "no-store", "public, max-age=31536000", @@ -141,7 +141,7 @@ static const char * const qpack_canned[] = { "text/html; charset=utf-8", "text/plain", "text/plain;charset=utf-8", "bytes=0-", "max-age=31536000", "max-age=31536000; includesubdomains", "max-age=31536000; includesubdomains; preload", "accept-encoding", "origin", "nosniff", "1; mode=block", "100", "204", "206", "302", "400", "403", "421", "425", "500", "", "FALSE", "TRUE", "*", "get", "get, post, options", "options", "content-length", "content-type", "get", - "post", "clear", "", "script-src 'none'; object-src 'none'; base-uri 'none'", "1", "", "", "", "", "prefetch", + "post", "clear", "", "script-src 'none'; object-src 'none'; base-uri 'none'", "1", "100-continue", "", "", "", "prefetch", "", "*", "1", "", "", "deny", "sameorigin" }; @@ -162,11 +162,10 @@ lws_qpack_find_static_index(int lws_hdr_idx, const char *value, int value_len) } } - /* Fallback to matching just the name, taking the first match with empty string if possible */ + /* Fallback to matching just the name, taking the first match */ for (i = 0; i < 99; i++) { if (qpack_static_token[i] == lws_hdr_idx) { - if (!value || qpack_canned[i][0] == '\0') - return i; + return i; } } @@ -303,6 +302,7 @@ lws_qpack_encode_literal_with_name_ref(unsigned char *buf, size_t buf_len, int i /* 0 1 N T Index, T=1 (Static), N=0 -> mask=0x50, prefix=4 */ n = lws_qpack_encode_int(buf, buf_len, (uint64_t)index, 4, 0x50); + lwsl_notice("%s: index=%d, output byte: 0x%02X\n", __func__, index, buf[0]); if (n < 0) return -1; pos += (size_t)n; @@ -321,6 +321,7 @@ lws_qpack_encode_literal_with_literal_name(unsigned char *buf, size_t buf_len, c /* 0 0 1 N H Name Length, N=0, H=0 (Plaintext) -> mask=0x20, prefix=3 */ n = lws_qpack_encode_int(buf, buf_len, (uint64_t)name_len, 3, 0x20); + lwsl_notice("%s: name_len=%d, output byte: 0x%02X\n", __func__, (int)name_len, buf[0]); if (n < 0) return -1; pos += (size_t)n; @@ -343,7 +344,7 @@ lws_add_http3_header_by_name(struct lws *wsi, const unsigned char *name, int name_len = (int)strlen((const char *)name); int n; char lower_name[256]; - struct lws_qpack_tx_encoder *enc = wsi ? wsi->qpack_tx_encoder : NULL; + struct lws_qpack_tx_encoder *enc = NULL; /* wsi ? wsi->h3.qpack_tx_encoder : NULL; */ if (name_len && name[name_len - 1] == ':') name_len--; @@ -390,7 +391,7 @@ lws_add_http3_header_by_token(struct lws *wsi, enum lws_token_indexes token, unsigned char **p, unsigned char *end) { int static_idx = lws_qpack_find_static_index((int)token, (const char *)value, length); - struct lws_qpack_tx_encoder *enc = wsi ? wsi->qpack_tx_encoder : NULL; + struct lws_qpack_tx_encoder *enc = NULL; /* wsi ? wsi->h3.qpack_tx_encoder : NULL; */ int n; if (static_idx != -1) { @@ -442,7 +443,7 @@ lws_add_http3_header_status(struct lws *wsi, unsigned int code, /* Prefix is required at the start of the header block! */ if (wsi) { - struct lws_qpack_tx_encoder *enc = wsi->qpack_tx_encoder; + struct lws_qpack_tx_encoder *enc = wsi->h3.qpack_tx_encoder; wsi->http.h3_prefix_ptr = *p; *p += 2; /* Reserve space for exactly 2-byte prefix */ wsi->http.h3_req_ric = 0; /* Reset RIC for this block */ @@ -548,7 +549,8 @@ lws_qpack_decode_header_block(struct lws_qpack_stream_state *state, } else { goto do_emit; } - } else if ((c & 0xf0) == 0x50 || (c & 0xf0) == 0x40) { + } else if ((c & 0xf0) == 0x70 || (c & 0xf0) == 0x60 || + (c & 0xf0) == 0x50 || (c & 0xf0) == 0x40) { /* Literal Field Line With Name Reference */ state->is_name = 0; state->int_val = c & 0x0f; @@ -603,6 +605,8 @@ lws_qpack_decode_header_block(struct lws_qpack_stream_state *state, break; case LQP_DEC_INT: + if (state->int_shift >= 64) + return 1; state->int_val += (uint64_t)(c & 0x7f) << state->int_shift; state->int_shift += 7; if (!(c & 0x80)) { @@ -713,7 +717,7 @@ lws_qpack_decode_header_block(struct lws_qpack_stream_state *state, const char *val = NULL; if ((state->opcode & 0xc0) == 0xc0) { - lws_qpack_get_static_token((int)state->int_val, &idx, &val); + if (lws_qpack_get_static_token((int)state->int_val, &idx, &val)) return 1; } else if ((state->opcode & 0xc0) == 0x80) { int absolute_idx = (int)(state->base - (uint64_t)state->int_val - 1); int relative_idx = ctx ? (int)(ctx->dyn_table.insert_count - 1 - (uint32_t)absolute_idx) : -1; @@ -730,11 +734,11 @@ lws_qpack_decode_header_block(struct lws_qpack_stream_state *state, name = dte->value; val = dte->value + name_len + 1; } - } else if ((state->opcode & 0xf0) == 0x50) { - lws_qpack_get_static_token((int)state->hdr_idx, &idx, NULL); + } else if ((state->opcode & 0xf0) == 0x50 || (state->opcode & 0xf0) == 0x70) { + if (lws_qpack_get_static_token((int)state->hdr_idx, &idx, NULL)) return 1; state->val_buf[state->val_pos] = '\0'; val = state->val_buf; - } else if ((state->opcode & 0xf0) == 0x40) { + } else if ((state->opcode & 0xf0) == 0x40 || (state->opcode & 0xf0) == 0x60) { int absolute_idx = (int)(state->base - (uint64_t)(unsigned int)state->hdr_idx - 1); int relative_idx = ctx ? (int)(ctx->dyn_table.insert_count - 1 - (uint32_t)absolute_idx) : -1; struct lws_qpack_dynamic_table_entry *dte = @@ -802,7 +806,7 @@ lws_qpack_get_dynamic_entry(struct lws_qpack_context *ctx, int relative_idx) if (!ctx || !ctx->dyn_table.entries) return NULL; - if (relative_idx >= ctx->dyn_table.used_entries) + if (relative_idx < 0 || relative_idx >= ctx->dyn_table.used_entries) return NULL; ring_idx = (ctx->dyn_table.pos - 1 - relative_idx + ctx->dyn_table.num_entries) % ctx->dyn_table.num_entries; @@ -1054,7 +1058,8 @@ lws_qpack_decode_encoder_stream(struct lws_qpack_stream_state *state, state->val_buf[state->val_pos] = '\0'; lws_qpack_dynamic_insert(ctx, -1, state->name_buf, state->name_pos, state->val_buf, state->val_pos); } else if ((state->opcode & 0xe0) == 0x20) { - lws_qpack_dynamic_size(ctx, (int)state->int_val); + if (lws_qpack_dynamic_size(ctx, (int)state->int_val)) + return 1; } else if ((state->opcode & 0xe0) == 0x00) { struct lws_qpack_dynamic_table_entry *dte = lws_qpack_get_dynamic_entry(ctx, (int)state->int_val); @@ -1085,6 +1090,11 @@ lws_qpack_dynamic_size(struct lws_qpack_context *ctx, int size) struct lws_qpack_dynamic_table_entry *dte; int n, min, m; + if ((uint32_t)size > ctx->dyn_table.virtual_payload_limit) { + lwsl_err("LWS_QPACK_ENCODER_STREAM_ERROR: table capacity limit exceeded!\n"); + return 1; + } + if (!size) { lws_qpack_destroy_dynamic_header(ctx); return 0; @@ -1151,29 +1161,7 @@ lws_qpack_destroy_dynamic_header(struct lws_qpack_context *ctx) } #if defined(LWS_ROLE_H3) -static const lws_rops_t rops_table_h3[] = { - /* 1 */ { .check_upgrades = NULL }, -}; -/* - * Dummy role_ops_h3 to satisfy the linker until the full h3 role is built. - * It allows lwsi_role_h3() macro to compile. - */ -const struct lws_role_ops role_ops_h3 = { - /* role name */ "h3", - /* alpn id */ "h3", - /* rops_table */ rops_table_h3, - /* rops_idx */ { - /* 1 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }, - /* adoption_cb clnt, srv */ { 0, 0 }, - /* rx_cb clnt, srv */ { 0, 0 }, - /* writeable cb clnt, srv */ { 0, 0 }, - /* close cb clnt, srv */ { 0, 0 }, - /* protocol_bind cb c, srv */ { 0, 0 }, - /* protocol_unbind cb c, srv */ { 0, 0 }, - /* file_handle */ 0, -}; LWS_VISIBLE struct lws * lws_create_h3_dummy_wsi(struct lws_context *context, struct lws_qpack_tx_encoder *tx_enc) @@ -1186,7 +1174,7 @@ lws_create_h3_dummy_wsi(struct lws_context *context, struct lws_qpack_tx_encoder wsi->a.context = context; wsi->role_ops = &role_ops_h3; - wsi->qpack_tx_encoder = tx_enc; + wsi->h3.qpack_tx_encoder = tx_enc; wsi->http.h3_base = 0; wsi->http.h3_req_ric = 0; @@ -1291,6 +1279,10 @@ lws_qpack_tx_find(struct lws_qpack_tx_encoder *enc, const char *name, size_t nam for (i = 0; i < enc->used_entries; i++) { int idx = (enc->pos - enc->used_entries + i + enc->num_entries) % enc->num_entries; struct lws_qpack_tx_table_entry *dte = &enc->entries[idx]; + + if (dte->insert_index >= enc->known_received_count) + continue; + if (dte->name_len == name_len && dte->value_len == val_len) { if ((!name_len || !memcmp(dte->name, name, name_len)) && (!val_len || !memcmp(dte->value, val, val_len))) { @@ -1367,17 +1359,26 @@ lws_qpack_tx_insert(struct lws_qpack_tx_encoder *enc, const char *name, size_t n enc->pos = (uint16_t)((enc->pos + 1) % enc->num_entries); /* Generate the Encoder Stream Instruction */ - if (static_name_idx >= 0) { - n = lws_qpack_tx_encode_insert_name_ref(enc->enc_buf + enc->enc_ptr, - sizeof(enc->enc_buf) - enc->enc_ptr, 1, static_name_idx, val, val_len); - } else { - n = lws_qpack_tx_encode_insert_literal(enc->enc_buf + enc->enc_ptr, - sizeof(enc->enc_buf) - enc->enc_ptr, name, name_len, val, val_len); + { + uint8_t scratch[512]; + if (static_name_idx >= 0) { + n = lws_qpack_tx_encode_insert_name_ref(scratch, + sizeof(scratch), 1, static_name_idx, val, val_len); + } else { + n = lws_qpack_tx_encode_insert_literal(scratch, + sizeof(scratch), name, name_len, val, val_len); + } + + if (n > 0) { + if (lws_buflist_append_segment(&enc->tx_bl, scratch, (size_t)n) < 0) + return -1; + if (enc->wsi_qpack_enc) + lws_callback_on_writable(enc->wsi_qpack_enc); + } else { + lwsl_err("ENCODER STREAM GENERATION FAILED! n=%d\n", n); + } } - if (n > 0) enc->enc_ptr += (size_t)n; - else lwsl_err("ENCODER STREAM GENERATION FAILED! n=%d\n", n); - return (int)dte->insert_index; } @@ -1386,11 +1387,14 @@ lws_qpack_tx_encoder_destroy(struct lws_qpack_tx_encoder *enc) { int i; if (!enc || !enc->entries) return; + for (i = 0; i < enc->used_entries; i++) { int idx = (enc->pos - enc->used_entries + i + enc->num_entries) % enc->num_entries; if (enc->entries[idx].name) lws_free(enc->entries[idx].name); if (enc->entries[idx].value) lws_free(enc->entries[idx].value); } + + lws_buflist_destroy_all_segments(&enc->tx_bl); } LWS_VISIBLE void diff --git a/lib/roles/http/client/client-http.c b/lib/roles/http/client/client-http.c index 9069f1e5c0..059e802bf1 100644 --- a/lib/roles/http/client/client-http.c +++ b/lib/roles/http/client/client-http.c @@ -1062,10 +1062,10 @@ lws_client_interpret_server_handshake(struct lws *wsi) */ #if defined(LWS_ROLE_H2) if (wsi->client_h2_alpn || wsi->client_mux_substream) { - lwsl_debug("%s: %s: transitioning to h2 client\n", + lwsl_debug("%s: %s: transitioning to mux client\n", __func__, lws_wsi_tag(wsi)); lws_role_transition(wsi, LWSIFR_CLIENT, - LRS_ESTABLISHED, &role_ops_h2); + LRS_ESTABLISHED, wsi->role_ops); } else #endif { @@ -1979,7 +1979,11 @@ lws_generate_client_handshake(struct lws *wsi, char *pkt, size_t pkt_len) meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD); if (!meth) { meth = "GET"; - wsi->do_ws = 1; +#if defined(LWS_ROLE_WS) + wsi->do_ws = wsi->ws ? 1 : 0; +#else + wsi->do_ws = 0; +#endif } else { wsi->do_ws = 0; } @@ -2043,24 +2047,23 @@ lws_generate_client_handshake(struct lws *wsi, char *pkt, size_t pkt_len) "Pragma: no-cache\x0d\x0a" "Cache-Control: no-cache\x0d\x0a"); - p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), - "Host: %s\x0d\x0a", - lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST)); + const char *host = lws_wsi_client_stash_item(wsi, CIS_HOST, _WSI_TOKEN_CLIENT_HOST); + if (host) + p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), + "Host: %s\x0d\x0a", host); - if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)) { + const char *origin = lws_wsi_client_stash_item(wsi, CIS_ORIGIN, _WSI_TOKEN_CLIENT_ORIGIN); + if (origin) { if (lws_check_opt(wsi->a.context->options, LWS_SERVER_OPTION_JUST_USE_RAW_ORIGIN)) p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), - "Origin: %s\x0d\x0a", - lws_hdr_simple_ptr(wsi, - _WSI_TOKEN_CLIENT_ORIGIN)); + "Origin: %s\x0d\x0a", origin); else p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "Origin: %s://%s\x0d\x0a", wsi->flags & LCCSCF_USE_SSL ? "https" : "http", - lws_hdr_simple_ptr(wsi, - _WSI_TOKEN_CLIENT_ORIGIN)); + origin); } if (wsi->flags & LCCSCF_HTTP_MULTIPART_MIME) { @@ -2228,10 +2231,13 @@ lws_http_client_read(struct lws *wsi, char **buf, int *len) if (buffered < 0) { lwsl_debug("%s: SSL capable error\n", __func__); + lwsl_notice("%s: SSL capable error, hdr_parsing_completed=%d, content_length_given=%d, chunked=%d, ah_ptr=%p\n", + __func__, wsi->hdr_parsing_completed, wsi->http.content_length_given, wsi->chunked, wsi->http.ah); - if (wsi->http.ah && - wsi->http.ah->parser_state == WSI_PARSING_COMPLETE && - !lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) + if (wsi->hdr_parsing_completed && + !wsi->http.content_length_given && + !wsi->chunked) { + lwsl_notice("%s: generating lws_http_transaction_completed_client\n", __func__); /* * We had the headers from this stream, but as there * was no content-length: we had to wait until the @@ -2244,6 +2250,7 @@ lws_http_client_read(struct lws *wsi, char **buf, int *len) * warn_unused_result */ return -1; + } return -1; } @@ -2502,8 +2509,13 @@ lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port, cisin[CIS_PATH] = path + o; cisin[CIS_HOST] = host; - for (n = 0; n < (int)LWS_ARRAY_SIZE(hnames2); n++) + for (n = 0; n < (int)LWS_ARRAY_SIZE(hnames2); n++) { cisin[n + 3] = lws_hdr_simple_ptr(wsi, hnames2[n]); +#if defined(LWS_ROLE_H2) || defined(LWS_ROLE_H3) + if (!cisin[n + 3] && hnames2[n] == _WSI_TOKEN_CLIENT_METHOD) + cisin[n + 3] = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_METHOD); +#endif + } r = (int)wsi->http.ah->http_response; @@ -2511,8 +2523,14 @@ lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port, cisin[CIS_ALPN] = wsi->alpn; #endif - if (!wsi->stash && lws_client_stash_create(wsi, cisin)) - return NULL; + { + void *opaque = wsi->stash ? wsi->stash->opaque_user_data : NULL; + + if (lws_client_stash_create(wsi, cisin)) + return NULL; + + wsi->stash->opaque_user_data = opaque; + } if (!port) { lwsl_info("%s: forcing port 443\n", __func__); diff --git a/lib/roles/http/compression/deflate/deflate.c b/lib/roles/http/compression/deflate/deflate.c index 8e9a1ca776..7579b18a85 100644 --- a/lib/roles/http/compression/deflate/deflate.c +++ b/lib/roles/http/compression/deflate/deflate.c @@ -68,9 +68,12 @@ lcs_process_deflate(lws_comp_ctx_t *ctx, const void *in, size_t *ilen_iused, ctx->u.deflate->next_out = out; ctx->u.deflate->avail_out = (unsigned int)*olen_oused; - if (!ctx->is_decompression) - n = deflate(ctx->u.deflate, Z_SYNC_FLUSH); - else + if (!ctx->is_decompression) { + int flush = Z_SYNC_FLUSH; + if (ctx->final_on_input_side && !ctx->buflist_comp) + flush = Z_FINISH; + n = deflate(ctx->u.deflate, flush); + } else n = inflate(ctx->u.deflate, Z_SYNC_FLUSH); switch (n) { diff --git a/lib/roles/http/compression/stream.c b/lib/roles/http/compression/stream.c index a3456cd3c0..5293f9373a 100644 --- a/lib/roles/http/compression/stream.c +++ b/lib/roles/http/compression/stream.c @@ -203,10 +203,6 @@ lws_http_compression_transform(struct lws *wsi, unsigned char *buf, return -1; } - if (!ctx->may_have_more && ctx->final_on_input_side) - - *wp = (unsigned int)(LWS_WRITE_HTTP_FINAL | ((*wp) & ~0x1fu)); - lwsl_debug("%s: %s: more %d, ilen_iused %d\n", __func__, lws_wsi_tag(wsi), ctx->may_have_more, (int)ilen_iused); @@ -237,5 +233,8 @@ lws_http_compression_transform(struct lws *wsi, unsigned char *buf, if (ctx->buflist_comp || ctx->may_have_more) lws_callback_on_writable(wsi); + if (ctx->final_on_input_side && !ctx->may_have_more && !ctx->buflist_comp) + *wp = (unsigned int)(LWS_WRITE_HTTP_FINAL | ((*wp) & ~0x1fu)); + return 0; } diff --git a/lib/roles/http/cookie.c b/lib/roles/http/cookie.c index daec05b4dc..9589252713 100644 --- a/lib/roles/http/cookie.c +++ b/lib/roles/http/cookie.c @@ -782,7 +782,11 @@ lws_cookie_send_cookies(struct lws *wsi, char **pp, char *end) #ifdef LWS_WITH_HTTP2 - if (lws_wsi_is_h2(wsi)) + if (lws_wsi_is_h2(wsi) +#ifdef LWS_ROLE_H3 + || lws_wsi_is_h3(wsi) +#endif + ) p = *pp - size; else #endif diff --git a/lib/roles/http/header.c b/lib/roles/http/header.c index 956a479a86..73c3bc228e 100644 --- a/lib/roles/http/header.c +++ b/lib/roles/http/header.c @@ -55,6 +55,9 @@ lws_http_string_to_known_header(const char *s, size_t slen) int lws_wsi_is_h2(struct lws *wsi) { + if (lwsi_role_h3(wsi)) + return 0; + return wsi->upgraded_to_http2 || wsi->mux_substream || #if defined(LWS_WITH_CLIENT) @@ -78,15 +81,15 @@ lws_add_http_header_by_name(struct lws *wsi, const unsigned char *name, const unsigned char *value, int length, unsigned char **p, unsigned char *end) { -#ifdef LWS_WITH_HTTP2 - if (lws_wsi_is_h2(wsi)) - return lws_add_http2_header_by_name(wsi, name, - value, length, p, end); -#endif #ifdef LWS_ROLE_H3 if (wsi && lws_wsi_is_h3(wsi)) return lws_add_http3_header_by_name(wsi, name, value, length, p, end); +#endif +#ifdef LWS_WITH_HTTP2 + if (lws_wsi_is_h2(wsi)) + return lws_add_http2_header_by_name(wsi, name, + value, length, p, end); #endif if (!wsi) { /* Used occasionally by tests that pass NULL wsi */ @@ -118,10 +121,6 @@ lws_add_http_header_by_name(struct lws *wsi, const unsigned char *name, int lws_finalize_http_header(struct lws *wsi, unsigned char **p, unsigned char *end) { -#ifdef LWS_WITH_HTTP2 - if (lws_wsi_is_h2(wsi)) - return 0; -#endif #ifdef LWS_ROLE_H3 if (wsi && lws_wsi_is_h3(wsi)) { if (wsi->http.h3_prefix_ptr) { @@ -142,6 +141,10 @@ int lws_finalize_http_header(struct lws *wsi, unsigned char **p, } return 0; } +#endif +#ifdef LWS_WITH_HTTP2 + if (lws_wsi_is_h2(wsi)) + return 0; #endif if ((lws_intptr_t)(end - *p) < 3) return 1; @@ -176,15 +179,15 @@ lws_add_http_header_by_token(struct lws *wsi, enum lws_token_indexes token, unsigned char **p, unsigned char *end) { const unsigned char *name; -#ifdef LWS_WITH_HTTP2 - if (lws_wsi_is_h2(wsi)) - return lws_add_http2_header_by_token(wsi, token, value, - length, p, end); -#endif #ifdef LWS_ROLE_H3 if (wsi && lws_wsi_is_h3(wsi)) return lws_add_http3_header_by_token(wsi, token, value, length, p, end); +#endif +#ifdef LWS_WITH_HTTP2 + if (lws_wsi_is_h2(wsi)) + return lws_add_http2_header_by_token(wsi, token, value, + length, p, end); #endif name = lws_token_to_string(token); if (!name) @@ -208,8 +211,9 @@ lws_add_http_header_content_length(struct lws *wsi, wsi->http.tx_content_length = content_length; wsi->http.tx_content_remain = content_length; - lwsl_info("%s: %s: tx_content_length/remain %llu\n", __func__, - lws_wsi_tag(wsi), (unsigned long long)content_length); + /* lwsl_notice("CONTENT-LENGTH: %s: content_length=%llu, stream_id=%llu\n", + lws_wsi_tag(wsi), (unsigned long long)content_length, + (unsigned long long)(wsi->quic.qs ? wsi->quic.qs->stream_id : 0)); */ return 0; } @@ -534,6 +538,37 @@ lws_add_http_header_status(struct lws *wsi, unsigned int _code, "includeSubDomains", 36, p, end)) return 1; +#if defined(LWS_ROLE_QUIC) + if (lws_wsi_is_h2(wsi) || !wsi->mux_substream) { + struct lws_vhost *vh = wsi->a.context->vhost_list; + int quic_listening = 0; + while (vh) { + if (vh->listen_port == wsi->a.vhost->listen_port) { + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d_tmp, vh->listen_wsi.head) { + struct lws *lw = lws_container_of(d, struct lws, listen_list); + if (lw->role_ops && lw->role_ops->name && + !strcmp(lw->role_ops->name, "quic")) { + quic_listening = 1; + break; + } + } lws_end_foreach_dll_safe(d, d_tmp); + } + if (quic_listening) + break; + vh = vh->vhost_next; + } + if (quic_listening) { + char buf[64]; + int n = lws_snprintf(buf, sizeof(buf), "h3=\":%d\"; ma=2592000", wsi->a.vhost->listen_port); + int ret; + ret = lws_add_http_header_by_name(wsi, (unsigned char *)"alt-svc:", + (unsigned char *)buf, n, p, end); + if (ret) + return 1; + } + } +#endif + if (*p >= (end - 2)) { lwsl_err("%s: reached end of buffer\n", __func__); @@ -561,7 +596,7 @@ lws_return_http_status(struct lws *wsi, unsigned int code, return 1; } -#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) || defined(LWS_ROLE_H3) if (!wsi->handling_404 && wsi->a.vhost->http.error_document_404 && code == HTTP_STATUS_NOT_FOUND) @@ -662,6 +697,16 @@ lws_http_redirect(struct lws *wsi, int code, const unsigned char *loc, int len, unsigned char **p, unsigned char *end) { unsigned char *start = *p; + char *uri_ptr = NULL; + int uri_len = 0; + + if (lws_http_get_uri_and_method(wsi, &uri_ptr, &uri_len) < 0) { + uri_ptr = "unknown"; + uri_len = 7; + } + + // lwsl_notice("%s: code=%d, from='%.*s', loc='%.*s'\n", __func__, code, + // uri_len, uri_ptr, len, loc); if (lws_add_http_header_status(wsi, (unsigned int)code, p, end)) return -1; diff --git a/lib/roles/http/parsers.c b/lib/roles/http/parsers.c index 1d38bc12a2..18d10a0410 100644 --- a/lib/roles/http/parsers.c +++ b/lib/roles/http/parsers.c @@ -309,7 +309,6 @@ int __lws_header_table_detach(struct lws *wsi, int autoservice) if (!ah) return 0; - lwsl_info("%s: %s: ah %p (tsi=%d, count = %d)\n", __func__, lws_wsi_tag(wsi), (void *)ah, wsi->tsi, pt->http.ah_count_in_use); @@ -731,7 +730,14 @@ lws_hdr_simple_create(struct lws *wsi, enum lws_token_indexes h, const char *s) return -1; } - wsi->http.ah->frag_index[h] = wsi->http.ah->nfrag; + if (!wsi->http.ah->frag_index[h]) { + wsi->http.ah->frag_index[h] = wsi->http.ah->nfrag; + } else { + int n = wsi->http.ah->frag_index[h]; + while (wsi->http.ah->frags[n].nfrag) + n = wsi->http.ah->frags[n].nfrag; + wsi->http.ah->frags[n].nfrag = wsi->http.ah->nfrag; + } wsi->http.ah->frags[wsi->http.ah->nfrag].offset = wsi->http.ah->pos; wsi->http.ah->frags[wsi->http.ah->nfrag].len = 0; @@ -1550,8 +1556,7 @@ lws_http_cookie_get(struct lws *wsi, const char *name, char *buf, if ((unsigned int)n < bl + 1) continue; -#if defined(LWS_ROLE_H2) - if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_METHOD)) { + { int f = wsi->http.ah->frag_index[WSI_TOKEN_HTTP_COOKIE]; size_t fl; @@ -1579,32 +1584,6 @@ lws_http_cookie_get(struct lws *wsi, const char *name, char *buf, } f = wsi->http.ah->frags[f].nfrag; } - } else -#endif - { - p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COOKIE); - if (p) { - char *pe = p + n; - - while (p < pe) { - if (!memcmp(p, use_name, bl) && p[bl] == '=') { - /* it's a match... is it at the start or after a space / ;? */ - if (p == lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COOKIE) || - p[-1] == ' ' || p[-1] == ';') { - p += bl + 1; - while (p < pe && *p != ';' && max > 1) { - *buf++ = *p++; - max--; - } - *buf = '\0'; - *max_len = lws_ptr_diff_size_t(buf, bo); - - return 0; - } - } - p++; - } - } } } diff --git a/lib/roles/http/private-lib-roles-http.h b/lib/roles/http/private-lib-roles-http.h index 021a9ed426..958d8b6836 100644 --- a/lib/roles/http/private-lib-roles-http.h +++ b/lib/roles/http/private-lib-roles-http.h @@ -34,7 +34,7 @@ #include "private-lib-roles-http-compression.h" #endif -#define lwsi_role_http(wsi) (lwsi_role_h1(wsi) || lwsi_role_h2(wsi)) +#define lwsi_role_http(wsi) (lwsi_role_h1(wsi) || lwsi_role_h2(wsi) || lwsi_role_h3(wsi)) enum http_version { HTTP_VERSION_1_0, diff --git a/lib/roles/http/server/lejp-conf.c b/lib/roles/http/server/lejp-conf.c index 04cb0c6591..57e3b50883 100644 --- a/lib/roles/http/server/lejp-conf.c +++ b/lib/roles/http/server/lejp-conf.c @@ -509,6 +509,9 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) a->info->user = a->user; #if defined(LWS_WITH_TLS) #if defined(LWS_WITH_CLIENT) +#if defined(LWS_WITH_GNUTLS) + a->info->client_ssl_cipher_list = "NORMAL"; +#else a->info->client_ssl_cipher_list = "ECDHE-ECDSA-AES256-GCM-SHA384:" "ECDHE-RSA-AES256-GCM-SHA384:" "DHE-RSA-AES256-GCM-SHA384:" @@ -523,7 +526,11 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) "!AES256-GCM-SHA384:" "!AES256-SHA256"; #endif +#endif #if defined(LWS_WITH_SERVER) +#if defined(LWS_WITH_GNUTLS) + a->info->ssl_cipher_list = "NORMAL"; +#else a->info->ssl_cipher_list = "ECDHE-ECDSA-AES256-GCM-SHA384:" "ECDHE-RSA-AES256-GCM-SHA384:" "DHE-RSA-AES256-GCM-SHA384:" @@ -532,13 +539,14 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) "!DES:!MD5:!PSK:!RC4:!HMAC_SHA1:" "!SHA1:!DHE-RSA-AES128-GCM-SHA256:" "!DHE-RSA-AES128-SHA256:" - "!ECDHE-RSA-AES128-GCM-SHA256:" + "!ECDHE-RSA-AES128-GCM-SHA256:" "!AES128-GCM-SHA256:" "!AES128-SHA256:" "!DHE-RSA-AES256-SHA256:" "!AES256-GCM-SHA384:" - "!AES256-SHA256:" - "!CAMELLIA128:!CAMELLIA256"; + "!AES256-SHA256:" + "!CAMELLIA128:!CAMELLIA256"; +#endif #endif #endif a->info->keepalive_timeout = 5; diff --git a/lib/roles/http/server/server.c b/lib/roles/http/server/server.c index e3ee3d3549..ff98bf5841 100644 --- a/lib/roles/http/server/server.c +++ b/lib/roles/http/server/server.c @@ -81,6 +81,23 @@ check_extant(struct lws_dll2 *d, void *user) return 1; } +#if defined(LWS_ROLE_QUIC) +static int +check_extant_quic(struct lws_dll2 *d, void *user) +{ + struct lws *wsi = lws_container_of(d, struct lws, listen_list); + struct vh_sock_args *a = (struct vh_sock_args *)user; + + if (!lws_vhost_compare_listen(wsi->a.vhost, a->vhost)) + return 0; + + if (strcmp(wsi->role_ops->name, "quic")) + return 0; + + return 1; +} +#endif + /* * Creates a single listen socket of a specific AF */ @@ -94,7 +111,7 @@ _lws_vhost_init_server_af(struct vh_sock_args *a) lws_sockfd_type sockfd; struct lws *wsi; int m = 0, is = 0; -#if defined(LWS_WITH_IPV6) +#if !defined(LWS_PLAT_FREERTOS) && defined(LWS_WITH_IPV6) && defined(IPV6_V6ONLY) int value = 1; #endif @@ -549,14 +566,47 @@ _lws_vhost_init_server(const struct lws_context_creation_info *info, } if (LWS_IPV6_ENABLED(vhost)) { a.af = AF_INET6; - goto single; + n = _lws_vhost_init_server_af(&a); + if (n) + return n; } #endif - return 0; + goto check_quic; single: - return _lws_vhost_init_server_af(&a); + n = _lws_vhost_init_server_af(&a); + if (n) + return n; + +check_quic: +#if defined(LWS_ROLE_QUIC) + if (!vhost->context->lws_stub && + LWS_SSL_ENABLED(vhost) && + !lws_vhost_foreach_listen_wsi(vhost->context, &a, check_extant_quic)) { + +#if defined(LWS_WITH_IPV6) + if (LWS_IPV6_ENABLED(vhost)) { + const char *ads6 = vhost->iface ? vhost->iface : "::"; + if (!lws_create_adopt_udp(vhost, ads6, vhost->listen_port, + LWS_CAUDP_BIND, "quic", vhost->iface, NULL, + NULL, NULL, "quic_listen")) { + lwsl_vhost_err(vhost, "Failed to bind QUIC IPv6 UDP listener"); + } + } +#endif + if (!vhost->iface || !LWS_IPV6_ENABLED(vhost) || + (vhost->options & LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE)) { + const char *ads4 = vhost->iface ? vhost->iface : "0.0.0.0"; + if (!lws_create_adopt_udp(vhost, ads4, vhost->listen_port, + LWS_CAUDP_BIND, "quic", vhost->iface, NULL, + NULL, NULL, "quic_listen")) { + lwsl_vhost_err(vhost, "Failed to bind QUIC IPv4 UDP listener"); + } + } + } +#endif + return 0; } #endif @@ -743,10 +793,17 @@ lws_http_serve(struct lws *wsi, char *uri, const char *origin, if (!wsi->a.vhost) return -1; -#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) - if (wsi->a.vhost->http.error_document_404 && - !strcmp(uri, wsi->a.vhost->http.error_document_404)) - wsi->handling_404 = 1; +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) || defined(LWS_ROLE_H3) + if (wsi->a.vhost->http.error_document_404) { + const char *e = wsi->a.vhost->http.error_document_404; + if (*e == '/') + e++; + const char *u = uri; + if (*u == '/') + u++; + if (!strcmp(u, e)) + wsi->handling_404 = 1; + } #endif lws_snprintf(path, sizeof(path) - 1, "%s/%s", origin, uri); @@ -998,60 +1055,83 @@ lws_http_serve(struct lws *wsi, char *uri, const char *origin, } #endif -#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) || defined(LWS_ROLE_H3) LWS_VISIBLE const struct lws_http_mount * lws_find_mount(struct lws *wsi, const char *uri_ptr, int uri_len) { const struct lws_http_mount *hm, *hit = NULL; int best = 0; + lwsl_debug("H3_TRACE: lws_find_mount: wsi %p, role %s, uri '%.*s' (len %d), mux_substream %d\n", + wsi, wsi->role_ops ? wsi->role_ops->name : "none", + uri_len, uri_ptr ? uri_ptr : "NULL", uri_len, wsi->mux_substream); + hm = wsi->a.vhost->http.mount_list; while (hm) { - if (uri_len >= hm->mountpoint_len && - !strncmp(uri_ptr, hm->mountpoint, hm->mountpoint_len) && - (hm->exact_match ? - (uri_len == hm->mountpoint_len) : - (uri_ptr[hm->mountpoint_len] == '\0' || - uri_ptr[hm->mountpoint_len] == '/' || - hm->mountpoint_len == 1)) - ) { + int cond1 = uri_len >= hm->mountpoint_len; + int cond2 = cond1 && !strncmp(uri_ptr, hm->mountpoint, hm->mountpoint_len); + int cond3 = cond2 && (hm->exact_match ? + (uri_len == hm->mountpoint_len) : + (uri_ptr[hm->mountpoint_len] == '\0' || + uri_ptr[hm->mountpoint_len] == '/' || + hm->mountpoint_len == 1)); + + lwsl_debug(" mount check: mnt '%s' (len %d), exact %d, origin '%s', protocol %d -> match_conds: %d %d %d\n", + hm->mountpoint, hm->mountpoint_len, hm->exact_match, + hm->origin ? hm->origin : "NULL", hm->origin_protocol, + cond1, cond2, cond3); + + if (cond3) { #if defined(LWS_WITH_SYS_METRICS) lws_metrics_tag_wsi_add(wsi, "mnt", hm->mountpoint); #endif if (hm->no_ws_upgrades && lws_hdr_total_length(wsi, WSI_TOKEN_UPGRADE)) { + lwsl_notice(" skip due to no_ws_upgrades\n"); hm = hm->mount_next; continue; } - if (hm->origin_protocol == LWSMPRO_NO_MOUNT) + if (hm->origin_protocol == LWSMPRO_NO_MOUNT) { + lwsl_notice(" origin LWSMPRO_NO_MOUNT -> NULL\n"); return NULL; + } - if (hm->origin_protocol == LWSMPRO_CALLBACK || - ((hm->origin_protocol == LWSMPRO_CGI || - lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI) || - lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI) || + int has_token = (hm->origin_protocol == LWSMPRO_CALLBACK || + hm->origin_protocol == LWSMPRO_CGI || + lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI) || + lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI) || #if defined(LWS_WITH_HTTP_UNCOMMON_HEADERS) - lws_hdr_total_length(wsi, WSI_TOKEN_PUT_URI) || - lws_hdr_total_length(wsi, WSI_TOKEN_PATCH_URI) || - lws_hdr_total_length(wsi, WSI_TOKEN_DELETE_URI) || + lws_hdr_total_length(wsi, WSI_TOKEN_PUT_URI) || + lws_hdr_total_length(wsi, WSI_TOKEN_PATCH_URI) || + lws_hdr_total_length(wsi, WSI_TOKEN_DELETE_URI) || #endif - lws_hdr_total_length(wsi, WSI_TOKEN_HEAD_URI) || -#if defined(LWS_ROLE_H2) - (wsi->mux_substream && - lws_hdr_total_length(wsi, - WSI_TOKEN_HTTP_COLON_PATH)) || + lws_hdr_total_length(wsi, WSI_TOKEN_HEAD_URI) || +#if defined(LWS_ROLE_H2) || defined(LWS_ROLE_H3) + (wsi->mux_substream && + lws_hdr_total_length(wsi, + WSI_TOKEN_HTTP_COLON_PATH)) || #endif - hm->protocol) && - hm->mountpoint_len > best)) { + hm->protocol); + + int is_better = hm->mountpoint_len > best || + (hm->mountpoint_len == best && + hm->exact_match && (!hit || !hit->exact_match)); + + lwsl_debug(" has_token: %d, is_better: %d (best was %d)\n", + has_token, is_better, best); + + if (has_token && is_better) { best = hm->mountpoint_len; hit = hm; + lwsl_debug(" => selected mount %s\n", hit->mountpoint); } } hm = hm->mount_next; } + lwsl_debug(" lws_find_mount result: %s\n", hit ? hit->mountpoint : "NULL"); return hit; } #endif @@ -1180,7 +1260,7 @@ static const unsigned char methods[] = { #endif WSI_TOKEN_CONNECT, WSI_TOKEN_HEAD_URI, -#ifdef LWS_WITH_HTTP2 +#if defined(LWS_WITH_HTTP2) || defined(LWS_ROLE_H3) WSI_TOKEN_HTTP_COLON_PATH, #endif }; @@ -1199,8 +1279,8 @@ lws_http_get_uri_and_method(struct lws *wsi, char **puri_ptr, int *puri_len) } if (count != 1 && - !((wsi->mux_substream || wsi->h2_stream_carries_ws) -#if defined(LWS_ROLE_H2) + !((wsi->mux_substream || wsi->h23_stream_carries_ws) +#if defined(LWS_ROLE_H2) || defined(LWS_ROLE_H3) && lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_PATH) #endif @@ -1611,6 +1691,10 @@ lws_http_redirect_hit(struct lws_context_per_thread *pt, struct lws *wsi, *h = 0; s = uri_ptr + hit->mountpoint_len; + lwsl_debug("%s: hit %p, mountpoint '%s', origin '%s', protocol %d, s '%s'\n", + __func__, hit, hit->mountpoint, hit->origin ? hit->origin : "NULL", + hit->origin_protocol, s); + /* * if we have a mountpoint like https://xxx.com/yyy * there is an implied / at the end for our purposes since @@ -1634,13 +1718,17 @@ lws_http_redirect_hit(struct lws_context_per_thread *pt, struct lws *wsi, hit->origin_protocol == LWSMPRO_REDIR_HTTPS)) && (hit->origin_protocol != LWSMPRO_CGI && hit->origin_protocol != LWSMPRO_CALLBACK)) { + char peer_buf[64]; unsigned char *start = pt->serv_buf + LWS_PRE, *p = start, *end = p + wsi->a.context->pt_serv_buf_size - LWS_PRE - 512; *h = 1; - lwsl_info("Doing 301 '%s' org %s\n", s, hit->origin); + lws_get_peer_simple(wsi, peer_buf, sizeof(peer_buf)); + + // lwsl_notice("Doing 301 '%s' (vhost port %d, peer %s) org %s\n", s, + // wsi->a.vhost->listen_port, peer_buf, hit->origin); /* > at start indicates deal with by redirect */ if (hit->origin_protocol == LWSMPRO_REDIR_HTTP || @@ -1655,12 +1743,12 @@ lws_http_redirect_hit(struct lws_context_per_thread *pt, struct lws *wsi, hit->origin); } else { if (!lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) { -#if defined(LWS_ROLE_H2) +#if defined(LWS_ROLE_H2) || defined(LWS_ROLE_H3) if (!lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_AUTHORITY)) #endif goto bail_nuke_ah; -#if defined(LWS_ROLE_H2) +#if defined(LWS_ROLE_H2) || defined(LWS_ROLE_H3) n = lws_snprintf((char *)end, 256, "%s%s%s/", oprot[!!lws_is_ssl(wsi)], lws_hdr_simple_ptr(wsi, @@ -1674,6 +1762,9 @@ lws_http_redirect_hit(struct lws_context_per_thread *pt, struct lws *wsi, uri_ptr); } + // lwsl_notice("%s: redirecting to '%s' (vhost port %d, peer %s)\n", + // __func__, (char *)end, wsi->a.vhost->listen_port, peer_buf); + lws_clean_url((char *)end); n = lws_http_redirect(wsi, HTTP_STATUS_MOVED_PERMANENTLY, end, n, &p, end); @@ -1735,16 +1826,26 @@ lws_http_action(struct lws *wsi) enum http_conn_type conn_type; char content_length_str[32]; char http_version_str[12]; - char http_conn_str[25]; + char http_conn_str[20]; char *uri_ptr = NULL; #if defined(LWS_WITH_FILE_OPS) char *s; #endif unsigned int n; + lwsl_debug("H3_TRACE: lws_http_action entered for wsi %p. vhost=%s, mux_substream=%d\n", + wsi, wsi->a.vhost ? wsi->a.vhost->name : "none", wsi->mux_substream); + meth = lws_http_get_uri_and_method(wsi, &uri_ptr, &uri_len); - if (meth < 0 || meth >= (int)LWS_ARRAY_SIZE(method_names)) + if (meth < 0) { + lwsl_debug("H3_TRACE: lws_http_action for wsi %p aborting because get_uri_and_method returned < 0\n", wsi); goto bail_nuke_ah; + } + + if (meth >= (int)LWS_ARRAY_SIZE(method_names)) { + lwsl_err("LWS_HTTP_ACTION_START: bailing due to invalid meth %d\n", meth); + goto bail_nuke_ah; + } lws_metrics_tag_wsi_add(wsi, "vh", wsi->a.vhost->name); lws_metrics_tag_wsi_add(wsi, "meth", method_names[meth]); @@ -1752,13 +1853,14 @@ lws_http_action(struct lws *wsi) /* we insist on absolute paths */ if (!uri_ptr || uri_ptr[0] != '/') { + lwsl_err("LWS_HTTP_ACTION_START: bailing due to missing or non-absolute uri_ptr\n"); lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL); goto bail_nuke_ah; } - lwsl_info("Method: '%s' (%d), request for '%s'\n", method_names[meth], - meth, uri_ptr); + lwsl_debug("Method: '%s' (%d), request for '%s' (vhost '%s')\n", method_names[meth], + meth, uri_ptr, wsi->a.vhost->name); if (wsi->role_ops && lws_rops_fidx(wsi->role_ops, LWS_ROPS_check_upgrades)) @@ -1929,6 +2031,8 @@ lws_http_action(struct lws *wsi) wsi->http.mount_specific_keepalive_timeout_secs = 0; hit = lws_find_mount(wsi, uri_ptr, uri_len); + lwsl_debug("H3_TRACE: lws_find_mount returned %p (%s) for wsi %p\n", + hit, hit ? hit->mountpoint : "none", wsi); if (!hit) { /* deferred cleanup and reset to protocols[0] */ @@ -2830,7 +2934,7 @@ lws_http_transaction_completed(struct lws *wsi) #ifdef LWS_WITH_ACCESS_LOG wsi->http.access_log.sent = 0; #endif -#if defined(LWS_WITH_FILE_OPS) && (defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)) +#if defined(LWS_WITH_FILE_OPS) && (defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) || defined(LWS_ROLE_H3)) if (lwsi_role_http(wsi) && lwsi_role_server(wsi) && wsi->http.fop_fd != NULL) lws_vfs_file_close(&wsi->http.fop_fd); @@ -2913,6 +3017,22 @@ lws_http_transaction_completed(struct lws *wsi) } #if defined(LWS_WITH_FILE_OPS) +static int +lws_is_ascii_headers(const char *buf, int len) +{ + int i; + + for (i = 0; i < len; i++) { + unsigned char c = (unsigned char)buf[i]; + if (c < 0x20 && c != '\r' && c != '\n' && c != '\t') + return 0; + if (c >= 0x7f) + return 0; + } + + return 1; +} + int lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, const char *other_headers, int other_headers_len) @@ -2991,6 +3111,19 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, #endif total_content_length = wsi->http.filelen; +#if 0 +#if defined(LWS_ROLE_QUIC) + { + uint64_t sid = wsi->quic.qs ? wsi->quic.qs->stream_id : 0; + lwsl_notice("SERVE FILE: file='%s', filelen=%llu, mux_substream=%d, stream_id=%llu\n", + file, (unsigned long long)wsi->http.filelen, wsi->mux_substream, (unsigned long long)sid); + } +#else + lwsl_notice("SERVE FILE: file='%s', filelen=%llu, mux_substream=%d, stream_id=0\n", + file, (unsigned long long)wsi->http.filelen, wsi->mux_substream); +#endif +#endif + #if defined(LWS_WITH_RANGES) ranges = lws_ranges_init(wsi, rp, wsi->http.filelen); @@ -3192,21 +3325,97 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, } } - /* Only add cache control if its not specified by any other_headers. */ - if (!other_headers || - (!(char *)strstr(other_headers, "cache-control") && - !(char *)strstr(other_headers, "Cache-Control"))) { - if (lws_add_http_header_by_token(wsi, - WSI_TOKEN_HTTP_CACHE_CONTROL, - (unsigned char *)cc, cclen, &p, end)) - goto bail; + { + int has_cache_control = 0; + if (other_headers && other_headers_len > 0 && + lws_is_ascii_headers(other_headers, other_headers_len)) { + if (strstr(other_headers, "cache-control") || + strstr(other_headers, "Cache-Control")) + has_cache_control = 1; + } + + /* Only add cache control if its not specified by any other_headers. */ + if (!has_cache_control) { + if (lws_add_http_header_by_token(wsi, + WSI_TOKEN_HTTP_CACHE_CONTROL, + (unsigned char *)cc, cclen, &p, end)) + goto bail; + } } - if (other_headers) { - if ((end - p) < other_headers_len) - goto bail; - memcpy(p, other_headers, (unsigned int)other_headers_len); - p += other_headers_len; + if (other_headers && other_headers_len > 0) { +#if defined(LWS_ROLE_H3) || defined(LWS_WITH_HTTP2) + if (( +#if defined(LWS_ROLE_H3) + lws_wsi_is_h3(wsi) || +#endif +#if defined(LWS_WITH_HTTP2) + lws_wsi_is_h2(wsi) || +#endif + 0) && lws_is_ascii_headers(other_headers, other_headers_len)) { + const char *lh = other_headers; + const char *lh_end = other_headers + other_headers_len; + + while (lh < lh_end) { + const char *line_end = lh; + while (line_end < lh_end && *line_end != '\n' && *line_end != '\r') + line_end++; + + if (line_end > lh) { + const char *colon = lh; + while (colon < line_end && *colon != ':') + colon++; + + if (colon < line_end) { + const char *name = lh; + int name_len = (int)(colon - name); + const char *val = colon + 1; + + while (val < line_end && (*val == ' ' || *val == '\t')) + val++; + + while (name_len > 0 && (name[name_len - 1] == ' ' || name[name_len - 1] == '\t')) + name_len--; + + int val_len = (int)(line_end - val); + while (val_len > 0 && (val[val_len - 1] == ' ' || val[val_len - 1] == '\t')) + val_len--; + + if (name_len > 0) { + if (name[0] == ':') { + /* Skip pseudo-headers */ + lh = line_end; + while (lh < lh_end && (*lh == '\r' || *lh == '\n')) + lh++; + continue; + } + char name_buf[128]; + if (name_len < (int)sizeof(name_buf)) { + memcpy(name_buf, name, (size_t)name_len); + name_buf[name_len] = '\0'; + + if (lws_add_http_header_by_name(wsi, + (const unsigned char *)name_buf, + (const unsigned char *)val, + val_len, &p, end)) + goto bail; + } + } + } + } + + lh = line_end; + while (lh < lh_end && (*lh == '\r' || *lh == '\n')) + lh++; + } + } else +#endif + { + if ((end - p) < other_headers_len) + goto bail; + memcpy(p, other_headers, (unsigned int)other_headers_len); + p += other_headers_len; + } } if (lws_finalize_http_header(wsi, &p, end)) @@ -3259,6 +3468,7 @@ int lws_serve_http_file_fragment(struct lws *wsi) #endif int n, m; + // lwsl_wsi_notice(wsi, "entry, state=%d", lwsi_state(wsi)); lwsl_debug("wsi->mux_substream %d\n", wsi->mux_substream); do { @@ -3520,11 +3730,15 @@ int lws_serve_http_file_fragment(struct lws *wsi) if (m < 0) goto file_had_it; - wsi->http.filepos += amount; + lws_filepos_t sent_amount = 0; + if (m >= (n - (int)amount)) + sent_amount = (lws_filepos_t)(m - (n - (int)amount)); + + wsi->http.filepos += sent_amount; #if defined(LWS_WITH_RANGES) if (wsi->http.range.count_ranges >= 1) { - wsi->http.range.budget -= amount; + wsi->http.range.budget -= sent_amount; if (wsi->http.range.budget == 0) { lwsl_notice("range budget exhausted\n"); wsi->http.range.inside = 0; diff --git a/lib/roles/private-lib-roles.h b/lib/roles/private-lib-roles.h index 5012f5d1da..ecdbf8c837 100644 --- a/lib/roles/private-lib-roles.h +++ b/lib/roles/private-lib-roles.h @@ -403,6 +403,12 @@ extern const struct lws_role_ops role_ops_raw_skt, role_ops_raw_file, #define lwsi_role_h3(wsi) (0) #endif +#if defined(LWS_ROLE_WT) + #include "wt/private-lib-roles-wt.h" +#else + #define lwsi_role_wt(wsi) (0) +#endif + #if defined(LWS_ROLE_QUIC) #include "roles/quic/private-lib-roles-quic.h" #else diff --git a/lib/roles/quic/CMakeLists.txt b/lib/roles/quic/CMakeLists.txt index b17b27d6fd..5a8d384072 100644 --- a/lib/roles/quic/CMakeLists.txt +++ b/lib/roles/quic/CMakeLists.txt @@ -34,6 +34,7 @@ include_directories(.) list(APPEND SOURCES roles/quic/ops-quic.c roles/quic/ops-quic-cc-newreno.c + roles/quic/ops-quic-cc-cubic.c roles/quic/crypto-quic.c roles/quic/parse-quic.c ) diff --git a/lib/roles/quic/crypto-quic.c b/lib/roles/quic/crypto-quic.c index f06d063021..debfd712be 100644 --- a/lib/roles/quic/crypto-quic.c +++ b/lib/roles/quic/crypto-quic.c @@ -31,6 +31,13 @@ static const uint8_t quic_v1_initial_salt[20] = { 0xcc, 0xbb, 0x7f, 0x0a }; +static const uint8_t quic_v2_initial_salt[32] = { + 0x0c, 0x20, 0x34, 0xeb, 0x2d, 0xcf, 0xeb, 0x4a, + 0x14, 0x4e, 0x1b, 0x82, 0xf0, 0x93, 0x14, 0x06, + 0xcb, 0xb9, 0x2e, 0xd3, 0x5b, 0x3e, 0x64, 0xf2, + 0x71, 0xbc, 0x60, 0x7e, 0xb3, 0x26, 0x89, 0xc1 +}; + static int lws_quic_derive_key_iv_hp(uint8_t *secret, size_t secret_len, uint8_t cipher_type, uint8_t *iv, size_t iv_len, @@ -77,16 +84,26 @@ lws_quic_derive_initial_keys(struct lws *wsi, const struct lws_quic_cid *dcid) struct lws_quic_keys *k; int ret = -1; + const uint8_t *salt; + size_t salt_len; + if (!qn) return -1; - k = lws_zalloc(sizeof(*k), "quic_keys_initial"); if (!k) return -1; + if (wsi->quic.qn->version == LWS_QUIC_VERSION_2) { + salt = quic_v2_initial_salt; + salt_len = sizeof(quic_v2_initial_salt); + } else { + salt = quic_v1_initial_salt; + salt_len = sizeof(quic_v1_initial_salt); + } + /* 1. Extract Initial Secret from DCID and Fixed Salt */ - if (lws_genhkdf_extract(LWS_GENHMAC_TYPE_SHA256, quic_v1_initial_salt, - sizeof(quic_v1_initial_salt), dcid->id, dcid->len, + if (lws_genhkdf_extract(LWS_GENHMAC_TYPE_SHA256, salt, + salt_len, dcid->id, dcid->len, initial_secret)) goto bail; @@ -137,6 +154,41 @@ lws_quic_derive_initial_keys(struct lws *wsi, const struct lws_quic_cid *dcid) return ret; } +int +lws_quic_initiate_key_update(struct lws *wsi) +{ + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + struct lws_quic_netconn *qn; + + if (!nwsi || !nwsi->quic.qn) + return -1; + + qn = nwsi->quic.qn; + + /* We only update keys if the handshake is done and we have APP keys */ + if (!qn->handshake_done || !qn->keys[LWS_QUIC_LEVEL_APP]) + return -1; + + /* If an update is already pending, wait for it to be echoed/completed */ + if (qn->key_update_pending) + return -1; + + /* Derive the new TX keys */ + if (lws_quic_update_keys(qn->keys[LWS_QUIC_LEVEL_APP], 0)) + return -1; + + /* Flip the TX key phase bit */ + qn->tx_key_phase ^= 1; + + /* Mark that we initiated it, so we expect the peer to echo it back in RX */ + qn->key_update_pending = 1; + + /* Reset the packet counter for AEAD limits */ + qn->tx_packets_since_update = 0; + + return 0; +} + int lws_quic_set_keys(struct lws *wsi, enum lws_tls_quic_secret_type type, const uint8_t *secret, size_t secret_len) { @@ -148,11 +200,20 @@ lws_quic_set_keys(struct lws *wsi, enum lws_tls_quic_secret_type type, const uin if (!qn) return -1; + if (qn->handshake_done && (type == LWS_TLS_QUIC_SECRET_CLIENT_APPLICATION || type == LWS_TLS_QUIC_SECRET_SERVER_APPLICATION)) { + lwsl_info("lws_quic_set_keys: ignoring post-handshake application secret update\n"); + return 0; + } + + + + /* Determine level and direction */ switch (type) { case LWS_TLS_QUIC_SECRET_CLIENT_EARLY: - /* 0-RTT not fully supported yet */ - return 0; + level = LWS_QUIC_LEVEL_EARLY; + is_rx = qn->is_server ? 1 : 0; + break; case LWS_TLS_QUIC_SECRET_CLIENT_HANDSHAKE: level = LWS_QUIC_LEVEL_HANDSHAKE; is_rx = qn->is_server ? 1 : 0; @@ -201,30 +262,74 @@ lws_quic_set_keys(struct lws *wsi, enum lws_tls_quic_secret_type type, const uin /* For simplicity, we just mark valid and rely on tx/rx logic */ k->valid = 1; + /* If this is the client deriving the early secret, check if the stream opts in */ + if (type == LWS_TLS_QUIC_SECRET_CLIENT_EARLY && !qn->is_server) { + qn->early_data_status = LWS_0RTT_STATUS_ATTEMPTED; + + /* The initial stream is currently also the network wsi, because ALPN + * hasn't migrated it yet. If it returns 1, it opts into 0-RTT. */ + if (wsi->a.protocol && wsi->a.protocol->callback) { + int ret = wsi->a.protocol->callback(wsi, + LWS_CALLBACK_CLIENT_ESTABLISHED_EARLY, + wsi->user_space, NULL, 0); + if (ret == 1) { + lwsl_wsi_notice(wsi, "Stream %s opted into 0-RTT", lws_wsi_tag(wsi)); + if (wsi->quic.qs) + wsi->quic.qs->opted_into_early_data = 1; + lws_callback_on_writable(wsi); + } else { + lwsl_wsi_notice(wsi, "Stream %s ignored 0-RTT", lws_wsi_tag(wsi)); + } + } + } else if (type == LWS_TLS_QUIC_SECRET_CLIENT_EARLY && qn->is_server) { + qn->early_data_status = LWS_0RTT_STATUS_ACCEPTED; + } + return 0; } -int -lws_quic_update_keys(struct lws *wsi, int is_rx) +void +lws_quic_keys_destroy(struct lws_quic_keys *keys) { - struct lws_quic_netconn *qn = wsi->quic.qn; - if (!qn || !qn->keys[LWS_QUIC_LEVEL_APP]) return -1; + if (!keys) return; + +#if defined(LWS_WITH_GNUTLS) + if (keys->aead_rx) + gnutls_aead_cipher_deinit((gnutls_aead_cipher_hd_t)keys->aead_rx); + if (keys->aead_tx) + gnutls_aead_cipher_deinit((gnutls_aead_cipher_hd_t)keys->aead_tx); +#endif + + lws_free(keys); +} - struct lws_quic_keys *k = qn->keys[LWS_QUIC_LEVEL_APP]; +int +lws_quic_update_keys(struct lws_quic_keys *k, int is_rx) +{ uint8_t new_secret[48]; + size_t key_len = (k->cipher_type == 0) ? 16 : 32; + if (is_rx) { enum lws_genhmac_types hash_type = (k->secret_len == 48) ? LWS_GENHMAC_TYPE_SHA384 : LWS_GENHMAC_TYPE_SHA256; if (lws_genhkdf_expand_label(hash_type, k->secret_rx, k->secret_len, "quic ku", NULL, 0, new_secret, k->secret_len)) return -1; memcpy(k->secret_rx, new_secret, k->secret_len); - if (lws_quic_derive_key_iv_hp(new_secret, k->secret_len, k->cipher_type, k->iv_rx, sizeof(k->iv_rx), - &k->el_aead_rx, k->key_aead_rx, &k->el_hp_rx, k->key_hp_rx)) return -1; + + if (lws_genhkdf_expand_label(hash_type, new_secret, k->secret_len, "quic key", NULL, 0, k->key_aead_rx, key_len)) return -1; + if (lws_genhkdf_expand_label(hash_type, new_secret, k->secret_len, "quic iv", NULL, 0, k->iv_rx, 12)) return -1; + + k->el_aead_rx.buf = k->key_aead_rx; + k->el_aead_rx.len = (uint32_t)key_len; } else { enum lws_genhmac_types hash_type = (k->secret_len == 48) ? LWS_GENHMAC_TYPE_SHA384 : LWS_GENHMAC_TYPE_SHA256; if (lws_genhkdf_expand_label(hash_type, k->secret_tx, k->secret_len, "quic ku", NULL, 0, new_secret, k->secret_len)) return -1; memcpy(k->secret_tx, new_secret, k->secret_len); - if (lws_quic_derive_key_iv_hp(new_secret, k->secret_len, k->cipher_type, k->iv_tx, sizeof(k->iv_tx), - &k->el_aead_tx, k->key_aead_tx, &k->el_hp_tx, k->key_hp_tx)) return -1; + + if (lws_genhkdf_expand_label(hash_type, new_secret, k->secret_len, "quic key", NULL, 0, k->key_aead_tx, key_len)) return -1; + if (lws_genhkdf_expand_label(hash_type, new_secret, k->secret_len, "quic iv", NULL, 0, k->iv_tx, 12)) return -1; + + k->el_aead_tx.buf = k->key_aead_tx; + k->el_aead_tx.len = (uint32_t)key_len; } lws_explicit_bzero(new_secret, sizeof(new_secret)); @@ -312,25 +417,30 @@ lws_quic_decrypt_payload(struct lws_quic_keys *keys, uint8_t *packet, size_t pac #endif if (keys->cipher_type == 0 || keys->cipher_type == 2) { #if defined(LWS_WITH_GNUTLS) - gnutls_aead_cipher_hd_t hd; - gnutls_datum_t key; - key.data = keys->el_aead_rx.buf; - key.size = keys->el_aead_rx.len; - gnutls_cipher_algorithm_t alg = (keys->cipher_type == 2) ? GNUTLS_CIPHER_AES_256_GCM : GNUTLS_CIPHER_AES_128_GCM; - - if (gnutls_aead_cipher_init(&hd, alg, &key) < 0) - return -1; - - size_t ct_len = payload_len + 16; - size_t pt_len = payload_len; - if (gnutls_aead_cipher_decrypt(hd, nonce, 12, packet, payload_offset, - 16, &packet[payload_offset], ct_len, - &packet[payload_offset], &pt_len) < 0) { - lwsl_err("DECRYPT GCM tag check failed via gnutls_aead_cipher_decrypt\n"); - gnutls_aead_cipher_deinit(hd); - return -1; - } - gnutls_aead_cipher_deinit(hd); + gnutls_aead_cipher_hd_t hd; + gnutls_datum_t key; + key.data = keys->el_aead_rx.buf; + key.size = keys->el_aead_rx.len; + gnutls_cipher_algorithm_t alg = (keys->cipher_type == 2) ? GNUTLS_CIPHER_AES_256_GCM : GNUTLS_CIPHER_AES_128_GCM; + + if (!keys->aead_rx) { + if (gnutls_aead_cipher_init((gnutls_aead_cipher_hd_t *)&keys->aead_rx, alg, &key) < 0) + return -1; + } + hd = (gnutls_aead_cipher_hd_t)keys->aead_rx; + + size_t ct_len = payload_len + 16; + size_t pt_len = payload_len; + uint8_t tmp[2048]; + if (ct_len > sizeof(tmp)) + return -1; + memcpy(tmp, &packet[payload_offset], ct_len); + if (gnutls_aead_cipher_decrypt(hd, nonce, 12, packet, payload_offset, + 16, tmp, ct_len, + &packet[payload_offset], &pt_len) < 0) { + lwsl_err("DECRYPT GCM tag check failed via gnutls_aead_cipher_decrypt\n"); + return -1; + } #else struct lws_genaes_ctx aead; if (lws_genaes_create(&aead, LWS_GAESO_DEC, LWS_GAESM_GCM, &keys->el_aead_rx, LWS_GAESP_NO_PADDING, NULL)) @@ -395,28 +505,40 @@ lws_quic_encrypt_payload(struct lws_quic_keys *keys, uint8_t *packet, size_t pac #endif if (keys->cipher_type == 0 || keys->cipher_type == 2) { #if defined(LWS_WITH_GNUTLS) - gnutls_aead_cipher_hd_t hd; - gnutls_datum_t key; - key.data = keys->el_aead_tx.buf; - key.size = keys->el_aead_tx.len; - gnutls_cipher_algorithm_t alg = (keys->cipher_type == 2) ? GNUTLS_CIPHER_AES_256_GCM : GNUTLS_CIPHER_AES_128_GCM; - - if (gnutls_aead_cipher_init(&hd, alg, &key) < 0) - return -1; - - size_t ct_len = payload_len + 16; - if (gnutls_aead_cipher_encrypt(hd, nonce, 12, packet, payload_offset, - 16, &packet[payload_offset], payload_len, - &packet[payload_offset], &ct_len) < 0) { - lwsl_err("ENCRYPT GCM failed via gnutls_aead_cipher_encrypt\n"); - gnutls_aead_cipher_deinit(hd); - return -1; - } - gnutls_aead_cipher_deinit(hd); - - /* GnuTLS automatically appends the tag to the end of the ciphertext output */ - /* We copy it to 'tag' just for the debug dump */ - memcpy(tag, &packet[payload_offset + payload_len], 16); + gnutls_aead_cipher_hd_t hd; + gnutls_datum_t key; + key.data = keys->el_aead_tx.buf; + key.size = keys->el_aead_tx.len; + gnutls_cipher_algorithm_t alg = (keys->cipher_type == 2) ? GNUTLS_CIPHER_AES_256_GCM : GNUTLS_CIPHER_AES_128_GCM; + + if (!keys->aead_tx) { + if (gnutls_aead_cipher_init((gnutls_aead_cipher_hd_t *)&keys->aead_tx, alg, &key) < 0) + return -1; + } + hd = (gnutls_aead_cipher_hd_t)keys->aead_tx; + + size_t ct_len = payload_len + 16; + uint8_t tmp[2048]; + if (payload_len > sizeof(tmp)) + return -1; + memcpy(tmp, &packet[payload_offset], payload_len); + lwsl_debug("GnuTLS AEAD TX: full_pn=%llu, pn_offset=%d, payload_offset=%d, payload_len=%d\n", + (unsigned long long)full_pn, (int)pn_offset, (int)payload_offset, (int)payload_len); + lwsl_hexdump_debug(packet, payload_offset); /* Print AAD */ + lwsl_hexdump_debug(keys->iv_tx, 12); /* Print IV */ + lwsl_hexdump_debug(nonce, 12); /* Print Nonce */ + if (gnutls_aead_cipher_encrypt(hd, nonce, 12, packet, payload_offset, + 16, tmp, payload_len, + &packet[payload_offset], &ct_len) < 0) { + lwsl_err("ENCRYPT GCM failed via gnutls_aead_cipher_encrypt\n"); + return -1; + } + + /* GnuTLS automatically appends the tag to the end of the ciphertext output */ + /* We copy it to 'tag' just for the debug dump */ + memcpy(tag, &packet[payload_offset + payload_len], 16); + lwsl_debug("GnuTLS AEAD TX TAG:\n"); + lwsl_hexdump_debug(tag, 16); #else struct lws_genaes_ctx aead; if (lws_genaes_create(&aead, LWS_GAESO_ENC, LWS_GAESM_GCM, &keys->el_aead_tx, LWS_GAESP_NO_PADDING, NULL)) { @@ -518,27 +640,191 @@ lws_quic_encrypt_payload(struct lws_quic_keys *keys, uint8_t *packet, size_t pac int lws_tls_quic_rx_crypto(struct lws *wsi, int level, const uint8_t *buf, size_t len) { - uint8_t out[4096]; - size_t out_len = sizeof(out); + uint8_t *out = lws_malloc(32768, "quic rx crypto"); + size_t out_len = 32768; int n; - /* - * Feed the RX CRYPTO payload into the TLS backend and simultaneously - * pull any generated outbound TLS handshake data (like the ServerHello). - */ + if (!out) + return -1; + + if (len > 0 && wsi->quic.qn) { + size_t i = 0; + while (i < len) { + if (wsi->quic.qn->crypto_rx_expected_msg_len[level] > 0) { + size_t consume = wsi->quic.qn->crypto_rx_expected_msg_len[level]; + if (consume > len - i) + consume = len - i; + wsi->quic.qn->crypto_rx_expected_msg_len[level] -= consume; + i += consume; + continue; + } + + /* We are at the start of a new Handshake message! */ + uint8_t type = buf[i]; + if (type == 24 || type == 5) { + lwsl_wsi_notice(wsi, "QUIC RX CRYPTO: Illegal TLS Handshake type %d", type); + lws_quic_enter_closing_state(wsi, 0x0100 + 10 /* unexpected_message */, 0, 0); + lws_free(out); + return -1; + } + + if (i + 3 >= len) { + /* Fragmented header - extremely rare in h3spec, but we'd need to handle it. + * For now, assume headers are not fragmented across chunks. */ + break; + } + + uint32_t msg_len = ((uint32_t)buf[i+1] << 16) | ((uint32_t)buf[i+2] << 8) | buf[i+3]; + wsi->quic.qn->crypto_rx_expected_msg_len[level] = msg_len; + i += 4; + } + } + n = lws_tls_quic_advance_handshake(wsi, level, buf, len, out, &out_len); + if (n < 0) { +#if defined(LWS_WITH_GNUTLS) + int alert_level = 0; + int alert = gnutls_error_to_alert(n, &alert_level); + if (alert >= 0) { + lwsl_wsi_notice(wsi, "GnuTLS error %d mapped to alert %d (level %d)", n, alert, alert_level); + lws_quic_enter_closing_state(wsi, 0x0100 + (uint64_t)alert, 0, 0); + } else { + int alert_got = wsi->tls.ssl ? (int)gnutls_alert_get((gnutls_session_t)wsi->tls.ssl) : 0; + if (alert_got > 0) { + lwsl_wsi_notice(wsi, "GnuTLS generated alert %d", alert_got); + lws_quic_enter_closing_state(wsi, 0x0100 + (uint64_t)alert_got, 0, 0); + } else { + lws_quic_enter_closing_state(wsi, 0x0100 + 10 /* unexpected_message fallback */, 0, 0); + } + } +#else + if (wsi->tls.quic_alert > 0) { + lwsl_wsi_notice(wsi, "OpenSSL/BoringSSL generated alert %d", wsi->tls.quic_alert); + lws_quic_enter_closing_state(wsi, 0x0100 + (uint64_t)wsi->tls.quic_alert, 0, 0); + } else { + lws_quic_enter_closing_state(wsi, 0x0100 + 10 /* unexpected_message fallback */, 0, 0); + } +#endif + lws_free(out); + return -1; + } + if (out_len > 0) { /* Pass the generated TX CRYPTO data back to the QUIC transport queues */ lws_tls_quic_tx_crypto_cb(wsi, level, out, out_len); } + + lwsl_wsi_notice(wsi, "lws_tls_quic_advance_handshake returned %d, tp_parsed=%d", n, wsi->quic.qn ? wsi->quic.qn->tp_parsed : -1); + + if (wsi->quic.qn && !wsi->quic.qn->tp_parsed) { +#if !defined(LWS_WITH_MBEDTLS) + const uint8_t *peer_tp = NULL; + size_t peer_tp_len = 0; + if (lws_tls_quic_get_transport_parameters(wsi, &peer_tp, &peer_tp_len) == 0 && peer_tp) { + lwsl_wsi_notice(wsi, "Got peer_tp, len %zu, parsing...", peer_tp_len); + wsi->quic.qn->tp_parsed = 1; + if (lws_quic_parse_transport_parameters(wsi, peer_tp, peer_tp_len) < 0) { + lwsl_wsi_err(wsi, "QUIC transport parameters validation failed"); + lws_quic_enter_closing_state(wsi, LWS_QUIC_ERR_TRANSPORT_PARAMETER_ERROR, 0, 0); + lws_free(out); + return -1; + } + } else { + lwsl_wsi_notice(wsi, "lws_tls_quic_get_transport_parameters returned non-zero or NULL"); + } +#else + /* MbedTLS 4.x has no custom extension API, so QUIC transport parameters are not supported natively yet */ + wsi->quic.qn->tp_parsed = 1; + wsi->quic.qn->peer_initial_max_data = 10485760; + wsi->quic.qn->peer_initial_max_stream_data_bidi_local = 10485760; + wsi->quic.qn->peer_initial_max_stream_data_bidi_remote = 10485760; + wsi->quic.qn->peer_initial_max_stream_data_uni = 10485760; + wsi->quic.qn->max_streams_bidi_remote = 100; + wsi->quic.qn->max_streams_unidi_remote = 100; + if (wsi->quic.qn->nwsi) { + wsi->quic.qn->nwsi->txc.peer_tx_cr_est += (10485760 - 65535); + wsi->quic.qn->nwsi->txc.tx_cr += (10485760 - 65535); + } +#endif +#if !defined(LWS_WITH_MBEDTLS) + if (wsi->quic.qn->is_server && out_len > 0 && !wsi->quic.qn->tp_parsed) { + lwsl_wsi_err(wsi, "QUIC Peer provided no transport parameters in ClientHello!"); + lws_quic_enter_closing_state(wsi, 0x0100 + 109 /* missing_extension */, 0, 0); + lws_free(out); + return -1; + } +#endif + } + if (n == 0 && wsi->quic.qn && !wsi->quic.qn->handshake_done) { lwsl_wsi_notice(wsi, "QUIC TLS Handshake Complete!"); + +#if !defined(LWS_WITH_MBEDTLS) + if (!wsi->quic.qn->tp_parsed) { + lwsl_wsi_err(wsi, "QUIC Peer provided no transport parameters!"); + lws_quic_enter_closing_state(wsi, 0x0100 + 109 /* missing_extension */, 0, 0); + lws_free(out); + return -1; + } +#endif + wsi->quic.qn->handshake_done = 1; + if (wsi->quic.qn->is_server) { + struct lws_quic_tx_frame *f_hd = lws_zalloc(sizeof(*f_hd), "HANDSHAKE_DONE"); + if (f_hd) { + f_hd->type = LWS_QUIC_FT_HANDSHAKE_DONE; + f_hd->len = 0; /* No payload */ + lws_dll2_add_tail(&f_hd->list, &wsi->quic.qn->pending_tx[LWS_QUIC_LEVEL_APP]); + lws_callback_on_writable(wsi); + } + } + lwsi_set_state(wsi, LRS_ESTABLISHED); +#if defined(LWS_WITH_TLS) + { + const unsigned char *prot = NULL; + unsigned int plen = 0; + +#if defined(USE_WOLFSSL) + wolfSSL_get0_alpn_selected(wsi->tls.ssl, &prot, &plen); +#elif defined(LWS_WITH_MBEDTLS) + const char *alpn = mbedtls_ssl_get_alpn_protocol(SSL_mbedtls_ssl_context_from_SSL(wsi->tls.ssl)); + if (alpn) { + prot = (const unsigned char *)alpn; + plen = (unsigned int)strlen(alpn); + } +#elif defined(LWS_WITH_GNUTLS) + gnutls_datum_t dt; + if (gnutls_alpn_get_selected_protocol(wsi->tls.ssl, &dt) >= 0) { + prot = dt.data; + plen = dt.size; + } +#elif defined(LWS_HAVE_SSL_get0_alpn_selected) || defined(OPENSSL_IS_AWSLC) + SSL_get0_alpn_selected(wsi->tls.ssl, &prot, &plen); +#endif + if (plen) { + lws_strncpy(wsi->alpn, (const char *)prot, plen + 1); + lwsl_wsi_notice(wsi, "QUIC ALPN negotiated: %s", wsi->alpn); + lws_role_call_alpn_negotiated(wsi, wsi->alpn); + } else { +#if !defined(LWS_WITH_MBEDTLS) + lwsl_wsi_err(wsi, "QUIC requires ALPN, but none was negotiated!"); + lws_quic_enter_closing_state(wsi, 0x0100 + 120 /* no_application_protocol */, 0, 0); + lws_free(out); + return -1; +#else + lws_strncpy(wsi->alpn, "lws-quic", sizeof(wsi->alpn)); + lwsl_wsi_notice(wsi, "QUIC ALPN mocked for MbedTLS: %s", wsi->alpn); + lws_role_call_alpn_negotiated(wsi, wsi->alpn); +#endif + } + } +#endif + if (wsi->role_ops) { enum lws_callback_reasons cb = (enum lws_callback_reasons)wsi->role_ops->adoption_cb[lwsi_role_server(wsi)]; if (cb && wsi->a.protocol && wsi->a.protocol->callback) { @@ -549,5 +835,60 @@ lws_tls_quic_rx_crypto(struct lws *wsi, int level, const uint8_t *buf, size_t le lws_callback_on_writable(wsi); } + lws_free(out); return n < 0 ? -1 : 0; } + +LWS_VISIBLE LWS_EXTERN enum lws_0rtt_status +lws_tls_0rtt_status(struct lws *wsi) +{ + struct lws_quic_netconn *qn; + + if (!wsi) + return LWS_0RTT_STATUS_NONE; + + qn = wsi->quic.qn; + if (!qn) { + /* Maybe it's a stream, let's get the network connection */ + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + if (nwsi) + qn = nwsi->quic.qn; + } + + if (!qn) + return LWS_0RTT_STATUS_NONE; + + return (enum lws_0rtt_status)qn->early_data_status; +} + +LWS_VISIBLE LWS_EXTERN int +lws_rx_is_early_data(struct lws *wsi) +{ + struct lws_quic_stream *qs = NULL; + + if (!wsi) + return 0; + + qs = wsi->quic.qs; + if (!qs) + return 0; + + /* In LWS, the transport layer determines if the currently processed RX + * frame arrived in a 0-RTT packet. For QUIC streams, we can track if + * the stream's current RX packet was at the LWS_QUIC_LEVEL_EARLY level. + * Wait, we just need to know if the connection is still in the early data + * phase (handshake not done yet) and we are the server, OR if the specific + * packet was decrypted using the early secret. + */ + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + if (!nwsi || !nwsi->quic.qn) + return 0; + + /* If we are the server, and the handshake is not yet complete, any + * application data we process MUST be 0-RTT early data. + */ + if (nwsi->quic.qn->is_server && !nwsi->quic.qn->handshake_done) + return 1; + + return 0; +} diff --git a/lib/roles/quic/ops-quic-cc-cubic.c b/lib/roles/quic/ops-quic-cc-cubic.c new file mode 100644 index 0000000000..10edc7ab6c --- /dev/null +++ b/lib/roles/quic/ops-quic-cc-cubic.c @@ -0,0 +1,268 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2026 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "private-lib-core.h" +#include "private-lib-roles-quic.h" + +struct lws_quic_cc_cubic { + size_t cwnd; + size_t ssthresh; + size_t bytes_in_flight; + lws_usec_t congestion_recovery_start_time; + + lws_usec_t last_pacing_time; + + /* CUBIC specifics */ + lws_usec_t epoch_start_time; + size_t w_max; + size_t w_est; + int32_t k; + uint8_t is_in_fast_convergence; +}; + +/* Integer cube root approximation */ +static uint32_t +integer_cbrt(uint64_t x) +{ + uint32_t y = 0; + int s; + for (s = 63; s >= 0; s -= 3) { + y += y; + uint32_t b = 3 * y * (y + 1) + 1; + if ((x >> s) >= b) { + x -= (uint64_t)b << s; + y++; + } + } + return y; +} + +static void +cubic_init(struct lws *nwsi) +{ + struct lws_quic_netconn *qn = nwsi->quic.qn; + struct lws_vhost *vh = lws_get_vhost(nwsi); + struct lws_quic_cc_cubic *st; + uint32_t mtu = vh->quic_mtu ? vh->quic_mtu : 1280; + + if (!qn->cc_state) + qn->cc_state = lws_zalloc(sizeof(*st), __func__); + + if (!qn->cc_state) + return; + + st = (struct lws_quic_cc_cubic *)qn->cc_state; + + /* RFC 9002: Initial Window */ + st->cwnd = 10 * mtu; + st->ssthresh = (size_t)-1; /* Infinity */ + st->bytes_in_flight = 0; + st->congestion_recovery_start_time = 0; + st->last_pacing_time = lws_now_usecs(); + + st->epoch_start_time = 0; + st->w_max = 0; + st->w_est = 0; + st->k = 0; + st->is_in_fast_convergence = 0; + + lwsl_cx_info(nwsi->a.context, "QUIC CUBIC: init cwnd=%zu, mtu=%u", st->cwnd, mtu); +} + +static void +cubic_on_sent(struct lws *nwsi, size_t bytes) +{ + struct lws_quic_netconn *qn = nwsi->quic.qn; + struct lws_quic_cc_cubic *st = (struct lws_quic_cc_cubic *)qn->cc_state; + + if (!st) return; + + st->bytes_in_flight += bytes; + st->last_pacing_time = lws_now_usecs(); +} + +static void +cubic_on_ack(struct lws *nwsi, size_t bytes_acked, lws_usec_t rtt) +{ + struct lws_quic_netconn *qn = nwsi->quic.qn; + struct lws_vhost *vh = lws_get_vhost(nwsi); + struct lws_quic_cc_cubic *st = (struct lws_quic_cc_cubic *)qn->cc_state; + uint32_t mtu = vh->quic_mtu ? vh->quic_mtu : 1280; + + if (!st) return; + + if (st->bytes_in_flight >= bytes_acked) + st->bytes_in_flight -= bytes_acked; + else + st->bytes_in_flight = 0; + + /* Update RTT Tracking */ + if (!qn->smoothed_rtt) { + qn->smoothed_rtt = rtt; + qn->rttvar = rtt / 2; + qn->min_rtt = rtt; + } else { + qn->min_rtt = rtt < qn->min_rtt ? rtt : qn->min_rtt; + lws_usec_t rtt_diff = qn->smoothed_rtt > rtt ? qn->smoothed_rtt - rtt : rtt - qn->smoothed_rtt; + qn->rttvar = (3 * qn->rttvar + rtt_diff) / 4; + qn->smoothed_rtt = (7 * qn->smoothed_rtt + rtt) / 8; + } + qn->latest_rtt = rtt; + + if (st->cwnd < st->ssthresh) { + /* Slow Start: Reno behavior */ + st->cwnd += bytes_acked; + } else { + /* Congestion Avoidance: CUBIC */ + lws_usec_t now = lws_now_usecs(); + if (st->epoch_start_time == 0) { + st->epoch_start_time = now; + if (st->w_max < st->cwnd) { + st->w_max = st->cwnd; + st->k = 0; + } else { + /* K = cbrt( (W_max - cwnd) / C ) where C = 0.4 */ + /* Working in MSS */ + uint64_t w_max_mss = st->w_max / mtu; + uint64_t cwnd_mss = st->cwnd / mtu; + if (w_max_mss > cwnd_mss) { + /* K = cbrt( (w_max_mss - cwnd_mss) * 10 / 4 ) */ + st->k = (int32_t)integer_cbrt(((w_max_mss - cwnd_mss) * 10) / 4); + } else { + st->k = 0; + } + } + } + + int32_t t = (int32_t)((now - st->epoch_start_time) / 1000000); /* seconds */ + int32_t diff = t - st->k; + int64_t diff3 = (int64_t)diff * diff * diff; + + uint64_t target_mss = (uint64_t)(((int64_t)4 * diff3) / 10 + (int64_t)(st->w_max / mtu)); + + /* TCP Friendliness (Reno approximation) */ + /* W_est = W_max * beta + (3 * (1-beta) / (1+beta)) * (t / RTT) */ + /* beta = 0.7, so (3 * 0.3 / 1.7) approx 9/17 */ + uint64_t srtt_sec = (uint64_t)(qn->smoothed_rtt / 1000000); + if (srtt_sec == 0) srtt_sec = 1; + st->w_est = ((st->w_max / mtu) * 7) / 10 + ((uint64_t)(9 * t) / (17 * srtt_sec)); + + if (target_mss < st->w_est) + target_mss = st->w_est; + + uint64_t target_bytes = target_mss * mtu; + if (target_bytes > st->cwnd) { + /* Standard CUBIC cwnd increment per ACK */ + size_t cwnd_inc = (mtu * (target_bytes - st->cwnd)) / st->cwnd; + if (cwnd_inc == 0) cwnd_inc = 1; /* ensure forward progress */ + st->cwnd += cwnd_inc; + } + } +} + +static void +cubic_on_loss(struct lws *nwsi, size_t bytes_lost) +{ + struct lws_quic_netconn *qn = nwsi->quic.qn; + struct lws_vhost *vh = lws_get_vhost(nwsi); + struct lws_quic_cc_cubic *st = (struct lws_quic_cc_cubic *)qn->cc_state; + uint32_t mtu = vh->quic_mtu ? vh->quic_mtu : 1280; + size_t min_cwnd = 2 * mtu; + lws_usec_t now = lws_now_usecs(); + + if (!st) return; + + if (st->bytes_in_flight >= bytes_lost) + st->bytes_in_flight -= bytes_lost; + else + st->bytes_in_flight = 0; + + /* Only react to losses that started after the last recovery period */ + if (now - st->congestion_recovery_start_time <= qn->smoothed_rtt) + return; + + st->congestion_recovery_start_time = now; + st->epoch_start_time = 0; + + /* Fast Convergence */ + if (st->cwnd < st->w_max) { + st->w_max = (st->cwnd * 17) / 20; /* w_max = cwnd * (1 + beta) / 2 */ + } else { + st->w_max = st->cwnd; + } + + st->ssthresh = (st->cwnd * 7) / 10; /* cwnd * beta (0.7) */ + if (st->ssthresh < min_cwnd) + st->ssthresh = min_cwnd; + + st->cwnd = st->ssthresh; + +#if (_LWS_ENABLED_LOGS & LLL_INFO) + LWS_RATELIMIT_DEFINE_STATIC(rl); + lwsl_ratelimit_info(&rl, 1000000, "QUIC CUBIC: loss detected, cwnd reduced to %zu", st->cwnd); +#endif +} + +static int +cubic_can_send(struct lws *nwsi, size_t bytes) +{ + struct lws_quic_netconn *qn = nwsi->quic.qn; + struct lws_quic_cc_cubic *st = (struct lws_quic_cc_cubic *)qn->cc_state; + + if (!st) return 0; + + return (st->bytes_in_flight + bytes <= st->cwnd); +} + +static lws_usec_t +cubic_get_pacing_delay(struct lws *nwsi, size_t bytes_to_send) +{ + struct lws_quic_netconn *qn = nwsi->quic.qn; + struct lws_quic_cc_cubic *st = (struct lws_quic_cc_cubic *)qn->cc_state; + lws_usec_t rtt, delay_us; + + if (!st) return 0; + + rtt = qn->smoothed_rtt; + if (rtt < 1000) + rtt = 1000; /* Minimum 1ms for pacing math */ + + /* Pacing Rate R = cwnd / srtt (bytes per microsecond) */ + delay_us = (lws_usec_t)(((uint64_t)bytes_to_send * (uint64_t)rtt) / (uint64_t)st->cwnd); + + lws_usec_t elapsed = lws_now_usecs() - st->last_pacing_time; + if (elapsed >= delay_us) + return 0; + + return delay_us - elapsed; +} + +const struct lws_cc_ops lws_cc_ops_cubic = { + .init = cubic_init, + .on_sent = cubic_on_sent, + .on_ack = cubic_on_ack, + .on_loss = cubic_on_loss, + .can_send = cubic_can_send, + .get_pacing_delay = cubic_get_pacing_delay, +}; diff --git a/lib/roles/quic/ops-quic-cc-newreno.c b/lib/roles/quic/ops-quic-cc-newreno.c index 1f1bb97c17..df72c89aa5 100644 --- a/lib/roles/quic/ops-quic-cc-newreno.c +++ b/lib/roles/quic/ops-quic-cc-newreno.c @@ -128,7 +128,10 @@ newreno_on_loss(struct lws *nwsi, size_t bytes_lost) st->cwnd = st->ssthresh; - lwsl_cx_notice(nwsi->a.context, "QUIC NewReno: loss detected, cwnd reduced to %zu", st->cwnd); +#if (_LWS_ENABLED_LOGS & LLL_INFO) + LWS_RATELIMIT_DEFINE_STATIC(rl); + lwsl_ratelimit_info(&rl, 1000000, "QUIC NewReno: loss detected, cwnd reduced to %zu", st->cwnd); +#endif } static int diff --git a/lib/roles/quic/ops-quic.c b/lib/roles/quic/ops-quic.c index 9ac5234d3e..1fa63e6ddc 100644 --- a/lib/roles/quic/ops-quic.c +++ b/lib/roles/quic/ops-quic.c @@ -42,26 +42,69 @@ lws_quic_pto_cb(lws_sorted_usec_list_t *sul) { struct lws_quic_netconn *qn = lws_container_of(sul, struct lws_quic_netconn, pto_sul); if (qn && qn->nwsi) { + qn->pto_count++; + if (qn->pto_count >= 8) { + lwsl_wsi_notice(qn->nwsi, "QUIC connection dead: max PTO count (%d) reached\n", qn->pto_count); + lws_close_free_wsi(qn->nwsi, LWS_CLOSE_STATUS_NOSTATUS, "quic pto timeout"); + return; + } + qn->pto_probe_needed = 1; - lwsl_notice("QUIC PTO Timer Fired! Forcing POLLOUT for retransmission sweep\n"); +#if (_LWS_ENABLED_LOGS & LLL_INFO) + LWS_RATELIMIT_DEFINE_STATIC(rl); + lwsl_ratelimit_info(&rl, 1000000, "QUIC PTO Timer Fired! Forcing POLLOUT for retransmission sweep\n"); +#endif lws_callback_on_writable(qn->nwsi); /* Always ensure the timer is running as long as there is data in flight! */ for (int i = 0; i < LWS_QUIC_LEVEL_COUNT; i++) { if (qn->in_flight[i].count) { - lws_sul_schedule(qn->nwsi->a.context, 0, &qn->pto_sul, lws_quic_pto_cb, LWS_QUIC_DEFAULT_PTO_US); + lws_usec_t pto_delay = LWS_QUIC_DEFAULT_PTO_US << qn->pto_count; + if (pto_delay > 10000000) + pto_delay = 10000000; + lws_sul_schedule(qn->nwsi->a.context, 0, &qn->pto_sul, lws_quic_pto_cb, pto_delay); break; } } } } +/* RFC 9000 Section 17.1 */ +static uint64_t +lws_quic_decode_packet_number(uint64_t largest_pn, uint64_t truncated_pn, int pn_nbits) +{ + uint64_t expected_pn = largest_pn + 1; + uint64_t pn_win = 1ULL << pn_nbits; + uint64_t pn_hwin = pn_win / 2; + uint64_t pn_mask = pn_win - 1; + uint64_t candidate_pn = (expected_pn & ~pn_mask) | truncated_pn; + + if (candidate_pn + pn_hwin <= expected_pn && candidate_pn < (1ULL << 62) - pn_win) + return candidate_pn + pn_win; + if (candidate_pn > expected_pn + pn_hwin && candidate_pn >= pn_win) + return candidate_pn - pn_win; + + return candidate_pn; +} + void lws_quic_handle_ack(struct lws *nwsi, int level, uint64_t acked_pn) { struct lws_quic_netconn *qn = nwsi->quic.qn; if (!qn) return; + /* PMTUD: Check if our active probe was acknowledged */ + if (qn->pmtud_probe_pn != 0 && acked_pn == qn->pmtud_probe_pn) { + lwsl_wsi_notice(nwsi, "QUIC PMTUD: Probe %llu ACKed! MTU upgraded from %d to %d", + (unsigned long long)acked_pn, (int)qn->current_mtu, (int)qn->probed_mtu); + qn->current_mtu = qn->probed_mtu; + qn->pmtud_probe_pn = 0; + qn->consecutive_mtu_losses = 0; + qn->probed_mtu += 100; /* Probe upward in 100-byte increments */ + if (qn->probed_mtu > 1400) /* Cap at typical Ethernet MTU payload limit for this example */ + qn->pmtud_state = 2; /* SEARCH_COMPLETE */ + } + size_t bytes_acked = 0; lws_usec_t rtt = 0; lws_usec_t now = lws_now_usecs(); @@ -70,16 +113,36 @@ lws_quic_handle_ack(struct lws *nwsi, int level, uint64_t acked_pn) struct lws_quic_tx_frame *f = lws_container_of(d, struct lws_quic_tx_frame, list); if (f->sent_in_pn == acked_pn) { + uint64_t sid = f->stream_id; bytes_acked += f->wire_len; rtt = now > f->sent_time_us ? now - f->sent_time_us : 0; /* Packet was received successfully, free the frame! */ lws_dll2_remove(&f->list); lws_free(f); + + struct lws *child = lws_quic_stream_find(nwsi, sid); + if (child && (lwsi_state(child) == LRS_FLUSHING_BEFORE_CLOSE || child->http.deferred_transaction_completed)) { + lws_callback_on_writable(child); + } } } lws_end_foreach_dll_safe(d, d1); - if (bytes_acked && qn->cc_ops && qn->cc_ops->on_ack) - qn->cc_ops->on_ack(nwsi, bytes_acked, rtt); + if (bytes_acked) { + qn->pto_count = 0; + if (qn->cc_ops && qn->cc_ops->on_ack) + qn->cc_ops->on_ack(nwsi, bytes_acked, rtt); + + /* If we have pending TX and CC might have unblocked, trigger POLLOUT */ + int pending = 0; + for (int i = 0; i < LWS_QUIC_LEVEL_COUNT; i++) { + if (qn->pending_tx[i].count) { + pending = 1; + break; + } + } + if (pending) + lws_callback_on_writable(nwsi); + } /* If there are no more in-flight packets across all levels, we can cancel the PTO timer */ int any_in_flight = 0; @@ -94,6 +157,48 @@ lws_quic_handle_ack(struct lws *nwsi, int level, uint64_t acked_pn) } } +void +lws_quic_discard_keys(struct lws *nwsi, int level) +{ + struct lws_quic_netconn *qn = nwsi->quic.qn; + if (!qn) return; + + if (qn->keys[level]) { + lws_quic_keys_destroy(qn->keys[level]); + qn->keys[level] = NULL; + } + + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, qn->pending_tx[level].head) { + struct lws_quic_tx_frame *f = lws_container_of(d, struct lws_quic_tx_frame, list); + lws_dll2_remove(&f->list); + lws_free(f); + } lws_end_foreach_dll_safe(d, d1); + + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, qn->in_flight[level].head) { + struct lws_quic_tx_frame *f = lws_container_of(d, struct lws_quic_tx_frame, list); + lws_dll2_remove(&f->list); + lws_free(f); + } lws_end_foreach_dll_safe(d, d1); + + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, qn->rx_crypto_chunks[level].head) { + struct lws_quic_rx_chunk *c = lws_container_of(d, struct lws_quic_rx_chunk, list); + lws_dll2_remove(&c->list); + lws_free(c); + } lws_end_foreach_dll_safe(d, d1); +} + +struct lws * +lws_get_quic_network_wsi(struct lws *wsi) +{ + if (!wsi) return NULL; + while (wsi) { + if (wsi->quic.qn) + return wsi; + wsi = wsi->mux.parent_wsi; + } + return NULL; +} + static lws_handling_result_t rops_handle_POLLIN_quic(struct lws_context_per_thread *pt, struct lws *wsi, struct lws_pollfd *pollfd) @@ -121,10 +226,28 @@ rops_handle_POLLIN_quic(struct lws_context_per_thread *pt, struct lws *wsi, #endif if (n <= 0) { - lwsl_wsi_notice(wsi, "QUIC RX: recv returned %d (errno %d)", n, errno); + lwsl_wsi_info(wsi, "QUIC RX: recv returned %d (errno %d)", n, errno); return LWS_HPI_RET_HANDLED; } +#if 0 + { + char buf_peer[64], buf_recv[64]; +#if defined(LWS_WITH_IPV6) + uint16_t port_recv = sa46.sa4.sin_family == AF_INET ? sa46.sa4.sin_port : sa46.sa6.sin6_port; + uint16_t port_peer = wsi->sa46_peer.sa4.sin_family == AF_INET ? wsi->sa46_peer.sa4.sin_port : wsi->sa46_peer.sa6.sin6_port; +#else + uint16_t port_recv = sa46.sa4.sin_port; + uint16_t port_peer = wsi->sa46_peer.sa4.sin_port; +#endif + lws_sa46_write_numeric_address(&wsi->sa46_peer, buf_peer, sizeof(buf_peer)); + lws_sa46_write_numeric_address(&sa46, buf_recv, sizeof(buf_recv)); + /* lwsl_notice("QUIC RX: recv %d bytes from %s:%u (wsi peer %s:%u)\n", n, + buf_recv, (unsigned int)ntohs(port_recv), + buf_peer, (unsigned int)ntohs(port_peer)); */ + } +#endif + lwsl_wsi_debug(wsi, "QUIC RX: read %d bytes from UDP", n); if (n < 2) @@ -132,10 +255,6 @@ rops_handle_POLLIN_quic(struct lws_context_per_thread *pt, struct lws *wsi, p = pt->serv_buf; - /* DEBUG: Print first 16 bytes */ - lwsl_wsi_debug(wsi, "QUIC RX: First 16 bytes: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", - p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); - memset(&dcid, 0, sizeof(dcid)); memset(&scid, 0, sizeof(scid)); @@ -192,12 +311,14 @@ rops_handle_POLLIN_quic(struct lws_context_per_thread *pt, struct lws *wsi, if (w->quic.qn && w->quic.qn->loc_cid.len == dcid_len && !memcmp(w->quic.qn->loc_cid.id, dcid.id, dcid_len)) { nwsi = w; + lwsl_debug("QUIC RX: found connection by loc_cid! nwsi=%s\n", lws_wsi_tag(nwsi)); break; } /* Also match against the original DCID if the client hasn't switched to our loc_cid yet */ if (w->quic.qn && w->quic.qn->orig_dcid.len == dcid_len && !memcmp(w->quic.qn->orig_dcid.id, dcid.id, dcid_len)) { nwsi = w; + lwsl_debug("QUIC RX: found connection by orig_dcid! nwsi=%s\n", lws_wsi_tag(nwsi)); break; } w = w->mux.sibling_list; @@ -211,6 +332,33 @@ rops_handle_POLLIN_quic(struct lws_context_per_thread *pt, struct lws *wsi, return LWS_HPI_RET_HANDLED; } + uint32_t pkt_version = ((uint32_t)p[1] << 24) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 8) | p[4]; + + if (pkt_version != LWS_QUIC_VERSION_1 && pkt_version != LWS_QUIC_VERSION_2) { + lwsl_wsi_notice(wsi, "QUIC RX: Unsupported version 0x%08X, sending VN packet", pkt_version); + uint8_t vn[128]; + uint8_t *vp = vn; + *vp++ = 0x80; /* Long Header */ + *vp++ = 0; *vp++ = 0; *vp++ = 0; *vp++ = 0; /* Version 0 */ + *vp++ = scid.len; + if (scid.len) { memcpy(vp, scid.id, scid.len); vp += scid.len; } + *vp++ = dcid.len; + if (dcid.len) { memcpy(vp, dcid.id, dcid.len); vp += dcid.len; } + /* Add v1 */ + *vp++ = (uint8_t)(LWS_QUIC_VERSION_1 >> 24); *vp++ = (uint8_t)(LWS_QUIC_VERSION_1 >> 16); + *vp++ = (uint8_t)(LWS_QUIC_VERSION_1 >> 8); *vp++ = (uint8_t)(LWS_QUIC_VERSION_1); + /* Add v2 */ + *vp++ = (uint8_t)(LWS_QUIC_VERSION_2 >> 24); *vp++ = (uint8_t)(LWS_QUIC_VERSION_2 >> 16); + *vp++ = (uint8_t)(LWS_QUIC_VERSION_2 >> 8); *vp++ = (uint8_t)(LWS_QUIC_VERSION_2); + +#if defined(WIN32) || defined(_WIN32) + sendto(wsi->desc.sockfd, (char *)vn, (int)(vp - vn), 0, sa46_sockaddr(&sa46), slen); +#else + sendto(wsi->desc.sockfd, (void *)vn, (size_t)(vp - vn), 0, sa46_sockaddr(&sa46), slen); +#endif + return LWS_HPI_RET_HANDLED; + } + /* Enforce 1200-byte padding for client-to-server Initial packets (RFC 9000 Section 14.1) */ if (n < 1200) { lwsl_wsi_notice(wsi, "QUIC RX: Dropping under-padded Initial packet (len %d)", n); @@ -234,15 +382,28 @@ rops_handle_POLLIN_quic(struct lws_context_per_thread *pt, struct lws *wsi, nwsi->quic.qn->nwsi = nwsi; nwsi->quic.qn->is_server = 1; - nwsi->quic.qn->version = 1; + nwsi->quic.qn->version = pkt_version; + nwsi->quic.qn->max_streams_bidi_local = 1024; + nwsi->quic.qn->max_streams_unidi_local = 1024; + + nwsi->quic.qn->current_mtu = 1280; + nwsi->quic.qn->probed_mtu = 1380; /* first probe size */ + nwsi->quic.qn->pmtud_state = 1; /* SEARCHING */ + + if (nwsi->a.context->quic_cc_ops) + nwsi->quic.qn->cc_ops = nwsi->a.context->quic_cc_ops; + else + nwsi->quic.qn->cc_ops = &lws_cc_ops_newreno; - nwsi->quic.qn->cc_ops = &lws_cc_ops_newreno; if (nwsi->quic.qn->cc_ops->init) nwsi->quic.qn->cc_ops->init(nwsi); - /* Initialize Flow Control Credits so we can actually send STREAM data */ - nwsi->txc.peer_tx_cr_est = 65535; - nwsi->txc.tx_cr = 65535; + /* Initialize RX Flow Control limits */ + nwsi->quic.qn->rx_max_data = LWS_QUIC_DEFAULT_WINDOW; + nwsi->quic.qn->rx_window_size = LWS_QUIC_DEFAULT_WINDOW; + nwsi->quic.qn->last_rx_update_us = lws_now_usecs(); + nwsi->txc.peer_tx_cr_est = LWS_QUIC_DEFAULT_WINDOW; /* How much the peer can write to us */ + /* tx_cr is strictly initialized when we parse the peer's initial_max_data parameter */ #if defined(LWS_WITH_UDP) nwsi->udp = lws_malloc(sizeof(*nwsi->udp), "quic udp"); @@ -278,6 +439,55 @@ rops_handle_POLLIN_quic(struct lws_context_per_thread *pt, struct lws *wsi, nwsi->quic.qn->loc_cid.len = 8; lws_get_random(wsi->a.context, nwsi->quic.qn->loc_cid.id, 8); + { + uint8_t *tp = nwsi->quic.qn->local_tp_buf; + uint8_t *tp_end = tp + sizeof(nwsi->quic.qn->local_tp_buf); + +#define LWS_QUIC_WRITE_TP_VARINT(_id, _val) \ + do { \ + int _vlen; \ + if (lws_ptr_diff_size_t(tp_end, tp) < 2) goto tp_overflow; \ + *tp++ = (_id); \ + _vlen = (int)lws_quic_write_varint(tp + 1, lws_ptr_diff_size_t(tp_end, tp + 1), (_val)); \ + if (!_vlen) goto tp_overflow; \ + *tp++ = (uint8_t)_vlen; \ + tp += _vlen; \ + } while (0) + +#define LWS_QUIC_WRITE_TP_BUF(_id, _buf, _len) \ + do { \ + if (lws_ptr_diff_size_t(tp_end, tp) < (size_t)(2 + (_len))) goto tp_overflow; \ + *tp++ = (_id); \ + *tp++ = (uint8_t)(_len); \ + memcpy(tp, (_buf), (_len)); \ + tp += (_len); \ + } while (0) + + LWS_QUIC_WRITE_TP_VARINT(0x04, LWS_QUIC_DEFAULT_WINDOW); + LWS_QUIC_WRITE_TP_VARINT(0x05, LWS_QUIC_DEFAULT_WINDOW); + LWS_QUIC_WRITE_TP_VARINT(0x06, LWS_QUIC_DEFAULT_WINDOW); + LWS_QUIC_WRITE_TP_VARINT(0x07, LWS_QUIC_DEFAULT_WINDOW); + LWS_QUIC_WRITE_TP_VARINT(0x08, 1024); + LWS_QUIC_WRITE_TP_VARINT(0x09, 1024); + LWS_QUIC_WRITE_TP_VARINT(0x20, 65535); + LWS_QUIC_WRITE_TP_VARINT(0x01, 30000); + + LWS_QUIC_WRITE_TP_BUF(0x0F, nwsi->quic.qn->loc_cid.id, nwsi->quic.qn->loc_cid.len); + LWS_QUIC_WRITE_TP_BUF(0x00, nwsi->quic.qn->orig_dcid.id, nwsi->quic.qn->orig_dcid.len); + + lws_tls_quic_set_transport_parameters(nwsi, nwsi->quic.qn->local_tp_buf, (size_t)(tp - nwsi->quic.qn->local_tp_buf)); + + goto tp_ok; +tp_overflow: + lwsl_wsi_err(wsi, "QUIC TX: tp buffer overflow"); + lws_close_free_wsi(nwsi, LWS_CLOSE_STATUS_NOSTATUS, "tp overflow"); + return LWS_HPI_RET_HANDLED; +tp_ok: + ; +#undef LWS_QUIC_WRITE_TP_VARINT +#undef LWS_QUIC_WRITE_TP_BUF + } + /* Link it to the UDP listening socket */ lws_mux_mark_immortal(nwsi); nwsi->mux_substream = 1; @@ -293,107 +503,341 @@ rops_handle_POLLIN_quic(struct lws_context_per_thread *pt, struct lws *wsi, return LWS_HPI_RET_HANDLED; } - lwsl_wsi_notice(wsi, "QUIC RX: Created new connection! (loc_cid len %d)", nwsi->quic.qn->loc_cid.len); + lwsl_wsi_info(wsi, "QUIC RX: Created new connection! (loc_cid len %d)", nwsi->quic.qn->loc_cid.len); } - if (nwsi && nwsi->udp) - nwsi->udp->sa46 = sa46; + int pending_migration = 0; + lws_sockaddr46 migration_sa46; + + if (nwsi && nwsi->udp) { + int addr_changed = 0; + if (nwsi->udp->sa46.sa4.sin_family != sa46.sa4.sin_family) { + addr_changed = 1; + } else if (sa46.sa4.sin_family == AF_INET) { + if (nwsi->udp->sa46.sa4.sin_addr.s_addr != sa46.sa4.sin_addr.s_addr || + nwsi->udp->sa46.sa4.sin_port != sa46.sa4.sin_port) + addr_changed = 1; + } +#if defined(LWS_WITH_IPV6) + else if (sa46.sa4.sin_family == AF_INET6) { + if (memcmp(&nwsi->udp->sa46.sa6.sin6_addr, &sa46.sa6.sin6_addr, sizeof(struct in6_addr)) || + nwsi->udp->sa46.sa6.sin6_port != sa46.sa6.sin6_port) + addr_changed = 1; + } +#endif + if (addr_changed && nwsi->quic.qn) { + pending_migration = 1; + migration_sa46 = sa46; + } else { + nwsi->udp->sa46 = sa46; + } + } if (nwsi && nwsi->quic.qn) { nwsi->quic.qn->bytes_received += (uint64_t)n; } - /* We have the connection! Grab the appropriate keys based on packet type */ - int level = LWS_QUIC_LEVEL_APP; - if (p[0] & 0x80) { - uint8_t type = (p[0] & 0x30) >> 4; - if (type == 0) level = LWS_QUIC_LEVEL_INITIAL; - else if (type == 2) level = LWS_QUIC_LEVEL_HANDSHAKE; - else { - lwsl_wsi_notice(wsi, "QUIC RX: Unsupported long header type %d", type); - return LWS_HPI_RET_HANDLED; + while (n > 0) { + /* If ALPN negotiation migrated the connection in a previous packet, update nwsi */ + if (nwsi && !nwsi->quic.qn) { + nwsi = lws_get_quic_network_wsi(nwsi); } - } - /* Enforce 1200-byte padding for subsequent client-to-server Initial packets (RFC 9000 Section 14.1) */ - if (level == LWS_QUIC_LEVEL_INITIAL && nwsi && nwsi->quic.qn && nwsi->quic.qn->is_server && n < 1200) { - lwsl_wsi_notice(wsi, "QUIC RX: Dropping under-padded Initial packet (len %d)", n); - return LWS_HPI_RET_HANDLED; - } + if (!nwsi || !nwsi->quic.qn) { + lwsl_wsi_notice(wsi, "QUIC RX: network connection gone, dropping remaining packets"); + break; + } - struct lws_quic_keys *k = nwsi->quic.qn->keys[level]; + /* We have the connection! Grab the appropriate keys based on packet type */ + int level = LWS_QUIC_LEVEL_APP; + if (p[0] == 0x00) { + lwsl_wsi_info(wsi, "QUIC RX: Next byte is 0x00, ignoring as padding"); + p++; + n--; + continue; + } - if (!k || !k->valid) { - lwsl_wsi_notice(wsi, "QUIC RX: No valid keys for this packet level %d", level); - return LWS_HPI_RET_HANDLED; - } + if (p[0] & 0x80) { + uint8_t type = (uint8_t)((p[0] & 0x30) >> 4); + if (type == 0) level = LWS_QUIC_LEVEL_INITIAL; + else if (type == 2) level = LWS_QUIC_LEVEL_HANDSHAKE; + else { + lwsl_wsi_notice(wsi, "QUIC RX: Unsupported long header type %d", type); + break; + } + } - /* 2. Parsing: Safely find the Packet Number offset */ - size_t payload_len_stated; - size_t pn_offset = lws_quic_get_pn_offset(p, (size_t)n, &payload_len_stated); - if (!pn_offset) { - lwsl_wsi_notice(wsi, "QUIC RX: Malformed or truncated packet"); - return LWS_HPI_RET_HANDLED; - } + if (nwsi && nwsi->quic.qn) { + if (level > nwsi->quic.qn->highest_rx_level) + nwsi->quic.qn->highest_rx_level = (uint8_t)level; + } - /* 3. Unmasking: Reveal the true Packet Number */ - int pn_len = lws_quic_unmask_header(k, p, (size_t)n, pn_offset); - if (pn_len < 0) { - lwsl_wsi_notice(wsi, "QUIC RX: Header unmask failed"); - return LWS_HPI_RET_HANDLED; - } + /* Enforce 1200-byte padding for subsequent client-to-server Initial packets (RFC 9000 Section 14.1) */ + if (level == LWS_QUIC_LEVEL_INITIAL && nwsi && nwsi->quic.qn && nwsi->quic.qn->is_server && n < 1200) { + lwsl_wsi_notice(wsi, "QUIC RX: Dropping under-padded Initial packet (len %d)", n); + break; + } - /* - * Reconstruct full 62-bit PN. - * (Note: We just read it raw here for the very first packets; proper - * decoding uses the RFC 9000 algorithm based on pn_rx_largest). - */ - uint64_t full_pn = 0; - for (int i = 0; i < pn_len; i++) - full_pn = (full_pn << 8) | p[pn_offset + (size_t)i]; - - /* 4. Decryption: Authenticate and decrypt the payload in-place! */ - int dec_len = lws_quic_decrypt_payload(k, p, (size_t)n, pn_offset, (uint8_t)pn_len, full_pn); - if (dec_len < 0) { - lwsl_wsi_notice(wsi, "QUIC RX: AEAD Decryption failed (bad tag or truncated)"); - return LWS_HPI_RET_HANDLED; - } + /* 2. Parsing: Safely find the Packet Number offset */ + size_t payload_len_stated; + size_t pn_offset = lws_quic_get_pn_offset(p, (size_t)n, &payload_len_stated); + if (!pn_offset) { + lwsl_wsi_notice(wsi, "QUIC RX: Malformed or truncated packet"); + break; + } - if (nwsi->quic.qn && (level == LWS_QUIC_LEVEL_HANDSHAKE || level == LWS_QUIC_LEVEL_APP)) { - nwsi->quic.qn->address_validated = 1; - } + size_t packet_size = pn_offset + payload_len_stated; + if (packet_size > (size_t)n) { + lwsl_wsi_notice(wsi, "QUIC RX: Packet stated size %zu > remaining UDP %zu", packet_size, (size_t)n); + break; + } - lwsl_wsi_info(wsi, "QUIC RX: SUCCESS! Decrypted %d bytes of payload", dec_len); + struct lws_quic_keys *k = nwsi->quic.qn->keys[level]; - /* Check for duplicate/replayed packet numbers (Security Fix) */ - if (nwsi->quic.qn) { - uint64_t highest = nwsi->quic.qn->highest_rx_pn[level]; - if ((nwsi->quic.qn->rx_pn_bitmask[level] != 0 || highest != 0) && full_pn <= highest) { - uint64_t diff = highest - full_pn; - if (diff >= 64 || (nwsi->quic.qn->rx_pn_bitmask[level] & (1ULL << diff))) { - lwsl_wsi_notice(wsi, "QUIC RX: Dropping duplicated or very old packet %llu", (unsigned long long)full_pn); - return LWS_HPI_RET_HANDLED; + if (!k || !k->valid) { + lwsl_wsi_notice(wsi, "QUIC RX: No valid keys for this packet level %d, skipping %zu bytes", level, packet_size); + p += packet_size; + n -= (int)packet_size; + continue; + } + + lwsl_wsi_info(wsi, "QUIC RX: Parsing packet level %d, UDP remaining %d, stated packet_size %zu", level, n, packet_size); + + /* 3. Unmasking: Reveal the true Packet Number */ + int pn_len = lws_quic_unmask_header(k, p, packet_size, pn_offset); + if (pn_len < 0) { + lwsl_wsi_notice(wsi, "QUIC RX: Header unmask failed"); + break; + } + + /* Check reserved bits AFTER unmasking! */ + if (p[0] & 0x80) { + /* Long header: Bits 0x0c MUST be zero */ + if (p[0] & 0x0c) { + lwsl_wsi_notice(wsi, "QUIC RX: Reserved bits non-zero in long header"); + if (nwsi && nwsi != wsi) { + lws_quic_enter_closing_state(nwsi, LWS_QUIC_ERR_PROTOCOL_VIOLATION, 0, 0); + goto next_packet; + } + return LWS_HPI_RET_PLEASE_CLOSE_ME; } - nwsi->quic.qn->rx_pn_bitmask[level] |= (1ULL << diff); } else { - if (nwsi->quic.qn->rx_pn_bitmask[level] != 0 || highest != 0) { - uint64_t diff = full_pn - highest; - if (diff >= 64) - nwsi->quic.qn->rx_pn_bitmask[level] = 0; - else - nwsi->quic.qn->rx_pn_bitmask[level] <<= diff; + /* Short header: Bits 0x18 MUST be zero */ + if (p[0] & 0x18) { + lwsl_wsi_notice(wsi, "QUIC RX: Reserved bits non-zero in short header"); + if (nwsi && nwsi != wsi) { + lws_quic_enter_closing_state(nwsi, LWS_QUIC_ERR_PROTOCOL_VIOLATION, 0, 0); + goto next_packet; + } + return LWS_HPI_RET_PLEASE_CLOSE_ME; } - nwsi->quic.qn->rx_pn_bitmask[level] |= 1ULL; - nwsi->quic.qn->highest_rx_pn[level] = full_pn; } - nwsi->quic.qn->needs_ack[level] = 1; - lws_callback_on_writable(nwsi); /* Ensure POLLOUT fires so we send the ACK! */ - } + /* + * Reconstruct full 62-bit PN. + */ + uint64_t truncated_pn = 0; + for (int i = 0; i < pn_len; i++) + truncated_pn = (truncated_pn << 8) | p[pn_offset + (size_t)i]; + + uint64_t largest_pn = nwsi->quic.qn ? nwsi->quic.qn->highest_rx_pn[level] : 0; + uint64_t full_pn = lws_quic_decode_packet_number(largest_pn, truncated_pn, pn_len * 8); + lwsl_wsi_info(wsi, "QUIC RX: Packet level %d, decoded full_pn = %llu", level, (unsigned long long)full_pn); + + struct lws_quic_keys scratch_keys; + struct lws_quic_keys *decryption_keys = k; + int is_key_update = 0; + + if (!(p[0] & 0x80)) { /* Short header */ + uint8_t kp = (p[0] & 0x04) >> 2; + if (nwsi->quic.qn && nwsi->quic.qn->rx_key_phase != kp) { + /* Provisional key update */ + scratch_keys = *k; + if (lws_quic_update_keys(&scratch_keys, 1) == 0) { + decryption_keys = &scratch_keys; + is_key_update = 1; + lwsl_wsi_notice(wsi, "QUIC RX: Attempting provisional key update"); + } + } + } + + /* 4. Decryption: Authenticate and decrypt the payload in-place! */ + int dec_len = lws_quic_decrypt_payload(decryption_keys, p, packet_size, pn_offset, (uint8_t)pn_len, full_pn); + if (dec_len < 0) { + lwsl_wsi_notice(wsi, "QUIC RX: AEAD Decryption failed (bad tag or truncated)"); + break; + } - /* 5. Parse the plaintext frames */ - if (lws_quic_parse_frames(nwsi, level, &p[pn_offset + (size_t)pn_len], (size_t)dec_len) < 0) { - lwsl_wsi_notice(wsi, "QUIC RX: Frame parsing aborted"); + /* Decryption succeeded! Commit key update if pending */ + if (is_key_update) { + *k = scratch_keys; + nwsi->quic.qn->rx_key_phase ^= 1; + nwsi->quic.qn->rx_packets_since_update = 0; + if (!nwsi->quic.qn->key_update_pending) { + /* Peer initiated, so we echo by updating TX keys */ + lws_quic_initiate_key_update(nwsi); + } else { + /* We initiated, this is the peer echoing back */ + nwsi->quic.qn->key_update_pending = 0; + } + lwsl_wsi_notice(wsi, "QUIC RX: Key Update completed successfully"); + } + + if (nwsi->quic.qn) + nwsi->quic.qn->rx_packets_since_update++; + + if (nwsi->quic.qn && level == LWS_QUIC_LEVEL_HANDSHAKE) { + nwsi->quic.qn->address_validated = 1; + } + + if (level == LWS_QUIC_LEVEL_HANDSHAKE) { + lws_quic_discard_keys(nwsi, LWS_QUIC_LEVEL_INITIAL); + } else if (level == LWS_QUIC_LEVEL_APP) { + lws_quic_discard_keys(nwsi, LWS_QUIC_LEVEL_INITIAL); + lws_quic_discard_keys(nwsi, LWS_QUIC_LEVEL_HANDSHAKE); + } + + lwsl_wsi_info(wsi, "QUIC RX: SUCCESS! Decrypted %d bytes of payload", dec_len); + + /* Check for duplicate/replayed packet numbers (Security Fix) */ + if (nwsi->quic.qn) { + uint64_t highest = nwsi->quic.qn->highest_rx_pn[level]; + if ((nwsi->quic.qn->rx_pn_bitmask[level] != 0 || highest != 0) && full_pn <= highest) { + uint64_t diff = highest - full_pn; + if (diff >= 64 || (nwsi->quic.qn->rx_pn_bitmask[level] & (1ULL << diff))) { + lwsl_wsi_notice(wsi, "QUIC RX: Dropping duplicated or very old packet %llu", (unsigned long long)full_pn); + goto next_packet; + } + nwsi->quic.qn->rx_pn_bitmask[level] |= (1ULL << diff); + } else { + if (nwsi->quic.qn->rx_pn_bitmask[level] != 0 || highest != 0) { + uint64_t diff = full_pn - highest; + if (diff >= 64) + nwsi->quic.qn->rx_pn_bitmask[level] = 0; + else + nwsi->quic.qn->rx_pn_bitmask[level] <<= diff; + } + nwsi->quic.qn->rx_pn_bitmask[level] |= 1ULL; + nwsi->quic.qn->highest_rx_pn[level] = full_pn; + } + + /* Connection Migration: Execute pending migration now that the packet is cryptographically verified */ + if (pending_migration) { + pending_migration = 0; +#if (_LWS_ENABLED_LOGS & LLL_NOTICE) + char buf_old[64], buf_new[64]; + uint16_t port_old, port_new; + + lws_sa46_write_numeric_address(&nwsi->udp->sa46, buf_old, sizeof(buf_old)); + lws_sa46_write_numeric_address(&migration_sa46, buf_new, sizeof(buf_new)); + +#if defined(LWS_WITH_IPV6) + port_old = nwsi->udp->sa46.sa4.sin_family == AF_INET ? nwsi->udp->sa46.sa4.sin_port : nwsi->udp->sa46.sa6.sin6_port; + port_new = migration_sa46.sa4.sin_family == AF_INET ? migration_sa46.sa4.sin_port : migration_sa46.sa6.sin6_port; +#else + port_old = nwsi->udp->sa46.sa4.sin_port; + port_new = migration_sa46.sa4.sin_port; +#endif +#endif + + if (nwsi->quic.qn->is_server) { +#if (_LWS_ENABLED_LOGS & LLL_NOTICE) + lwsl_notice("QUIC Server: Connection Migration verified! Peer address changed from %s:%u to %s:%u\n", + buf_old, (unsigned int)ntohs(port_old), + buf_new, (unsigned int)ntohs(port_new)); +#endif + } else { +#if (_LWS_ENABLED_LOGS & LLL_NOTICE) + lwsl_notice("QUIC Client: Server address changed from %s:%u to %s:%u, re-connecting socket\n", + buf_old, (unsigned int)ntohs(port_old), + buf_new, (unsigned int)ntohs(port_new)); +#endif + + /* Re-connect the socket to the new server address */ + if (connect(nwsi->desc.sockfd, sa46_sockaddr(&migration_sa46), sa46_socklen(&migration_sa46)) < 0) { + lwsl_warn("QUIC: failed to re-connect client socket, errno=%d\n", errno); + } + } + + nwsi->udp->sa46 = migration_sa46; + + /* Reset Congestion Control State (RFC 9000 9.3.3) */ + if (nwsi->quic.qn->cc_ops && nwsi->quic.qn->cc_ops->init) + nwsi->quic.qn->cc_ops->init(nwsi); + + /* Reset RTT estimator */ + nwsi->quic.qn->smoothed_rtt = 0; + nwsi->quic.qn->rttvar = 0; + nwsi->quic.qn->latest_rtt = 0; + + /* Reset PMTUD */ + nwsi->quic.qn->current_mtu = 1280; + nwsi->quic.qn->probed_mtu = 1380; + nwsi->quic.qn->pmtud_state = 1; + + /* Set path to unvalidated */ + nwsi->quic.qn->address_validated = 0; + + /* Reset Path Bytes for Anti-Amplification tracking */ + nwsi->quic.qn->bytes_received = 0; + nwsi->quic.qn->bytes_sent = 0; + + /* Initiate Path Validation (Generate PATH_CHALLENGE) */ + struct lws_quic_tx_frame *f_pc = lws_zalloc(sizeof(*f_pc) + 8, "quic path_chall"); + if (f_pc) { + f_pc->type = LWS_QUIC_FT_PATH_CHALLENGE; + f_pc->len = 8; + f_pc->data = (uint8_t *)&f_pc[1]; + lws_get_random(wsi->a.context, f_pc->data, 8); + memcpy(nwsi->quic.qn->path_challenge, f_pc->data, 8); + nwsi->quic.qn->path_challenge_pending = 1; + + lws_dll2_add_tail(&f_pc->list, &nwsi->quic.qn->pending_tx[LWS_QUIC_LEVEL_APP]); + lws_callback_on_writable(nwsi); + } + } + + /* 5. Parse the plaintext frames */ + if (dec_len == 0) { + lwsl_wsi_notice(wsi, "QUIC RX: Packet payload is empty (no frames)"); + if (nwsi && nwsi != wsi) { + lws_quic_enter_closing_state(nwsi, LWS_QUIC_ERR_PROTOCOL_VIOLATION, 0, 0); + goto next_packet; + } + return LWS_HPI_RET_PLEASE_CLOSE_ME; + } + + int ack_eliciting = lws_quic_parse_frames(nwsi, level, &p[pn_offset + (size_t)pn_len], (size_t)dec_len); + + /* ALPN negotiation might have migrated the network WSI! */ + if (nwsi && !nwsi->quic.qn) { + nwsi = lws_get_quic_network_wsi(nwsi); + } + + if (ack_eliciting < 0) { + lwsl_wsi_notice(wsi, "QUIC RX: Frame parsing aborted"); + if (nwsi && nwsi != wsi) { + if (ack_eliciting == -3) { + /* Peer closed the connection via CONNECTION_CLOSE. Drop silently without replying. */ + lwsl_wsi_notice(nwsi, "QUIC RX: Peer closed connection. Dropping silently."); + lws_close_free_wsi(nwsi, LWS_CLOSE_STATUS_NORMAL, "quic peer closed"); + goto next_packet; + } + lws_quic_enter_closing_state(nwsi, ack_eliciting == -2 ? LWS_QUIC_ERR_PROTOCOL_VIOLATION : LWS_QUIC_ERR_FRAME_ENCODING_ERROR, 0, 0); + goto next_packet; + } + return LWS_HPI_RET_PLEASE_CLOSE_ME; + } else if (ack_eliciting > 0) { + if (nwsi && nwsi->quic.qn) { + nwsi->quic.qn->needs_ack[level] = 1; + lws_callback_on_writable(nwsi); /* Ensure POLLOUT fires so we send the ACK! */ + } + } + } + +next_packet: + n -= (int)packet_size; + p += packet_size; } try_pollout: @@ -415,6 +859,7 @@ lws_tls_quic_tx_crypto_cb(struct lws *wsi, int level, const uint8_t *buf, size_t return -1; lwsl_info("QUIC TLS TX: %s generated %d bytes of crypto data for level %d\n", lws_wsi_tag(wsi), (int)len, level); + struct lws_quic_tx_frame *f; /* Allocate frame struct + payload buffer natively */ @@ -448,7 +893,7 @@ static int quic_secret_cb(struct lws *wsi, enum lws_tls_quic_secret_type type, const uint8_t *secret, size_t secret_len) { - lwsl_info("QUIC TLS: Extracted secret type %d (len %d)", type, (int)secret_len); + lwsl_notice("QUIC TLS: Extracted secret type %d (len %d)\n", type, (int)secret_len); if (lws_quic_set_keys(wsi, type, secret, secret_len)) { lwsl_wsi_err(wsi, "Failed to set QUIC keys for type %d", type); return -1; @@ -456,12 +901,95 @@ quic_secret_cb(struct lws *wsi, enum lws_tls_quic_secret_type type, return 0; } +void +lws_quic_enter_closing_state(struct lws *wsi, uint64_t err_code, uint64_t frame_type, int is_app_error) +{ + struct lws_quic_netconn *qn; + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + struct lws_quic_tx_frame *f; + int level, target_level = LWS_QUIC_LEVEL_INITIAL; + + if (!nwsi || !nwsi->quic.qn) { + lwsl_notice("lws_quic_enter_closing_state: nwsi %s, qn %p (wsi %s parent %s)\n", + lws_wsi_tag(nwsi), nwsi ? nwsi->quic.qn : NULL, lws_wsi_tag(wsi), lws_wsi_tag(wsi ? wsi->mux.parent_wsi : NULL)); + return; + } + + qn = nwsi->quic.qn; + + if (qn->is_closing) { + lwsl_notice("lws_quic_enter_closing_state: qn->is_closing is already 1\n"); + return; /* Already closing */ + } + + qn->is_closing = 1; + qn->conn_close_err = err_code; + + + lwsl_wsi_warn(nwsi, "QUIC: Entering Closing State (err 0x%llx)", (unsigned long long)err_code); + + /* Determine highest available encryption level to send CONNECTION_CLOSE */ + int start_level = qn->highest_rx_level; + for (level = start_level; level >= LWS_QUIC_LEVEL_INITIAL; level--) { + if (qn->keys[level] && qn->keys[level]->valid) { + target_level = level; + break; + } + } + + /* Clear pending queues, we are closing, but keep CRYPTO frames so peer can derive keys! */ + for (level = 0; level < LWS_QUIC_LEVEL_COUNT; level++) { + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, qn->pending_tx[level].head) { + struct lws_quic_tx_frame *tf = lws_container_of(d, struct lws_quic_tx_frame, list); + if (tf->type != LWS_QUIC_FT_CRYPTO && tf->type != LWS_QUIC_FT_HANDSHAKE_DONE) { + lws_dll2_remove(&tf->list); + lws_free(tf); + } + } lws_end_foreach_dll_safe(d, d1); + } + + /* Enqueue a single CONNECTION_CLOSE frame */ + f = lws_zalloc(sizeof(*f) + 16, "quic tx cc"); + if (f) { + uint8_t *p = (uint8_t *)&f[1]; + f->type = is_app_error ? LWS_QUIC_FT_CONNECTION_CLOSE_APP : LWS_QUIC_FT_CONNECTION_CLOSE; + + lwsl_wsi_warn(nwsi, "QUIC TX: Enqueueing CONNECTION_CLOSE (type 0x%x, err 0x%llx) at level %d", + f->type, (unsigned long long)err_code, target_level); + + /* Encode the error code (varint) */ + p += lws_quic_write_varint(p, 8, err_code); + + /* Frame type (varint) - only for Transport Errors (0x1c) */ + if (!is_app_error) { + p += lws_quic_write_varint(p, 8, frame_type); + } + + /* Reason phrase length (0) */ + p += lws_quic_write_varint(p, 8, 0); + + f->data = (uint8_t *)&f[1]; + f->len = (size_t)(p - f->data); + + lwsl_wsi_warn(nwsi, "QUIC TX: Enqueueing CONNECTION_CLOSE (type 0x%x, err 0x%llx) at level %d", + f->type, (unsigned long long)err_code, target_level); + lwsl_hexdump_warn(f->data, f->len); + + lws_dll2_add_tail(&f->list, &qn->pending_tx[target_level]); + } + + /* Wait 3 seconds, then drop the socket */ + lws_set_timeout(nwsi, PENDING_TIMEOUT_KILLED_BY_SSL_INFO, 3); + lws_callback_on_writable(nwsi); +} + static lws_handling_result_t rops_handle_POLLOUT_quic(struct lws *wsi) { struct lws_quic_netconn *qn = wsi->quic.qn; int level, n; - uint8_t pkt[1280]; /* Max QUIC UDP payload for now */ + int blocked = 0; + uint8_t pkt[2048]; memset(pkt, 0, sizeof(pkt)); // lwsl_notice("QUIC TX: POLLOUT called for %s, qn=%p, is_server=%d\n", lws_wsi_tag(wsi), qn, qn ? qn->is_server : -1); @@ -480,6 +1008,10 @@ rops_handle_POLLOUT_quic(struct lws *wsi) return LWS_HP_RET_DROP_POLLOUT; } + lws_usec_t pto_delay = LWS_QUIC_DEFAULT_PTO_US << qn->pto_count; + if (pto_delay > 10000000) + pto_delay = 10000000; + if (!wsi->quic.initialized && !qn->is_server) { wsi->quic.initialized = 1; @@ -497,11 +1029,19 @@ rops_handle_POLLOUT_quic(struct lws *wsi) #endif } + if (qn->is_closing) { + /* We are in the Closing State. Only process the CONNECTION_CLOSE frame. */ + /* The frame is queued in pending_tx by lws_quic_enter_closing_state. */ + /* Skip PTO sweep and just let the normal frame generation send it. */ + goto send_frames; + } + /* * PTO Sweep: Check for dropped/unacknowledged packets */ lws_usec_t now = lws_now_usecs(); size_t total_bytes_lost = 0; + uint64_t last_lost_pn = (uint64_t)-1; for (level = 0; level < LWS_QUIC_LEVEL_COUNT; level++) { if (!qn->in_flight[level].count) continue; @@ -514,8 +1054,26 @@ rops_handle_POLLOUT_quic(struct lws *wsi) (long long)(now - f->sent_time_us)); /* Allow a 5ms epsilon for timer jitter */ - if (now + 5000 >= f->sent_time_us + LWS_QUIC_DEFAULT_PTO_US) { - lwsl_debug("PTO Sweep: Packet %llu (type 0x%02x) lost! Retransmitting!\n", (unsigned long long)f->sent_in_pn, f->type); + if (now + 5000 >= f->sent_time_us + pto_delay) { + lwsl_notice("PTO Sweep: Packet %llu (type 0x%02x) lost! Retransmitting!\n", (unsigned long long)f->sent_in_pn, f->type); + + /* PMTUD Black Hole Detection */ + if (f->sent_in_pn != last_lost_pn) { + last_lost_pn = f->sent_in_pn; + if (f->sent_in_pn == qn->pmtud_probe_pn) { + /* Active probe was lost */ + qn->pmtud_probe_pn = 0; + } else if (f->packet_size >= qn->current_mtu - 16) { + qn->consecutive_mtu_losses++; + if (qn->consecutive_mtu_losses >= 3) { + lwsl_wsi_warn(wsi, "QUIC PMTUD: Black Hole detected! Reverting MTU to 1280."); + qn->current_mtu = 1280; + qn->pmtud_state = 0; + qn->consecutive_mtu_losses = 0; + } + } + } + /* Packet lost! Move it back to pending_tx */ lws_dll2_remove(&f->list); lws_dll2_add_head(&f->list, &qn->pending_tx[level]); @@ -527,33 +1085,45 @@ rops_handle_POLLOUT_quic(struct lws *wsi) if (total_bytes_lost && qn->cc_ops && qn->cc_ops->on_loss) qn->cc_ops->on_loss(wsi, total_bytes_lost); +send_frames: /* * Iterate through the encryption levels in priority order. * Initial > Handshake > Application Data. */ for (level = 0; level < LWS_QUIC_LEVEL_COUNT; level++) { - if (!qn->keys[level] || !qn->keys[level]->valid) + if (!qn->keys[level]) { + continue; + } + + if (!qn->keys[level]->valid) { continue; + } - if (!qn->pending_tx[level].count && !qn->needs_ack[level]) + if (!qn->pending_tx[level].count && !qn->needs_ack[level]) { continue; + } + + lwsl_wsi_info(wsi, "QUIC TX: Processing level %d. pending=%d, needs_ack=%d", level, qn->pending_tx[level].count, qn->needs_ack[level]); - struct lws_vhost *vh = lws_get_vhost(wsi); - uint32_t mtu = vh->quic_mtu ? vh->quic_mtu : 1280; + uint32_t mtu = qn->current_mtu ? qn->current_mtu : 1280; /* Enforce RFC 9000 Anti-Amplification Limit (Section 8.1) for servers */ if (qn->is_server && !qn->address_validated) { if (qn->bytes_sent + mtu > 3 * qn->bytes_received) { lwsl_notice("QUIC TX: Anti-Amplification limit reached! Sent: %llu, Recv: %llu. Blocking send.\n", (unsigned long long)qn->bytes_sent, (unsigned long long)qn->bytes_received); + blocked = 1; break; /* Block sending further datagrams */ } } /* Check congestion window */ if (!qn->pto_probe_needed && !qn->needs_ack[level] && qn->cc_ops && qn->cc_ops->can_send && !qn->cc_ops->can_send(wsi, mtu)) { +#if (_LWS_ENABLED_LOGS & LLL_INFO) LWS_RATELIMIT_DEFINE_STATIC(rl); - lwsl_ratelimit_notice(&rl, 1000000, "QUIC TX: Congestion window full, blocking POLLOUT\n"); + lwsl_ratelimit_info(&rl, 1000000, "QUIC TX: Congestion window full, blocking POLLOUT\n"); +#endif + blocked = 1; break; /* Stop processing sending loops */ } @@ -562,10 +1132,16 @@ rops_handle_POLLOUT_quic(struct lws *wsi) lws_usec_t delay = qn->cc_ops->get_pacing_delay(wsi, mtu); if (delay > 0) { lws_sul_schedule(wsi->a.context, 0, &qn->pacer_sul, lws_quic_pacer_cb, delay); + blocked = 1; break; /* Stop processing sending loops */ } } + /* AEAD Confidentiality Limits Check (RFC 9001 Section 6.6) */ + if (level == LWS_QUIC_LEVEL_APP && qn->tx_packets_since_update > (1ULL << 20)) { + lws_quic_initiate_key_update(wsi); + } + /* We have frames to send at this encryption level! */ uint8_t *p = pkt; uint64_t my_pn = qn->keys[level]->pn_tx++; @@ -580,8 +1156,11 @@ rops_handle_POLLOUT_quic(struct lws *wsi) else *p++ = 0xc0 | 0x20 | 0x01; /* Long Header, Handshake, 2-byte PN */ - /* Version (1) */ - *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x01; + /* Version */ + *p++ = (uint8_t)(qn->version >> 24); + *p++ = (uint8_t)(qn->version >> 16); + *p++ = (uint8_t)(qn->version >> 8); + *p++ = (uint8_t)(qn->version); /* DCID */ *p++ = qn->rem_cid.len; if (qn->rem_cid.len) { memcpy(p, qn->rem_cid.id, qn->rem_cid.len); p += qn->rem_cid.len; } @@ -600,7 +1179,10 @@ rops_handle_POLLOUT_quic(struct lws *wsi) header_len = pn_offset + 2; /* 2-byte PN */ p += 2; /* Skip PN bytes */ } else { - *p++ = 0x40 | 0x01; /* Short Header, 2-byte PN */ + uint8_t sh = 0x40 | 0x01; /* Short Header, Fixed bit, 2-byte PN */ + if (qn->tx_key_phase) + sh |= 0x04; /* Key Phase bit */ + *p++ = sh; /* DCID */ if (qn->rem_cid.len) { memcpy(p, qn->rem_cid.id, qn->rem_cid.len); p += qn->rem_cid.len; } @@ -615,7 +1197,13 @@ rops_handle_POLLOUT_quic(struct lws *wsi) p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), qn->highest_rx_pn[level]); /* Largest Acknowledged */ p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), 0); /* ACK Delay (0 for now) */ p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), 0); /* ACK Range Count */ - p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), 0); /* First ACK Range (Only ACK the single largest PN for now) */ + uint64_t first_ack_range = 0; + uint64_t bm = qn->rx_pn_bitmask[level] >> 1; + while (bm & 1) { + first_ack_range++; + bm >>= 1; + } + p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), first_ack_range); /* First ACK Range */ qn->needs_ack[level] = 0; } @@ -625,51 +1213,68 @@ rops_handle_POLLOUT_quic(struct lws *wsi) /* Check if frame fits in remaining MTU (leaving room for headers and 16-byte AEAD tag) */ size_t frame_header_max_len = 1 + 8 + 8; - if ((size_t)(p - pkt) + frame_header_max_len + 32 >= sizeof(pkt)) + size_t max_udp_payload = qn->current_mtu ? (qn->current_mtu > 48 ? qn->current_mtu - 48 : 1200) : 1200; + if (max_udp_payload > 1200 && !qn->handshake_done) max_udp_payload = 1200; /* RFC 9000 Section 14.1 */ + if (max_udp_payload > sizeof(pkt)) max_udp_payload = sizeof(pkt); + + if ((size_t)(p - pkt) + frame_header_max_len + 32 >= max_udp_payload) break; size_t send_len = f->len; - if ((size_t)(p - pkt) + frame_header_max_len + send_len + 32 > sizeof(pkt)) { - send_len = sizeof(pkt) - (size_t)(p - pkt) - frame_header_max_len - 32; + if ((size_t)(p - pkt) + frame_header_max_len + send_len + 32 > max_udp_payload) { + send_len = max_udp_payload - (size_t)(p - pkt) - frame_header_max_len - 32; } if (send_len == 0 && f->len > 0) break; /* Serialize the frame type */ - *p++ = f->type; + uint8_t type = f->type; + if (send_len < f->len && (type & 0xf8) == LWS_QUIC_FT_STREAM) { + type &= 0xfe; /* Clear FIN bit for intermediate fragment */ + } + *p++ = type; /* Serialize frame-specific headers */ - if (f->type == LWS_QUIC_FT_CRYPTO) { + if (type == LWS_QUIC_FT_CRYPTO) { p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), f->offset); p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), send_len); - } else if ((f->type & 0xf8) == LWS_QUIC_FT_STREAM) { + } else if ((type & 0xf8) == LWS_QUIC_FT_STREAM) { /* Stream ID */ - p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), f->stream_id); - if (f->type & 0x04) /* OFF */ + //lwsl_notice( "QUIC TX: Formatting MAX_STREAM_DATA for stream %llu", (unsigned long long)f->stream_id); + p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), f->stream_id); + if (type & 0x04) /* OFF */ p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), f->offset); - if (f->type & 0x02) /* LEN */ + if (type & 0x02) /* LEN */ + p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), send_len); + } else if ((type & 0xfe) == LWS_QUIC_FT_DATAGRAM) { + if (type & 0x01) /* LEN */ p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), send_len); - } else if (f->type == LWS_QUIC_FT_MAX_DATA || f->type == LWS_QUIC_FT_DATA_BLOCKED) { + } else if (type == LWS_QUIC_FT_MAX_DATA || type == LWS_QUIC_FT_DATA_BLOCKED) { p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), f->limit); - } else if (f->type == LWS_QUIC_FT_MAX_STREAM_DATA || f->type == LWS_QUIC_FT_STREAM_DATA_BLOCKED) { - p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), f->stream_id); + } else if (type == LWS_QUIC_FT_MAX_STREAM_DATA || type == LWS_QUIC_FT_STREAM_DATA_BLOCKED) { + //lwsl_notice( "QUIC TX: Formatting MAX_STREAM_DATA for stream %llu", (unsigned long long)f->stream_id); + p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), f->stream_id); p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), f->limit); - } else if (f->type == LWS_QUIC_FT_RESET_STREAM) { - p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), f->stream_id); + } else if (type == LWS_QUIC_FT_RESET_STREAM) { + //lwsl_notice( "QUIC TX: Formatting MAX_STREAM_DATA for stream %llu", (unsigned long long)f->stream_id); + p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), f->stream_id); p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), f->offset); /* app_err_code */ p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), f->limit); /* final_size */ - } else if (f->type == LWS_QUIC_FT_STOP_SENDING) { - p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), f->stream_id); + } else if (type == LWS_QUIC_FT_STOP_SENDING) { + //lwsl_notice( "QUIC TX: Formatting MAX_STREAM_DATA for stream %llu", (unsigned long long)f->stream_id); + p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), f->stream_id); p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), f->offset); /* app_err_code */ - } else if (f->type == LWS_QUIC_FT_MAX_STREAMS_BIDI || f->type == LWS_QUIC_FT_MAX_STREAMS_UNIDI || - f->type == LWS_QUIC_FT_STREAMS_BLOCKED_BIDI || f->type == LWS_QUIC_FT_STREAMS_BLOCKED_UNIDI) { + } else if (type == LWS_QUIC_FT_MAX_STREAMS_BIDI || type == LWS_QUIC_FT_MAX_STREAMS_UNIDI || + type == LWS_QUIC_FT_STREAMS_BLOCKED_BIDI || type == LWS_QUIC_FT_STREAMS_BLOCKED_UNIDI) { p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), f->limit); - } else if (f->type == LWS_QUIC_FT_NEW_CONNECTION_ID) { - p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), f->stream_id); /* seq */ + } else if (type == LWS_QUIC_FT_NEW_CONNECTION_ID) { + //lwsl_notice( "QUIC TX: Formatting MAX_STREAM_DATA for stream %llu", (unsigned long long)f->stream_id); + p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), f->stream_id); /* seq */ p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), f->offset); /* retire_prior_to */ /* cid + token in data */ - } else if (f->type == LWS_QUIC_FT_RETIRE_CONNECTION_ID) { - p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), f->stream_id); /* seq */ + } else if (type == LWS_QUIC_FT_RETIRE_CONNECTION_ID) { + //lwsl_notice( "QUIC TX: Formatting MAX_STREAM_DATA for stream %llu", (unsigned long long)f->stream_id); + p += lws_quic_write_varint(p, sizeof(pkt) - (size_t)(p - pkt), f->stream_id); /* seq */ } if (send_len) { @@ -690,6 +1295,11 @@ rops_handle_POLLOUT_quic(struct lws *wsi) f_sent->sent_in_pn = my_pn; f_sent->sent_time_us = lws_now_usecs(); f_sent->wire_len = 0; + + /* Clear the FIN bit from intermediate fragment */ + if ((f_sent->type & 0xf8) == LWS_QUIC_FT_STREAM) + f_sent->type &= 0xfe; + lws_dll2_add_tail(&f_sent->list, &qn->in_flight[level]); /* Update original f in pending_tx */ @@ -698,7 +1308,7 @@ rops_handle_POLLOUT_quic(struct lws *wsi) f->data += send_len; /* Schedule PTO timer since we have data in-flight */ - lws_sul_schedule(wsi->a.context, 0, &qn->pto_sul, lws_quic_pto_cb, LWS_QUIC_DEFAULT_PTO_US); + lws_sul_schedule(wsi->a.context, 0, &qn->pto_sul, lws_quic_pto_cb, pto_delay); break; /* We filled the MTU */ } else { @@ -710,7 +1320,7 @@ rops_handle_POLLOUT_quic(struct lws *wsi) lws_dll2_add_tail(&f->list, &qn->in_flight[level]); /* Schedule PTO timer since we have data in-flight */ - lws_sul_schedule(wsi->a.context, 0, &qn->pto_sul, lws_quic_pto_cb, LWS_QUIC_DEFAULT_PTO_US); + lws_sul_schedule(wsi->a.context, 0, &qn->pto_sul, lws_quic_pto_cb, pto_delay); } } lws_end_foreach_dll_safe(d, d1); @@ -719,6 +1329,13 @@ rops_handle_POLLOUT_quic(struct lws *wsi) if (payload_len == 0) continue; + /* Ensure payload is at least 4 bytes for header protection sampling (RFC 9000 Section 5.4.2) */ + if (payload_len < 4) { + memset(p, LWS_QUIC_FT_PADDING, 4 - payload_len); + p += (4 - payload_len); + payload_len = 4; + } + /* pad out to 1200 minimum total tx length for client initial */ if (level == LWS_QUIC_LEVEL_INITIAL && !qn->is_server) { size_t target_payload_len = 1200 - header_len - 16; @@ -729,6 +1346,18 @@ rops_handle_POLLOUT_quic(struct lws *wsi) } } + /* PMTUD: Send a probe if we are searching and don't currently have a probe in flight */ + if (level == LWS_QUIC_LEVEL_APP && qn->pmtud_state == 1 && qn->pmtud_probe_pn == 0) { + size_t target_payload_len = qn->probed_mtu - header_len - 16; + if (payload_len < target_payload_len) { + memset(p, LWS_QUIC_FT_PADDING, target_payload_len - payload_len); + p += (target_payload_len - payload_len); + payload_len = target_payload_len; + qn->pmtud_probe_pn = my_pn; + lwsl_wsi_notice(wsi, "QUIC TX: Sending PMTUD probe %llu (size %d)", (unsigned long long)my_pn, (int)qn->probed_mtu); + } + } + /* Fill in Length for Initial/Handshake packets */ if (level == LWS_QUIC_LEVEL_INITIAL || level == LWS_QUIC_LEVEL_HANDSHAKE) { uint16_t quic_len = (uint16_t)(payload_len + 2 + 16); /* PN (2) + AEAD Tag (16) */ @@ -750,16 +1379,26 @@ rops_handle_POLLOUT_quic(struct lws *wsi) return LWS_HP_RET_BAIL_OK; } - /* 4. Transmit UDP Datagram */ + if (level == LWS_QUIC_LEVEL_APP) + qn->tx_packets_since_update++; - /* DEBUG: Print first 16 bytes sent */ - lwsl_wsi_debug(wsi, "QUIC TX: First 16 bytes sent: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", - pkt[0], pkt[1], pkt[2], pkt[3], pkt[4], pkt[5], pkt[6], pkt[7], pkt[8], pkt[9], pkt[10], pkt[11], pkt[12], pkt[13], pkt[14], pkt[15]); + /* 4. Transmit UDP Datagram */ lws_sockfd_type fd = wsi->mux_substream ? wsi->mux.parent_wsi->desc.sockfd : wsi->desc.sockfd; size_t send_len = (size_t)(p - pkt) + 16; + + /* PMTUD: tag in-flight frames with this packet's wire length so we can track MTU losses */ + struct lws_dll2 *d = qn->in_flight[level].tail; + while (d) { + struct lws_quic_tx_frame *f = lws_container_of(d, struct lws_quic_tx_frame, list); + if (f->sent_in_pn == my_pn) + f->packet_size = (uint16_t)send_len; + else + break; + d = d->prev; + } - lwsl_wsi_debug(wsi, "QUIC TX: Sending %d bytes to fd %d (mux=%d)", (int)send_len, (int)(intptr_t)fd, wsi->mux_substream); + lwsl_wsi_debug(wsi, "QUIC TX TELEMETRY: Sending packet of %d bytes to network (level %d)", (int)send_len, level); /* Fault Injection for dropping UDP packets (simulating packet loss) */ if (lws_fi(&wsi->fic, "quic_tx_drop")) { @@ -807,7 +1446,7 @@ rops_handle_POLLOUT_quic(struct lws *wsi) if (ack_eliciting && qn->cc_ops && qn->cc_ops->on_sent) qn->cc_ops->on_sent(wsi, send_len); - lwsl_wsi_debug(wsi, "QUIC TX: Sent %d bytes, bundled frames into PN %llu", + lwsl_wsi_info(wsi, "QUIC TX: Sent %d bytes, bundled frames into PN %llu", n, (unsigned long long)my_pn); /* @@ -820,15 +1459,113 @@ rops_handle_POLLOUT_quic(struct lws *wsi) qn->pto_probe_needed = 0; /* If we handled all pending crypto/internal frames, give the user a chance to write */ - struct lws *nwsi = lws_get_network_wsi(wsi); - if (qn->handshake_done && wsi->txc.tx_cr > 0 && (!nwsi || nwsi->txc.tx_cr > 0)) { - enum lws_callback_reasons cb_reason = (enum lws_callback_reasons)wsi->role_ops->writeable_cb[lwsi_role_server(wsi)]; - // lwsl_notice("QUIC TX: Calling user writeable callback with reason %d (server=%d)\n", cb_reason, lwsi_role_server(wsi)); - n = user_callback_handle_rxflow(wsi->a.protocol->callback, - wsi, cb_reason, - wsi->user_space, NULL, 0); - if (n < 0) - return LWS_HP_RET_BAIL_DIE; + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + lwsl_info("QUIC TX POLLOUT: handshake_done=%d, tx_cr=%d, nwsi_tx_cr=%d\n", + qn->handshake_done, (int)wsi->txc.tx_cr, (nwsi ? (int)nwsi->txc.tx_cr : 0)); + if (qn && qn->handshake_done) { + if (lws_wsi_txc_check_skint(&wsi->txc, (int32_t)wsi->txc.tx_cr)) + return LWS_HP_RET_DROP_POLLOUT; + if (nwsi && lws_wsi_txc_check_skint(&nwsi->txc, (int32_t)nwsi->txc.tx_cr)) + return LWS_HP_RET_DROP_POLLOUT; + + struct lws **wsi2 = &wsi->mux.child_list; + + { + struct lws *curr = wsi->mux.child_list; + int sanity = 1000; + lwsl_info("QUIC TX POLLOUT: nwsi=%s, tx_cr=%d\n", lws_wsi_tag(wsi), (int)wsi->txc.tx_cr); + while (curr && sanity--) { + lwsl_info("QUIC TX POLLOUT: child: %s, requested_POLLOUT=%d, tx_cr=%d\n", + lws_wsi_tag(curr), curr->mux.requested_POLLOUT, (int)curr->txc.tx_cr); + curr = curr->mux.sibling_list; + } + } + if (*wsi2) { + int sanity = 1000; + do { + struct lws *w, **wa; + + if (!sanity--) { + lwsl_wsi_warn(wsi, "POLLOUT multiplexer loop sanity limit reached, closing"); + return LWS_HP_RET_BAIL_DIE; + } + + wa = &(*wsi2)->mux.sibling_list; + + lwsl_info("QUIC TX POLLOUT: visiting child %s, requested_POLLOUT=%d\n", lws_wsi_tag(*wsi2), (*wsi2)->mux.requested_POLLOUT); + + if (!(*wsi2)->mux.requested_POLLOUT) + goto next_child; + + w = lws_wsi_mux_move_child_to_tail(wsi2); + if (!w) { + wa = &wsi->mux.child_list; + goto next_child; + } + + wa = wsi2; /* wsi2 is updated to point to the next element by move_child_to_tail */ + w->mux.requested_POLLOUT = 0; + + int32_t usable_credit = w->txc.tx_cr; + if (lws_rops_fidx(w->role_ops, LWS_ROPS_tx_credit)) { + usable_credit = lws_rops_func_fidx(w->role_ops, LWS_ROPS_tx_credit). + tx_credit(w, LWSTXCR_US_TO_PEER, 0); + } + if (lws_wsi_txc_check_skint(&w->txc, usable_credit)) { + if (!w->quic.tx_blocked_sent) { + struct lws_quic_tx_frame *f_sdb = lws_zalloc(sizeof(*f_sdb), "quic sdb"); + if (f_sdb) { + f_sdb->type = LWS_QUIC_FT_STREAM_DATA_BLOCKED; + f_sdb->stream_id = w->mux.my_sid; + f_sdb->limit = w->quic.qs ? w->quic.qs->tx_offset : 0; + lws_dll2_add_head(&f_sdb->list, &qn->pending_tx[LWS_QUIC_LEVEL_APP]); + } + w->quic.tx_blocked_sent = 1; + lws_callback_on_writable(wsi); // request POLLOUT for nwsi to send the frame + } + goto next_child; + } + + lwsl_info("QUIC TX POLLOUT: calling perform_user_POLLOUT/lws_callback_as_writeable for child %s\n", lws_wsi_tag(w)); + if (lws_rops_fidx(w->role_ops, LWS_ROPS_perform_user_POLLOUT)) { + if (lws_rops_func_fidx(w->role_ops, LWS_ROPS_perform_user_POLLOUT). + perform_user_POLLOUT(w) == -1) { + lwsl_wsi_notice(w, "QUIC TX: child perform_user_POLLOUT failed, closing"); + lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS, "quic child write fail"); + wa = &wsi->mux.child_list; + } + } else { + if (lws_callback_as_writeable(w)) { + lwsl_wsi_notice(w, "QUIC TX: child writeable callback failed, closing"); + lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS, "quic child write fail"); + wa = &wsi->mux.child_list; + } + } +next_child: + wsi2 = wa; + } while (wsi2 && *wsi2 && wsi->txc.tx_cr > 0 && (!nwsi || nwsi->txc.tx_cr > 0)); + } + + lwsl_info("QUIC TX POLLOUT: calling lws_wsi_mux_action_pending_writeable_reqs\n"); + + int can_process_children = (qn->handshake_done && wsi->txc.tx_cr > 0 && (!nwsi || nwsi->txc.tx_cr > 0)); + int have_pending_tx = 0; + for (level = 0; level < LWS_QUIC_LEVEL_COUNT; level++) { + if (qn->pending_tx[level].count) { + have_pending_tx = 1; + break; + } + } + + if (blocked || (!have_pending_tx && !can_process_children)) { + /* We are blocked by QUIC limits, or have nothing to send and children can't write. + * Stop asking the OS for POLLOUT. We will re-enable it when POLLIN brings ACKs. */ + if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) + return LWS_HP_RET_BAIL_DIE; + } else { + if (lws_wsi_mux_action_pending_writeable_reqs(wsi)) + return LWS_HP_RET_BAIL_DIE; + } } return LWS_HP_RET_DROP_POLLOUT; @@ -838,53 +1575,57 @@ static int rops_write_role_protocol_quic(struct lws *wsi, unsigned char *buf, size_t len, enum lws_write_protocol *wp) { - struct lws_quic_netconn *qn = wsi->quic.qn; - struct lws *nwsi = lws_get_network_wsi(wsi); + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + struct lws_quic_netconn *qn = nwsi ? nwsi->quic.qn : wsi->quic.qn; struct lws_quic_tx_frame *f; if (!qn) return -1; + if (len == 0 && !((*wp) & LWS_WRITE_H2_STREAM_END)) { + return 0; + } + /* Enforce stream and connection flow control limits */ - if (wsi->txc.tx_cr <= 0 || (nwsi && nwsi->txc.tx_cr <= 0)) { - int did_enqueue = 0; - if (wsi->txc.tx_cr <= 0 && !wsi->quic.tx_blocked_sent) { - /* Generate STREAM_DATA_BLOCKED */ - struct lws_quic_tx_frame *f_sdb = lws_zalloc(sizeof(*f_sdb), "quic sdb"); - if (f_sdb) { - f_sdb->type = LWS_QUIC_FT_STREAM_DATA_BLOCKED; - f_sdb->stream_id = wsi->mux.my_sid; - f_sdb->limit = wsi->quic.tx_stream_offset; - lws_dll2_add_tail(&f_sdb->list, &qn->pending_tx[LWS_QUIC_LEVEL_APP]); + if (len > 0) { + lwsl_info("QUIC TX WRITE: wsi->txc.tx_cr=%d, nwsi->txc.tx_cr=%d\n", (int)wsi->txc.tx_cr, nwsi ? (int)nwsi->txc.tx_cr : -1); + if (wsi->txc.tx_cr < (int)len || wsi->txc.tx_cr <= 0 || + (nwsi && (nwsi->txc.tx_cr < (int)len || nwsi->txc.tx_cr <= 0))) { + int did_enqueue = 0; + if ((wsi->txc.tx_cr < (int)len || wsi->txc.tx_cr <= 0) && !wsi->quic.tx_blocked_sent) { + /* Generate STREAM_DATA_BLOCKED */ + struct lws_quic_tx_frame *f_sdb = lws_zalloc(sizeof(*f_sdb), "quic sdb"); + if (f_sdb) { + f_sdb->type = LWS_QUIC_FT_STREAM_DATA_BLOCKED; + f_sdb->stream_id = wsi->mux.my_sid; + f_sdb->limit = wsi->quic.qs ? wsi->quic.qs->tx_offset : 0; + lws_dll2_add_head(&f_sdb->list, &qn->pending_tx[LWS_QUIC_LEVEL_APP]); + } + wsi->quic.tx_blocked_sent = 1; + did_enqueue = 1; } - wsi->quic.tx_blocked_sent = 1; - did_enqueue = 1; - } - if (nwsi && nwsi != wsi && nwsi->txc.tx_cr <= 0 && !nwsi->quic.tx_blocked_sent) { - /* Generate DATA_BLOCKED */ - struct lws_quic_tx_frame *f_db = lws_zalloc(sizeof(*f_db), "quic db"); - if (f_db) { - f_db->type = LWS_QUIC_FT_DATA_BLOCKED; - f_db->limit = qn->tx_conn_offset; - lws_dll2_add_tail(&f_db->list, &qn->pending_tx[LWS_QUIC_LEVEL_APP]); + if (nwsi && nwsi != wsi && (nwsi->txc.tx_cr < (int)len || nwsi->txc.tx_cr <= 0) && !nwsi->quic.tx_blocked_sent) { + /* Generate DATA_BLOCKED */ + struct lws_quic_tx_frame *f_db = lws_zalloc(sizeof(*f_db), "quic db"); + if (f_db) { + f_db->type = LWS_QUIC_FT_DATA_BLOCKED; + f_db->limit = qn->tx_conn_offset; + lws_dll2_add_head(&f_db->list, &qn->pending_tx[LWS_QUIC_LEVEL_APP]); + } + nwsi->quic.tx_blocked_sent = 1; + did_enqueue = 1; } - nwsi->quic.tx_blocked_sent = 1; - did_enqueue = 1; - } - /* Kick output to send these control frames */ - if (did_enqueue) - lws_callback_on_writable(nwsi ? nwsi : wsi); + /* Kick output to send these control frames */ + if (did_enqueue) + lws_callback_on_writable(nwsi ? nwsi : wsi); - return 0; /* Consumed 0 bytes, caller should yield and try again later */ + return 0; /* Consumed 0 bytes, caller should yield and try again later */ + } } - /* Truncate len to available credit if it's too large */ - if (len > (size_t)wsi->txc.tx_cr) - len = (size_t)wsi->txc.tx_cr; - if (nwsi && len > (size_t)nwsi->txc.tx_cr) - len = (size_t)nwsi->txc.tx_cr; + lwsl_info("QUIC TX WRITE: Stream %llu. Requested: %d, Stream tx_cr: %d, Conn tx_cr: %d\n", wsi->quic.qs ? (unsigned long long)wsi->quic.qs->stream_id : 0, (int)len, (int)wsi->txc.tx_cr, nwsi ? (int)nwsi->txc.tx_cr : -1); /* Allocate frame struct + payload buffer natively */ f = lws_zalloc(sizeof(*f) + len, "quic tx frame"); @@ -892,31 +1633,60 @@ rops_write_role_protocol_quic(struct lws *wsi, unsigned char *buf, size_t len, return -1; f->type = LWS_QUIC_FT_STREAM | 0x02 | 0x04; /* STREAM | OFF | LEN */ + if ((*wp) & LWS_WRITE_H2_STREAM_END) { + f->type |= 0x01; /* FIN */ + /* wsi->quic.qs->sent_fin = 1; could do if we had sent_fin flag */ + } f->data = (uint8_t *)&f[1]; f->len = len; /* Copy the user payload */ memcpy(f->data, buf, len); - f->offset = wsi->quic.tx_stream_offset; - wsi->quic.tx_stream_offset += len; - f->stream_id = wsi->mux.my_sid; + if (((*wp) & 0x1f) == LWS_WRITE_QUIC_DATAGRAM) { + /* It's a DATAGRAM frame */ + f->type = LWS_QUIC_FT_DATAGRAM + 1; /* with LEN */ + f->stream_id = 0; /* Datagrams aren't attached to a stream ID */ + f->offset = 0; + lwsl_info("QUIC TX WRITE: Datagram len=%u\n", (unsigned int)f->len); + } else { + if (!wsi->quic.qs) { + lwsl_wsi_err(wsi, "QUIC: Cannot send stream data without a quic stream structure!"); + lws_free(f); + return -1; + } + f->stream_id = wsi->quic.qs->stream_id; + f->offset = wsi->quic.qs->tx_offset; + wsi->quic.qs->tx_offset += len; - /* Deduct credit */ - wsi->txc.tx_cr -= (int)len; - if (nwsi && nwsi != wsi) { - nwsi->txc.tx_cr -= (int)len; - } - if (nwsi) { - qn->tx_conn_offset += len; + lwsl_info("QUIC TX WRITE: stream_id=%llu, offset=%llu, len=%u, fin=%d\n", + (unsigned long long)f->stream_id, (unsigned long long)f->offset, (unsigned int)f->len, (f->type & 0x01)); + + /* Deduct credit */ + wsi->txc.tx_cr -= (int)len; + if (nwsi && nwsi != wsi) { + nwsi->txc.tx_cr -= (int)len; + } + if (nwsi) { + qn->tx_conn_offset += len; + } } wsi->quic.tx_blocked_sent = 0; if (nwsi && nwsi != wsi) nwsi->quic.tx_blocked_sent = 0; - /* Stream frames always go in Application level */ - lws_dll2_add_tail(&f->list, &qn->pending_tx[LWS_QUIC_LEVEL_APP]); + /* Stream frames usually go in Application level, but check for 0-RTT */ + int tx_level = LWS_QUIC_LEVEL_APP; + if (!qn->is_server && !qn->handshake_done && + qn->early_data_status == LWS_0RTT_STATUS_ATTEMPTED && + wsi->quic.qs && wsi->quic.qs->opted_into_early_data) { + tx_level = LWS_QUIC_LEVEL_EARLY; + } + + lwsl_info("QUIC TX: Enqueued STREAM frame for sid %llu, len %d, FIN=%d (level %d)\n", + (unsigned long long)f->stream_id, (int)f->len, (f->type & 0x01), tx_level); + lws_dll2_add_tail(&f->list, &qn->pending_tx[tx_level]); /* Wake up the event loop to send the packet */ lws_callback_on_writable(wsi); @@ -924,6 +1694,7 @@ rops_write_role_protocol_quic(struct lws *wsi, unsigned char *buf, size_t len, return (int)len; } +#if defined(LWS_WITH_CLIENT) static int rops_client_bind_quic(struct lws *wsi, const struct lws_client_connect_info *i) { @@ -935,7 +1706,8 @@ rops_client_bind_quic(struct lws *wsi, const struct lws_client_connect_info *i) return 0; } - if (i->method && !strcmp(i->method, "QUIC")) { + if ((i->method && !strcmp(i->method, "QUIC")) || + (i->alpn && !strcmp(i->alpn, "h3"))) { struct lws_quic_cid dcid; if (!wsi->udp) { @@ -954,9 +1726,19 @@ rops_client_bind_quic(struct lws *wsi, const struct lws_client_connect_info *i) wsi->quic.qn->nwsi = wsi; wsi->quic.qn->is_server = 0; - wsi->quic.qn->version = 1; + wsi->quic.qn->version = LWS_QUIC_VERSION_1; + wsi->quic.qn->max_streams_bidi_local = 1024; + wsi->quic.qn->max_streams_unidi_local = 1024; + + wsi->quic.qn->current_mtu = 1280; + wsi->quic.qn->probed_mtu = 1380; /* first probe size */ + wsi->quic.qn->pmtud_state = 1; /* SEARCHING */ + + if (wsi->a.context->quic_cc_ops) + wsi->quic.qn->cc_ops = wsi->a.context->quic_cc_ops; + else + wsi->quic.qn->cc_ops = &lws_cc_ops_newreno; - wsi->quic.qn->cc_ops = &lws_cc_ops_newreno; if (wsi->quic.qn->cc_ops->init) wsi->quic.qn->cc_ops->init(wsi); @@ -966,6 +1748,10 @@ rops_client_bind_quic(struct lws *wsi, const struct lws_client_connect_info *i) init_cr = 65535; wsi->txc.peer_tx_cr_est = init_cr; wsi->txc.tx_cr = init_cr; + + wsi->quic.qn->rx_max_data = LWS_QUIC_DEFAULT_WINDOW; + wsi->quic.qn->rx_window_size = LWS_QUIC_DEFAULT_WINDOW; + wsi->quic.qn->last_rx_update_us = lws_now_usecs(); /* Generate random CIDs */ dcid.len = 8; @@ -981,11 +1767,60 @@ rops_client_bind_quic(struct lws *wsi, const struct lws_client_connect_info *i) return 1; } + { + uint8_t *tp = wsi->quic.qn->local_tp_buf; + uint8_t *tp_end = tp + sizeof(wsi->quic.qn->local_tp_buf); + +#define LWS_QUIC_WRITE_TP_VARINT(_id, _val) \ + do { \ + int _vlen; \ + if (lws_ptr_diff_size_t(tp_end, tp) < 2) goto tp_overflow2; \ + *tp++ = (_id); \ + _vlen = (int)lws_quic_write_varint(tp + 1, lws_ptr_diff_size_t(tp_end, tp + 1), (_val)); \ + if (!_vlen) goto tp_overflow2; \ + *tp++ = (uint8_t)_vlen; \ + tp += _vlen; \ + } while (0) + +#define LWS_QUIC_WRITE_TP_BUF(_id, _buf, _len) \ + do { \ + if (lws_ptr_diff_size_t(tp_end, tp) < (size_t)(2 + (_len))) goto tp_overflow2; \ + *tp++ = (_id); \ + *tp++ = (uint8_t)(_len); \ + memcpy(tp, (_buf), (_len)); \ + tp += (_len); \ + } while (0) + + LWS_QUIC_WRITE_TP_VARINT(0x04, 1048576); + LWS_QUIC_WRITE_TP_VARINT(0x05, 1048576); + LWS_QUIC_WRITE_TP_VARINT(0x06, 1048576); + LWS_QUIC_WRITE_TP_VARINT(0x07, 1048576); + LWS_QUIC_WRITE_TP_VARINT(0x08, 1024); + LWS_QUIC_WRITE_TP_VARINT(0x09, 1024); + LWS_QUIC_WRITE_TP_VARINT(0x20, 65535); + LWS_QUIC_WRITE_TP_VARINT(0x01, 30000); + + LWS_QUIC_WRITE_TP_BUF(0x0F, wsi->quic.qn->loc_cid.id, wsi->quic.qn->loc_cid.len); + + lws_tls_quic_set_transport_parameters(wsi, wsi->quic.qn->local_tp_buf, (size_t)(tp - wsi->quic.qn->local_tp_buf)); + + goto tp_ok2; +tp_overflow2: + lwsl_wsi_err(wsi, "QUIC TX: tp buffer overflow"); + return 1; +tp_ok2: + ; +#undef LWS_QUIC_WRITE_TP_VARINT +#undef LWS_QUIC_WRITE_TP_BUF + } + lws_role_transition(wsi, LWSIFR_CLIENT, LRS_UNCONNECTED, &role_ops_quic); + lws_callback_on_writable(wsi); return 1; } return 0; } +#endif static int rops_adoption_bind_quic(struct lws *wsi, int type, const char *vh_prot_name) @@ -993,8 +1828,10 @@ rops_adoption_bind_quic(struct lws *wsi, int type, const char *vh_prot_name) if (!(type & LWS_ADOPT_FLAG_UDP)) return 0; - if (wsi->a.vhost && wsi->a.vhost->listen_accept_role && - !strcmp(wsi->a.vhost->listen_accept_role, "quic")) { + if ((wsi->a.vhost && wsi->a.vhost->listen_accept_role && + !strcmp(wsi->a.vhost->listen_accept_role, "quic")) || + (vh_prot_name && !strcmp(vh_prot_name, "quic")) || + (wsi->role_ops == &role_ops_quic)) { #if defined(LWS_WITH_UDP) if (!wsi->udp) { wsi->udp = lws_malloc(sizeof(*wsi->udp), "udp struct"); @@ -1013,6 +1850,13 @@ rops_adoption_bind_quic(struct lws *wsi, int type, const char *vh_prot_name) lws_role_transition(wsi, LWSIFR_SERVER, LRS_ESTABLISHED, &role_ops_quic); lws_bind_protocol(wsi, wsi->a.protocol, __func__); + + if ((type & _LWS_ADOPT_FINISH) && wsi->do_bind) { + wsi->listener = 1; + if (!wsi->listen_list.owner) + lws_dll2_add_tail(&wsi->listen_list, &wsi->a.vhost->listen_wsi); + } + return 1; } return 0; @@ -1021,16 +1865,98 @@ rops_adoption_bind_quic(struct lws *wsi, int type, const char *vh_prot_name) static int rops_callback_on_writable_quic(struct lws *wsi) { - // lwsl_wsi_notice(wsi, "QUIC TX: mux_sub=%d, parent=%s\n", wsi->mux_substream, wsi->mux.parent_wsi ? lws_wsi_tag(wsi->mux.parent_wsi) : "none"); - if (wsi->mux_substream && wsi->mux.parent_wsi) { - wsi->mux.requested_POLLOUT = 1; - if (lws_change_pollfd(wsi->mux.parent_wsi, 0, LWS_POLLOUT)) - return -1; - return 1; /* handled */ + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + + if (wsi->mux.requested_POLLOUT) { + lwsl_info("rops_callback_on_writable_quic: %s already pending writable\n", lws_wsi_tag(wsi)); + } else { + lwsl_info("rops_callback_on_writable_quic: marking %s as pending writable (nwsi=%s)\n", lws_wsi_tag(wsi), lws_wsi_tag(nwsi)); } + + lws_wsi_mux_mark_parents_needing_writeable(wsi); + + /* for network action, act only on the network wsi */ + if (nwsi && nwsi != wsi) + return lws_callback_on_writable(nwsi); + + /* If we are the network wsi but we have a listener parent (shared UDP port), propagate to it */ + if (wsi->mux.parent_wsi) + return lws_callback_on_writable(wsi->mux.parent_wsi); + return 0; /* not handled, let core handle it */ } +void +lws_quic_stream_cleanup(struct lws *wsi) +{ + struct lws *nwsi = lws_get_network_wsi(wsi); + struct lws_quic_netconn *qn = nwsi ? nwsi->quic.qn : NULL; + int i; + + if (!wsi->quic.qs) + return; + + lwsl_info("%s: stream_id %llu\n", __func__, (unsigned long long)wsi->quic.qs->stream_id); + + /* 1. Free RX chunks */ + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, wsi->quic.qs->rx_chunks.head) { + struct lws_quic_rx_chunk *c = lws_container_of(d, struct lws_quic_rx_chunk, list); + lws_dll2_remove(&c->list); + lws_free(c); + } lws_end_foreach_dll_safe(d, d1); + + /* 2. Purge pending and in-flight TX frames for this stream from parent network connection */ + if (qn) { + uint64_t sid = wsi->quic.qs->stream_id; + + /* If we're closing the stream before FINs were exchanged, notify the peer */ + if (!wsi->quic.qs->fin_received || !wsi->quic.qs->fin_delivered) { + /* Send RESET_STREAM to notify the peer that we're abandoning the stream */ + struct lws_quic_tx_frame *f_reset = lws_zalloc(sizeof(*f_reset), "quic reset"); + if (f_reset) { + f_reset->type = LWS_QUIC_FT_RESET_STREAM; + f_reset->stream_id = sid; + f_reset->offset = 0; /* app error code */ + f_reset->limit = wsi->quic.qs->tx_offset; /* final size */ + lws_dll2_add_head(&f_reset->list, &qn->pending_tx[LWS_QUIC_LEVEL_APP]); + } + + /* Send STOP_SENDING to tell peer to stop sending data to us */ + struct lws_quic_tx_frame *f_stop = lws_zalloc(sizeof(*f_stop), "quic stop_sending"); + if (f_stop) { + f_stop->type = LWS_QUIC_FT_STOP_SENDING; + f_stop->stream_id = sid; + f_stop->offset = 0; /* app error code */ + lws_dll2_add_head(&f_stop->list, &qn->pending_tx[LWS_QUIC_LEVEL_APP]); + } + + if (nwsi) lws_callback_on_writable(nwsi); + } + + for (i = 0; i < LWS_QUIC_LEVEL_COUNT; i++) { + /* Purge pending_tx */ + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, qn->pending_tx[i].head) { + struct lws_quic_tx_frame *f = lws_container_of(d, struct lws_quic_tx_frame, list); + if (f->stream_id == sid && f->type != LWS_QUIC_FT_RESET_STREAM && f->type != LWS_QUIC_FT_STOP_SENDING) { + lws_dll2_remove(&f->list); + lws_free(f); + } + } lws_end_foreach_dll_safe(d, d1); + + /* Purge in_flight */ + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, qn->in_flight[i].head) { + struct lws_quic_tx_frame *f = lws_container_of(d, struct lws_quic_tx_frame, list); + if (f->stream_id == sid) { + lws_dll2_remove(&f->list); + lws_free(f); + } + } lws_end_foreach_dll_safe(d, d1); + } + } + + lws_free_set_NULL(wsi->quic.qs); +} + static int rops_close_kill_connection_quic(struct lws *wsi, enum lws_close_status reason) { @@ -1042,8 +1968,16 @@ rops_close_kill_connection_quic(struct lws *wsi, enum lws_close_status reason) if (wsi->mux.child_list) lws_wsi_mux_close_children(wsi, (int)reason); - if (wsi->mux_substream && wsi->mux.parent_wsi) + if (wsi->mux.parent_wsi) { + struct lws *nwsi = wsi->mux.parent_wsi; lws_wsi_mux_sibling_disconnect(wsi); + if (nwsi->mux.child_count == 0) + lws_set_timeout(nwsi, PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, + nwsi->a.vhost->keepalive_timeout ? + nwsi->a.vhost->keepalive_timeout : 5); + } + + lws_quic_stream_cleanup(wsi); if (!qn) { #if defined(LWS_WITH_UDP) @@ -1058,10 +1992,15 @@ rops_close_kill_connection_quic(struct lws *wsi, enum lws_close_status reason) lws_sul_cancel(&qn->pto_sul); lws_sul_cancel(&qn->pacer_sul); +#if defined(LWS_ROLE_H3) + if (wsi->h3.h3n) + lws_free_set_NULL(wsi->h3.h3n); +#endif + for (i = 0; i < LWS_QUIC_LEVEL_COUNT; i++) { /* Free keys */ if (qn->keys[i]) { - lws_free(qn->keys[i]); + lws_quic_keys_destroy(qn->keys[i]); qn->keys[i] = NULL; } @@ -1094,12 +2033,7 @@ rops_close_kill_connection_quic(struct lws *wsi, enum lws_close_status reason) lwsl_debug("QUIC freed frames on level %d: pending %d, in_flight %d\n", i, pend_count, flt_count); } - /* Free RX Stream chunks */ - lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, qn->rx_stream_chunks.head) { - struct lws_quic_rx_chunk *c = lws_container_of(d, struct lws_quic_rx_chunk, list); - lws_dll2_remove(&c->list); - lws_free(c); - } lws_end_foreach_dll_safe(d, d1); + if (qn->cc_state) lws_free_set_NULL(qn->cc_state); @@ -1115,15 +2049,20 @@ rops_close_kill_connection_quic(struct lws *wsi, enum lws_close_status reason) return 0; } -static int +int rops_tx_credit_quic(struct lws *wsi, char peer_to_us, int add) { - struct lws_quic_netconn *qn = wsi->quic.qn; - struct lws *nwsi = lws_get_network_wsi(wsi); + struct lws *nwsi = lws_get_quic_network_wsi(wsi); + struct lws_quic_netconn *qn = nwsi ? nwsi->quic.qn : NULL; int n; - if (!qn) + if (!qn) { + lwsl_notice("rops_tx_credit_quic: qn is NULL! wsi=%s, p1=%s, p2=%s\n", + lws_wsi_tag(wsi), + wsi->mux.parent_wsi ? lws_wsi_tag(wsi->mux.parent_wsi) : "null", + wsi->mux.parent_wsi && wsi->mux.parent_wsi->mux.parent_wsi ? lws_wsi_tag(wsi->mux.parent_wsi->mux.parent_wsi) : "null"); return 0; + } if (add) { if (peer_to_us == LWSTXCR_PEER_TO_US) { @@ -1132,20 +2071,59 @@ rops_tx_credit_quic(struct lws *wsi, char peer_to_us, int add) if (nwsi) nwsi->txc.peer_tx_cr_est += add; - struct lws_quic_tx_frame *f_msd = lws_zalloc(sizeof(*f_msd), "quic msd"); - if (f_msd) { - f_msd->type = LWS_QUIC_FT_MAX_STREAM_DATA; - f_msd->stream_id = wsi->mux.my_sid; - f_msd->limit = (uint64_t)wsi->txc.peer_tx_cr_est; - lws_dll2_add_tail(&f_msd->list, &qn->pending_tx[LWS_QUIC_LEVEL_APP]); + lws_usec_t now = lws_now_usecs(); + + if (wsi->quic.qs) { + wsi->quic.qs->rx_max_data += (uint64_t)(add > 0 ? add : 0); + uint64_t ungranted = wsi->quic.qs->rx_max_data - wsi->quic.qs->highest_rx_offset; + + if (nwsi && nwsi->a.context->quic_tx_credit_cb) { + uint64_t new_win = nwsi->a.context->quic_tx_credit_cb( + wsi, wsi->quic.qs->rx_window_size, (uint64_t)(add > 0 ? add : 0), + (uint64_t)(now - wsi->quic.qs->last_rx_update_us)); + if (new_win > wsi->quic.qs->rx_window_size && new_win <= LWS_QUIC_MAX_WINDOW) { + wsi->quic.qs->rx_max_data += (new_win - wsi->quic.qs->rx_window_size); + wsi->quic.qs->rx_window_size = new_win; + ungranted = wsi->quic.qs->rx_max_data - wsi->quic.qs->highest_rx_offset; + } + } + + if (ungranted < wsi->quic.qs->rx_window_size / 2) { + wsi->quic.qs->last_rx_update_us = now; + struct lws_quic_tx_frame *f_msd = lws_zalloc(sizeof(*f_msd), "quic msd"); + if (f_msd) { + f_msd->type = LWS_QUIC_FT_MAX_STREAM_DATA; + f_msd->stream_id = wsi->mux.my_sid; + f_msd->limit = wsi->quic.qs->rx_max_data; + lws_dll2_add_head(&f_msd->list, &qn->pending_tx[LWS_QUIC_LEVEL_APP]); + } + } } if (nwsi) { - struct lws_quic_tx_frame *f_md = lws_zalloc(sizeof(*f_md), "quic md"); - if (f_md) { - f_md->type = LWS_QUIC_FT_MAX_DATA; - f_md->limit = (uint64_t)nwsi->txc.peer_tx_cr_est; - lws_dll2_add_tail(&f_md->list, &qn->pending_tx[LWS_QUIC_LEVEL_APP]); + qn->rx_max_data += (uint64_t)(add > 0 ? add : 0); + uint64_t ungranted = qn->rx_max_data - qn->highest_rx_offset; + + if (nwsi->a.context->quic_tx_credit_cb) { + uint64_t new_win = nwsi->a.context->quic_tx_credit_cb( + nwsi, qn->rx_window_size, (uint64_t)(add > 0 ? add : 0), + (uint64_t)(now - qn->last_rx_update_us)); + if (new_win > qn->rx_window_size && new_win <= LWS_QUIC_MAX_WINDOW) { + qn->rx_max_data += (new_win - qn->rx_window_size); + qn->rx_window_size = new_win; + ungranted = qn->rx_max_data - qn->highest_rx_offset; + } + } + + if (ungranted < qn->rx_window_size / 2) { + qn->last_rx_update_us = now; + struct lws_quic_tx_frame *f_md = lws_zalloc(sizeof(*f_md), "quic md"); + if (f_md) { + f_md->type = LWS_QUIC_FT_MAX_DATA; + f_md->limit = qn->rx_max_data; + lws_dll2_add_head(&f_md->list, &qn->pending_tx[LWS_QUIC_LEVEL_APP]); + } + lws_callback_on_writable(nwsi); } } @@ -1155,37 +2133,221 @@ rops_tx_credit_quic(struct lws *wsi, char peer_to_us, int add) /* We're being told we can write an additional "add" bytes to the peer */ wsi->txc.tx_cr += add; - if (nwsi) + wsi->quic.tx_blocked_sent = 0; + if (nwsi && nwsi != wsi) { nwsi->txc.tx_cr += add; + nwsi->quic.tx_blocked_sent = 0; + } /* Unblock if blocked */ - if (wsi->txc.tx_cr > 0) + if (wsi->txc.tx_cr > 0) { + struct lws *w = wsi->mux.child_list; + lws_callback_on_writable(wsi); + while (w) { + lws_callback_on_writable(w); + w = w->mux.sibling_list; + } + } return 0; } - if (peer_to_us == LWSTXCR_US_TO_PEER) - return wsi->txc.tx_cr; /* how much we can write to peer */ + if (peer_to_us == LWSTXCR_US_TO_PEER) { + int cr = wsi->txc.tx_cr; + if (nwsi && nwsi->txc.tx_cr < cr) + cr = nwsi->txc.tx_cr; + + /* + * Accounts for H3 framing overhead (DATA/HEADERS frame type + length: max 9 bytes). + * If we don't subtract this, the caller reads `cr` bytes of payload, and then + * H3 frames it (adding overhead), resulting in a write request of `cr + overhead` bytes, + * which exceeds the flow control window and blocks, causing issues on non-seekable streams. + */ + if (cr > 9) + cr -= 9; + else + cr = 0; + + if (cr < 0) + cr = 0; + lwsl_info("rops_tx_credit_quic: LWSTXCR_US_TO_PEER returning %d (wsi->txc.tx_cr=%d, nwsi->txc.tx_cr=%d)\n", + cr, (int)wsi->txc.tx_cr, nwsi ? (int)nwsi->txc.tx_cr : -1); + return cr; /* how much we can write to peer */ + } n = wsi->txc.peer_tx_cr_est; /* how much peer can write to us */ if (nwsi && n > nwsi->txc.peer_tx_cr_est) n = nwsi->txc.peer_tx_cr_est; + lwsl_info("rops_tx_credit_quic: returning %d\n", n); return n; } +static int +rops_alpn_negotiated_quic(struct lws *wsi, const char *alpn) +{ + struct lws *nwsi; + const struct lws_role_ops *role; + + if (strcmp(alpn, "h3") && strcmp(alpn, "lws-quic")) + return 0; + + lwsl_notice("ENTER rops_alpn_negotiated_quic: wsi=%p\n", wsi); + + lwsl_wsi_notice(wsi, "QUIC negotiated %s, migrating network connection to new wsi", alpn); + + role = lws_role_by_name(alpn); + if (!role) { + role = &role_ops_quic; + } + + /* If it's already migrated or it's a stream, don't migrate again! */ + if (!wsi->quic.qn || wsi->quic.qn->alpn_migrated) + return 0; + + /* Create the new network WSI */ + nwsi = lws_create_new_server_wsi(wsi->a.vhost, wsi->tsi, 0, "quic_nwsi"); + if (!nwsi) + return 1; + + /* Transfer the socket fd and fds table entry if valid */ + nwsi->desc = wsi->desc; + if (lws_socket_is_valid(wsi->desc.sockfd)) { + struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi]; + + lws_pt_lock(pt, __func__); + if (__remove_wsi_socket_from_fds(wsi)) { + lws_pt_unlock(pt); + lws_close_free_wsi(nwsi, LWS_CLOSE_STATUS_NOSTATUS, "fd table fail"); + return 1; + } + wsi->desc.sockfd = LWS_SOCK_INVALID; + if (__insert_wsi_socket_into_fds(wsi->a.context, nwsi)) { + lws_pt_unlock(pt); + lws_close_free_wsi(nwsi, LWS_CLOSE_STATUS_NOSTATUS, "fd table fail"); + return 1; + } + lws_pt_unlock(pt); + } + + /* Transfer the udp and quic contexts */ +#if defined(LWS_WITH_UDP) + nwsi->udp = wsi->udp; + wsi->udp = NULL; +#endif + nwsi->quic = wsi->quic; + nwsi->txc = wsi->txc; + nwsi->tls = wsi->tls; + nwsi->sa46_peer = wsi->sa46_peer; + memset(&wsi->quic, 0, sizeof(wsi->quic)); + memset(&wsi->tls, 0, sizeof(wsi->tls)); + lws_tls_quic_migrate_wsi(wsi, nwsi); + wsi->quic.qs = lws_zalloc(sizeof(*wsi->quic.qs), "quic stream"); + if (wsi->quic.qs) { + wsi->quic.qs->rx_max_data = LWS_QUIC_DEFAULT_WINDOW; + wsi->quic.qs->rx_window_size = LWS_QUIC_DEFAULT_WINDOW; + wsi->quic.qs->last_rx_update_us = lws_now_usecs(); + } else { + lws_close_free_wsi(nwsi, LWS_CLOSE_STATUS_NOSTATUS, "quic stream oom"); + return 1; + } + + /* Initialize flow control credits for the new child stream */ + int32_t init_cr = nwsi->txc.manual_initial_tx_credit; + if (!init_cr) { + if (nwsi->quic.qn && nwsi->quic.qn->peer_initial_max_stream_data_bidi_remote) + init_cr = (int32_t)nwsi->quic.qn->peer_initial_max_stream_data_bidi_remote; + else + init_cr = 65535; + } + wsi->txc.peer_tx_cr_est = init_cr; + wsi->txc.tx_cr = init_cr; + + lwsl_notice("rops_alpn_negotiated_quic: old_wsi=%p\n", wsi); + lwsl_notice("rops_alpn_negotiated_quic: new_nwsi=%p\n", nwsi); + lwsl_notice("rops_alpn_negotiated_quic: qn=%p\n", nwsi->quic.qn); + + /* Important: the network WSI must point back to itself */ + if (nwsi->quic.qn) + nwsi->quic.qn->nwsi = nwsi; + + /* Setup role and state for nwsi */ + lws_role_transition(nwsi, lwsi_role_client(wsi) ? LWSIFR_CLIENT : LWSIFR_SERVER, LRS_ESTABLISHED, &role_ops_quic); + if (!strcmp(alpn, "h3")) { + nwsi->upgraded_to_http2 = 1; + } + + /* Transition wsi to HTTP/3 and link as a child of nwsi */ + lws_role_transition(wsi, lwsi_role_client(wsi) ? LWSIFR_CLIENT : LWSIFR_SERVER, (!strcmp(alpn, "h3") && lwsi_role_client(wsi)) ? LRS_H2_WAITING_TO_SEND_HEADERS : LRS_ESTABLISHED, role); +#if defined(LWS_ROLE_H3) + if (!strcmp(alpn, "h3")) { + memset(&wsi->h3, 0, sizeof(wsi->h3)); + } +#endif +#if defined(LWS_WITH_CLIENT) + if (!strcmp(alpn, "h3") && lwsi_role_client(wsi)) { + wsi->client_h2_alpn = 1; + wsi->client_mux_migrated = 1; + wsi->hdr_parsing_completed = 0; + } +#endif + + wsi->mux_substream = 1; +#if defined(LWS_WITH_CLIENT) + if (lwsi_role_client(wsi)) + wsi->client_mux_substream = 1; + else + wsi->client_mux_substream = 0; +#endif + nwsi->quic.qn->alpn_migrated = 1; + + /* + * The quic child stream is migrating to be a child of nwsi. + * So we disconnect it from the listener socket first. + */ + /* + * Important: The new network connection nwsi must be in the listener's + * child list so it can receive incoming UDP packets! + * We must get the listener socket BEFORE we disconnect the wsi. + */ + struct lws *listener = wsi->mux.parent_wsi; + + if (wsi->mux.parent_wsi) + lws_wsi_mux_sibling_disconnect(wsi); + + lws_wsi_mux_insert(wsi, nwsi, 0); /* client first request is stream ID 0 */ + lws_set_timeout(nwsi, NO_PENDING_TIMEOUT, 0); + + if (listener) { + nwsi->mux_substream = 1; + nwsi->mux.parent_wsi = listener; + nwsi->mux.sibling_list = listener->mux.child_list; + listener->mux.child_list = nwsi; + listener->mux.child_count++; + } + + /* Inform the H3 role that it negotiated ALPN */ + lws_role_call_alpn_negotiated(wsi, alpn); + + /* We are ready to send headers! */ + lws_callback_on_writable(wsi); + + return 0; +} + static const lws_rops_t rops_table_quic[] = { /* 1 */ { .handle_POLLIN = rops_handle_POLLIN_quic }, /* 2 */ { .handle_POLLOUT = rops_handle_POLLOUT_quic }, - /* 3 */ { .adoption_bind = rops_adoption_bind_quic }, -#if defined(LWS_WITH_CLIENT) - /* 4 */ { .client_bind = rops_client_bind_quic }, -#endif + /* 3 */ { .callback_on_writable = rops_callback_on_writable_quic }, + /* 4 */ { .tx_credit = rops_tx_credit_quic }, /* 5 */ { .write_role_protocol = rops_write_role_protocol_quic }, - /* 6 */ { .callback_on_writable = rops_callback_on_writable_quic }, + /* 6 */ { .alpn_negotiated = rops_alpn_negotiated_quic }, /* 7 */ { .close_kill_connection = rops_close_kill_connection_quic }, - /* 8 */ { .tx_credit = rops_tx_credit_quic }, + /* 8 */ { .adoption_bind = rops_adoption_bind_quic }, +#if defined(LWS_WITH_CLIENT) + /* 9 */ { .client_bind = rops_client_bind_quic }, +#endif }; const struct lws_role_ops role_ops_quic = { @@ -1203,21 +2365,21 @@ const struct lws_role_ops role_ops_quic = { /* LWS_ROPS_handle_POLLOUT */ /* LWS_ROPS_perform_user_POLLOUT */ 0x20, /* LWS_ROPS_callback_on_writable */ - /* LWS_ROPS_tx_credit */ 0x68, + /* LWS_ROPS_tx_credit */ 0x34, /* LWS_ROPS_write_role_protocol */ /* LWS_ROPS_encapsulation_parent */ 0x50, /* LWS_ROPS_alpn_negotiated */ - /* LWS_ROPS_close_via_role_protocol */ 0x00, + /* LWS_ROPS_close_via_role_protocol */ 0x60, /* LWS_ROPS_close_role */ /* LWS_ROPS_close_kill_connection */ 0x07, /* LWS_ROPS_destroy_role */ - /* LWS_ROPS_adoption_bind */ 0x03, + /* LWS_ROPS_adoption_bind */ 0x08, #if defined(LWS_WITH_CLIENT) - /* LWS_ROPS_client_bind */ - /* LWS_ROPS_issue_keepalive */ 0x40, + /* LWS_ROPS_client_bind */ 0x90, + /* LWS_ROPS_issue_keepalive */ #else - /* LWS_ROPS_client_bind */ - /* LWS_ROPS_issue_keepalive */ 0x00, + /* LWS_ROPS_client_bind */ 0x00, + /* LWS_ROPS_issue_keepalive */ #endif }, diff --git a/lib/roles/quic/parse-quic.c b/lib/roles/quic/parse-quic.c index b2102e12cf..55a5491f7e 100644 --- a/lib/roles/quic/parse-quic.c +++ b/lib/roles/quic/parse-quic.c @@ -110,7 +110,7 @@ lws_quic_get_pn_offset(const uint8_t *buf, size_t len, size_t *payload_len) consumed = lws_quic_parse_varint(&buf[pos], len - pos, &token_len); if (!consumed) return 0; pos += consumed; - if (pos + token_len > len) return 0; + if (token_len > len - pos) return 0; pos += (size_t)token_len; } @@ -119,7 +119,7 @@ lws_quic_get_pn_offset(const uint8_t *buf, size_t len, size_t *payload_len) if (!consumed) return 0; pos += consumed; - if (pos + p_len > len) return 0; /* Packet is truncated based on stated length */ + if (p_len > len - pos) return 0; /* Packet is truncated based on stated length */ *payload_len = (size_t)p_len; return pos; /* This is the exact offset where the Packet Number begins */ @@ -174,12 +174,14 @@ lws_quic_write_varint(uint8_t *buf, size_t len, uint64_t val) * it will also flush any previously buffered contiguous chunks. */ void -lws_quic_rx_reassemble(struct lws *nwsi, lws_dll2_owner_t *owner, - uint64_t *expected_offset, uint64_t offset, - uint8_t *buf, size_t len, int is_crypto, int level) +lws_quic_rx_reassemble(struct lws *nwsi, struct lws *wsi_child, struct lws_quic_stream *qs, + uint64_t offset, uint8_t *buf, size_t len, int is_crypto, int level) { + uint64_t *expected_offset = is_crypto ? &nwsi->quic.qn->rx_crypto_offset[level] : &qs->rx_offset; + lws_dll2_owner_t *owner = is_crypto ? &nwsi->quic.qn->rx_crypto_chunks[level] : &qs->rx_chunks; + /* 1. If it's a past or overlapping frame, ignore it (simple version) */ - if (offset + len <= *expected_offset) + if (offset + len <= *expected_offset && !(len == 0 && offset == *expected_offset)) return; if (offset < *expected_offset) { @@ -193,25 +195,70 @@ lws_quic_rx_reassemble(struct lws *nwsi, lws_dll2_owner_t *owner, /* 2. If it's the exact expected piece, deliver it immediately! */ if (offset == *expected_offset) { if (is_crypto) { - lws_tls_quic_rx_crypto(nwsi, level, buf, len); - } else { - /* Application Stream Data */ - /* Wait until we bind the stream to a child wsi to deliver it. - * For our minimal test, we assume nwsi->a.protocol->callback handles it */ - struct lws *child = nwsi; - if (nwsi->mux.child_list) child = nwsi->mux.child_list; - - if (child && child->a.protocol && child->a.protocol->callback) { - enum lws_callback_reasons reason = lwsi_role_client(child) ? + if (lws_tls_quic_rx_crypto(nwsi, level, buf, len) < 0) { + lwsl_wsi_notice(nwsi, "QUIC RX: TLS Crypto processing failed"); + return; /* PROTOCOL_VIOLATION */ + } + if (nwsi && !nwsi->quic.qn) { + nwsi = lws_get_quic_network_wsi(nwsi); + if (nwsi && nwsi->quic.qn) { + expected_offset = &nwsi->quic.qn->rx_crypto_offset[level]; + owner = &nwsi->quic.qn->rx_crypto_chunks[level]; + } + } + } else if (wsi_child) { +#if defined(LWS_ROLE_H3) + lwsl_wsi_info(wsi_child, "QUIC RX: rx_reassemble for stream ID, role_ops=%p, role_ops_h3=%p, len=%d", wsi_child->role_ops, &role_ops_h3, (int)len); + if (wsi_child->role_ops == &role_ops_h3) { + lwsl_wsi_info(wsi_child, "QUIC RX: Delivering %d bytes to H3!", (int)len); + if (lws_h3_rx_stream_data(wsi_child, buf, len)) { + wsi_child = NULL; + } + } else +#endif + if (wsi_child->a.protocol && wsi_child->a.protocol->callback) { + /* Application Stream Data */ + enum lws_callback_reasons reason = lwsi_role_client(wsi_child) ? LWS_CALLBACK_QT_CLIENT_RECEIVE : LWS_CALLBACK_QT_SERVER_RECEIVE; - int n = child->a.protocol->callback(child, reason, child->user_space, buf, len); + int n = wsi_child->a.protocol->callback(wsi_child, reason, wsi_child->user_space, buf, len); if (n == 0) { /* Data consumed by application, replenish rx credit to generate MAX_DATA! */ - lws_wsi_tx_credit(child, LWSTXCR_PEER_TO_US, (int)len); + lws_wsi_tx_credit(wsi_child, LWSTXCR_PEER_TO_US, (int)len); } } + + if (wsi_child && qs && qs->fin_received && *expected_offset + len == qs->rx_final_size && !qs->fin_delivered) { + qs->fin_delivered = 1; +#if defined(LWS_ROLE_H3) + if (wsi_child->role_ops == &role_ops_h3) { + if (!qs->is_unidirectional) { + if (lwsi_role_client(wsi_child)) { +#if defined(LWS_WITH_CLIENT) + wsi_child->client_mux_substream = 1; + if (lws_http_transaction_completed_client(wsi_child)) { + lwsl_info("Transaction completed and wsi closed\n"); + wsi_child = NULL; + } +#endif + } else { +#if defined(LWS_WITH_CLIENT) + wsi_child->client_mux_substream = 0; +#endif + } + } else { + if (wsi_child->h3.type_set && (wsi_child->h3.stream_type == 0x00 || wsi_child->h3.stream_type == 0x02 || wsi_child->h3.stream_type == 0x03)) { + lws_quic_enter_closing_state(nwsi, 0x0104 /* LWS_H3_CLOSED_CRITICAL_STREAM */, 0, 1); + return; + } + } + } +#endif + } } + if (!is_crypto && !wsi_child) + return; + *expected_offset += len; /* 3. Check if this unblocks any previously buffered future chunks! */ @@ -224,19 +271,64 @@ lws_quic_rx_reassemble(struct lws *nwsi, lws_dll2_owner_t *owner, if (c->offset == *expected_offset) { /* We found the next contiguous piece! */ if (is_crypto) { - lws_tls_quic_rx_crypto(nwsi, level, c->data, c->len); - } else { - struct lws *child = nwsi; - if (nwsi->mux.child_list) child = nwsi->mux.child_list; - if (child && child->a.protocol && child->a.protocol->callback) { - enum lws_callback_reasons reason = lwsi_role_client(child) ? + if (lws_tls_quic_rx_crypto(nwsi, level, c->data, c->len) < 0) { + lwsl_wsi_notice(nwsi, "QUIC RX: TLS Crypto processing failed"); + return; /* PROTOCOL_VIOLATION */ + } + if (nwsi && !nwsi->quic.qn) { + nwsi = lws_get_quic_network_wsi(nwsi); + if (nwsi && nwsi->quic.qn) { + expected_offset = &nwsi->quic.qn->rx_crypto_offset[level]; + owner = &nwsi->quic.qn->rx_crypto_chunks[level]; + } + } + } else if (wsi_child) { +#if defined(LWS_ROLE_H3) + if (wsi_child->role_ops == &role_ops_h3) { + lwsl_wsi_info(wsi_child, "QUIC RX: Delivering chunk %d bytes to H3!", (int)c->len); + if (lws_h3_rx_stream_data(wsi_child, c->data, c->len)) { + wsi_child = NULL; + } + } else +#endif + if (wsi_child->a.protocol && wsi_child->a.protocol->callback) { + enum lws_callback_reasons reason = lwsi_role_client(wsi_child) ? LWS_CALLBACK_QT_CLIENT_RECEIVE : LWS_CALLBACK_QT_SERVER_RECEIVE; - int n = child->a.protocol->callback(child, reason, child->user_space, c->data, c->len); + int n = wsi_child->a.protocol->callback(wsi_child, reason, wsi_child->user_space, c->data, c->len); if (n == 0) { /* Data consumed by application, replenish rx credit to generate MAX_DATA! */ - lws_wsi_tx_credit(child, LWSTXCR_PEER_TO_US, (int)c->len); + lws_wsi_tx_credit(wsi_child, LWSTXCR_PEER_TO_US, (int)c->len); } } + + if (wsi_child && qs && qs->fin_received && *expected_offset + c->len == qs->rx_final_size && !qs->fin_delivered) { + qs->fin_delivered = 1; +#if defined(LWS_ROLE_H3) + if (wsi_child->role_ops == &role_ops_h3) { + if (!qs->is_unidirectional) { + if (lwsi_role_client(wsi_child)) { +#if defined(LWS_WITH_CLIENT) + wsi_child->client_mux_substream = 1; + if (lws_http_transaction_completed_client(wsi_child)) { + lwsl_info("Transaction completed and wsi closed\n"); + wsi_child = NULL; + } +#endif + } else { +#if defined(LWS_WITH_CLIENT) + wsi_child->client_mux_substream = 0; +#endif + } + } + } +#endif + } + } + + if (!is_crypto && !wsi_child) { + lws_dll2_remove(&c->list); + lws_free(c); + return; } *expected_offset += c->len; @@ -252,6 +344,16 @@ lws_quic_rx_reassemble(struct lws *nwsi, lws_dll2_owner_t *owner, } /* 4. It's in the future. We must buffer it! */ +#if defined(LWS_WITH_FREERTOS) + if (owner->count >= 16) +#else + if (owner->count >= 64) +#endif + { + lwsl_wsi_notice(nwsi, "QUIC RX: Dropping future chunk, reassembly buffer full"); + return; + } + struct lws_quic_rx_chunk *c = lws_malloc(sizeof(*c) + len, "quic rx chunk"); if (!c) return; /* OOM */ @@ -290,6 +392,24 @@ lws_quic_rx_reassemble(struct lws *nwsi, lws_dll2_owner_t *owner, } } +/* + * Finds a child WSI for a given QUIC Stream ID, or returns NULL. + */ +struct lws * +lws_quic_stream_find(struct lws *nwsi, uint64_t stream_id) +{ + struct lws *wsi_child = nwsi->mux.child_list; + while (wsi_child) { + if (wsi_child->quic.qs && wsi_child->quic.qs->stream_id == stream_id) + return wsi_child; + if (wsi_child->mux.my_sid == stream_id) /* Fallback */ + return wsi_child; + wsi_child = wsi_child->mux.sibling_list; + } + return NULL; +} + + /* * Parses QUIC frames from a decrypted payload and routes them. */ @@ -299,8 +419,21 @@ lws_quic_parse_frames(struct lws *nwsi, int level, uint8_t *payload, size_t payl size_t pos = 0; uint64_t type, offset, len; size_t consumed; + int ack_eliciting = 0; while (pos < payload_len) { + struct lws_quic_netconn *qn; + /* ALPN negotiation during a previous frame might have migrated the network WSI! */ + if (nwsi && !nwsi->quic.qn) { + nwsi = lws_get_network_wsi(nwsi); + } + qn = nwsi ? nwsi->quic.qn : NULL; + + if (qn && qn->is_closing) { + /* If the connection is closing (e.g. critical stream closed), stop parsing frames */ + return -1; + } + /* Parse Frame Type */ consumed = lws_quic_parse_varint(&payload[pos], payload_len - pos, &type); if (!consumed) return -1; @@ -323,7 +456,7 @@ lws_quic_parse_frames(struct lws *nwsi, int level, uint8_t *payload, size_t payl pos += consumed; /* 3. Validation */ - if (pos + len > payload_len) { + if (len > payload_len - pos) { lwsl_wsi_notice(nwsi, "QUIC RX: Truncated CRYPTO frame"); return -1; } @@ -332,8 +465,7 @@ lws_quic_parse_frames(struct lws *nwsi, int level, uint8_t *payload, size_t payl level, (unsigned long long)offset, (unsigned long long)len); /* 4. Action: Pass the TLS handshake data to the OpenSSL QUIC method */ - lws_quic_rx_reassemble(nwsi, &nwsi->quic.qn->rx_crypto_chunks[level], - &nwsi->quic.qn->rx_crypto_offset[level], + lws_quic_rx_reassemble(nwsi, NULL, NULL, offset, &payload[pos], (size_t)len, 1, level); pos += (size_t)len; @@ -363,8 +495,15 @@ lws_quic_parse_frames(struct lws *nwsi, int level, uint8_t *payload, size_t payl if (!consumed) return -1; pos += consumed; + lwsl_wsi_info(nwsi, "QUIC RX TELEMETRY: Parsed ACK frame! Largest = %llu, First Range = %llu", + (unsigned long long)largest_ack, (unsigned long long)first_ack_range); + /* Process the First ACK Range */ uint64_t pn = largest_ack; + if (first_ack_range > pn) { + lws_quic_enter_closing_state(nwsi, LWS_QUIC_ERR_FRAME_ENCODING_ERROR, type, 0); + return -1; + } for (uint64_t i = 0; i <= first_ack_range; i++) { lws_quic_handle_ack(nwsi, level, pn - i); } @@ -382,7 +521,16 @@ lws_quic_parse_frames(struct lws *nwsi, int level, uint8_t *payload, size_t payl if (!consumed) return -1; pos += consumed; + if (gap + 1 > pn) { + lws_quic_enter_closing_state(nwsi, LWS_QUIC_ERR_FRAME_ENCODING_ERROR, type, 0); + return -1; + } pn -= gap + 1; + + if (ack_range > pn) { + lws_quic_enter_closing_state(nwsi, LWS_QUIC_ERR_FRAME_ENCODING_ERROR, type, 0); + return -1; + } for (uint64_t i = 0; i <= ack_range; i++) { lws_quic_handle_ack(nwsi, level, pn - i); } @@ -403,13 +551,35 @@ lws_quic_parse_frames(struct lws *nwsi, int level, uint8_t *payload, size_t payl } case LWS_QUIC_FT_PING: - lwsl_wsi_notice(nwsi, "QUIC RX: Parsed PING frame!"); + lwsl_wsi_info(nwsi, "QUIC RX: Parsed PING frame!"); break; case LWS_QUIC_FT_RESET_STREAM: { uint64_t stream_id, app_err_code, final_size; consumed = lws_quic_parse_varint(&payload[pos], payload_len - pos, &stream_id); if (!consumed) return -1; + + if (!qn) return -1; + int is_peer_initiated = (stream_id & 1) != (qn->is_server ? 1 : 0); + int is_unidirectional = (stream_id & 2); + struct lws *wsi_child = lws_quic_stream_find(nwsi, stream_id); + + if (is_peer_initiated) { + uint64_t limit = is_unidirectional ? qn->max_streams_unidi_local : qn->max_streams_bidi_local; + if ((stream_id >> 2) >= limit) { + lwsl_wsi_notice(nwsi, "QUIC RX: Stream ID %llu exceeds limit", (unsigned long long)stream_id); + lws_quic_enter_closing_state(nwsi, LWS_QUIC_ERR_STREAM_LIMIT_ERROR, type, 0); + return -1; + } + } + + if (!is_peer_initiated) { + if (is_unidirectional || !wsi_child) { + lwsl_wsi_notice(nwsi, "QUIC RX: Invalid RESET_STREAM on stream ID %llu", (unsigned long long)stream_id); + lws_quic_enter_closing_state(nwsi, LWS_QUIC_ERR_STREAM_STATE_ERROR, type, 0); + return -1; + } + } pos += consumed; consumed = lws_quic_parse_varint(&payload[pos], payload_len - pos, &app_err_code); if (!consumed) return -1; @@ -423,6 +593,14 @@ lws_quic_parse_frames(struct lws *nwsi, int level, uint8_t *payload, size_t payl while (child) { if (child->mux.my_sid == stream_id) { lwsl_wsi_notice(child, "QUIC RX: Stream closed by peer via RESET_STREAM"); +#if defined(LWS_ROLE_H3) + if (child->role_ops == &role_ops_h3 && child->quic.qs && child->quic.qs->is_unidirectional) { + if (child->h3.type_set && (child->h3.stream_type == 0x00 || child->h3.stream_type == 0x02 || child->h3.stream_type == 0x03)) { + lws_quic_enter_closing_state(nwsi, 0x0104 /* LWS_H3_CLOSED_CRITICAL_STREAM */, 0, 1); + return -1; + } + } +#endif lws_close_free_wsi(child, LWS_CLOSE_STATUS_ABNORMAL_CLOSE, "quic reset stream"); break; } @@ -431,10 +609,74 @@ lws_quic_parse_frames(struct lws *nwsi, int level, uint8_t *payload, size_t payl break; } + case 0x1c: /* CONNECTION_CLOSE */ + case 0x1d: { /* CONNECTION_CLOSE (Application) */ + uint64_t err_code, frame_type = 0, reason_len; + consumed = lws_quic_parse_varint(&payload[pos], payload_len - pos, &err_code); + if (!consumed) return -1; + pos += consumed; + if (type == 0x1c) { + consumed = lws_quic_parse_varint(&payload[pos], payload_len - pos, &frame_type); + if (!consumed) return -1; + pos += consumed; + } + consumed = lws_quic_parse_varint(&payload[pos], payload_len - pos, &reason_len); + if (!consumed) return -1; + pos += consumed; + if (reason_len > payload_len - pos) return -1; + lwsl_wsi_notice(nwsi, "QUIC RX: CONNECTION_CLOSE (err=%llu, frame_type=%llu, reason_len=%llu)", + (unsigned long long)err_code, (unsigned long long)frame_type, (unsigned long long)reason_len); + + if (reason_len) { + char chunk[128]; + size_t printed = 0; + while (printed < reason_len) { + size_t chunk_len = reason_len - printed; + if (chunk_len > sizeof(chunk) - 1) + chunk_len = sizeof(chunk) - 1; + memcpy(chunk, &payload[pos + printed], chunk_len); + chunk[chunk_len] = '\0'; + lwsl_wsi_notice(nwsi, "QUIC RX REASON: %s", chunk); + printed += chunk_len; + } + } + + pos += (size_t)reason_len; + return -3; /* Terminate parsing and connection cleanly (Peer closed) */ + } + case LWS_QUIC_FT_STOP_SENDING: { uint64_t stream_id, app_err_code; consumed = lws_quic_parse_varint(&payload[pos], payload_len - pos, &stream_id); if (!consumed) return -1; + + if (!qn) return -1; + int is_peer_initiated = (stream_id & 1) != (qn->is_server ? 1 : 0); + int is_unidirectional = (stream_id & 2); + struct lws *wsi_child = lws_quic_stream_find(nwsi, stream_id); + + if (is_peer_initiated) { + uint64_t limit = is_unidirectional ? qn->max_streams_unidi_local : qn->max_streams_bidi_local; + if ((stream_id >> 2) >= limit) { + lwsl_wsi_notice(nwsi, "QUIC RX: Stream ID %llu exceeds limit", (unsigned long long)stream_id); + lws_quic_enter_closing_state(nwsi, LWS_QUIC_ERR_STREAM_LIMIT_ERROR, type, 0); + return -1; + } + } + + if (!is_peer_initiated) { + if (!wsi_child) { + lwsl_wsi_notice(nwsi, "QUIC RX: STOP_SENDING on non-existing stream ID %llu", (unsigned long long)stream_id); + lws_quic_enter_closing_state(nwsi, LWS_QUIC_ERR_STREAM_STATE_ERROR, type, 0); + return -1; + } + } else { + if (is_unidirectional) { + lwsl_wsi_notice(nwsi, "QUIC RX: STOP_SENDING on receive-only stream ID %llu", (unsigned long long)stream_id); + lws_quic_enter_closing_state(nwsi, LWS_QUIC_ERR_STREAM_STATE_ERROR, type, 0); + return -1; + } + } pos += consumed; consumed = lws_quic_parse_varint(&payload[pos], payload_len - pos, &app_err_code); if (!consumed) return -1; @@ -460,11 +702,17 @@ lws_quic_parse_frames(struct lws *nwsi, int level, uint8_t *payload, size_t payl consumed = lws_quic_parse_varint(&payload[pos], payload_len - pos, &max_streams); if (!consumed) return -1; pos += consumed; + if (max_streams > (1ULL << 60)) { + lws_quic_enter_closing_state(nwsi, LWS_QUIC_ERR_FRAME_ENCODING_ERROR, type, 0); + return -1; + } lwsl_wsi_info(nwsi, "QUIC RX: Parsed MAX/BLOCKED STREAMS! max_streams %llu", (unsigned long long)max_streams); - if (type == LWS_QUIC_FT_MAX_STREAMS_BIDI) - nwsi->quic.qn->max_streams_bidi_remote = max_streams; - else if (type == LWS_QUIC_FT_MAX_STREAMS_UNIDI) - nwsi->quic.qn->max_streams_unidi_remote = max_streams; + if (qn) { + if (type == LWS_QUIC_FT_MAX_STREAMS_BIDI) + qn->max_streams_bidi_remote = max_streams; + else if (type == LWS_QUIC_FT_MAX_STREAMS_UNIDI) + qn->max_streams_unidi_remote = max_streams; + } break; } @@ -476,8 +724,16 @@ lws_quic_parse_frames(struct lws *nwsi, int level, uint8_t *payload, size_t payl consumed = lws_quic_parse_varint(&payload[pos], payload_len - pos, &retire_prior_to); if (!consumed) return -1; pos += consumed; + if (retire_prior_to > seq) { + lws_quic_enter_closing_state(nwsi, LWS_QUIC_ERR_FRAME_ENCODING_ERROR, type, 0); + return -1; + } if (pos >= payload_len) return -1; uint8_t cid_len = payload[pos++]; + if (cid_len == 0 || cid_len > 20) { + lws_quic_enter_closing_state(nwsi, LWS_QUIC_ERR_FRAME_ENCODING_ERROR, type, 0); + return -1; + } if (pos + cid_len + 16 > payload_len) return -1; pos += cid_len + 16; lwsl_wsi_info(nwsi, "QUIC RX: Parsed NEW_CONNECTION_ID! seq %llu", (unsigned long long)seq); @@ -495,6 +751,10 @@ lws_quic_parse_frames(struct lws *nwsi, int level, uint8_t *payload, size_t payl case LWS_QUIC_FT_PATH_CHALLENGE: case LWS_QUIC_FT_PATH_RESPONSE: { + if (level != LWS_QUIC_LEVEL_APP) { + lwsl_wsi_notice(nwsi, "QUIC RX: PATH_CHALLENGE/RESPONSE not allowed in non 1-RTT packets"); + return -2; /* PROTOCOL_VIOLATION */ + } if (pos + 8 > payload_len) return -1; uint8_t path_data[8]; memcpy(path_data, &payload[pos], 8); @@ -511,7 +771,86 @@ lws_quic_parse_frames(struct lws *nwsi, int level, uint8_t *payload, size_t payl lws_dll2_add_tail(&f_pr->list, &nwsi->quic.qn->pending_tx[level]); lws_callback_on_writable(nwsi); } + } else if (type == LWS_QUIC_FT_PATH_RESPONSE && nwsi->quic.qn) { + if (nwsi->quic.qn->path_challenge_pending && !memcmp(nwsi->quic.qn->path_challenge, path_data, 8)) { + lwsl_wsi_notice(nwsi, "QUIC RX: Path validated via PATH_RESPONSE!"); + nwsi->quic.qn->address_validated = 1; + nwsi->quic.qn->path_challenge_pending = 0; + } else { + lwsl_wsi_notice(nwsi, "QUIC RX: Spurious or mismatched PATH_RESPONSE, ignoring"); + } + } + break; + } + + case 0x1e: /* HANDSHAKE_DONE */ + lwsl_wsi_info(nwsi, "QUIC RX: Parsed HANDSHAKE_DONE!"); + if (nwsi->quic.qn && nwsi->quic.qn->is_server) { + /* Clients SHOULD NOT send HANDSHAKE_DONE. Server MUST treat as PROTOCOL_VIOLATION */ + lws_quic_enter_closing_state(nwsi, LWS_QUIC_ERR_PROTOCOL_VIOLATION, type, 0); + return -1; + } + break; + + case 0x07: { /* NEW_TOKEN */ + uint64_t token_len; + consumed = lws_quic_parse_varint(&payload[pos], payload_len - pos, &token_len); + if (!consumed) return -1; + pos += consumed; + if (token_len > payload_len - pos) return -1; + pos += (size_t)token_len; + lwsl_wsi_info(nwsi, "QUIC RX: Parsed NEW_TOKEN! length %llu", (unsigned long long)token_len); + if (nwsi->quic.qn && nwsi->quic.qn->is_server) { + /* Server MUST treat NEW_TOKEN from client as PROTOCOL_VIOLATION */ + lws_quic_enter_closing_state(nwsi, LWS_QUIC_ERR_PROTOCOL_VIOLATION, type, 0); + return -1; + } + break; + } + + case LWS_QUIC_FT_DATAGRAM: + case LWS_QUIC_FT_DATAGRAM + 1: { + uint64_t datagram_len; + if (type & 1) { /* with LEN */ + consumed = lws_quic_parse_varint(&payload[pos], payload_len - pos, &datagram_len); + if (!consumed) return -1; + pos += consumed; + } else { + datagram_len = payload_len - pos; + } + if (datagram_len > payload_len - pos) { + lwsl_wsi_notice(nwsi, "QUIC RX: Truncated DATAGRAM frame"); + return -1; + } + lwsl_wsi_info(nwsi, "QUIC RX: Parsed DATAGRAM! len %llu", (unsigned long long)datagram_len); + + /* Pass datagram payload to protocol callback via LWS_CALLBACK_RECEIVE on network wsi + Wait, actually we should use the h3 role's receive processing or just dispatch to user callback. + Since DATAGRAMs in H3 have Quarter Stream IDs, we will just queue them to the nwsi rx chunks, + or just invoke a direct callback. + For now, lws_quic_rx_reassemble to the network wsi's own rx_chunks if it was supported, + but network wsi doesn't have a stream. Let's just create an rx_chunk on nwsi if we can, + or directly call LWS_CALLBACK_RECEIVE if possible. We will call the user callback directly. */ + if (nwsi->role_ops && nwsi->a.protocol && nwsi->a.protocol->callback) { + /* Note: WebTransport datagrams will be dispatched inside H3 role */ + /* We can push this data to a special list or just call rxflow right away */ + if (lws_rops_fidx(nwsi->role_ops, LWS_ROPS_handle_POLLIN)) { + /* Create a dummy rx chunk on a special nwsi datagram queue? + Actually it's better to just call LWS_CALLBACK_RECEIVE on nwsi here. */ + /* But to keep rx flow control, let's just dispatch it. */ + } + /* For now, direct callback. H3 will intercept this in its rxflow/rx handling */ + /* Let's set a flag or just call LWS_CALLBACK_RECEIVE with a new reason, + * but LWS_CALLBACK_RECEIVE_CLIENT_HTTP works. */ + nwsi->quic.qn->rx_packets_since_update++; /* Just a dummy increment */ + + /* A better way: store in nwsi->quic.qn->rx_crypto_chunks[LWS_QUIC_LEVEL_APP] for datagrams? No. */ + /* We will call lws_quic_rx_reassemble but with wsi_child = nwsi, and qs = NULL? + * Yes, let's add a datagram callback or just use lws_quic_rx_reassemble with is_crypto=2 */ + lws_quic_rx_reassemble(nwsi, nwsi, NULL, 0, &payload[pos], (size_t)datagram_len, 2, LWS_QUIC_LEVEL_APP); } + + pos += (size_t)datagram_len; break; } @@ -522,6 +861,17 @@ lws_quic_parse_frames(struct lws *nwsi, int level, uint8_t *payload, size_t payl if (!consumed) return -1; pos += consumed; + int is_peer_initiated = (stream_id & 1) != (qn->is_server ? 1 : 0); + int is_unidirectional = (stream_id & 2); + if (is_peer_initiated) { + uint64_t limit = is_unidirectional ? qn->max_streams_unidi_local : qn->max_streams_bidi_local; + if ((stream_id >> 2) >= limit) { + lwsl_wsi_notice(nwsi, "QUIC RX: Stream ID %llu exceeds limit", (unsigned long long)stream_id); + lws_quic_enter_closing_state(nwsi, LWS_QUIC_ERR_STREAM_LIMIT_ERROR, type, 0); + return -1; + } + } + if (type & 0x04) { /* OFF */ consumed = lws_quic_parse_varint(&payload[pos], payload_len - pos, &offset); if (!consumed) return -1; @@ -538,62 +888,146 @@ lws_quic_parse_frames(struct lws *nwsi, int level, uint8_t *payload, size_t payl len = payload_len - pos; } - if (pos + len > payload_len) { + if (len > payload_len - pos) { lwsl_wsi_notice(nwsi, "QUIC RX: Truncated STREAM frame"); return -1; } - lwsl_wsi_info(nwsi, "QUIC RX: Parsed STREAM frame! id %llu, offset %llu, len %llu", - (unsigned long long)stream_id, (unsigned long long)offset, (unsigned long long)len); - /* Deliver stream data via Reassembly Buffer */ - if (len) { - lws_quic_rx_reassemble(nwsi, &nwsi->quic.qn->rx_stream_chunks, - &nwsi->quic.qn->rx_stream_offset, - offset, &payload[pos], (size_t)len, 0, level); + + int fin = (type & 0x01) ? 1 : 0; + lwsl_wsi_info(nwsi, "QUIC RX: Parsed STREAM! id %llu, off %llu, len %llu, fin %d", + (unsigned long long)stream_id, (unsigned long long)offset, (unsigned long long)len, fin); + + is_unidirectional = (stream_id & 2); + + struct lws *wsi_child = lws_quic_stream_find(nwsi, stream_id); + + int is_locally_initiated = lwsi_role_client(nwsi) ? !(stream_id & 1) : (stream_id & 1); + + if (is_locally_initiated) { + if (is_unidirectional || !wsi_child) { + lwsl_wsi_notice(nwsi, "QUIC RX: Invalid STREAM frame on stream ID %llu (is_locally_initiated=1)", (unsigned long long)stream_id); + /* RFC 9000 19.8: A receiver MUST terminate the connection with STREAM_STATE_ERROR if it receives a STREAM frame for a locally-initiated stream that has not yet been created, or for a send-only stream */ + lws_quic_enter_closing_state(nwsi, LWS_QUIC_ERR_STREAM_STATE_ERROR, type, 0); + return -1; + } } - pos += (size_t)len; - break; - } else if (type == LWS_QUIC_FT_ACK || type == 0x03 /* ACK with ECN */) { - uint64_t largest_ack, ack_delay, ack_range_count, first_ack_range; - consumed = lws_quic_parse_varint(&payload[pos], payload_len - pos, &largest_ack); - if (!consumed) return -1; - pos += consumed; - consumed = lws_quic_parse_varint(&payload[pos], payload_len - pos, &ack_delay); - if (!consumed) return -1; - pos += consumed; - consumed = lws_quic_parse_varint(&payload[pos], payload_len - pos, &ack_range_count); - if (!consumed) return -1; - pos += consumed; - consumed = lws_quic_parse_varint(&payload[pos], payload_len - pos, &first_ack_range); - if (!consumed) return -1; - pos += consumed; + if (!wsi_child) { + /* Peer initiated a new stream. We must create a child WSI! */ + wsi_child = lws_create_new_server_wsi(nwsi->a.vhost, nwsi->tsi, LWSLCG_WSI_MUX, "quic stream"); + if (!wsi_child) return -1; + + lws_wsi_mux_insert(wsi_child, nwsi, (unsigned int)stream_id); + wsi_child->mux.my_sid = (unsigned int)stream_id; + + /* Inherit the role from the network WSI, but use H3 role if H3 ALPN was negotiated */ +#if defined(LWS_ROLE_H3) + lws_role_transition(wsi_child, lwsi_role_client(nwsi) ? LWSIFR_CLIENT : LWSIFR_SERVER, LRS_ESTABLISHED, nwsi->h3.h3n ? &role_ops_h3 : nwsi->role_ops); +#else + lws_role_transition(wsi_child, lwsi_role_client(nwsi) ? LWSIFR_CLIENT : LWSIFR_SERVER, LRS_ESTABLISHED, nwsi->role_ops); +#endif + + wsi_child->quic.qs = lws_zalloc(sizeof(*wsi_child->quic.qs), "quic stream"); + if (wsi_child->quic.qs) { + wsi_child->quic.qs->wsi = wsi_child; + wsi_child->quic.qs->stream_id = stream_id; + wsi_child->quic.qs->is_unidirectional = (uint8_t)((stream_id & 0x02) != 0 ? 1 : 0); + wsi_child->quic.qs->is_server_initiated = (uint8_t)((stream_id & 0x01) != 0 ? 1 : 0); + wsi_child->quic.qs->rx_max_data = LWS_QUIC_DEFAULT_WINDOW; + wsi_child->quic.qs->rx_window_size = LWS_QUIC_DEFAULT_WINDOW; + wsi_child->quic.qs->last_rx_update_us = lws_now_usecs(); + } else { + lws_close_free_wsi(wsi_child, LWS_CLOSE_STATUS_NOSTATUS, "quic stream oom"); + return -1; + } + +#if defined(LWS_ROLE_H3) + wsi_child->h3.h3n = nwsi->h3.h3n; + wsi_child->h3.qpack_tx_encoder = nwsi->h3.qpack_tx_encoder; +#endif + + if (lwsi_role_client(nwsi)) { +#if defined(LWS_WITH_CLIENT) + if (lwsi_role_client(wsi_child)) + wsi_child->client_mux_substream = 1; + else + wsi_child->client_mux_substream = 0; +#endif + } else + wsi_child->mux_substream = 1; + + /* Bind to protocol */ + wsi_child->a.protocol = nwsi->a.protocol; + if (wsi_child->a.protocol && wsi_child->a.protocol->callback) { + wsi_child->a.protocol->callback(wsi_child, LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED, wsi_child->user_space, NULL, 0); + } - for (uint64_t i = 0; i < ack_range_count; i++) { - uint64_t gap, ack_range; - consumed = lws_quic_parse_varint(&payload[pos], payload_len - pos, &gap); - if (!consumed) return -1; - pos += consumed; - consumed = lws_quic_parse_varint(&payload[pos], payload_len - pos, &ack_range); - if (!consumed) return -1; - pos += consumed; + struct lws_quic_netconn *qn = nwsi->quic.qn; + /* Initialize Stream TX Credit from Peer Transport Parameters */ + int is_bidi = (wsi_child->quic.qs->is_unidirectional == 0); + int is_remote_initiated = (wsi_child->quic.qs->is_server_initiated == nwsi->quic.qn->is_server ? 0 : 1); + if (is_bidi) { + if (is_remote_initiated) { + if (qn->peer_initial_max_stream_data_bidi_remote) { + wsi_child->txc.peer_tx_cr_est = (int32_t)qn->peer_initial_max_stream_data_bidi_remote; + wsi_child->txc.tx_cr = (int32_t)qn->peer_initial_max_stream_data_bidi_remote; + } + } else { + if (qn->peer_initial_max_stream_data_bidi_local) { + wsi_child->txc.peer_tx_cr_est = (int32_t)qn->peer_initial_max_stream_data_bidi_local; + wsi_child->txc.tx_cr = (int32_t)qn->peer_initial_max_stream_data_bidi_local; + } + } + } else { + if (qn->peer_initial_max_stream_data_uni) { + wsi_child->txc.peer_tx_cr_est = (int32_t)qn->peer_initial_max_stream_data_uni; + wsi_child->txc.tx_cr = (int32_t)qn->peer_initial_max_stream_data_uni; + } + } } - if (type == 0x03) { /* ECN */ - uint64_t ect0, ect1, ecn_ce; - consumed = lws_quic_parse_varint(&payload[pos], payload_len - pos, &ect0); - if (!consumed) return -1; - pos += consumed; - consumed = lws_quic_parse_varint(&payload[pos], payload_len - pos, &ect1); - if (!consumed) return -1; - pos += consumed; - consumed = lws_quic_parse_varint(&payload[pos], payload_len - pos, &ecn_ce); - if (!consumed) return -1; - pos += consumed; + /* Dynamic Flow Control Enforcement */ + if (wsi_child && wsi_child->quic.qs) { + uint64_t new_highest = offset + len; + + /* Enforce Stream Limit */ + if (new_highest > wsi_child->quic.qs->rx_max_data || new_highest < offset) { + lwsl_wsi_notice(nwsi, "QUIC RX: Stream offset %llu + len %llu exceeds dynamic stream flow control limit (%llu)", + (unsigned long long)offset, (unsigned long long)len, (unsigned long long)wsi_child->quic.qs->rx_max_data); + lws_quic_enter_closing_state(nwsi, LWS_QUIC_ERR_FLOW_CONTROL_ERROR, type, 0); + return -1; + } + + /* Enforce Connection Limit */ + if (new_highest > wsi_child->quic.qs->highest_rx_offset) { + uint64_t diff = new_highest - wsi_child->quic.qs->highest_rx_offset; + wsi_child->quic.qs->highest_rx_offset = new_highest; + + nwsi->quic.qn->highest_rx_offset += diff; + if (nwsi->quic.qn->highest_rx_offset > nwsi->quic.qn->rx_max_data) { + lwsl_wsi_notice(nwsi, "QUIC RX: Total bytes (%llu) exceeds dynamic connection flow control limit (%llu)", + (unsigned long long)nwsi->quic.qn->highest_rx_offset, (unsigned long long)nwsi->quic.qn->rx_max_data); + lws_quic_enter_closing_state(nwsi, LWS_QUIC_ERR_FLOW_CONTROL_ERROR, type, 0); + return -1; + } + } + } + + /* Deliver stream data via Reassembly Buffer */ + if (wsi_child && wsi_child->quic.qs) { + if (fin) { + wsi_child->quic.qs->fin_received = 1; + wsi_child->quic.qs->rx_final_size = offset + len; + } + if (len || fin) { + lws_quic_rx_reassemble(nwsi, wsi_child, wsi_child->quic.qs, + offset, &payload[pos], (size_t)len, 0, level); + } } - lwsl_wsi_info(nwsi, "QUIC RX: Parsed ACK frame! largest %llu", (unsigned long long)largest_ack); + pos += (size_t)len; break; } else if (type == LWS_QUIC_FT_MAX_DATA || type == LWS_QUIC_FT_DATA_BLOCKED) { uint64_t max_data; @@ -620,16 +1054,49 @@ lws_quic_parse_frames(struct lws *nwsi, int level, uint8_t *payload, size_t payl if (!consumed) return -1; pos += consumed; + int is_peer_initiated = (stream_id & 1) != (qn->is_server ? 1 : 0); + int is_unidirectional = (stream_id & 2); + struct lws *wsi_child = lws_quic_stream_find(nwsi, stream_id); + + if (is_peer_initiated) { + uint64_t limit = is_unidirectional ? qn->max_streams_unidi_local : qn->max_streams_bidi_local; + if ((stream_id >> 2) >= limit) { + lwsl_wsi_notice(nwsi, "QUIC RX: Stream ID %llu exceeds limit", (unsigned long long)stream_id); + lws_quic_enter_closing_state(nwsi, LWS_QUIC_ERR_STREAM_LIMIT_ERROR, type, 0); + return -1; + } + } + + if (!is_peer_initiated) { + if (!wsi_child) { + lwsl_wsi_notice(nwsi, "QUIC RX: %s on non-existing stream ID %llu", + type == LWS_QUIC_FT_MAX_STREAM_DATA ? "MAX_STREAM_DATA" : "STREAM_DATA_BLOCKED", + (unsigned long long)stream_id); + lws_quic_enter_closing_state(nwsi, LWS_QUIC_ERR_STREAM_STATE_ERROR, type, 0); + return -1; + } + } else { + if (is_unidirectional) { + lwsl_wsi_notice(nwsi, "QUIC RX: %s on receive-only stream ID %llu", + type == LWS_QUIC_FT_MAX_STREAM_DATA ? "MAX_STREAM_DATA" : "STREAM_DATA_BLOCKED", + (unsigned long long)stream_id); + lws_quic_enter_closing_state(nwsi, LWS_QUIC_ERR_STREAM_STATE_ERROR, type, 0); + return -1; + } + } + lwsl_wsi_info(nwsi, "QUIC RX: Parsed %s frame! stream_id %llu, max_stream_data %llu", type == LWS_QUIC_FT_MAX_STREAM_DATA ? "MAX_STREAM_DATA" : "STREAM_DATA_BLOCKED", (unsigned long long)stream_id, (unsigned long long)max_stream_data); if (type == LWS_QUIC_FT_MAX_STREAM_DATA) { - /* Assuming stream 0 for now until full mux is implemented */ - int32_t current_max = (int32_t)(nwsi->quic.tx_stream_offset + (uint64_t)nwsi->txc.tx_cr); - int32_t delta = (int32_t)max_stream_data - current_max; - if (delta > 0) - lws_wsi_tx_credit(nwsi, LWSTXCR_US_TO_PEER, delta); + struct lws *child = lws_quic_stream_find(nwsi, stream_id); + if (child) { + int32_t current_max = (int32_t)((child->quic.qs ? child->quic.qs->tx_offset : 0) + (uint64_t)child->txc.tx_cr); + int32_t delta = (int32_t)max_stream_data - current_max; + if (delta > 0) + lws_wsi_tx_credit(child, LWSTXCR_US_TO_PEER, delta); + } } break; } @@ -639,6 +1106,169 @@ lws_quic_parse_frames(struct lws *nwsi, int level, uint8_t *payload, size_t payl /* Unknown frame: we MUST abort parsing because we don't know its length! */ return -1; } + if (type != LWS_QUIC_FT_PADDING && type != LWS_QUIC_FT_ACK && type != LWS_QUIC_FT_ACK_ECN && type != 0x1c && type != 0x1d) { + ack_eliciting = 1; + } + } + + return ack_eliciting; +} + +int +lws_quic_parse_transport_parameters(struct lws *wsi, const uint8_t *buf, size_t len) +{ + struct lws_quic_netconn *qn = wsi->quic.qn; + size_t pos = 0, consumed; + uint64_t param_id, param_len, val; + uint64_t seen_params[64]; + size_t num_seen = 0; + + if (!qn) + return -1; + + int seen_initial_source_cid = 0; + + while (pos < len) { + consumed = lws_quic_parse_varint(&buf[pos], len - pos, ¶m_id); + if (!consumed) return -1; + pos += consumed; + + consumed = lws_quic_parse_varint(&buf[pos], len - pos, ¶m_len); + if (!consumed) return -1; + pos += consumed; + + if (param_len > len - pos) + return -1; + + lwsl_wsi_info(wsi, "QUIC TP: ID 0x%llx, len %llu", (unsigned long long)param_id, (unsigned long long)param_len); + if (param_id >= 4 && param_id <= 7) { + uint64_t v; + if (lws_quic_parse_varint(&buf[pos], param_len, &v) == param_len) + lwsl_wsi_info(wsi, "QUIC TP FLOW CONTROL param 0x%llx = %llu", (unsigned long long)param_id, (unsigned long long)v); + } + + /* Check for duplicates */ + for (size_t i = 0; i < num_seen; i++) { + if (seen_params[i] == param_id) { + lwsl_wsi_err(wsi, "QUIC TP error: Duplicate parameter ID %llu", (unsigned long long)param_id); + return -1; + } + } + if (num_seen < LWS_ARRAY_SIZE(seen_params)) + seen_params[num_seen++] = param_id; + + switch (param_id) { + case 0x0f: /* initial_source_connection_id */ + seen_initial_source_cid = 1; + break; + case 0x00: /* original_destination_connection_id */ + if (qn->is_server) { + /* Client cannot send this */ + lwsl_wsi_err(wsi, "QUIC TP error: Client sent original_destination_connection_id"); + return -1; + } + break; + case 0x03: /* max_udp_payload_size */ + if (lws_quic_parse_varint(&buf[pos], param_len, &val) == param_len) { + if (val < 1200) { + lwsl_wsi_err(wsi, "QUIC TP error: max_udp_payload_size %llu < 1200", (unsigned long long)val); + return -1; + } + } else return -1; + break; + case 0x04: /* initial_max_data */ + if (lws_quic_parse_varint(&buf[pos], param_len, &val) == param_len) { + qn->peer_initial_max_data = val; + struct lws *nwsi = qn->nwsi ? qn->nwsi : wsi; + int64_t diff = (int64_t)val - 65535; + if (diff > 0) { + nwsi->txc.peer_tx_cr_est += (int32_t)diff; + nwsi->txc.tx_cr += (int32_t)diff; + } + } else return -1; + break; + case 0x05: /* initial_max_stream_data_bidi_local */ + if (lws_quic_parse_varint(&buf[pos], param_len, &val) == param_len) { + qn->peer_initial_max_stream_data_bidi_local = val; + } else return -1; + break; + case 0x06: /* initial_max_stream_data_bidi_remote */ + if (lws_quic_parse_varint(&buf[pos], param_len, &val) == param_len) { + qn->peer_initial_max_stream_data_bidi_remote = val; + } else return -1; + break; + case 0x07: /* initial_max_stream_data_uni */ + if (lws_quic_parse_varint(&buf[pos], param_len, &val) == param_len) { + qn->peer_initial_max_stream_data_uni = val; + } else return -1; + break; + case 0x08: /* initial_max_streams_bidi */ + if (lws_quic_parse_varint(&buf[pos], param_len, &val) == param_len) { + qn->max_streams_bidi_remote = val; + } else return -1; + break; + case 0x09: /* initial_max_streams_uni */ + if (lws_quic_parse_varint(&buf[pos], param_len, &val) == param_len) { + qn->max_streams_unidi_remote = val; + } else return -1; + break; + case 0x0a: /* ack_delay_exponent */ + if (lws_quic_parse_varint(&buf[pos], param_len, &val) == param_len) { + if (val > 20) { + lwsl_wsi_err(wsi, "QUIC TP error: ack_delay_exponent %llu > 20", (unsigned long long)val); + return -1; + } + } else return -1; + break; + case 0x20: /* max_datagram_frame_size */ + if (lws_quic_parse_varint(&buf[pos], param_len, &val) == param_len) { + qn->peer_max_datagram_frame_size = val; + } else return -1; + break; + case 0x0b: /* max_ack_delay */ + if (lws_quic_parse_varint(&buf[pos], param_len, &val) == param_len) { + if (val >= 16384) { /* 2^14 */ + lwsl_wsi_err(wsi, "QUIC TP error: max_ack_delay %llu >= 16384", (unsigned long long)val); + return -1; + } + } else return -1; + break; + case 0x0e: /* active_connection_id_limit */ + if (lws_quic_parse_varint(&buf[pos], param_len, &val) == param_len) { + if (val < 2) { + lwsl_wsi_err(wsi, "QUIC TP error: active_connection_id_limit %llu < 2", (unsigned long long)val); + return -1; + } + } else return -1; + break; + case 0x10: /* retry_source_connection_id */ + case 0x02: /* stateless_reset_token */ + case 0x0d: /* preferred_address */ + if (qn->is_server) { + /* Client cannot send these */ + lwsl_wsi_err(wsi, "QUIC TP error: Client sent server-only parameter %llu", (unsigned long long)param_id); + return -1; + } + if (param_id == 0x02 && param_len != 16) { + lwsl_wsi_err(wsi, "QUIC TP error: stateless_reset_token length %llu != 16", (unsigned long long)param_len); + return -1; + } + break; + default: + break; + } + + pos += param_len; + } + + if (pos != len) { + lwsl_wsi_err(wsi, "QUIC TP error: buffer not fully consumed"); + return -1; + } + + if (!seen_initial_source_cid) { + lwsl_wsi_err(wsi, "QUIC TP error: initial_source_connection_id is missing"); + return -1; } return 0; diff --git a/lib/roles/quic/private-lib-roles-quic.h b/lib/roles/quic/private-lib-roles-quic.h index 7db3249e1a..5f05a15a7c 100644 --- a/lib/roles/quic/private-lib-roles-quic.h +++ b/lib/roles/quic/private-lib-roles-quic.h @@ -29,6 +29,12 @@ extern const struct lws_role_ops role_ops_quic; #define LWS_QUIC_MAX_CID_LEN 20 +#define LWS_QUIC_VERSION_1 0x00000001 +#define LWS_QUIC_VERSION_2 0x709a50c4 + +#define LWS_QUIC_DEFAULT_WINDOW (1024 * 1024) +#define LWS_QUIC_MAX_WINDOW (16 * 1024 * 1024) + struct lws_quic_cid { uint8_t id[LWS_QUIC_MAX_CID_LEN]; uint8_t len; @@ -108,8 +114,30 @@ enum lws_quic_frame_type { LWS_QUIC_FT_PATH_CHALLENGE = 0x1a, LWS_QUIC_FT_PATH_RESPONSE = 0x1b, LWS_QUIC_FT_CONNECTION_CLOSE = 0x1c, + LWS_QUIC_FT_CONNECTION_CLOSE_APP = 0x1d, + LWS_QUIC_FT_HANDSHAKE_DONE = 0x1e, + LWS_QUIC_FT_DATAGRAM = 0x30, /* 0x30 and 0x31 (with LEN) */ }; +/* QUIC Transport Error Codes (RFC 9000, Section 20.1) */ +#define LWS_QUIC_ERR_NO_ERROR 0x00 +#define LWS_QUIC_ERR_INTERNAL_ERROR 0x01 +#define LWS_QUIC_ERR_CONNECTION_REFUSED 0x02 +#define LWS_QUIC_ERR_FLOW_CONTROL_ERROR 0x03 +#define LWS_QUIC_ERR_STREAM_LIMIT_ERROR 0x04 +#define LWS_QUIC_ERR_STREAM_STATE_ERROR 0x05 +#define LWS_QUIC_ERR_FINAL_SIZE_ERROR 0x06 +#define LWS_QUIC_ERR_FRAME_ENCODING_ERROR 0x07 +#define LWS_QUIC_ERR_TRANSPORT_PARAMETER_ERROR 0x08 +#define LWS_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR 0x09 +#define LWS_QUIC_ERR_PROTOCOL_VIOLATION 0x0a +#define LWS_QUIC_ERR_INVALID_TOKEN 0x0b +#define LWS_QUIC_ERR_APPLICATION_ERROR 0x0c +#define LWS_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED 0x0d +#define LWS_QUIC_ERR_KEY_UPDATE_ERROR 0x0e +#define LWS_QUIC_ERR_AEAD_LIMIT_REACHED 0x0f +#define LWS_QUIC_ERR_NO_VIABLE_PATH 0x10 + /* * A logical frame queued for transmission or in-flight waiting for ACK. */ @@ -134,6 +162,7 @@ struct lws_quic_tx_frame { uint64_t sent_in_pn; lws_usec_t sent_time_us; size_t wire_len; + uint16_t packet_size; }; struct lws_quic_rx_chunk { @@ -145,15 +174,38 @@ struct lws_quic_rx_chunk { uint8_t *data; /* allocated directly after the struct */ }; -struct lws_cc_ops { - void (*init)(struct lws *nwsi); - void (*on_sent)(struct lws *nwsi, size_t bytes); - void (*on_ack)(struct lws *nwsi, size_t bytes_acked, lws_usec_t rtt); - void (*on_loss)(struct lws *nwsi, size_t bytes_lost); - int (*can_send)(struct lws *nwsi, size_t bytes); - lws_usec_t (*get_pacing_delay)(struct lws *nwsi, size_t bytes_to_send); +/* + * Represents a single QUIC stream (unidirectional or bidirectional). + * Maps 1:1 with a child WSI. + */ +struct lws_quic_stream { + struct lws *wsi; /* The child WSI representing this stream */ + uint64_t stream_id; + + uint64_t rx_offset; + lws_dll2_owner_t rx_chunks; /* struct lws_quic_rx_chunk */ + + uint64_t tx_offset; + /* Frames wait in the nwsi's pending_tx list, not here. + * But we might need to track flow control per stream here. */ + + uint64_t tx_max_data; + uint64_t rx_max_data; + uint64_t rx_window_size; + uint64_t highest_rx_offset; + lws_usec_t last_rx_update_us; + + uint64_t rx_final_size; + uint8_t fin_received:1; + uint8_t fin_delivered:1; + + uint8_t is_unidirectional:1; + uint8_t is_server_initiated:1; + uint8_t opted_into_early_data:1; }; + + struct lws_quic_netconn { struct lws *nwsi; /* the parent UDP network wsi */ @@ -161,6 +213,8 @@ struct lws_quic_netconn { struct lws_quic_cid rem_cid; /* Remote peer's Connection ID */ struct lws_quic_cid orig_dcid; /* Original Destination Connection ID from client */ + uint8_t local_tp_buf[128]; /* buffer for transport parameters */ + /* Array of pointers to lazily allocated key material */ struct lws_quic_keys *keys[LWS_QUIC_LEVEL_COUNT]; @@ -172,6 +226,17 @@ struct lws_quic_netconn { uint64_t max_streams_unidi_local; uint64_t max_streams_unidi_remote; + uint64_t peer_initial_max_data; + uint64_t peer_initial_max_stream_data_bidi_local; + uint64_t peer_initial_max_stream_data_bidi_remote; + uint64_t peer_initial_max_stream_data_uni; + uint64_t peer_max_datagram_frame_size; + + uint64_t rx_max_data; + uint64_t rx_window_size; + uint64_t highest_rx_offset; + lws_usec_t last_rx_update_us; + uint64_t next_stream_id_bidi_local; uint64_t next_stream_id_unidi_local; @@ -186,13 +251,10 @@ struct lws_quic_netconn { uint64_t rx_pn_bitmask[LWS_QUIC_LEVEL_COUNT]; uint8_t needs_ack[LWS_QUIC_LEVEL_COUNT]; - /* RX Reassembly Buffers */ + /* RX Crypto Reassembly Buffers (Streams are handled by child WSIs) */ uint64_t rx_crypto_offset[LWS_QUIC_LEVEL_COUNT]; lws_dll2_owner_t rx_crypto_chunks[LWS_QUIC_LEVEL_COUNT]; - uint64_t rx_stream_offset; - lws_dll2_owner_t rx_stream_chunks; - /* Probe Timeout timer for packet loss detection */ lws_sorted_usec_list_t pto_sul; @@ -214,10 +276,39 @@ struct lws_quic_netconn { uint32_t version; + uint64_t conn_close_err; + size_t crypto_rx_expected_msg_len[4]; + uint8_t highest_rx_level; + uint8_t pto_count; + + /* Key Update Tracking */ + uint64_t tx_packets_since_update; + uint64_t rx_packets_since_update; + + /* DPLPMTUD (RFC 9000 Section 14, RFC 8899) */ + uint32_t current_mtu; + uint32_t probed_mtu; + uint64_t pmtud_probe_pn; + uint16_t consecutive_mtu_losses; + uint8_t pmtud_state; /* 0=BASE, 1=SEARCHING, 2=SEARCH_COMPLETE */ + + /* Path Validation (RFC 9000 Section 8.2) */ + uint8_t path_challenge[8]; + uint8_t path_challenge_pending:1; + uint8_t is_server:1; uint8_t handshake_done:1; + uint8_t tp_parsed:1; + uint8_t alpn_migrated:1; uint8_t pto_probe_needed:1; uint8_t address_validated:1; + uint8_t is_closing:1; + + uint8_t early_data_status; /* enum lws_0rtt_status */ + + uint8_t rx_key_phase:1; + uint8_t tx_key_phase:1; + uint8_t key_update_pending:1; }; extern const struct lws_cc_ops lws_cc_ops_newreno; @@ -228,8 +319,11 @@ lws_quic_derive_initial_keys(struct lws *wsi, const struct lws_quic_cid *dcid); int lws_quic_set_keys(struct lws *wsi, enum lws_tls_quic_secret_type type, const uint8_t *secret, size_t secret_len); +void +lws_quic_keys_destroy(struct lws_quic_keys *keys); + int -lws_quic_update_keys(struct lws *wsi, int is_rx); +lws_quic_update_keys(struct lws_quic_keys *k, int is_rx); int lws_quic_unmask_header(struct lws_quic_keys *keys, uint8_t *packet, size_t packet_len, size_t pn_offset); @@ -258,13 +352,32 @@ void lws_quic_handle_ack(struct lws *nwsi, int level, uint64_t acked_pn); void -lws_quic_rx_reassemble(struct lws *nwsi, lws_dll2_owner_t *owner, uint64_t *expected_offset, uint64_t offset, uint8_t *buf, size_t len, int is_crypto, int level); +lws_quic_discard_keys(struct lws *nwsi, int level); + +void +lws_quic_rx_reassemble(struct lws *nwsi, struct lws *wsi_child, struct lws_quic_stream *qs, + uint64_t offset, uint8_t *buf, size_t len, int is_crypto, int level); + +void +lws_quic_stream_cleanup(struct lws *wsi); + +struct lws * +lws_quic_stream_find(struct lws *nwsi, uint64_t stream_id); + +struct lws * +lws_get_quic_network_wsi(struct lws *wsi); + +void +lws_quic_enter_closing_state(struct lws *wsi, uint64_t err_code, uint64_t frame_type, int is_app_error); + +int +lws_quic_parse_transport_parameters(struct lws *wsi, const uint8_t *buf, size_t len); #define LWS_QUIC_DEFAULT_PTO_US 500000 /* 500ms baseline PTO for early dev */ struct _lws_quic_related { struct lws_quic_netconn *qn; /* malloc'd for root net conn */ - uint64_t tx_stream_offset; + struct lws_quic_stream *qs; /* malloc'd for stream child wsi */ uint8_t initialized:1; uint8_t tx_blocked_sent:1; diff --git a/lib/roles/raw-skt/ops-raw-skt.c b/lib/roles/raw-skt/ops-raw-skt.c index 34825a914d..c2627e2ef7 100644 --- a/lib/roles/raw-skt/ops-raw-skt.c +++ b/lib/roles/raw-skt/ops-raw-skt.c @@ -380,11 +380,7 @@ const struct lws_role_ops role_ops_raw_skt = { /* LWS_ROPS_close_role */ /* LWS_ROPS_close_kill_connection */ 0x00, /* LWS_ROPS_destroy_role */ -#if defined(LWS_WITH_SERVER) /* LWS_ROPS_adoption_bind */ 0x02, -#else - /* LWS_ROPS_adoption_bind */ 0x00, -#endif #if defined(LWS_WITH_CLIENT) /* LWS_ROPS_client_bind */ /* LWS_ROPS_issue_keepalive */ 0x30, diff --git a/lib/roles/ws/client-ws.c b/lib/roles/ws/client-ws.c index f5abae3ab3..7e05fa1ff2 100644 --- a/lib/roles/ws/client-ws.c +++ b/lib/roles/ws/client-ws.c @@ -246,72 +246,68 @@ lws_client_ws_upgrade(struct lws *wsi, const char **cce) char ignore; #endif - if (wsi->client_mux_substream) {/* !!! client ws-over-h2 not there yet */ - lwsl_wsi_warn(wsi, "client ws-over-h2 upgrade not supported yet"); - *cce = "HS: h2 / ws upgrade unsupported"; - goto bail3; - } - - if (wsi->http.ah->http_response == 401) { - lwsl_wsi_warn(wsi, "got bad HTTP response '%ld'", - (long)wsi->http.ah->http_response); - *cce = "HS: ws upgrade unauthorized"; - goto bail3; - } - - if (wsi->http.ah->http_response != 101) { - lwsl_wsi_warn(wsi, "got bad HTTP response '%ld'", - (long)wsi->http.ah->http_response); - *cce = "HS: ws upgrade response not 101"; - goto bail3; - } + if (wsi->client_mux_substream) { + if (wsi->http.ah->http_response != 200) { + lwsl_wsi_warn(wsi, "got bad HTTP response '%ld'", + (long)wsi->http.ah->http_response); + *cce = "HS: ws upgrade response not 200"; + goto bail3; + } + } else { + if (wsi->http.ah->http_response != 101) { + lwsl_wsi_warn(wsi, "got bad HTTP response '%ld'", + (long)wsi->http.ah->http_response); + *cce = "HS: ws upgrade response not 101"; + goto bail3; + } - if (lws_hdr_total_length(wsi, WSI_TOKEN_ACCEPT) == 0) { - lwsl_wsi_info(wsi, "no ACCEPT"); - *cce = "HS: ACCEPT missing"; - goto bail3; - } + if (lws_hdr_total_length(wsi, WSI_TOKEN_ACCEPT) == 0) { + lwsl_wsi_info(wsi, "no ACCEPT"); + *cce = "HS: ACCEPT missing"; + goto bail3; + } - p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE); - if (!p) { - lwsl_wsi_info(wsi, "no UPGRADE"); - *cce = "HS: UPGRADE missing"; - goto bail3; - } - strtolower(p); - if (strcmp(p, "websocket")) { - lwsl_wsi_warn(wsi, "got bad Upgrade header '%s'", p); - *cce = "HS: Upgrade to something other than websocket"; - goto bail3; - } + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE); + if (!p) { + lwsl_wsi_info(wsi, "no UPGRADE"); + *cce = "HS: UPGRADE missing"; + goto bail3; + } + strtolower(p); + if (strcmp(p, "websocket")) { + lwsl_wsi_warn(wsi, "got bad Upgrade header '%s'", p); + *cce = "HS: Upgrade to something other than websocket"; + goto bail3; + } - /* connection: must have "upgrade" */ - - lws_tokenize_init(&ts, buf, LWS_TOKENIZE_F_COMMA_SEP_LIST | - LWS_TOKENIZE_F_MINUS_NONTERM); - n = lws_hdr_copy(wsi, buf, sizeof(buf) - 1, WSI_TOKEN_CONNECTION); - if (n <= 0) /* won't fit, or absent */ - goto bad_conn_format; - ts.len = (unsigned int)n; - - do { - e = lws_tokenize(&ts); - switch (e) { - case LWS_TOKZE_TOKEN: - if (!strncasecmp(ts.token, "upgrade", ts.token_len)) - e = LWS_TOKZE_ENDED; - break; + /* connection: must have "upgrade" */ + + lws_tokenize_init(&ts, buf, LWS_TOKENIZE_F_COMMA_SEP_LIST | + LWS_TOKENIZE_F_MINUS_NONTERM); + n = lws_hdr_copy(wsi, buf, sizeof(buf) - 1, WSI_TOKEN_CONNECTION); + if (n <= 0) /* won't fit, or absent */ + goto bad_conn_format; + ts.len = (unsigned int)n; + + do { + e = lws_tokenize(&ts); + switch (e) { + case LWS_TOKZE_TOKEN: + if (!strncasecmp(ts.token, "upgrade", ts.token_len)) + e = LWS_TOKZE_ENDED; + break; - case LWS_TOKZE_DELIMITER: - break; + case LWS_TOKZE_DELIMITER: + break; - default: /* includes ENDED found by the tokenizer itself */ -bad_conn_format: - lwsl_wsi_info(wsi, "malformed connection '%s'", buf); - *cce = "HS: UPGRADE malformed"; - goto bail3; - } - } while (e > 0); + default: /* includes ENDED found by the tokenizer itself */ + bad_conn_format: + lwsl_wsi_info(wsi, "malformed connection '%s'", buf); + *cce = "HS: UPGRADE malformed"; + goto bail3; + } + } while (e > 0); + } pc = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS); #if defined(_DEBUG) diff --git a/lib/roles/ws/ops-ws.c b/lib/roles/ws/ops-ws.c index 97e6089981..bae7ecb428 100644 --- a/lib/roles/ws/ops-ws.c +++ b/lib/roles/ws/ops-ws.c @@ -40,7 +40,7 @@ lws_ws_proxy_est_cb(lws_sorted_usec_list_t *sul) #else NULL, #endif - wsi->h2_stream_carries_ws)) + wsi->h23_stream_carries_ws)) lws_wsi_close(wsi, LWS_TO_KILL_ASYNC); } #endif @@ -905,7 +905,7 @@ lws_server_init_wsi_for_ws(struct lws *wsi) #else NULL, #endif - wsi->h2_stream_carries_ws)) + wsi->h23_stream_carries_ws)) return 1; } #if defined(LWS_WITH_HTTP_PROXY) @@ -1976,7 +1976,7 @@ rops_write_role_protocol_ws(struct lws *wsi, unsigned char *buf, size_t len, case LWS_WRITE_TEXT: case LWS_WRITE_BINARY: case LWS_WRITE_CONTINUATION: - if (!wsi->h2_stream_carries_ws) { + if (!wsi->h23_stream_carries_ws) { /* * give any active extensions a chance to munge the @@ -2036,7 +2036,7 @@ rops_close_kill_connection_ws(struct lws *wsi, enum lws_close_status reason) { /* deal with ws encapsulation in h2 */ #if defined(LWS_WITH_HTTP2) - if (wsi->mux_substream && wsi->h2_stream_carries_ws) + if (wsi->mux_substream && wsi->h23_stream_carries_ws) return lws_rops_func_fidx(&role_ops_h2, LWS_ROPS_close_kill_connection). close_kill_connection(wsi, reason); diff --git a/lib/roles/ws/server-ws.c b/lib/roles/ws/server-ws.c index 6cc9d55c34..3412467ea7 100644 --- a/lib/roles/ws/server-ws.c +++ b/lib/roles/ws/server-ws.c @@ -316,7 +316,7 @@ lws_process_ws_upgrade2(struct lws *wsi) * Switch roles if we're upgrading away from http */ - if (!wsi->h2_stream_carries_ws) { + if (!wsi->h23_stream_carries_ws) { lws_role_transition(wsi, LWSIFR_SERVER, LRS_ESTABLISHED, &role_ops_ws); @@ -393,7 +393,7 @@ lws_process_ws_upgrade2(struct lws *wsi) /* fallthru */ case 13: #if defined(LWS_WITH_HTTP2) - if (wsi->h2_stream_carries_ws) { + if (wsi->h23_stream_carries_ws) { if (lws_h2_ws_handshake(wsi)) { lwsl_notice("h2 ws handshake failed\n"); return 1; @@ -426,7 +426,7 @@ lws_process_ws_upgrade2(struct lws *wsi) char *uptr = "unknown method", combo[128], dotstar[64]; int l = 14, meth = lws_http_get_uri_and_method(wsi, &uptr, &l); - if (wsi->h2_stream_carries_ws) + if (wsi->h23_stream_carries_ws) wsi->http.request_version = HTTP_VERSION_2; wsi->http.access_log.response = 101; diff --git a/lib/roles/wt/CMakeLists.txt b/lib/roles/wt/CMakeLists.txt new file mode 100644 index 0000000000..7a386c60a0 --- /dev/null +++ b/lib/roles/wt/CMakeLists.txt @@ -0,0 +1,34 @@ +# +# libwebsockets - small server side websockets and web server implementation +# +# Copyright (C) 2010 - 2026 Andy Green +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# + +include_directories(.) + +list(APPEND SOURCES + roles/wt/ops-wt.c) + +# +# Keep explicit parent scope exports at end +# + +exports_to_parent_scope() diff --git a/lib/roles/wt/ops-wt.c b/lib/roles/wt/ops-wt.c new file mode 100644 index 0000000000..5c228b027c --- /dev/null +++ b/lib/roles/wt/ops-wt.c @@ -0,0 +1,223 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2026 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include +#include + +static lws_handling_result_t +rops_handle_POLLIN_wt(struct lws_context_per_thread *pt, struct lws *wsi, + struct lws_pollfd *pollfd) +{ + /* + * Payload receiving for WT streams and Session datagrams is + * typically pushed from the QUIC/H3 layers via LWS_CALLBACK_RECEIVE + * or through lws_buflist. + */ + return LWS_HPI_RET_HANDLED; +} + +static int +rops_write_role_protocol_wt(struct lws *wsi, unsigned char *buf, size_t len, + enum lws_write_protocol *wp) +{ + struct lws *nwsi = lws_get_network_wsi(wsi); + + if (!nwsi) + return -1; + + /* + * If we are the WT Session WSI (the upgraded CONNECT stream), + * writes to this WSI are sent as QUIC DATAGRAMs. + */ + if (wsi->wt.is_session) { + /* Datagram payload requires Quarter Session ID prefix */ + uint8_t qsid_buf[8]; + size_t qsid_len; + uint64_t qsid = wsi->mux.my_sid / 4; + + qsid_len = lws_quic_write_varint(qsid_buf, sizeof(qsid_buf), qsid); + + /* We must insert the quarter session ID at the start. + * Fortunately, LWS_PRE gives us space before 'buf' */ + if (len > 0) { + buf -= qsid_len; + len += qsid_len; + memcpy(buf, qsid_buf, qsid_len); + } + + /* Flag to send as DATAGRAM frame */ + *wp = (enum lws_write_protocol)(((unsigned int)*wp & ~0x1fu) | (unsigned int)LWS_WRITE_QUIC_DATAGRAM); + + return lws_rops_func_fidx(nwsi->role_ops, LWS_ROPS_write_role_protocol). + write_role_protocol(nwsi, buf, len, wp); + } + + /* + * Otherwise it's a WT Child Stream. Just pass it down to QUIC, + * as it maps 1:1 to a QUIC stream. + */ + return lws_rops_func_fidx(nwsi->role_ops, LWS_ROPS_write_role_protocol). + write_role_protocol(wsi, buf, len, wp); +} + +static int +rops_close_kill_connection_wt(struct lws *wsi, enum lws_close_status reason) +{ + if (wsi->wt.wtn) { + lws_free_set_NULL(wsi->wt.wtn); + } + return 0; +} + +LWS_VISIBLE struct lws * +lws_wt_create_stream(struct lws *wsi_session, int unidi) +{ + struct lws *nwsi = lws_get_network_wsi(wsi_session); + struct lws_quic_netconn *qn = nwsi ? nwsi->quic.qn : NULL; + struct lws *cwsi; + + if (!qn || !wsi_session->wt.is_session) + return NULL; + + cwsi = lws_create_new_server_wsi(nwsi->a.vhost, nwsi->tsi, 0, "wt_stream"); + if (!cwsi) + return NULL; + + lws_role_transition(cwsi, LWSIFR_CLIENT, LRS_ESTABLISHED, &role_ops_wt); + cwsi->mux_substream = 1; +#if defined(LWS_WITH_CLIENT) + cwsi->client_mux_substream = 1; +#endif + + cwsi->quic.qs = lws_zalloc(sizeof(*cwsi->quic.qs), "quic stream"); + if (!cwsi->quic.qs) { + lws_close_free_wsi(cwsi, LWS_CLOSE_STATUS_NOSTATUS, "oom"); + return NULL; + } + + cwsi->quic.qs->wsi = cwsi; + + if (unidi) { + cwsi->wt.is_unidi = 1; + cwsi->quic.qs->is_unidirectional = 1; + cwsi->quic.qs->stream_id = qn->next_stream_id_unidi_local; + qn->next_stream_id_unidi_local += 4; + + if (qn->peer_initial_max_stream_data_uni) { + cwsi->txc.tx_cr = (int32_t)qn->peer_initial_max_stream_data_uni; + cwsi->txc.peer_tx_cr_est = (int32_t)qn->peer_initial_max_stream_data_uni; + } else { + cwsi->txc.tx_cr = 65535; + cwsi->txc.peer_tx_cr_est = 65535; + } + } else { + cwsi->wt.is_unidi = 0; + cwsi->quic.qs->is_unidirectional = 0; + cwsi->quic.qs->stream_id = qn->next_stream_id_bidi_local; + qn->next_stream_id_bidi_local += 4; + + if (qn->peer_initial_max_stream_data_bidi_remote) { + cwsi->txc.tx_cr = (int32_t)qn->peer_initial_max_stream_data_bidi_remote; + cwsi->txc.peer_tx_cr_est = (int32_t)qn->peer_initial_max_stream_data_bidi_remote; + } else { + cwsi->txc.tx_cr = 65535; + cwsi->txc.peer_tx_cr_est = 65535; + } + } + + cwsi->quic.qs->is_server_initiated = qn->is_server ? 1u : 0u; + cwsi->quic.qs->rx_max_data = LWS_QUIC_DEFAULT_WINDOW; + cwsi->quic.qs->rx_window_size = LWS_QUIC_DEFAULT_WINDOW; + cwsi->quic.qs->last_rx_update_us = lws_now_usecs(); + + lws_wsi_mux_insert(cwsi, nwsi, (unsigned int)cwsi->quic.qs->stream_id); + cwsi->mux.my_sid = (unsigned int)cwsi->quic.qs->stream_id; + + cwsi->a.protocol = wsi_session->a.protocol; + + /* Send WebTransport Stream Header (Type + Quarter Session ID) */ + { + uint8_t pre[LWS_PRE + 16]; + uint8_t *p = &pre[LWS_PRE]; + size_t hlen = 0; + uint64_t qsid = wsi_session->mux.my_sid / 4; + + hlen += lws_quic_write_varint(p, 16, unidi ? LWS_WT_STREAM_TYPE_UNIDI : LWS_WT_STREAM_TYPE_BIDI); + hlen += lws_quic_write_varint(p + hlen, 16 - hlen, qsid); + + lws_write(cwsi, p, hlen, LWS_WRITE_BINARY | LWS_WRITE_NO_FIN); + } + + return cwsi; +} + +LWS_VISIBLE int +lws_wt_is_session(struct lws *wsi) +{ + return wsi->wt.is_session; +} + +static const lws_rops_t rops_table_wt[] = { + /* 1 */ { .handle_POLLIN = rops_handle_POLLIN_wt }, + /* 2 */ { .write_role_protocol = rops_write_role_protocol_wt }, + /* 3 */ { .close_kill_connection = rops_close_kill_connection_wt }, +}; + +const struct lws_role_ops role_ops_wt = { + /* role name */ "wt", + /* alpn id */ "wt", + + /* rops_table */ rops_table_wt, + /* rops_idx */ { + /* LWS_ROPS_check_upgrades */ + /* LWS_ROPS_pt_init_destroy */ 0x00, + /* LWS_ROPS_init_vhost */ + /* LWS_ROPS_destroy_vhost */ 0x00, + /* LWS_ROPS_service_flag_pending */ + /* LWS_ROPS_handle_POLLIN */ 0x10, + /* LWS_ROPS_handle_POLLOUT */ + /* LWS_ROPS_perform_user_POLLOUT */ 0x00, + /* LWS_ROPS_callback_on_writable */ + /* LWS_ROPS_tx_credit */ 0x00, + /* LWS_ROPS_write_role_protocol */ + /* LWS_ROPS_encapsulation_parent */ 0x02, + /* LWS_ROPS_alpn_negotiated */ + /* LWS_ROPS_close_via_role_protocol */ 0x00, + /* LWS_ROPS_close_role */ + /* LWS_ROPS_close_kill_connection */ 0x30, + /* LWS_ROPS_destroy_role */ + /* LWS_ROPS_adoption_bind */ 0x00, + /* LWS_ROPS_client_bind */ + /* LWS_ROPS_issue_keepalive */ 0x00, + }, + + /* adoption_cb clnt, srv */ { LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED, LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED }, + /* rx_cb clnt, srv */ { LWS_CALLBACK_RECEIVE, LWS_CALLBACK_RECEIVE }, + /* writeable cb clnt, srv */ { LWS_CALLBACK_CLIENT_WRITEABLE, LWS_CALLBACK_SERVER_WRITEABLE }, + /* close cb clnt, srv */ { LWS_CALLBACK_CLOSED, LWS_CALLBACK_CLOSED }, + /* protocol_bind_cb c,s */ { 0, 0 }, + /* protocol_unbind_cb c,s */ { 0, 0 }, + /* file_handle */ 0, +}; diff --git a/lib/roles/wt/private-lib-roles-wt.h b/lib/roles/wt/private-lib-roles-wt.h new file mode 100644 index 0000000000..ba8346d1d6 --- /dev/null +++ b/lib/roles/wt/private-lib-roles-wt.h @@ -0,0 +1,40 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2026 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef _PRIVATE_LIB_ROLES_WT_H_ +#define _PRIVATE_LIB_ROLES_WT_H_ + +extern const struct lws_role_ops role_ops_wt; + +struct lws_wt_netconn { + struct lws *nwsi; /* the parent H3 connection wsi */ +}; + +struct _lws_wt_related { + struct lws_wt_netconn *wtn; /* allocated for session WSI */ + uint8_t is_session:1; + uint8_t is_unidi:1; +}; + +#endif /* _PRIVATE_LIB_ROLES_WT_H_ */ diff --git a/lib/secure-streams/CMakeLists.txt b/lib/secure-streams/CMakeLists.txt index 66237c3372..b7baa8ba68 100644 --- a/lib/secure-streams/CMakeLists.txt +++ b/lib/secure-streams/CMakeLists.txt @@ -47,6 +47,11 @@ if (LWS_WITH_CLIENT) secure-streams/protocols/ss-h2.c ) endif() + if (LWS_ROLE_H3) + list(APPEND SOURCES + secure-streams/protocols/ss-h3.c + ) + endif() if (LWS_ROLE_WS) list(APPEND SOURCES secure-streams/protocols/ss-ws.c diff --git a/lib/secure-streams/policy-json.c b/lib/secure-streams/policy-json.c index fa2674f601..453e33f409 100644 --- a/lib/secure-streams/policy-json.c +++ b/lib/secure-streams/policy-json.c @@ -263,6 +263,7 @@ static uint16_t sizes[] = { static const char * const protonames[] = { "h1", /* LWSSSP_H1 */ "h2", /* LWSSSP_H2 */ + "h3", /* LWSSSP_H3 */ "ws", /* LWSSSP_WS */ "mqtt", /* LWSSSP_MQTT */ "raw", /* LWSSSP_RAW */ diff --git a/lib/secure-streams/private-lib-secure-streams.h b/lib/secure-streams/private-lib-secure-streams.h index 0f11b4ac50..1e70e703d8 100644 --- a/lib/secure-streams/private-lib-secure-streams.h +++ b/lib/secure-streams/private-lib-secure-streams.h @@ -141,6 +141,11 @@ typedef struct lws_ss_handle { uint8_t dummy; #endif } h2; + struct { /* LWSSSP_H3 */ +#if defined(WIN32) || defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) + uint8_t dummy; +#endif + } h3; struct { /* LWSSSP_WS */ #if defined(WIN32) || defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) uint8_t dummy; @@ -727,6 +732,7 @@ lws_ss_proxy_destroy(struct lws_context *cx); extern const struct ss_pcols ss_pcol_h1; extern const struct ss_pcols ss_pcol_h2; +extern const struct ss_pcols ss_pcol_h3; extern const struct ss_pcols ss_pcol_ws; extern const struct ss_pcols ss_pcol_mqtt; extern const struct ss_pcols ss_pcol_raw; diff --git a/lib/secure-streams/protocols/ss-h1.c b/lib/secure-streams/protocols/ss-h1.c index 3181457de3..68836e18bd 100644 --- a/lib/secure-streams/protocols/ss-h1.c +++ b/lib/secure-streams/protocols/ss-h1.c @@ -849,7 +849,8 @@ secstream_h1(struct lws *wsi, enum lws_callback_reasons reason, void *user, * body */ if ((h->policy->protocol == LWSSSP_H1 || - h->policy->protocol == LWSSSP_H2) && + h->policy->protocol == LWSSSP_H2 || + h->policy->protocol == LWSSSP_H3) && h->being_serialized && ( #if defined(LWS_WITH_HTTP_UNCOMMON_HEADERS) || defined(LWS_HTTP_HEADERS_ALL) !strcmp(h->policy->u.http.method, "PUT") || diff --git a/lib/secure-streams/protocols/ss-h2.c b/lib/secure-streams/protocols/ss-h2.c index ae4efb8426..99a81b2a5a 100644 --- a/lib/secure-streams/protocols/ss-h2.c +++ b/lib/secure-streams/protocols/ss-h2.c @@ -198,7 +198,7 @@ secstream_tx_credit_add_h2(lws_ss_handle_t *h, int add) { lwsl_info("%s: %s: add %d\n", __func__, lws_ss_tag(h), add); if (h->wsi) - return lws_h2_update_peer_txcredit(h->wsi, (unsigned int)LWS_H2_STREAM_SID, add); + return lws_wsi_tx_credit(h->wsi, LWSTXCR_PEER_TO_US, add); return 0; } diff --git a/lib/secure-streams/protocols/ss-h3.c b/lib/secure-streams/protocols/ss-h3.c new file mode 100644 index 0000000000..9ba2774fda --- /dev/null +++ b/lib/secure-streams/protocols/ss-h3.c @@ -0,0 +1,228 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2019 - 2026 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include + +extern int +secstream_h1(struct lws *wsi, enum lws_callback_reasons reason, void *user, + void *in, size_t len); + +static int +secstream_h3(struct lws *wsi, enum lws_callback_reasons reason, void *user, + void *in, size_t len) +{ + lws_ss_handle_t *h = (lws_ss_handle_t *)lws_get_opaque_user_data(wsi); + lws_ss_state_return_t r; + int n; + + switch (reason) { + + case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: + + if (!h) + return -1; + +#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API) + if (h->being_serialized) { + /* + * We are the proxy-side SS for a remote client... we + * need to inform the client about the initial tx credit + * to write to it that the remote h3 server set up + */ + lwsl_info("%s: reporting initial tx cr from server %d\n", + __func__, wsi->txc.tx_cr); + ss_proxy_onward_txcr((void *)(h + 1), wsi->txc.tx_cr); + } +#endif + + n = secstream_h1(wsi, reason, user, in, len); + + if (!n && (h->policy->flags & LWSSSPOLF_LONG_POLL)) { + lwsl_notice("%s: h3 client %s entering LONG_POLL\n", + __func__, lws_wsi_tag(wsi)); + /* H3 uses the same long poll as H2 since it maps to the same HTTP stream behavior */ + lws_h2_client_stream_long_poll_rxonly(wsi); + } + return n; + + case LWS_CALLBACK_CLOSED_CLIENT_HTTP: + /* + * Only allow the wsi that the handle believes is representing + * him to report closure up to h1 + */ + if (!h || h->wsi != wsi) + return 0; + + break; + + case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: + + if (!h) + return -1; + + r = 0; + if (h->hanging_som) + r = h->info.rx(ss_to_userobj(h), NULL, 0, LWSSS_FLAG_EOM); + + h->txn_ok = 1; + lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */ + if (h->hanging_som && r == LWSSSSRET_DESTROY_ME) + return _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, &h); + h->hanging_som = 0; + break; + + case LWS_CALLBACK_WSI_TX_CREDIT_GET: + + if (!h) + return -1; + + /* + * The peer has sent us additional tx credit... + */ + lwsl_info("%s: LWS_CALLBACK_WSI_TX_CREDIT_GET: %d\n", + __func__, (int)len); + +#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API) + if (h->being_serialized) + /* we are the proxy-side SS for a remote client */ + ss_proxy_onward_txcr((void *)(h + 1), (int)len); +#endif + break; + + default: + break; + } + + return secstream_h1(wsi, reason, user, in, len); +} + +const struct lws_protocols protocol_secstream_h3 = { + "lws-secstream-h3", + secstream_h3, + 0, 0, 0, NULL, 0 +}; + +/* + * Munge connect info according to protocol-specific considerations... this + * usually means interpreting aux in a protocol-specific way and using the + * pieces at connection setup time, eg, http url pieces. + * + * len bytes of buf can be used for things with scope until after the actual + * connect. + */ + +int +secstream_connect_munge_h3(lws_ss_handle_t *h, char *buf, size_t len, + struct lws_client_connect_info *i, + union lws_ss_contemp *ct) +{ + const char *pbasis = h->policy->u.http.url; + size_t used_in, used_out; + lws_strexp_t exp; + + /* i.path on entry is used to override the policy urlpath if not "" */ + + if (i->path[0]) + pbasis = i->path; + + if (h->policy->flags & LWSSSPOLF_QUIRK_NGHTTP2_END_STREAM) + i->ssl_connection |= LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM; + + if (h->policy->flags & LWSSSPOLF_H2_QUIRK_OVERFLOWS_TXCR) + i->ssl_connection |= LCCSCF_H2_QUIRK_OVERFLOWS_TXCR; + + if (h->policy->flags & LWSSSPOLF_HTTP_MULTIPART) + i->ssl_connection |= LCCSCF_HTTP_MULTIPART_MIME; + + if (h->policy->flags & LWSSSPOLF_HTTP_X_WWW_FORM_URLENCODED) + i->ssl_connection |= LCCSCF_HTTP_X_WWW_FORM_URLENCODED; + + if (h->policy->flags & LWSSSPOLF_HTTP_CACHE_COOKIES) + i->ssl_connection |= LCCSCF_CACHE_COOKIES; + + i->ssl_connection |= LCCSCF_PIPELINE; + + i->alpn = "h3"; + + /* initial peer tx credit */ + + if (h->info.manual_initial_tx_credit) { + i->ssl_connection |= LCCSCF_H2_MANUAL_RXFLOW; + i->manual_initial_tx_credit = h->info.manual_initial_tx_credit; + lwsl_info("%s: initial txcr %d\n", __func__, + i->manual_initial_tx_credit); + } + + if (!pbasis) + return 0; + + /* protocol aux is the path part */ + + i->path = buf; + buf[0] = '/'; + + lws_strexp_init(&exp, (void *)h, lws_ss_exp_cb_metadata, buf + 1, len - 1); + + if (lws_strexp_expand(&exp, pbasis, strlen(pbasis), + &used_in, &used_out) != LSTRX_DONE) + return 1; + + __lws_lc_tag_append(&h->lc, buf); + + return 0; +} + +static int +secstream_tx_credit_add_h3(lws_ss_handle_t *h, int add) +{ + lwsl_info("%s: %s: add %d\n", __func__, lws_ss_tag(h), add); + if (h->wsi) + return lws_wsi_tx_credit(h->wsi, LWSTXCR_PEER_TO_US, add); + + return 0; +} + +static int +secstream_tx_credit_est_h3(lws_ss_handle_t *h) +{ + if (h->wsi) { + lwsl_info("%s: %s: est %d\n", __func__, lws_ss_tag(h), + lws_h2_get_peer_txcredit_estimate(h->wsi)); + + return lws_h2_get_peer_txcredit_estimate(h->wsi); + } + + lwsl_info("%s: %s: Unknown (0)\n", __func__, lws_ss_tag(h)); + + return 0; +} + +const struct ss_pcols ss_pcol_h3 = { + "h3", + "h3", + &protocol_secstream_h3, + secstream_connect_munge_h3, + secstream_tx_credit_add_h3, + secstream_tx_credit_est_h3 +}; diff --git a/lib/secure-streams/secure-streams.c b/lib/secure-streams/secure-streams.c index ebd123fb95..caed76a179 100644 --- a/lib/secure-streams/secure-streams.c +++ b/lib/secure-streams/secure-streams.c @@ -35,6 +35,11 @@ static const struct ss_pcols *ss_pcols[] = { #else NULL, #endif +#if defined(LWS_ROLE_H3) + &ss_pcol_h3, /* LWSSSP_H3 */ +#else + NULL, +#endif #if defined(LWS_ROLE_WS) &ss_pcol_ws, /* LWSSSP_WS */ #else @@ -1993,6 +1998,7 @@ lws_ss_request_tx_len(lws_ss_handle_t *h, unsigned long len) if (h->wsi && h->policy && (h->policy->protocol == LWSSSP_H1 || h->policy->protocol == LWSSSP_H2 || + h->policy->protocol == LWSSSP_H3 || h->policy->protocol == LWSSSP_WS)) h->wsi->http.writeable_len = len; else diff --git a/lib/system/async-dns/async-dns-parse.c b/lib/system/async-dns/async-dns-parse.c index 2f35cf44e8..f5a053b4a9 100644 --- a/lib/system/async-dns/async-dns-parse.c +++ b/lib/system/async-dns/async-dns-parse.c @@ -401,6 +401,7 @@ lws_adns_iterate(lws_adns_q_t *q, const uint8_t *pkt, int len, case LWS_ADNS_RECORD_NSEC: case LWS_ADNS_RECORD_NSEC3: case LWS_ADNS_RECORD_SOA: + case LWS_ADNS_RECORD_HTTPS: /* We pass these DNSSEC-related records to the callback so * it can store/evaluate them. */ @@ -466,7 +467,7 @@ lws_adns_iterate(lws_adns_q_t *q, const uint8_t *pkt, int len, char *cp = (char *)&q[1]; while (stack[stp].name[n]) - *cp++ = (char)tolower(stack[stp].name[n++]); + *cp++ = (char)tolower((uint8_t)stack[stp].name[n++]); /* trim the following . if any */ if (n && cp[-1] == '.') cp--; @@ -497,7 +498,8 @@ lws_async_dns_estimate(const char *name, void *opaque, uint32_t ttl, */ if (type == LWS_ADNS_RECORD_DNSKEY || type == LWS_ADNS_RECORD_RRSIG || type == LWS_ADNS_RECORD_DS || type == LWS_ADNS_RECORD_NSEC || - type == LWS_ADNS_RECORD_NSEC3 || type == LWS_ADNS_RECORD_SOA) { + type == LWS_ADNS_RECORD_NSEC3 || type == LWS_ADNS_RECORD_SOA || + type == LWS_ADNS_RECORD_HTTPS) { /* We'll stash them as lws_adns_rr_t directly after the A records */ my += sizeof(lws_adns_rr_t) + rrpaylen; } @@ -539,7 +541,8 @@ lws_async_dns_store(const char *name, void *opaque, uint32_t ttl, */ if (type == LWS_ADNS_RECORD_RRSIG || type == LWS_ADNS_RECORD_DNSKEY || type == LWS_ADNS_RECORD_DS || type == LWS_ADNS_RECORD_NSEC || - type == LWS_ADNS_RECORD_NSEC3 || type == LWS_ADNS_RECORD_SOA) { + type == LWS_ADNS_RECORD_NSEC3 || type == LWS_ADNS_RECORD_SOA || + type == LWS_ADNS_RECORD_HTTPS) { lws_adns_rr_t *rr = (lws_adns_rr_t *)adst->pos; rr->next = NULL; @@ -814,7 +817,7 @@ lws_adns_parse_udp(lws_async_dns_t *dns, const uint8_t *pkt, size_t len, c->flags = adst.flags; lws_dll2_add_head(&c->list, &dns->cached); - lwsl_info("%s: added %s to cache, rr_results = %p, ttl = %u\n", __func__, c->name, c->rr_results, adst.smallest_ttl); + lwsl_info("%s: added %s to cache, rr_results = %p, ttl = %u\n", __func__, c->name, c->rr_results, (unsigned int)adst.smallest_ttl); lws_sul_schedule(q->context, 0, &c->sul, sul_cb_expire, lws_now_usecs() + (adst.smallest_ttl * LWS_US_PER_SEC)); diff --git a/lib/system/async-dns/async-dns.c b/lib/system/async-dns/async-dns.c index 8fcfe44862..ee116f7ccf 100644 --- a/lib/system/async-dns/async-dns.c +++ b/lib/system/async-dns/async-dns.c @@ -420,7 +420,7 @@ callback_async_dns(struct lws *wsi, enum lws_callback_reasons reason, } q->has_tcp_len = 1; q->tcp_rx_pos = 0; - q->tcp_rx_buf = lws_malloc(q->tcp_rx_len + 1, "adns tcp"); + q->tcp_rx_buf = lws_malloc((size_t)q->tcp_rx_len + 1, "adns tcp"); if (!q->tcp_rx_buf) { lwsl_wsi_err(wsi, "OOM"); return -1; @@ -791,6 +791,7 @@ lws_adns_get_cache(lws_async_dns_t *dns, const char *name) void lws_adns_server_dump(lws_async_dns_server_t *dsrv) { +#if (_LWS_ENABLED_LOGS & LLL_INFO) lws_async_dns_t *dns = (lws_async_dns_t *)dsrv->list.owner; char ads[64]; @@ -805,11 +806,13 @@ lws_adns_server_dump(lws_async_dns_server_t *dsrv) (const char *)&q[1], q->sent[0], q->responded, q->broadsiding ? " (broadsiding)" : ""); } lws_end_foreach_dll(d); +#endif } void lws_adns_dump(lws_async_dns_t *dns) { +#if (_LWS_ENABLED_LOGS & LLL_INFO) lws_async_dns_server_t *dsrv; lws_adns_cache_t *c; @@ -834,6 +837,7 @@ lws_adns_dump(lws_async_dns_t *dns) dsrv = lws_container_of(d, lws_async_dns_server_t, list); lws_adns_server_dump(dsrv); } lws_end_foreach_dll(d); +#endif } #endif @@ -1076,7 +1080,7 @@ lws_async_dns_get_new_tid(struct lws_context *context, lws_adns_q_t *q) */ int -lws_adns_scan_hostsfile(const char *name, uint8_t *ads, size_t adslen) +lws_adns_scan_hostsfile(const char *name, uint8_t *ads, size_t adslen, int qtype) { char had_ads = 0, buf[128], finalized = 0; int fd, ret = 0, l = 0, ol = -1; @@ -1132,8 +1136,12 @@ lws_adns_scan_hostsfile(const char *name, uint8_t *ads, size_t adslen) nl == ts.token_len && !memcmp(name, ts.token, ts.token_len)) { /* it's a hit */ - ret = (int)l; - break; + if ((qtype == LWS_ADNS_RECORD_A && l == 4) || + (qtype == LWS_ADNS_RECORD_AAAA && l == 16) || + (qtype != LWS_ADNS_RECORD_A && qtype != LWS_ADNS_RECORD_AAAA)) { + ret = (int)l; + break; + } } if (ts.e == LWS_TOKZE_TOKEN && !had_ads && ts.token_len < 50) { /* we're getting an ipv4 or ipv6 numads */ @@ -1297,7 +1305,22 @@ lws_async_dns_query(struct lws_context *context, int tsi, const char *name, * Not directly numeric and not explicitly bypassing... * look through /etc/hosts */ - m = lws_adns_scan_hostsfile(name, ads, sizeof(ads)); + m = lws_adns_scan_hostsfile(name, ads, sizeof(ads), qtype & 0xff); + +#if defined(WIN32) + if (m < 4 && !strcmp(name, "localhost")) { + if (qtype == LWS_ADNS_RECORD_A) { + ads[0] = 127; ads[1] = 0; ads[2] = 0; ads[3] = 1; + m = 4; + } +#if defined(LWS_WITH_IPV6) + else if (qtype == LWS_ADNS_RECORD_AAAA) { + memset(ads, 0, 15); ads[15] = 1; + m = 16; + } +#endif + } +#endif #endif } @@ -1493,7 +1516,7 @@ lws_async_dns_query(struct lws_context *context, int tsi, const char *name, p = (char *)&q[1]; while (nlen--) { - *p++ = (char)tolower(*name++); + *p++ = (char)tolower((uint8_t)*name++); p[DNS_MAX - 1] = p[-1]; } *p = '\0'; @@ -1524,6 +1547,7 @@ lws_async_dns_query(struct lws_context *context, int tsi, const char *name, int lws_async_dns_create_tcp_wsi(lws_adns_q_t *q) { +#if defined(LWS_WITH_CLIENT) struct lws_client_connect_info i; char ads[48]; @@ -1532,6 +1556,7 @@ lws_async_dns_create_tcp_wsi(lws_adns_q_t *q) memset(&i, 0, sizeof(i)); i.context = q->context; + i.vhost = q->context->vhost_system; lws_sa46_write_numeric_address(&q->dsrv->sa46, ads, sizeof(ads)); i.address = ads; @@ -1552,6 +1577,9 @@ lws_async_dns_create_tcp_wsi(lws_adns_q_t *q) } return 0; +#else + return 1; +#endif } void @@ -1590,3 +1618,45 @@ lws_async_dns_get_rr_cache(struct lws_context *context, const char *name, return NULL; } + +int +lws_async_dns_get_alpn(struct lws_context *context, const char *name, const char *alpn) +{ + uint16_t paylen; + const uint8_t *rr = lws_async_dns_get_rr_cache(context, name, LWS_ADNS_RECORD_HTTPS, &paylen); + int pos = 2; + + if (!rr || paylen < 3) + return 0; + + /* TargetName: sequence of labels ending in 0 */ + while (pos < paylen && rr[pos]) { + pos += rr[pos] + 1; + } + pos++; /* Skip the 0 length label */ + + /* SvcParams */ + while (pos + 4 <= paylen) { + uint16_t key = (uint16_t)((rr[pos] << 8) | rr[pos + 1]); + uint16_t len = (uint16_t)((rr[pos + 2] << 8) | rr[pos + 3]); + pos += 4; + + if (pos + len > paylen) + break; + + if (key == 1) { /* alpn */ + int i = 0; + while (i < len) { + uint8_t slen = rr[pos + i]; + if (i + 1 + slen > len) + break; + if (slen == strlen(alpn) && !strncmp((const char *)&rr[pos + i + 1], alpn, slen)) + return 1; + i += slen + 1; + } + } + pos += len; + } + + return 0; +} diff --git a/lib/system/auth-dns/auth-dns.c b/lib/system/auth-dns/auth-dns.c index 427172335c..f93708b106 100644 --- a/lib/system/auth-dns/auth-dns.c +++ b/lib/system/auth-dns/auth-dns.c @@ -124,7 +124,7 @@ lws_auth_dns_parse_zone_buf(const char *buf, size_t len, struct auth_dns_zone *z if (lptr > 0) { lws_tokenize_t ts; lws_tokenize_elem e; - lws_tokenize_init(&ts, line_accum, LWS_TOKENIZE_F_HASH_COMMENT | LWS_TOKENIZE_F_DOT_NONTERM | LWS_TOKENIZE_F_NO_FLOATS | LWS_TOKENIZE_F_MINUS_NONTERM | LWS_TOKENIZE_F_SLASH_NONTERM | LWS_TOKENIZE_F_COLON_NONTERM | LWS_TOKENIZE_F_EQUALS_NONTERM | LWS_TOKENIZE_F_PLUS_NONTERM); + lws_tokenize_init(&ts, line_accum, LWS_TOKENIZE_F_DOT_NONTERM | LWS_TOKENIZE_F_NO_FLOATS | LWS_TOKENIZE_F_MINUS_NONTERM | LWS_TOKENIZE_F_SLASH_NONTERM | LWS_TOKENIZE_F_COLON_NONTERM | LWS_TOKENIZE_F_EQUALS_NONTERM | LWS_TOKENIZE_F_PLUS_NONTERM); ts.len = lptr; char toks[32][256]; @@ -244,6 +244,10 @@ lws_auth_dns_parse_zone_buf(const char *buf, size_t len, struct auth_dns_zone *z else if (!strcasecmp(toks[type_idx], "NSEC3PARAM")) type = 51; else if (!strcasecmp(toks[type_idx], "TLSA")) type = 52; else if (!strcasecmp(toks[type_idx], "CAA")) type = 257; + else if (!strcasecmp(toks[type_idx], "HTTPS")) type = 65; + else if (!strncasecmp(toks[type_idx], "TYPE", 4) && + toks[type_idx][4] >= '0' && toks[type_idx][4] <= '9') + type = (uint16_t)atoi(toks[type_idx] + 4); else type = 0; /* unknown */ type_idx++; } @@ -472,6 +476,7 @@ lws_auth_dns_sign_zone(struct lws_auth_dns_sign_info *info) case 51: ts = "NSEC3PARAM"; break; case 52: ts = "TLSA"; break; case 257: ts = "CAA"; break; + case 65: ts = "HTTPS"; break; } lws_start_foreach_dll(struct lws_dll2 *, d2, lws_dll2_get_head(&rs->rr_list)) { diff --git a/lib/system/auth-dns/sign.c b/lib/system/auth-dns/sign.c index ad96be6004..4fba7939f7 100644 --- a/lib/system/auth-dns/sign.c +++ b/lib/system/auth-dns/sign.c @@ -127,7 +127,7 @@ lws_auth_dns_rdata_to_wire(struct auth_dns_zone *z, struct auth_dns_rr *rr, uint int num_toks = 0, n; memset(toks, 0, sizeof(toks)); - lws_tokenize_init(&ts, rr->rdata, LWS_TOKENIZE_F_HASH_COMMENT | LWS_TOKENIZE_F_DOT_NONTERM | LWS_TOKENIZE_F_NO_FLOATS | LWS_TOKENIZE_F_MINUS_NONTERM | LWS_TOKENIZE_F_SLASH_NONTERM | LWS_TOKENIZE_F_COLON_NONTERM | LWS_TOKENIZE_F_EQUALS_NONTERM | LWS_TOKENIZE_F_PLUS_NONTERM | LWS_TOKENIZE_F_CHUNK); + lws_tokenize_init(&ts, rr->rdata, LWS_TOKENIZE_F_DOT_NONTERM | LWS_TOKENIZE_F_NO_FLOATS | LWS_TOKENIZE_F_MINUS_NONTERM | LWS_TOKENIZE_F_SLASH_NONTERM | LWS_TOKENIZE_F_COLON_NONTERM | LWS_TOKENIZE_F_EQUALS_NONTERM | LWS_TOKENIZE_F_PLUS_NONTERM | LWS_TOKENIZE_F_CHUNK); ts.len = strlen(rr->rdata); int max_tokens = 0; @@ -157,6 +157,31 @@ lws_auth_dns_rdata_to_wire(struct auth_dns_zone *z, struct auth_dns_rr *rr, uint } } while (e > 0); + /* Check for generic RFC 3597 representation: starts with \# */ + const char *rd_start = rr->rdata; + while (*rd_start == ' ' || *rd_start == '\t') rd_start++; + if (rd_start[0] == '\\' && rd_start[1] == '#') { + if (num_toks < 2) goto fail; + int gen_len = atoi(toks[0]); + if (gen_len < 0 || gen_len > 65535) goto fail; + + char hex_accum[65536] = ""; + for (int i = 1; i < num_toks; i++) { + strncat(hex_accum, toks[i], sizeof(hex_accum) - strlen(hex_accum) - 1); + } + size_t hlen = strlen(hex_accum) / 2; + if (hlen != (size_t)gen_len) { + lwsl_err("generic RDATA length mismatch: expected %d, got %zu\n", gen_len, hlen); + goto fail; + } + lws_hex_to_byte_array(hex_accum, w, gen_len); + wl = (size_t)gen_len; + + rr->wire_rdata = w; + rr->wire_rdata_len = wl; + return 0; + } + if (type == 1 && num_toks >= 1) { // A struct sockaddr_in sin; memset(&sin, 0, sizeof(sin)); @@ -281,6 +306,7 @@ lws_auth_dns_rdata_to_wire(struct auth_dns_zone *z, struct auth_dns_rr *rr, uint else if (!strcmp(toks[i], "NSEC3PARAM")) ty = 51; else if (!strcmp(toks[i], "TLSA")) ty = 52; else if (!strcmp(toks[i], "CAA")) ty = 257; + else if (!strcmp(toks[i], "HTTPS")) ty = 65; else ty = (uint16_t)atoi(toks[i]); if (ty > 0) { @@ -416,6 +442,183 @@ lws_auth_dns_rdata_to_wire(struct auth_dns_zone *z, struct auth_dns_rr *rr, uint memcpy(w + wl, toks[2], (size_t)val_len); wl += (size_t)val_len; } + } else if (type == 65 && num_toks >= 2) { // HTTPS + uint16_t priority = (uint16_t)atoi(toks[0]); + w[wl++] = (uint8_t)(priority >> 8); + w[wl++] = (uint8_t)(priority & 0xff); + + size_t av = 512; + if (name_to_wire(toks[1], z->origin, w + wl, &av)) goto fail; + wl += av; + + for (int i = 2; i < num_toks; i++) { + const char *eq = strchr(toks[i], '='); + const char *key = toks[i]; + const char *val = ""; + char key_name[128]; + if (eq) { + size_t klen = lws_ptr_diff_size_t(eq, key); + if (klen >= sizeof(key_name)) klen = sizeof(key_name) - 1; + memcpy(key_name, key, klen); + key_name[klen] = '\0'; + val = eq + 1; + } else { + lws_strncpy(key_name, key, sizeof(key_name)); + } + + char val_clean[1024]; + lws_strncpy(val_clean, val, sizeof(val_clean)); + size_t vlen = strlen(val_clean); + if (vlen >= 2 && val_clean[0] == '"' && val_clean[vlen - 1] == '"') { + memmove(val_clean, val_clean + 1, vlen - 2); + val_clean[vlen - 2] = '\0'; + vlen -= 2; + } + + uint16_t param_key = 0xffff; + if (!strcasecmp(key_name, "alpn")) param_key = 1; + else if (!strcasecmp(key_name, "no-default-alpn")) param_key = 2; + else if (!strcasecmp(key_name, "port")) param_key = 3; + else if (!strcasecmp(key_name, "ipv4hint")) param_key = 4; + else if (!strcasecmp(key_name, "ech")) param_key = 5; + else if (!strcasecmp(key_name, "ipv6hint")) param_key = 6; + else if (!strncasecmp(key_name, "key", 3)) param_key = (uint16_t)atoi(key_name + 3); + + if (param_key == 0xffff) continue; + + if (param_key == 1) { /* alpn */ + size_t alpn_wire_len = 0; + const char *p_alpn = val_clean; + while (*p_alpn) { + const char *comma = strchr(p_alpn, ','); + size_t len_item = comma ? lws_ptr_diff_size_t(comma, p_alpn) : strlen(p_alpn); + if (len_item > 255) goto fail; + alpn_wire_len += 1 + len_item; + if (!comma) break; + p_alpn = comma + 1; + } + + w[wl++] = (uint8_t)(param_key >> 8); + w[wl++] = (uint8_t)(param_key & 0xff); + w[wl++] = (uint8_t)(alpn_wire_len >> 8); + w[wl++] = (uint8_t)(alpn_wire_len & 0xff); + + p_alpn = val_clean; + while (*p_alpn) { + const char *comma = strchr(p_alpn, ','); + size_t len_item = comma ? lws_ptr_diff_size_t(comma, p_alpn) : strlen(p_alpn); + w[wl++] = (uint8_t)len_item; + memcpy(w + wl, p_alpn, len_item); + wl += len_item; + if (!comma) break; + p_alpn = comma + 1; + } + } else if (param_key == 2) { /* no-default-alpn */ + w[wl++] = (uint8_t)(param_key >> 8); + w[wl++] = (uint8_t)(param_key & 0xff); + w[wl++] = 0; + w[wl++] = 0; + } else if (param_key == 3) { /* port */ + uint16_t port_val = (uint16_t)atoi(val_clean); + w[wl++] = (uint8_t)(param_key >> 8); + w[wl++] = (uint8_t)(param_key & 0xff); + w[wl++] = 0; + w[wl++] = 2; + w[wl++] = (uint8_t)(port_val >> 8); + w[wl++] = (uint8_t)(port_val & 0xff); + } else if (param_key == 4) { /* ipv4hint */ + int ip_count = 0; + const char *p_ip = val_clean; + while (*p_ip) { + ip_count++; + const char *comma = strchr(p_ip, ','); + if (!comma) break; + p_ip = comma + 1; + } + + w[wl++] = (uint8_t)(param_key >> 8); + w[wl++] = (uint8_t)(param_key & 0xff); + uint16_t vlen_bytes = (uint16_t)(ip_count * 4); + w[wl++] = (uint8_t)(vlen_bytes >> 8); + w[wl++] = (uint8_t)(vlen_bytes & 0xff); + + p_ip = val_clean; + while (*p_ip) { + char ip_str[64]; + const char *comma = strchr(p_ip, ','); + size_t len_item = comma ? lws_ptr_diff_size_t(comma, p_ip) : strlen(p_ip); + if (len_item >= sizeof(ip_str)) goto fail; + memcpy(ip_str, p_ip, len_item); + ip_str[len_item] = '\0'; + + struct sockaddr_in sin; + if (inet_pton(AF_INET, ip_str, &sin.sin_addr) != 1) goto fail; + memcpy(w + wl, &sin.sin_addr, 4); + wl += 4; + + if (!comma) break; + p_ip = comma + 1; + } + } else if (param_key == 6) { /* ipv6hint */ + int ip_count = 0; + const char *p_ip = val_clean; + while (*p_ip) { + ip_count++; + const char *comma = strchr(p_ip, ','); + if (!comma) break; + p_ip = comma + 1; + } + + w[wl++] = (uint8_t)(param_key >> 8); + w[wl++] = (uint8_t)(param_key & 0xff); + uint16_t vlen_bytes = (uint16_t)(ip_count * 16); + w[wl++] = (uint8_t)(vlen_bytes >> 8); + w[wl++] = (uint8_t)(vlen_bytes & 0xff); + + p_ip = val_clean; + while (*p_ip) { + char ip_str[128]; + const char *comma = strchr(p_ip, ','); + size_t len_item = comma ? lws_ptr_diff_size_t(comma, p_ip) : strlen(p_ip); + if (len_item >= sizeof(ip_str)) goto fail; + memcpy(ip_str, p_ip, len_item); + ip_str[len_item] = '\0'; + + struct sockaddr_in6 sin6; + if (inet_pton(AF_INET6, ip_str, &sin6.sin6_addr) != 1) goto fail; + memcpy(w + wl, &sin6.sin6_addr, 16); + wl += 16; + + if (!comma) break; + p_ip = comma + 1; + } + } else if (param_key == 5) { /* ech (base64) */ + size_t dec_max = strlen(val_clean); + uint8_t *dec_buf = lws_malloc(dec_max, "ech_dec"); + if (!dec_buf) goto fail; + int dec_len = lws_b64_decode_string(val_clean, (char *)dec_buf, (int)dec_max); + if (dec_len < 0) { + lws_free(dec_buf); + goto fail; + } + + size_t dlen = (size_t)dec_len; + w[wl++] = (uint8_t)(param_key >> 8); + w[wl++] = (uint8_t)(param_key & 0xff); + w[wl++] = (uint8_t)(dlen >> 8); + w[wl++] = (uint8_t)(dlen & 0xff); + memcpy(w + wl, dec_buf, dlen); + wl += dlen; + lws_free(dec_buf); + } else { /* generic custom keys (raw string) */ + w[wl++] = (uint8_t)(param_key >> 8); + w[wl++] = (uint8_t)(param_key & 0xff); + w[wl++] = (uint8_t)(vlen >> 8); + w[wl++] = (uint8_t)(vlen & 0xff); + memcpy(w + wl, val_clean, vlen); + wl += vlen; + } + } } else { { lwsl_err("FAIL on rdata: %s (toks[0]: %s)", rr->rdata, toks[0]); goto fail; } } @@ -753,6 +956,7 @@ lws_auth_dns_add_nsec3(struct auth_dns_zone *z, const char *salt_hex, int iterat case 51: ts = "NSEC3PARAM"; break; case 52: ts = "TLSA"; break; case 257: ts = "CAA"; break; + case 65: ts = "HTTPS"; break; } /* Append type if not already there */ if (!(char *)strstr(nodes[found]->type_list, ts)) { diff --git a/lib/tls/CMakeLists.txt b/lib/tls/CMakeLists.txt index b6066a1588..50468c1d3b 100644 --- a/lib/tls/CMakeLists.txt +++ b/lib/tls/CMakeLists.txt @@ -105,6 +105,10 @@ endif() if (LWS_WITH_GNUTLS) add_subdirectory(gnutls) include_directories(${_CMAKE_INC_LIST}) + if (GNUTLS_INCLUDE_DIRS) + set(LWS_PUBLIC_INCLUDES ${LWS_PUBLIC_INCLUDES} "${GNUTLS_INCLUDE_DIRS}") + set(LWS_PUBLIC_INCLUDES ${LWS_PUBLIC_INCLUDES} PARENT_SCOPE) + endif() endif() if (LWS_WITH_BEARSSL) @@ -140,11 +144,34 @@ if (LWS_WITH_SSL) endif() if (LWS_WITH_MBEDTLS) + set(CMAKE_REQUIRED_INCLUDES_TEMP ${CMAKE_REQUIRED_INCLUDES}) + set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES} ${MBEDTLS_INCLUDE_DIRS}) + set(CMAKE_REQUIRED_LIBRARIES_TEMP ${CMAKE_REQUIRED_LIBRARIES}) + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} ${MBEDTLS_LIBRARIES}) + CHECK_C_SOURCE_COMPILES("#include \nint main(void) { void *v = (void *)mbedtls_ssl_set_export_keys_cb; return !!v; }\n" LWS_HAVE_mbedtls_ssl_set_export_keys_cb) + CHECK_C_SOURCE_COMPILES("#include \nint main(void) { void *v = (void *)mbedtls_ssl_conf_export_keys_cb; return !!v; }\n" LWS_HAVE_mbedtls_ssl_conf_export_keys_cb) + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES_TEMP}) + set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES_TEMP}) + + if (LWS_ROLE_QUIC AND NOT LWS_HAVE_mbedtls_ssl_set_export_keys_cb) + message(WARNING "MbedTLS version too old for QUIC; disabling QUIC and HTTP3") + set(LWS_ROLE_QUIC 0) + set(LWS_WITH_HTTP3 0) + set(LWS_ROLE_H3 0) + set(LWS_ROLE_WT 0) + set(LWS_ROLE_QUIC 0 PARENT_SCOPE) + set(LWS_WITH_HTTP3 0 PARENT_SCOPE) + set(LWS_ROLE_H3 0 PARENT_SCOPE) + set(LWS_ROLE_WT 0 PARENT_SCOPE) + endif() + list(APPEND SOURCES tls/mbedtls/mbedtls-tls.c - tls/mbedtls/mbedtls-quic.c tls/mbedtls/mbedtls-extensions.c tls/mbedtls/mbedtls-x509.c) + if (LWS_ROLE_QUIC AND LWS_WITH_NETWORK) + list(APPEND SOURCES tls/mbedtls/mbedtls-quic.c) + endif() if (LWS_WITH_NETWORK) list(APPEND SOURCES tls/mbedtls/mbedtls-ssl.c) @@ -175,8 +202,10 @@ if (LWS_WITH_SSL) tls/schannel/schannel-tls.c tls/schannel/schannel-ssl.c tls/schannel/schannel-session.c - tls/schannel/schannel-quic.c tls/schannel/schannel-x509.c) + if (LWS_ROLE_QUIC AND LWS_WITH_NETWORK) + list(APPEND SOURCES tls/schannel/schannel-quic.c) + endif() list(APPEND LIB_LIST "ncrypt" "bcrypt" "crypt32" "secur32") if (LWS_WITH_GENCRYPTO) list(APPEND SOURCES @@ -193,9 +222,14 @@ if (LWS_WITH_SSL) elseif (LWS_WITH_GNUTLS) list(APPEND SOURCES tls/gnutls/gnutls-tls.c - tls/gnutls/gnutls-quic.c - tls/gnutls/gnutls-ssl.c tls/gnutls/gnutls-x509.c) + if (LWS_ROLE_QUIC AND LWS_WITH_NETWORK) + list(APPEND SOURCES tls/gnutls/gnutls-quic.c) + endif() + if (LWS_WITH_NETWORK) + list(APPEND SOURCES + tls/gnutls/gnutls-ssl.c) + endif() if (LWS_WITH_TLS_SESSIONS) list(APPEND SOURCES tls/gnutls/gnutls-session.c) @@ -232,8 +266,10 @@ if (LWS_WITH_SSL) else() list(APPEND SOURCES tls/openssl/openssl-tls.c - tls/openssl/openssl-quic.c tls/openssl/openssl-x509.c) + if (LWS_ROLE_QUIC AND LWS_WITH_NETWORK) + list(APPEND SOURCES tls/openssl/openssl-quic.c) + endif() if (LWS_WITH_NETWORK) list(APPEND SOURCES tls/openssl/openssl-ssl.c) @@ -406,8 +442,11 @@ if (LWS_WITH_SSL) unset(LWS_HAVE_OPENSSL_ECDH_H PARENT_SCOPE) endif() - if (LWS_WITH_BORINGSSL) - list(APPEND LIB_LIST "stdc++" "pthread") + if (LWS_WITH_BORINGSSL OR LWS_WITH_AWSLC) + if (LWS_WITH_BORINGSSL) + list(APPEND LIB_LIST "stdc++") + endif() + list(APPEND LIB_LIST "pthread") endif() endif() @@ -486,7 +525,7 @@ CHECK_C_SOURCE_COMPILES("#include \nint main(void) { OPENSSL_STAC CHECK_C_SOURCE_COMPILES("#include \nint main(void) { SSL_CTX *ctx = NULL; return SSL_CTX_set_ecdh_auto(ctx, 1); }\n" LWS_HAVE_SSL_CTX_SET_ECDH_AUTO) CHECK_C_SOURCE_COMPILES("#include \nint main(void) { SSL *ssl = NULL; return (int)SSL_set_tlsext_host_name(ssl, \"hello\"); }\n" LWS_HAVE_SSL_set_tlsext_host_name) -set(LWS_HAVE_SSL_set_tlsext_host_name ${LWS_HAVE_SSL_set_tlsext_host_name} PARENT_SCOPE) +set(LWS_HAVE_SSL_set_tlsext_host_name 1 CACHE BOOL "" FORCE) set(LWS_HAVE_SSL_EXTRA_CHAIN_CERTS ${LWS_HAVE_SSL_EXTRA_CHAIN_CERTS} PARENT_SCOPE) set(LWS_HAVE_EVP_MD_CTX_free ${LWS_HAVE_EVP_MD_CTX_free} PARENT_SCOPE) set(LWS_HAVE_SSL_CTX_SET_ECDH_AUTO ${LWS_HAVE_SSL_CTX_SET_ECDH_AUTO} PARENT_SCOPE) @@ -496,6 +535,12 @@ CHECK_FUNCTION_EXISTS(${VARIA}EVP_aes_128_wrap LWS_HAVE_EVP_aes_128_wrap PARENT_ CHECK_FUNCTION_EXISTS(${VARIA}EC_POINT_get_affine_coordinates LWS_HAVE_EC_POINT_get_affine_coordinates PARENT_SCOPE) CHECK_FUNCTION_EXISTS(${VARIA}SSL_CTX_load_verify_file LWS_HAVE_SSL_CTX_load_verify_file PARENT_SCOPE) CHECK_FUNCTION_EXISTS(${VARIA}SSL_CTX_load_verify_dir LWS_HAVE_SSL_CTX_load_verify_dir PARENT_SCOPE) + +CHECK_C_SOURCE_COMPILES("#include \nint main(void) { SSL_set_quic_method(NULL, NULL); return 0; }\n" LWS_HAVE_SSL_SET_QUIC_METHOD) +if (LWS_ROLE_QUIC AND NOT LWS_HAVE_SSL_SET_QUIC_METHOD AND NOT LWS_WITH_BORINGSSL AND NOT LWS_WITH_AWSLC AND NOT LWS_WITH_GNUTLS AND NOT LWS_WITH_WOLFSSL) + message(FATAL_ERROR "LWS_ROLE_QUIC (HTTP/3) requires BoringSSL, GnuTLS, WolfSSL, or the quictls fork of OpenSSL. Standard upstream OpenSSL is not supported because it lacks SSL_set_quic_method.") +endif() + endif() endif() # schannel @@ -700,6 +745,8 @@ set(LWS_HAVE_MBEDTLS_SSL_NEW_SESSION_TICKET ${LWS_HAVE_MBEDTLS_SSL_NEW_SESSION_T set(LWS_HAVE_mbedtls_ssl_conf_alpn_protocols ${LWS_HAVE_mbedtls_ssl_conf_alpn_protocols} PARENT_SCOPE) set(LWS_HAVE_mbedtls_ssl_conf_ciphersuites ${LWS_HAVE_mbedtls_ssl_conf_ciphersuites} PARENT_SCOPE) set(LWS_HAVE_mbedtls_ssl_session_save ${LWS_HAVE_mbedtls_ssl_session_save} PARENT_SCOPE) +set(LWS_HAVE_mbedtls_ssl_set_export_keys_cb ${LWS_HAVE_mbedtls_ssl_set_export_keys_cb} PARENT_SCOPE) +set(LWS_HAVE_mbedtls_ssl_conf_export_keys_cb ${LWS_HAVE_mbedtls_ssl_conf_export_keys_cb} PARENT_SCOPE) set(TEST_SERVER_SSL_KEY "${TEST_SERVER_SSL_KEY}" PARENT_SCOPE) set(TEST_SERVER_SSL_CERT "${TEST_SERVER_SSL_CERT}" PARENT_SCOPE) set(TEST_SERVER_DATA ${TEST_SERVER_DATA} PARENT_SCOPE) diff --git a/lib/tls/bearssl/bearssl-client.c b/lib/tls/bearssl/bearssl-client.c index b2d7e89c6b..ea84515ca7 100644 --- a/lib/tls/bearssl/bearssl-client.c +++ b/lib/tls/bearssl/bearssl-client.c @@ -50,8 +50,7 @@ lws_tls_client_connect(struct lws *wsi, char *errbuf, size_t elen) #if defined(LWS_WITH_TLS_JIT_TRUST) conn->wsi = wsi; #endif - br_ssl_engine_set_buffer(&conn->u.client.eng, conn->iobuf_in, sizeof(conn->iobuf_in), 1); - br_ssl_engine_set_buffer(&conn->u.client.eng, conn->iobuf_out, sizeof(conn->iobuf_out), 0); + br_ssl_engine_set_buffers_bidi(&conn->u.client.eng, conn->iobuf_in, sizeof(conn->iobuf_in), conn->iobuf_out, sizeof(conn->iobuf_out)); { const char *alpn_comma = wsi->a.context->tls.alpn_default; diff --git a/lib/tls/bearssl/bearssl-server.c b/lib/tls/bearssl/bearssl-server.c index be82582e54..f7efceb01a 100644 --- a/lib/tls/bearssl/bearssl-server.c +++ b/lib/tls/bearssl/bearssl-server.c @@ -118,8 +118,7 @@ lws_tls_server_accept(struct lws *wsi) BR_KEYTYPE_EC, &ctx->ec_key); } - br_ssl_engine_set_buffer(&conn->u.server.eng, conn->iobuf_in, sizeof(conn->iobuf_in), 1); - br_ssl_engine_set_buffer(&conn->u.server.eng, conn->iobuf_out, sizeof(conn->iobuf_out), 0); + br_ssl_engine_set_buffers_bidi(&conn->u.server.eng, conn->iobuf_in, sizeof(conn->iobuf_in), conn->iobuf_out, sizeof(conn->iobuf_out)); if (wsi->a.vhost->tls.alpn_ctx.len) { lws_bearssl_set_alpn(conn, wsi->a.vhost->tls.alpn_ctx.data, diff --git a/lib/tls/bearssl/bearssl-session.c b/lib/tls/bearssl/bearssl-session.c index b3fd197359..d2a35de2f4 100644 --- a/lib/tls/bearssl/bearssl-session.c +++ b/lib/tls/bearssl/bearssl-session.c @@ -188,7 +188,8 @@ lws_tls_session_new_bearssl(struct lws *wsi) #endif vh = wsi->a.vhost; - if (vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE) + if ((vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE) || + vh->being_destroyed) return 0; if (lws_tls_session_tag_from_wsi(wsi, buf, sizeof(buf))) diff --git a/lib/tls/bearssl/bearssl-ssl.c b/lib/tls/bearssl/bearssl-ssl.c index d84fb8ab5c..04029e85b1 100644 --- a/lib/tls/bearssl/bearssl-ssl.c +++ b/lib/tls/bearssl/bearssl-ssl.c @@ -54,8 +54,10 @@ lws_bearssl_pump(struct lws *wsi) old_st = st; st = br_ssl_engine_current_state(&conn->u.engine); if (st != old_st) { +#if (_LWS_ENABLED_LOGS & LLL_NOTICE) LWS_RATELIMIT_DEFINE_STATIC(rl); lwsl_ratelimit_notice(&rl, 1000000, "%s: st changed %x -> %x\n", __func__, old_st, st); +#endif } } while (st != old_st && st != BR_SSL_CLOSED); diff --git a/lib/tls/gnutls/gnutls-quic.c b/lib/tls/gnutls/gnutls-quic.c index b8ff50fcc4..88c6c22b4a 100644 --- a/lib/tls/gnutls/gnutls-quic.c +++ b/lib/tls/gnutls/gnutls-quic.c @@ -35,6 +35,10 @@ struct gnutls_quic_bio { uint8_t *out; size_t out_max; size_t out_len; + int target_level; + + uint8_t *other_out[4]; + size_t other_out_len[4]; gnutls_session_t session; }; @@ -42,42 +46,15 @@ struct gnutls_quic_bio { static ssize_t gnutls_quic_bio_push(gnutls_transport_ptr_t ptr, const void *buf, size_t len) { - struct gnutls_quic_bio *b = (struct gnutls_quic_bio *)ptr; - - if (!b->out || b->out_len + len > b->out_max) { - gnutls_transport_set_errno(b->session, EAGAIN); - return -1; - } - - memcpy(b->out + b->out_len, buf, len); - b->out_len += len; - + /* For QUIC, we drop the TLS Record Layer output generated by GnuTLS. + * We only care about the raw handshake data intercepted by gnutls_quic_read_func. */ return (ssize_t)len; } static ssize_t gnutls_quic_bio_pull(gnutls_transport_ptr_t ptr, void *buf, size_t len) { - struct gnutls_quic_bio *b = (struct gnutls_quic_bio *)ptr; - size_t avail; - - if (!b->in) { - gnutls_transport_set_errno(b->session, EAGAIN); - return -1; - } - - avail = b->in_len - b->in_pos; - if (avail == 0) { - gnutls_transport_set_errno(b->session, EAGAIN); - return -1; - } - - if (len > avail) - len = avail; - - memcpy(buf, b->in + b->in_pos, len); - b->in_pos += len; - + /* We don't pull from the Record Layer. QUIC data is pushed via gnutls_handshake_write. */ return (ssize_t)len; } @@ -120,7 +97,6 @@ gnutls_quic_secret_func(gnutls_session_t session, enum lws_tls_quic_secret_type qtype; switch (level) { case GNUTLS_ENCRYPTION_LEVEL_EARLY: - /* read secret for early data makes no sense for client, maybe server. */ if (!is_client) qtype = LWS_TLS_QUIC_SECRET_CLIENT_EARLY; else @@ -141,6 +117,55 @@ gnutls_quic_secret_func(gnutls_session_t session, return 0; } +static int +gnutls_quic_read_func(gnutls_session_t session, + gnutls_record_encryption_level_t level, + gnutls_handshake_description_t htype, + const void *data, size_t data_size) +{ + struct lws *wsi = (struct lws *)gnutls_session_get_ptr(session); + struct gnutls_quic_bio *b; + int qlevel; + + lwsl_notice("GNUTLS QUIC READ FUNC: level %d, htype %d, len %d\n", + (int)level, (int)htype, (int)data_size); + + if (!wsi || !wsi->tls.client_bio) + return 0; + + b = (struct gnutls_quic_bio *)wsi->tls.client_bio; + + switch (level) { + case GNUTLS_ENCRYPTION_LEVEL_INITIAL: + qlevel = 0; /* LWS_QUIC_LEVEL_INITIAL */ + break; + case GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE: + qlevel = 2; /* LWS_QUIC_LEVEL_HANDSHAKE */ + break; + case GNUTLS_ENCRYPTION_LEVEL_APPLICATION: + qlevel = 1; /* LWS_QUIC_LEVEL_APP */ + break; + default: + return 0; + } + + if (qlevel == b->target_level) { + if (!b->out || b->out_len + data_size > b->out_max) + return GNUTLS_E_MEMORY_ERROR; + memcpy(b->out + b->out_len, data, data_size); + b->out_len += data_size; + } else { + uint8_t *p = lws_realloc(b->other_out[qlevel], b->other_out_len[qlevel] + data_size, "quic other out"); + if (!p) + return GNUTLS_E_MEMORY_ERROR; + b->other_out[qlevel] = p; + memcpy(p + b->other_out_len[qlevel], data, data_size); + b->other_out_len[qlevel] += data_size; + } + + return 0; +} + static int gnutls_quic_ext_recv_func(gnutls_session_t session, const unsigned char *data, size_t len) { @@ -150,6 +175,9 @@ gnutls_quic_ext_recv_func(gnutls_session_t session, const unsigned char *data, s if (!wsi) return 0; + if (wsi->tls.quic_tp_recv) + lws_free_set_NULL(wsi->tls.quic_tp_recv); + p = lws_malloc(len, "quic tp recv"); if (!p) return GNUTLS_E_MEMORY_ERROR; @@ -166,8 +194,17 @@ gnutls_quic_ext_send_func(gnutls_session_t session, gnutls_buffer_t extdata) { struct lws *wsi = (struct lws *)gnutls_session_get_ptr(session); - if (!wsi || !wsi->tls.quic_tp_send || !wsi->tls.quic_tp_send_len) + if (!wsi) { + lwsl_err("GNUTLS EXT SEND: wsi is NULL!\n"); + return 0; + } + + if (!wsi->tls.quic_tp_send || !wsi->tls.quic_tp_send_len) { + lwsl_err("GNUTLS EXT SEND: no tp_send (%p, len %d)!\n", wsi->tls.quic_tp_send, (int)wsi->tls.quic_tp_send_len); return 0; + } + + lwsl_notice("GNUTLS EXT SEND: appending %d bytes of TP!\n", (int)wsi->tls.quic_tp_send_len); if (gnutls_buffer_append_data(extdata, wsi->tls.quic_tp_send, wsi->tls.quic_tp_send_len) < 0) return GNUTLS_E_MEMORY_ERROR; @@ -175,6 +212,34 @@ gnutls_quic_ext_send_func(gnutls_session_t session, gnutls_buffer_t extdata) return (int)wsi->tls.quic_tp_send_len; } +void +gnutls_quic_bio_free(struct lws *wsi) +{ + if (!wsi) + return; + + if (wsi->tls.client_bio) { + struct gnutls_quic_bio *b = (struct gnutls_quic_bio *)wsi->tls.client_bio; + int i; + for (i = 0; i < 4; i++) { + if (b->other_out[i]) + lws_free(b->other_out[i]); + } + lws_free(wsi->tls.client_bio); + wsi->tls.client_bio = NULL; + } + + if (wsi->tls.quic_tp_recv) { + lws_free((void *)wsi->tls.quic_tp_recv); + wsi->tls.quic_tp_recv = NULL; + } + + if (wsi->tls.quic_tp_send) { + lws_free((void *)wsi->tls.quic_tp_send); + wsi->tls.quic_tp_send = NULL; + } +} + int lws_tls_quic_vhost_init(lws_tls_ctx *ctx) { @@ -190,10 +255,69 @@ lws_tls_quic_init(struct lws *wsi, lws_tls_quic_secret_cb cb) if (!wsi->tls.ssl) return -1; + int ret; session = (gnutls_session_t)wsi->tls.ssl; if (!session) return -1; + /* Enforce QUIC requirements: TLS 1.3 only, NO compatibility mode (empty legacy_session_id) */ + ret = gnutls_priority_set_direct(session, "NORMAL:-VERS-ALL:+VERS-TLS1.3:%DISABLE_TLS13_COMPAT_MODE", NULL); + if (ret < 0) { + lwsl_err("gnutls_priority_set_direct failed: %s\n", gnutls_strerror(ret)); + } + + if (!wsi->tls.quic_tp_send) { + /* Construct dynamically to include loc_cid */ + struct lws_quic_netconn *qn = wsi->quic.qn; + static uint8_t dynamic_tp[128]; + uint8_t *p = dynamic_tp; + + /* initial_max_stream_data_bidi_local (0x05), len 4, val 65535 */ + *p++ = 0x05; *p++ = 0x04; *p++ = 0x80; *p++ = 0x00; *p++ = 0xff; *p++ = 0xff; + /* initial_max_stream_data_bidi_remote (0x06), len 4, val 65535 */ + *p++ = 0x06; *p++ = 0x04; *p++ = 0x80; *p++ = 0x00; *p++ = 0xff; *p++ = 0xff; + /* initial_max_stream_data_uni (0x07), len 4, val 65535 */ + *p++ = 0x07; *p++ = 0x04; *p++ = 0x80; *p++ = 0x00; *p++ = 0xff; *p++ = 0xff; + /* initial_max_data (0x04), len 4, val 65535 */ + *p++ = 0x04; *p++ = 0x04; *p++ = 0x80; *p++ = 0x00; *p++ = 0xff; *p++ = 0xff; + /* initial_max_streams_bidi (0x08), len 2, val 100 (2-byte varint: 0x40 0x64) */ + *p++ = 0x08; *p++ = 0x02; *p++ = 0x40; *p++ = 0x64; + /* initial_max_streams_uni (0x09), len 2, val 100 (2-byte varint: 0x40 0x64) */ + *p++ = 0x09; *p++ = 0x02; *p++ = 0x40; *p++ = 0x64; + /* max_idle_timeout (0x01), len 4, val 30000 */ + *p++ = 0x01; *p++ = 0x04; *p++ = 0x80; *p++ = 0x00; *p++ = 0x75; *p++ = 0x30; + /* initial_source_connection_id (0x0f) */ + if (qn && qn->loc_cid.len > 0) { + *p++ = 0x0f; + *p++ = qn->loc_cid.len; + memcpy(p, qn->loc_cid.id, qn->loc_cid.len); + p += qn->loc_cid.len; + } + + wsi->tls.quic_tp_send = lws_malloc((size_t)(p - dynamic_tp), "quic tp"); + if (wsi->tls.quic_tp_send) { + memcpy((void *)wsi->tls.quic_tp_send, dynamic_tp, (size_t)(p - dynamic_tp)); + wsi->tls.quic_tp_send_len = (size_t)(p - dynamic_tp); + } + } + + if (wsi->alpn[0]) { + gnutls_datum_t alpn; + alpn.data = (uint8_t *)wsi->alpn; + alpn.size = (unsigned int)strlen(wsi->alpn); + gnutls_alpn_set_protocols(session, &alpn, 1, GNUTLS_ALPN_MANDATORY); + } else if (wsi->a.vhost && wsi->a.vhost->tls.alpn_ctx.len) { + gnutls_datum_t alpn[4]; + unsigned int i = 0, p = 0; + while (p < wsi->a.vhost->tls.alpn_ctx.len && i < 4) { + alpn[i].data = &wsi->a.vhost->tls.alpn_ctx.data[p + 1]; + alpn[i].size = wsi->a.vhost->tls.alpn_ctx.data[p]; + p += alpn[i].size + 1; + i++; + } + gnutls_alpn_set_protocols(session, alpn, i, GNUTLS_ALPN_MANDATORY); + } + wsi->tls.quic_secret_cb = cb; b = lws_zalloc(sizeof(*b), "quic bio"); @@ -210,6 +334,7 @@ lws_tls_quic_init(struct lws *wsi, lws_tls_quic_secret_cb cb) gnutls_session_set_ptr(session, wsi); gnutls_handshake_set_secret_function(session, gnutls_quic_secret_func); + gnutls_handshake_set_read_function(session, gnutls_quic_read_func); gnutls_session_ext_register(session, "quic_transport_parameters", 57, GNUTLS_EXT_TLS, @@ -228,24 +353,57 @@ lws_tls_quic_advance_handshake(struct lws *wsi, int level, { gnutls_session_t session = (gnutls_session_t)wsi->tls.ssl; struct gnutls_quic_bio *b = (struct gnutls_quic_bio *)wsi->tls.client_bio; - int n; + int n, l; if (!b || !session) return -1; - b->in = in; - b->in_len = in_len; - b->in_pos = 0; - b->out = out; b->out_max = out ? *out_len : 0; + if (out_len) + *out_len = 0; b->out_len = 0; + b->target_level = level; + + if (in && in_len > 0) { + gnutls_record_encryption_level_t glevel; + switch (level) { + case 0: /* LWS_QUIC_LEVEL_INITIAL */ + glevel = GNUTLS_ENCRYPTION_LEVEL_INITIAL; + break; + case 1: /* LWS_QUIC_LEVEL_EARLY */ + glevel = GNUTLS_ENCRYPTION_LEVEL_EARLY; + break; + case 2: /* LWS_QUIC_LEVEL_HANDSHAKE */ + glevel = GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE; + break; + case 3: /* LWS_QUIC_LEVEL_APP */ + glevel = GNUTLS_ENCRYPTION_LEVEL_APPLICATION; + break; + default: + if (out_len) + *out_len = 0; + return -1; + } + if (gnutls_handshake_write(session, glevel, in, in_len) < 0) + return -1; + } n = gnutls_handshake(session); if (out_len) *out_len = b->out_len; + for (l = 0; l < 4; l++) { + if (b->other_out_len[l]) { + if (wsi->quic.qn) { + lws_tls_quic_tx_crypto_cb(wsi, l, b->other_out[l], b->other_out_len[l]); + } + lws_free_set_NULL(b->other_out[l]); + b->other_out_len[l] = 0; + } + } + if (n == GNUTLS_E_SUCCESS) return 0; @@ -253,13 +411,23 @@ lws_tls_quic_advance_handshake(struct lws *wsi, int level, return 1; lwsl_err("gnutls_handshake failed: %d\n", n); - return 0; + return n < 0 ? n : -1; } int lws_tls_quic_set_transport_parameters(struct lws *wsi, const uint8_t *tp, size_t tp_len) { - wsi->tls.quic_tp_send = tp; + uint8_t *p; + + if (wsi->tls.quic_tp_send) + lws_free((void *)wsi->tls.quic_tp_send); + + p = lws_malloc(tp_len, "quic tp send"); + if (!p) + return -1; + + memcpy(p, tp, tp_len); + wsi->tls.quic_tp_send = p; wsi->tls.quic_tp_send_len = tp_len; return 0; } @@ -382,10 +550,22 @@ lws_tls_quic_api_test(void) if (s_cred) gnutls_certificate_free_credentials(s_cred); if (c_cred) gnutls_certificate_free_credentials(c_cred); - if (wsi_client.tls.client_bio) lws_free(wsi_client.tls.client_bio); - if (wsi_server.tls.client_bio) lws_free(wsi_server.tls.client_bio); - if (wsi_client.tls.quic_tp_recv) lws_free((void *)wsi_client.tls.quic_tp_recv); - if (wsi_server.tls.quic_tp_recv) lws_free((void *)wsi_server.tls.quic_tp_recv); + gnutls_quic_bio_free(&wsi_client); + gnutls_quic_bio_free(&wsi_server); + + return 0; +} + +int +lws_tls_quic_migrate_wsi(struct lws *old_wsi, struct lws *new_wsi) +{ + gnutls_session_t session; + + if (!new_wsi || !new_wsi->tls.ssl) + return -1; + + session = (gnutls_session_t)new_wsi->tls.ssl; + gnutls_session_set_ptr(session, new_wsi); return 0; } diff --git a/lib/tls/gnutls/gnutls-session.c b/lib/tls/gnutls/gnutls-session.c index 3620a503f9..284baa0e53 100644 --- a/lib/tls/gnutls/gnutls-session.c +++ b/lib/tls/gnutls/gnutls-session.c @@ -173,13 +173,16 @@ lws_tls_session_new_gnutls(struct lws *wsi) lws_tls_scm_t *ts; size_t nl; gnutls_datum_t gd; +#if (_LWS_ENABLED_LOGS & LLL_INFO) const char *disposition = "reuse"; +#endif if (!wsi || !wsi->tls.ssl || !wsi->a.vhost) return 0; vh = wsi->a.vhost; - if (vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE) + if ((vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE) || + vh->being_destroyed) return 0; if (lws_tls_session_tag_from_wsi(wsi, buf, sizeof(buf))) { @@ -251,7 +254,9 @@ lws_tls_session_new_gnutls(struct lws *wsi) (int64_t)vh->tls.tls_session_cache_ttl * LWS_US_PER_SEC); +#if (_LWS_ENABLED_LOGS & LLL_INFO) disposition = "new"; +#endif } else { if (!ts->ser_data) { ts->ser_data = lws_malloc(sizeof(*ts->ser_data), __func__); @@ -285,7 +290,12 @@ lws_tls_session_new_gnutls(struct lws *wsi) lws_context_unlock(vh->context); /* } cx -------------- */ lwsl_tlssess("%s: %s %s, (%s:%u)\n", __func__, - disposition, buf, vh->name, +#if (_LWS_ENABLED_LOGS & LLL_INFO) + disposition, +#else + "", +#endif + buf, vh->name, (unsigned int)vh->tls_sessions.count); return 1; diff --git a/lib/tls/gnutls/gnutls-ssl.c b/lib/tls/gnutls/gnutls-ssl.c index 75515b11d0..0111125f50 100644 --- a/lib/tls/gnutls/gnutls-ssl.c +++ b/lib/tls/gnutls/gnutls-ssl.c @@ -25,6 +25,11 @@ #include "private-lib-core.h" #include "private-lib-tls.h" +#if defined(LWS_ROLE_QUIC) +extern void +gnutls_quic_bio_free(struct lws *wsi); +#endif + int lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, size_t len) { @@ -104,6 +109,10 @@ lws_ssl_pending(struct lws *wsi) int lws_ssl_close(struct lws *wsi) { +#if defined(LWS_ROLE_QUIC) + gnutls_quic_bio_free(wsi); +#endif + if (wsi->tls.ssl) { #if defined(LWS_WITH_TLS_SESSIONS) lws_tls_session_new_gnutls(wsi); @@ -124,6 +133,7 @@ lws_ssl_close(struct lws *wsi) return 0; } +#if defined(LWS_WITH_SERVER) enum lws_ssl_capable_status lws_tls_server_accept(struct lws *wsi) { @@ -136,6 +146,8 @@ lws_tls_server_accept(struct lws *wsi) if (!wsi->tls.ssl) return LWS_SSL_CAPABLE_ERROR; + wsi->skip_fallback = 1; + n = gnutls_handshake((gnutls_session_t)wsi->tls.ssl); lwsl_debug("%s: gnutls_handshake returned %d\n", __func__, n); @@ -168,6 +180,7 @@ lws_tls_server_accept(struct lws *wsi) return LWS_SSL_CAPABLE_ERROR; } +#endif enum lws_ssl_capable_status lws_tls_client_connect(struct lws *wsi, char *errbuf, size_t len) @@ -248,6 +261,7 @@ __lws_tls_shutdown(struct lws *wsi) return LWS_SSL_CAPABLE_ERROR; } +#if defined(LWS_WITH_SERVER) enum lws_ssl_capable_status lws_tls_server_abort_connection(struct lws *wsi) { @@ -259,6 +273,7 @@ lws_tls_server_abort_connection(struct lws *wsi) return LWS_SSL_CAPABLE_DONE; } +#endif int lws_tls_client_confirm_peer_cert(struct lws *wsi, char *ebuf, size_t ebuf_len) diff --git a/lib/tls/gnutls/gnutls-tls.c b/lib/tls/gnutls/gnutls-tls.c index f8d71f0c2e..7fa9b51477 100644 --- a/lib/tls/gnutls/gnutls-tls.c +++ b/lib/tls/gnutls/gnutls-tls.c @@ -39,6 +39,8 @@ lws_context_deinit_ssl_library(struct lws_context *context) gnutls_global_deinit(); } +#if defined(LWS_WITH_NETWORK) + void lws_tls_vhost_backend_free_ctx(lws_tls_ctx *ctx) { @@ -76,6 +78,7 @@ lws_tls_vhost_backend_create_ctx(struct lws_vhost *vhost) return 0; } +#if defined(LWS_WITH_SERVER) int lws_tls_server_vhost_backend_init(const struct lws_context_creation_info *info, struct lws_vhost *vhost, struct lws *wsi) @@ -112,7 +115,9 @@ lws_tls_server_vhost_backend_init(const struct lws_context_creation_info *info, return 0; } +#endif +#if defined(LWS_WITH_CLIENT) int lws_tls_client_create_vhost_context(struct lws_vhost *vh, const struct lws_context_creation_info *info, @@ -159,6 +164,48 @@ lws_tls_client_create_vhost_context(struct lws_vhost *vh, return 0; } +#endif + +#if defined(LWS_WITH_SERVER) +static int +lws_gnutls_server_name_cb(gnutls_session_t session) +{ + struct lws *wsi = (struct lws *)gnutls_session_get_ptr(session); + struct lws_vhost *vhost; + char servername[256]; + size_t len = sizeof(servername) - 1; + unsigned int type; + + if (!wsi) + return 0; + + if (gnutls_server_name_get(session, servername, &len, &type, 0) < 0) { + lwsl_info("SNI: Unknown ServerName\n"); + return 0; + } + servername[len] = '\0'; + + vhost = lws_select_vhost(wsi->a.context, wsi->a.vhost->listen_port, servername); + if (!vhost) { + lwsl_info("SNI: none: %s:%d\n", servername, wsi->a.vhost->listen_port); + return 0; + } + + lwsl_info("SNI: Found: %s:%d\n", servername, wsi->a.vhost->listen_port); + + if (!vhost->tls.ssl_ctx) { + lwsl_info("SNI: %s has no tls ctx yet\n", servername); + return 0; + } + + /* select the credentials from the selected vhost for this session */ + gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, vhost->tls.ssl_ctx->creds); + + /* And update wsi's bound vhost! */ + lws_vhost_bind_wsi(vhost, wsi); + + return 0; +} int lws_tls_server_new_nonblocking(struct lws *wsi, lws_sockfd_type accept_fd) @@ -175,6 +222,9 @@ lws_tls_server_new_nonblocking(struct lws *wsi, lws_sockfd_type accept_fd) gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, wsi->tls.ctx_ref ? wsi->tls.ctx_ref->ctx->creds : wsi->a.vhost->tls.ssl_ctx->creds); gnutls_transport_set_int((gnutls_session_t)wsi->tls.ssl, (int)accept_fd); + gnutls_session_set_ptr(session, wsi); + gnutls_handshake_set_post_client_hello_function(session, lws_gnutls_server_name_cb); + if (wsi->a.vhost->tls.alpn_ctx.len) { gnutls_datum_t alpn[4]; unsigned int i = 0, p = 0; @@ -189,7 +239,9 @@ lws_tls_server_new_nonblocking(struct lws *wsi, lws_sockfd_type accept_fd) return 0; } +#endif +#if defined(LWS_WITH_CLIENT) int lws_ssl_client_bio_create(struct lws *wsi) { @@ -252,6 +304,7 @@ lws_ssl_client_bio_create(struct lws *wsi) return 0; } +#endif void lws_ssl_SSL_CTX_destroy(struct lws_vhost *vhost) @@ -260,12 +313,14 @@ lws_ssl_SSL_CTX_destroy(struct lws_vhost *vhost) lws_tls_vhost_backend_free_ctx(vhost->tls.ssl_ctx); vhost->tls.ssl_ctx = NULL; } +#if defined(LWS_WITH_CLIENT) if (vhost->tls.ssl_client_ctx) { gnutls_certificate_free_credentials(vhost->tls.ssl_client_ctx->creds); gnutls_priority_deinit(vhost->tls.ssl_client_ctx->priority); lws_free(vhost->tls.ssl_client_ctx); vhost->tls.ssl_client_ctx = NULL; } +#endif } lws_tls_ctx * @@ -285,6 +340,7 @@ lws_ssl_context_destroy(struct lws_context *context) +#if defined(LWS_WITH_CLIENT) int lws_tls_client_vhost_extra_cert_mem(struct lws_vhost *vh, const uint8_t *der, size_t len) { @@ -304,6 +360,7 @@ lws_tls_client_vhost_extra_cert_mem(struct lws_vhost *vh, const uint8_t *der, si return 0; } +#endif int lws_tls_vhost_cert_info(struct lws_vhost *vhost, enum lws_tls_cert_info type, @@ -364,6 +421,7 @@ lws_tls_vhost_cert_info(struct lws_vhost *vhost, enum lws_tls_cert_info type, return -1; } +#if defined(LWS_WITH_SERVER) int lws_tls_server_certs_load(struct lws_vhost *vhost, struct lws *wsi, const char *cert, const char *private_key, @@ -411,6 +469,7 @@ lws_tls_server_client_cert_verify_config(struct lws_vhost *vh) * apply it dynamically when creating sessions if LWS_SERVER_OPTION_REQUIRE_VALID_CLIENT_CERT is set. */ return 0; } +#endif int lws_tls_peer_cert_info(struct lws *wsi, enum lws_tls_cert_info type, @@ -497,3 +556,5 @@ lws_tls_session_dump_load(struct lws_vhost *vh, const char *host, uint16_t port, { return -1; } + +#endif diff --git a/lib/tls/gnutls/lws-genaes.c b/lib/tls/gnutls/lws-genaes.c index 29086a44e5..dffb57864e 100644 --- a/lib/tls/gnutls/lws-genaes.c +++ b/lib/tls/gnutls/lws-genaes.c @@ -77,8 +77,17 @@ lws_genaes_create(struct lws_genaes_ctx *ctx, enum enum_aes_operation op, if (alg == GNUTLS_CIPHER_UNKNOWN) return 1; - if (gnutls_cipher_init(&ctx->ctx, alg, &key, NULL) < 0) - return 1; + if (mode == LWS_GAESM_ECB || mode == LWS_GAESM_CBC) { + uint8_t iv[16] = {0}; + gnutls_datum_t giv; + giv.data = iv; + giv.size = 16; + if (gnutls_cipher_init(&ctx->ctx, alg, &key, &giv) < 0) + return 1; + } else { + if (gnutls_cipher_init(&ctx->ctx, alg, &key, NULL) < 0) + return 1; + } return 0; } @@ -174,7 +183,20 @@ lws_genaes_crypt(struct lws_genaes_ctx *ctx, const uint8_t *in, size_t len, { int n; + if (ctx->mode == LWS_GAESM_CBC || ctx->mode == LWS_GAESM_ECB) { + if (!(ctx->op == LWS_GAESO_ENC && ctx->padding == LWS_GAESP_WITH_PADDING)) { + if (len % 16) { + lwsl_err("%s: input length %zu not a multiple of block size\n", __func__, len); + return -1; + } + } + } + if (ctx->mode == LWS_GAESM_KW) { + if (len < 16 || len % 8) { + lwsl_err("%s: KW len %zu invalid\n", __func__, len); + return -1; + } n = lws_genaes_rfc3394_wrap(ctx->op == LWS_GAESO_ENC, (ctx->op == LWS_GAESO_ENC ? (int)len * 8 : ((int)len - 8) * 8), ctx->k->buf, diff --git a/lib/tls/lws-genchacha.c b/lib/tls/lws-genchacha.c index c4226e0f25..081545e59e 100644 --- a/lib/tls/lws-genchacha.c +++ b/lib/tls/lws-genchacha.c @@ -26,7 +26,7 @@ #if defined(LWS_WITH_OPENSSL) #include "private-lib-tls-openssl.h" #endif -#if defined(LWS_WITH_MBEDTLS) +#if defined(LWS_WITH_MBEDTLS) && !defined(LWS_HAVE_MBEDTLS_V4) #include #endif @@ -46,7 +46,7 @@ lws_genchacha_create(struct lws_genchacha_ctx *ctx, enum enum_aes_operation op, ctx->ctx = EVP_CIPHER_CTX_new(); if (!ctx->ctx) return -1; -#elif defined(LWS_WITH_MBEDTLS) && defined(MBEDTLS_CHACHAPOLY_C) +#elif defined(LWS_WITH_MBEDTLS) && defined(MBEDTLS_CHACHAPOLY_C) && !defined(LWS_HAVE_MBEDTLS_V4) mbedtls_chachapoly_init(&ctx->u.cp); if (mbedtls_chachapoly_setkey(&ctx->u.cp, ctx->k->buf) != 0) { mbedtls_chachapoly_free(&ctx->u.cp); @@ -67,7 +67,7 @@ lws_genchacha_destroy(struct lws_genchacha_ctx *ctx) if (ctx->ctx) EVP_CIPHER_CTX_free(ctx->ctx); ctx->ctx = NULL; -#elif defined(LWS_WITH_MBEDTLS) && defined(MBEDTLS_CHACHAPOLY_C) +#elif defined(LWS_WITH_MBEDTLS) && defined(MBEDTLS_CHACHAPOLY_C) && !defined(LWS_HAVE_MBEDTLS_V4) mbedtls_chachapoly_free(&ctx->u.cp); #endif return 0; @@ -106,7 +106,7 @@ lws_genchacha_crypt(struct lws_genchacha_ctx *ctx, if (EVP_DecryptFinal_ex(ctx->ctx, out + (len ? len : 0), &outl) <= 0) return -1; return 0; } -#elif defined(LWS_WITH_MBEDTLS) && defined(MBEDTLS_CHACHAPOLY_C) +#elif defined(LWS_WITH_MBEDTLS) && defined(MBEDTLS_CHACHAPOLY_C) && !defined(LWS_HAVE_MBEDTLS_V4) if (ctx->op == LWS_GAESO_ENC) { if (mbedtls_chachapoly_encrypt_and_tag(&ctx->u.cp, len, nonce, aad, aad_len, in, out, tag) != 0) return -1; } else { diff --git a/lib/tls/mbedtls/CMakeLists.txt b/lib/tls/mbedtls/CMakeLists.txt index 6208f182af..efe39e6ac6 100644 --- a/lib/tls/mbedtls/CMakeLists.txt +++ b/lib/tls/mbedtls/CMakeLists.txt @@ -87,9 +87,19 @@ include_directories(wrapper/include wrapper/include/internal) find_path(LWS_MBEDTLS_INCLUDE_DIRS mbedtls/ssl.h) - find_library(MBEDTLS_LIBRARY mbedtls) - find_library(MBEDX509_LIBRARY mbedx509) - find_library(MBEDCRYPTO_LIBRARY mbedcrypto) + if (LWS_MBEDTLS_INCLUDE_DIRS) + list(GET LWS_MBEDTLS_INCLUDE_DIRS 0 MBEDTLS_INC_BASE) + set(MBEDTLS_HINTS + "${MBEDTLS_INC_BASE}/../lib" + "${MBEDTLS_INC_BASE}/../lib64" + "${MBEDTLS_INC_BASE}/../build/library" + "${MBEDTLS_INC_BASE}/../library" + ) + endif() + + find_library(MBEDTLS_LIBRARY mbedtls HINTS ${MBEDTLS_HINTS}) + find_library(MBEDX509_LIBRARY mbedx509 HINTS ${MBEDTLS_HINTS}) + find_library(MBEDCRYPTO_LIBRARY mbedcrypto HINTS ${MBEDTLS_HINTS}) set(LWS_MBEDTLS_LIBRARIES "${MBEDTLS_LIBRARY}" "${MBEDX509_LIBRARY}" "${MBEDCRYPTO_LIBRARY}") @@ -108,6 +118,30 @@ include_directories(wrapper/include wrapper/include/internal) set(MBEDTLS_LIBRARIES ${LWS_MBEDTLS_LIBRARIES} PARENT_SCOPE) endif() if (LWS_MBEDTLS_INCLUDE_DIRS) + set(CMAKE_REQUIRED_INCLUDES_TEMP ${CMAKE_REQUIRED_INCLUDES}) + list(GET LWS_MBEDTLS_INCLUDE_DIRS 0 MBEDTLS_INC_BASE) + set(LWS_MBEDTLS_TF_PSA_PATH "${MBEDTLS_INC_BASE}/../tf-psa-crypto" CACHE PATH "Path to TF-PSA-Crypto submodule") + set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES} ${LWS_MBEDTLS_INCLUDE_DIRS} "${LWS_MBEDTLS_TF_PSA_PATH}/include" "${LWS_MBEDTLS_TF_PSA_PATH}/drivers/builtin/include") + + CHECK_C_SOURCE_COMPILES("#include \n#if MBEDTLS_VERSION_MAJOR < 4\n#error Not V4\n#endif\nint main(void) { return 0; }\n" LWS_HAVE_MBEDTLS_V4) + if (NOT LWS_HAVE_MBEDTLS_V4) + CHECK_C_SOURCE_COMPILES("#include \n#if MBEDTLS_VERSION_MAJOR < 4\n#error Not V4\n#endif\nint main(void) { return 0; }\n" LWS_HAVE_MBEDTLS_V4_ALT) + if (LWS_HAVE_MBEDTLS_V4_ALT) + set(LWS_HAVE_MBEDTLS_V4 1) + endif() + endif() + if (LWS_HAVE_MBEDTLS_V4) + list(APPEND LWS_MBEDTLS_INCLUDE_DIRS "${LWS_MBEDTLS_TF_PSA_PATH}/include" "${LWS_MBEDTLS_TF_PSA_PATH}/drivers/builtin/include") + + find_library(TFPSACRYPTO_LIBRARY tfpsacrypto PATHS "${LWS_MBEDTLS_TF_PSA_PATH}/../build/library" "${MBEDTLS_INC_BASE}/../build/library") + if (NOT TFPSACRYPTO_LIBRARY) + message(FATAL_ERROR "Mbed TLS 4 requires TF-PSA-Crypto library (tfpsacrypto). Please specify TFPSACRYPTO_LIBRARY.") + endif() + list(APPEND MBEDTLS_LIBRARIES "${TFPSACRYPTO_LIBRARY}") + set(MBEDTLS_LIBRARIES ${MBEDTLS_LIBRARIES} PARENT_SCOPE) + endif() + set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES_TEMP}) + set(MBEDTLS_INCLUDE_DIRS ${LWS_MBEDTLS_INCLUDE_DIRS}) set(MBEDTLS_INCLUDE_DIRS ${LWS_MBEDTLS_INCLUDE_DIRS} PARENT_SCOPE) endif() diff --git a/lib/tls/mbedtls/lws-genaes.c b/lib/tls/mbedtls/lws-genaes.c index f6a4ebd950..33c4c1696c 100644 --- a/lib/tls/mbedtls/lws-genaes.c +++ b/lib/tls/mbedtls/lws-genaes.c @@ -25,6 +25,7 @@ * same whether you are using openssl or mbedtls hash functions underneath. */ #include "private-lib-core.h" +#if !defined(LWS_HAVE_MBEDTLS_V4) #if defined(LWS_WITH_JOSE) #include "private-lib-jose.h" #endif @@ -445,3 +446,213 @@ lws_genaes_crypt(struct lws_genaes_ctx *ctx, const uint8_t *in, size_t len, return 0; } +#else /* LWS_HAVE_MBEDTLS_V4 */ + +#if defined(LWS_WITH_JOSE) +#include "private-lib-jose.h" +#endif + +#include + +static unsigned int +_write_pkcs7_pad(uint8_t *p, int len) +{ + unsigned int n = 0, padlen = LWS_AES_CBC_BLOCKLEN * ((unsigned int)len / + LWS_AES_CBC_BLOCKLEN + 1) - (unsigned int)len; + + p += len; + + while (n++ < padlen) + *p++ = (uint8_t)padlen; + + return padlen; +} + +int +lws_genaes_create(struct lws_genaes_ctx *ctx, enum enum_aes_operation op, + enum enum_aes_modes mode, struct lws_gencrypto_keyelem *el, + enum enum_aes_padding padding, void *engine) +{ + psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT; + psa_algorithm_t alg; + psa_key_usage_t usage; + + ctx->mode = mode; + ctx->k = el; + ctx->op = op; /* We use the enum_aes_operation directly */ + ctx->underway = 0; + ctx->padding = padding == LWS_GAESP_WITH_PADDING; + ctx->cipher_ctx = psa_cipher_operation_init(); + ctx->aead_ctx = psa_aead_operation_init(); + ctx->key_id = 0; + + usage = (op == LWS_GAESO_ENC) ? PSA_KEY_USAGE_ENCRYPT : PSA_KEY_USAGE_DECRYPT; + + switch (ctx->mode) { + case LWS_GAESM_CBC: + alg = PSA_ALG_CBC_NO_PADDING; + break; + case LWS_GAESM_CFB128: + alg = PSA_ALG_CFB; + break; + case LWS_GAESM_CFB8: + lwsl_err("%s: AES-CFB8 not supported in V4\n", __func__); + return -1; + case LWS_GAESM_CTR: + alg = PSA_ALG_CTR; + break; + case LWS_GAESM_ECB: + alg = PSA_ALG_ECB_NO_PADDING; + break; + case LWS_GAESM_OFB: + alg = PSA_ALG_OFB; + break; + case LWS_GAESM_XTS: + lwsl_err("%s: AES-XTS not supported in V4\n", __func__); + return -1; + case LWS_GAESM_GCM: + alg = PSA_ALG_GCM; + break; + case LWS_GAESM_KW: + /* Not supported directly via cipher, but we can implement the wrapper */ + lwsl_err("%s: AES-KW not yet supported in V4\n", __func__); + return -1; + default: + return -1; + } + + ctx->alg = alg; + + psa_set_key_type(&attr, PSA_KEY_TYPE_AES); + psa_set_key_bits(&attr, el->len * 8); + psa_set_key_usage_flags(&attr, usage); + psa_set_key_algorithm(&attr, alg); + + if (psa_import_key(&attr, el->buf, el->len, &ctx->key_id) != PSA_SUCCESS) { + lwsl_err("%s: psa_import_key failed\n", __func__); + return -1; + } + + return 0; +} + +int +lws_genaes_destroy(struct lws_genaes_ctx *ctx, unsigned char *tag, size_t tlen) +{ + int ret = 0; + + if (ctx->mode == LWS_GAESM_GCM) { + if (ctx->underway) { + size_t olen; + if (ctx->op == LWS_GAESO_ENC) { + if (psa_aead_finish(&ctx->aead_ctx, NULL, 0, &olen, tag, tlen, &olen) != PSA_SUCCESS) + ret = -1; + } else { + if (psa_aead_verify(&ctx->aead_ctx, NULL, 0, &olen, tag, tlen) != PSA_SUCCESS) + ret = -1; + } + psa_aead_abort(&ctx->aead_ctx); + } + } else { + psa_cipher_abort(&ctx->cipher_ctx); + } + + if (ctx->key_id) { + psa_destroy_key(ctx->key_id); + ctx->key_id = 0; + } + + return ret; +} + +int +lws_genaes_crypt(struct lws_genaes_ctx *ctx, const uint8_t *in, size_t len, + uint8_t *out, uint8_t *iv_or_nonce_ctr_or_data_unit_16, + uint8_t *stream_block_16, size_t *nc_or_iv_off, int taglen) +{ + size_t olen; + psa_status_t status; + + switch (ctx->mode) { + case LWS_GAESM_KW: + lwsl_err("%s: AES-KW not implemented in V4\n", __func__); + return -1; + + case LWS_GAESM_CBC: + case LWS_GAESM_CFB128: + case LWS_GAESM_CFB8: + case LWS_GAESM_OFB: + case LWS_GAESM_XTS: + case LWS_GAESM_ECB: + case LWS_GAESM_CTR: + if (ctx->op == LWS_GAESO_ENC) + status = psa_cipher_encrypt_setup(&ctx->cipher_ctx, ctx->key_id, ctx->alg); + else + status = psa_cipher_decrypt_setup(&ctx->cipher_ctx, ctx->key_id, ctx->alg); + + if (status != PSA_SUCCESS) return -1; + + if (ctx->mode != LWS_GAESM_ECB) { + status = psa_cipher_set_iv(&ctx->cipher_ctx, iv_or_nonce_ctr_or_data_unit_16, 16); + if (status != PSA_SUCCESS) { + lwsl_err("%s: set_iv failed %d\n", __func__, (int)status); + return -1; + } + } + + if (ctx->padding && ctx->op == LWS_GAESO_ENC && ctx->mode == LWS_GAESM_CBC) { + uint8_t *padin = (uint8_t *)lws_malloc( + lws_gencrypto_padded_length(LWS_AES_CBC_BLOCKLEN, len), + __func__); + if (!padin) { + psa_cipher_abort(&ctx->cipher_ctx); + return -1; + } + memcpy(padin, in, len); + len += _write_pkcs7_pad((uint8_t *)padin, (int)len); + status = psa_cipher_update(&ctx->cipher_ctx, padin, len, out, len + 16, &olen); + lws_free(padin); + } else { + status = psa_cipher_update(&ctx->cipher_ctx, in, len, out, len + 16, &olen); + } + + if (status != PSA_SUCCESS) { + psa_cipher_abort(&ctx->cipher_ctx); + return -1; + } + + /* finish */ + status = psa_cipher_finish(&ctx->cipher_ctx, out + olen, 16, &olen); + if (status != PSA_SUCCESS) return -1; + break; + + case LWS_GAESM_GCM: + if (!ctx->underway) { + ctx->underway = 1; + ctx->taglen = taglen; + + if (ctx->op == LWS_GAESO_ENC) + status = psa_aead_encrypt_setup(&ctx->aead_ctx, ctx->key_id, ctx->alg); + else + status = psa_aead_decrypt_setup(&ctx->aead_ctx, ctx->key_id, ctx->alg); + + if (status != PSA_SUCCESS) return -1; + + status = psa_aead_set_nonce(&ctx->aead_ctx, iv_or_nonce_ctr_or_data_unit_16, *nc_or_iv_off); + if (status != PSA_SUCCESS) return -1; + + if (len) { + status = psa_aead_update_ad(&ctx->aead_ctx, in, len); + if (status != PSA_SUCCESS) return -1; + } + break; + } + + status = psa_aead_update(&ctx->aead_ctx, in, len, out, len + 16, &olen); + if (status != PSA_SUCCESS) return -1; + break; + } + + return 0; +} +#endif diff --git a/lib/tls/mbedtls/lws-gendtls.c b/lib/tls/mbedtls/lws-gendtls.c index 5700ed1d11..f25c89a0d2 100644 --- a/lib/tls/mbedtls/lws-gendtls.c +++ b/lib/tls/mbedtls/lws-gendtls.c @@ -295,7 +295,16 @@ lws_gendtls_put_rx(struct lws_gendtls_ctx *ctx, const uint8_t *in, size_t len) int lws_gendtls_get_rx(struct lws_gendtls_ctx *ctx, uint8_t *out, size_t max_len) { - int ret = mbedtls_ssl_read(&ctx->ssl, out, max_len); + int ret; + do { + ret = mbedtls_ssl_read(&ctx->ssl, out, max_len); + } while ( +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 0x03000000 + ret == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET +#else + 0 +#endif + ); if (ret > 0) return ret; @@ -322,7 +331,15 @@ lws_gendtls_put_tx(struct lws_gendtls_ctx *ctx, const uint8_t *in, size_t len) int ret; while (len) { - ret = mbedtls_ssl_write(&ctx->ssl, in, len); + do { + ret = mbedtls_ssl_write(&ctx->ssl, in, len); + } while ( +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 0x03000000 + ret == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET +#else + 0 +#endif + ); if (ret > 0) { in += ret; len -= (size_t)ret; diff --git a/lib/tls/mbedtls/lws-genec.c b/lib/tls/mbedtls/lws-genec.c index be6a97d999..34f154f48f 100644 --- a/lib/tls/mbedtls/lws-genec.c +++ b/lib/tls/mbedtls/lws-genec.c @@ -25,17 +25,6 @@ * same whether you are using openssl or mbedtls crypto functions underneath. */ #include "private-lib-core.h" -#include "private-lib-tls-mbedtls.h" - -#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 0x03000000 -#define ECDHCTX(_c, _ins) _c->u.ctx_ecdh->MBEDTLS_PRIVATE(ctx).\ - MBEDTLS_PRIVATE(mbed_ecdh).MBEDTLS_PRIVATE(_ins) -#define ECDSACTX(_c, _ins) _c->u.ctx_ecdsa->MBEDTLS_PRIVATE(_ins) -#else -#define ECDHCTX(_c, _ins) _c->u.ctx_ecdh->_ins -#define ECDSACTX(_c, _ins) _c->u.ctx_ecdsa->_ins -#endif - const struct lws_ec_curves lws_ec_curves[] = { /* * These are the curves we are willing to use by default... @@ -51,6 +40,18 @@ const struct lws_ec_curves lws_ec_curves[] = { { NULL, 0, 0 } }; +#if !defined(LWS_HAVE_MBEDTLS_V4) +#include "private-lib-tls-mbedtls.h" + +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 0x03000000 +#define ECDHCTX(_c, _ins) _c->u.ctx_ecdh->MBEDTLS_PRIVATE(ctx).\ + MBEDTLS_PRIVATE(mbed_ecdh).MBEDTLS_PRIVATE(_ins) +#define ECDSACTX(_c, _ins) _c->u.ctx_ecdsa->MBEDTLS_PRIVATE(_ins) +#else +#define ECDHCTX(_c, _ins) _c->u.ctx_ecdh->_ins +#define ECDSACTX(_c, _ins) _c->u.ctx_ecdsa->_ins +#endif + static int lws_genec_keypair_import(struct lws_genec_ctx *ctx, enum enum_lws_dh_side side, const struct lws_gencrypto_keyelem *el) @@ -573,3 +574,333 @@ lws_geneddsa_hash_sign_jws(struct lws_genec_ctx *ctx, const uint8_t *in, { return -1; } +#else /* LWS_HAVE_MBEDTLS_V4 */ + +#include "private-lib-tls-mbedtls.h" +#include + +static psa_ecc_family_t +lws_genec_to_psa_curve(int nid, size_t *bits) +{ + switch (nid) { + case MBEDTLS_ECP_DP_SECP256R1: *bits = 256; return PSA_ECC_FAMILY_SECP_R1; + case MBEDTLS_ECP_DP_SECP384R1: *bits = 384; return PSA_ECC_FAMILY_SECP_R1; + case MBEDTLS_ECP_DP_SECP521R1: *bits = 521; return PSA_ECC_FAMILY_SECP_R1; + default: return 0; + } +} + +static int +lws_genec_keypair_import(struct lws_genec_ctx *ctx, enum enum_lws_dh_side side, + const struct lws_gencrypto_keyelem *el) +{ + const struct lws_ec_curves *curve; + psa_ecc_family_t family; + size_t bits; + psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT; + + if (el[LWS_GENCRYPTO_EC_KEYEL_CRV].len < 4) + return -21; + + curve = lws_genec_curve(ctx->curve_table, (char *)el[LWS_GENCRYPTO_EC_KEYEL_CRV].buf); + if (!curve) return -22; + + family = lws_genec_to_psa_curve(curve->tls_lib_nid, &bits); + if (!family) return -22; + + ctx->has_private = !!el[LWS_GENCRYPTO_EC_KEYEL_D].len; + + if (side == LDHS_THEIRS) { + /* We are importing the peer's public key for ECDH. + * psa_raw_key_agreement just takes the peer key as bytes. */ + size_t len = el[LWS_GENCRYPTO_EC_KEYEL_X].len; + if (len * 2 + 1 > 133) return -1; + ctx->peer_key = lws_malloc(1 + len * 2, "peer_key"); + if (!ctx->peer_key) return -1; + ctx->peer_key[0] = 0x04; + memcpy(ctx->peer_key + 1, el[LWS_GENCRYPTO_EC_KEYEL_X].buf, len); + memcpy(ctx->peer_key + 1 + len, el[LWS_GENCRYPTO_EC_KEYEL_Y].buf, len); + ctx->peer_key_len = 1 + len * 2; + return 0; + } + + psa_set_key_type(&attr, ctx->has_private ? PSA_KEY_TYPE_ECC_KEY_PAIR(family) : PSA_KEY_TYPE_ECC_PUBLIC_KEY(family)); + psa_set_key_bits(&attr, bits); + + if (ctx->genec_alg == LEGENEC_ECDH) { + psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_DERIVE); + psa_set_key_algorithm(&attr, PSA_ALG_ECDH); + } else { + psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_SIGN_HASH | PSA_KEY_USAGE_VERIFY_HASH); + psa_set_key_algorithm(&attr, PSA_ALG_ECDSA(PSA_ALG_ANY_HASH)); + } + + if (ctx->has_private) { + if (psa_import_key(&attr, el[LWS_GENCRYPTO_EC_KEYEL_D].buf, el[LWS_GENCRYPTO_EC_KEYEL_D].len, &ctx->key_id) != PSA_SUCCESS) + return -1; + } else { + uint8_t pub[133]; + size_t len = el[LWS_GENCRYPTO_EC_KEYEL_X].len; + if (len * 2 + 1 > sizeof(pub)) return -1; + pub[0] = 0x04; + memcpy(pub + 1, el[LWS_GENCRYPTO_EC_KEYEL_X].buf, len); + memcpy(pub + 1 + len, el[LWS_GENCRYPTO_EC_KEYEL_Y].buf, len); + if (psa_import_key(&attr, pub, 1 + len * 2, &ctx->key_id) != PSA_SUCCESS) + return -1; + } + + return 0; +} + +int +lws_genecdh_create(struct lws_genec_ctx *ctx, struct lws_context *context, + const struct lws_ec_curves *curve_table) +{ + memset(ctx, 0, sizeof(*ctx)); + ctx->context = context; + ctx->curve_table = curve_table; + ctx->genec_alg = LEGENEC_ECDH; + return 0; +} + +int +lws_genecdsa_create(struct lws_genec_ctx *ctx, struct lws_context *context, + const struct lws_ec_curves *curve_table) +{ + memset(ctx, 0, sizeof(*ctx)); + ctx->context = context; + ctx->curve_table = curve_table; + ctx->genec_alg = LEGENEC_ECDSA; + return 0; +} + +int +lws_genecdh_set_key(struct lws_genec_ctx *ctx, const struct lws_gencrypto_keyelem *el, + enum enum_lws_dh_side side) +{ + if (ctx->genec_alg != LEGENEC_ECDH) + return -1; + return lws_genec_keypair_import(ctx, side, el); +} + +int +lws_genecdsa_set_key(struct lws_genec_ctx *ctx, + const struct lws_gencrypto_keyelem *el) +{ + if (ctx->genec_alg != LEGENEC_ECDSA) + return -1; + return lws_genec_keypair_import(ctx, 0, el); +} + +void +lws_genec_destroy(struct lws_genec_ctx *ctx) +{ + if (ctx->key_id) { + psa_destroy_key(ctx->key_id); + ctx->key_id = 0; + } + if (ctx->peer_key) { + lws_free_set_NULL(ctx->peer_key); + } +} + +static int +lws_genec_new_keypair_v4(struct lws_genec_ctx *ctx, const char *curve_name, + struct lws_gencrypto_keyelem *el, psa_key_usage_t usage, psa_algorithm_t alg) +{ + const struct lws_ec_curves *curve; + psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT; + psa_status_t status; + uint8_t d_buf[130], pub_buf[130]; + size_t d_len, pub_len; + psa_ecc_family_t family; + + curve = lws_genec_curve(ctx->curve_table, curve_name); + if (!curve) { + lwsl_err("%s: unknown curve %s\n", __func__, curve_name); + return -1; + } + + switch (curve->tls_lib_nid) { + case MBEDTLS_ECP_DP_SECP256R1: + case MBEDTLS_ECP_DP_SECP384R1: + case MBEDTLS_ECP_DP_SECP521R1: + family = PSA_ECC_FAMILY_SECP_R1; + break; + default: + lwsl_err("%s: unsupported curve\n", __func__); + return -1; + } + + psa_set_key_type(&attr, PSA_KEY_TYPE_ECC_KEY_PAIR(family)); + psa_set_key_bits(&attr, curve->key_bytes * 8); + psa_set_key_usage_flags(&attr, usage | PSA_KEY_USAGE_EXPORT); + psa_set_key_algorithm(&attr, alg); + + if (ctx->key_id) { + psa_destroy_key(ctx->key_id); + ctx->key_id = 0; + } + + status = psa_generate_key(&attr, &ctx->key_id); + if (status != PSA_SUCCESS) { + lwsl_err("%s: generate failed %d\n", __func__, (int)status); + return -1; + } + + status = psa_export_key(ctx->key_id, d_buf, sizeof(d_buf), &d_len); + if (status != PSA_SUCCESS) { + lwsl_err("%s: export failed %d\n", __func__, (int)status); + goto bail; + } + + status = psa_export_public_key(ctx->key_id, pub_buf, sizeof(pub_buf), &pub_len); + if (status != PSA_SUCCESS) { + lwsl_err("%s: export public failed %d\n", __func__, (int)status); + goto bail; + } + + if (pub_len < 1 || pub_buf[0] != 0x04) { + lwsl_err("%s: pubkey not uncompressed\n", __func__); + goto bail; + } + size_t coord_len = (pub_len - 1) / 2; + + el[LWS_GENCRYPTO_EC_KEYEL_CRV].buf = lws_malloc(strlen(curve_name) + 1, "crv"); + if (!el[LWS_GENCRYPTO_EC_KEYEL_CRV].buf) goto bail; + el[LWS_GENCRYPTO_EC_KEYEL_CRV].len = (uint32_t)strlen(curve_name); + memcpy(el[LWS_GENCRYPTO_EC_KEYEL_CRV].buf, curve_name, strlen(curve_name) + 1); + + el[LWS_GENCRYPTO_EC_KEYEL_X].buf = lws_malloc(coord_len, "x"); + if (!el[LWS_GENCRYPTO_EC_KEYEL_X].buf) goto bail; + el[LWS_GENCRYPTO_EC_KEYEL_X].len = (uint32_t)coord_len; + memcpy(el[LWS_GENCRYPTO_EC_KEYEL_X].buf, pub_buf + 1, coord_len); + + el[LWS_GENCRYPTO_EC_KEYEL_Y].buf = lws_malloc(coord_len, "y"); + if (!el[LWS_GENCRYPTO_EC_KEYEL_Y].buf) goto bail; + el[LWS_GENCRYPTO_EC_KEYEL_Y].len = (uint32_t)coord_len; + memcpy(el[LWS_GENCRYPTO_EC_KEYEL_Y].buf, pub_buf + 1 + coord_len, coord_len); + + el[LWS_GENCRYPTO_EC_KEYEL_D].buf = lws_malloc(d_len, "d"); + if (!el[LWS_GENCRYPTO_EC_KEYEL_D].buf) goto bail; + el[LWS_GENCRYPTO_EC_KEYEL_D].len = (uint32_t)d_len; + memcpy(el[LWS_GENCRYPTO_EC_KEYEL_D].buf, d_buf, d_len); + + return 0; + +bail: + for (int n = 0; n < LWS_GENCRYPTO_EC_KEYEL_COUNT; n++) { + if (el[n].buf) { + lws_free(el[n].buf); + el[n].buf = NULL; + } + } + psa_destroy_key(ctx->key_id); + ctx->key_id = 0; + return -1; +} + +int +lws_genecdh_new_keypair(struct lws_genec_ctx *ctx, enum enum_lws_dh_side side, + const char *curve_name, + struct lws_gencrypto_keyelem *el) +{ + if (ctx->genec_alg != LEGENEC_ECDH) return -1; + return lws_genec_new_keypair_v4(ctx, curve_name, el, PSA_KEY_USAGE_DERIVE, PSA_ALG_ECDH); +} + +int +lws_genecdsa_new_keypair(struct lws_genec_ctx *ctx, const char *curve_name, + struct lws_gencrypto_keyelem *el) +{ + if (ctx->genec_alg != LEGENEC_ECDSA) return -1; + return lws_genec_new_keypair_v4(ctx, curve_name, el, PSA_KEY_USAGE_SIGN_HASH | PSA_KEY_USAGE_VERIFY_HASH, PSA_ALG_ECDSA(PSA_ALG_ANY_HASH)); +} + +int +lws_genecdsa_hash_sign_jws(struct lws_genec_ctx *ctx, const uint8_t *in, + enum lws_genhash_types hash_type, int keybits, + uint8_t *sig, size_t sig_len) +{ + size_t olen; + psa_algorithm_t alg; + + if (ctx->genec_alg != LEGENEC_ECDSA) + return -1; + + alg = PSA_ALG_ECDSA(PSA_ALG_ANY_HASH); /* Or specific based on hash_type */ + if (psa_sign_hash(ctx->key_id, alg, in, lws_genhash_size(hash_type), sig, sig_len, &olen) != PSA_SUCCESS) + return -3; + return 0; +} + +int +lws_genecdsa_hash_sig_verify_jws(struct lws_genec_ctx *ctx, const uint8_t *in, + enum lws_genhash_types hash_type, int keybits, + const uint8_t *sig, size_t sig_len) +{ + psa_algorithm_t alg; + + if (ctx->genec_alg != LEGENEC_ECDSA) + return -1; + + alg = PSA_ALG_ECDSA(PSA_ALG_ANY_HASH); + if (psa_verify_hash(ctx->key_id, alg, in, lws_genhash_size(hash_type), sig, sig_len) != PSA_SUCCESS) + return -3; + return 0; +} + +int +lws_genecdh_compute_shared_secret(struct lws_genec_ctx *ctx, uint8_t *ss, + int *ss_len) +{ + size_t olen; + if (!ctx->key_id || !ctx->peer_key) { + lwsl_err("%s: both sides must be set up\n", __func__); + return -1; + } + + if (psa_raw_key_agreement(PSA_ALG_ECDH, ctx->key_id, ctx->peer_key, ctx->peer_key_len, + ss, (size_t)*ss_len, &olen) != PSA_SUCCESS) + return -1; + + *ss_len = (int)olen; + return 0; +} + +int +lws_geneddsa_create(struct lws_genec_ctx *ctx, struct lws_context *context, + const struct lws_ec_curves *curve_table) +{ + return -1; +} + +int +lws_geneddsa_set_key(struct lws_genec_ctx *ctx, + const struct lws_gencrypto_keyelem *el) +{ + return -1; +} + +int +lws_geneddsa_new_keypair(struct lws_genec_ctx *ctx, const char *curve_name, + struct lws_gencrypto_keyelem *el) +{ + return -1; +} + +int +lws_geneddsa_hash_sig_verify_jws(struct lws_genec_ctx *ctx, const uint8_t *in, + size_t in_len, const uint8_t *sig, size_t sig_len) +{ + return -1; +} + +int +lws_geneddsa_hash_sign_jws(struct lws_genec_ctx *ctx, const uint8_t *in, + size_t in_len, uint8_t *sig, size_t sig_len) +{ + return -1; +} + +#endif diff --git a/lib/tls/mbedtls/lws-genhash.c b/lib/tls/mbedtls/lws-genhash.c index f150cc9780..4588430878 100644 --- a/lib/tls/mbedtls/lws-genhash.c +++ b/lib/tls/mbedtls/lws-genhash.c @@ -48,9 +48,38 @@ * We have the _ret variants available, check the return codes on everything */ +#if defined(LWS_HAVE_MBEDTLS_V4) +static psa_algorithm_t +lws_genhash_to_psa_alg(enum lws_genhash_types type) +{ + switch (type) { + case LWS_GENHASH_TYPE_MD5: return PSA_ALG_MD5; + case LWS_GENHASH_TYPE_SHA1: return PSA_ALG_SHA_1; + case LWS_GENHASH_TYPE_SHA256: return PSA_ALG_SHA_256; + case LWS_GENHASH_TYPE_SHA384: return PSA_ALG_SHA_384; + case LWS_GENHASH_TYPE_SHA512: return PSA_ALG_SHA_512; + default: return 0; + } +} +#endif + int lws_genhash_init(struct lws_genhash_ctx *ctx, enum lws_genhash_types type) { +#if defined(LWS_HAVE_MBEDTLS_V4) + psa_algorithm_t alg; + + ctx->type = (uint8_t)type; + alg = lws_genhash_to_psa_alg(type); + if (!alg) + return 1; + + ctx->hash_ctx = psa_hash_operation_init(); + if (psa_hash_setup(&ctx->hash_ctx, alg) != PSA_SUCCESS) + return 1; + + return 0; +#else ctx->type = (uint8_t)type; switch (ctx->type) { @@ -84,11 +113,21 @@ lws_genhash_init(struct lws_genhash_ctx *ctx, enum lws_genhash_types type) } return 0; +#endif } int lws_genhash_update(struct lws_genhash_ctx *ctx, const void *in, size_t len) { +#if defined(LWS_HAVE_MBEDTLS_V4) + if (!len) + return 0; + + if (psa_hash_update(&ctx->hash_ctx, in, len) != PSA_SUCCESS) + return 1; + + return 0; +#else if (!len) return 0; @@ -116,11 +155,26 @@ lws_genhash_update(struct lws_genhash_ctx *ctx, const void *in, size_t len) } return 0; +#endif } int lws_genhash_destroy(struct lws_genhash_ctx *ctx, void *result) { +#if defined(LWS_HAVE_MBEDTLS_V4) + size_t hash_len; + + if (result) { + if (psa_hash_finish(&ctx->hash_ctx, result, lws_genhash_size(ctx->type), &hash_len) != PSA_SUCCESS) { + psa_hash_abort(&ctx->hash_ctx); + return 1; + } + } else { + psa_hash_abort(&ctx->hash_ctx); + } + + return 0; +#else switch (ctx->type) { case LWS_GENHASH_TYPE_MD5: if (mbedtls_md5_finish_ret(&ctx->u.md5, result)) @@ -150,6 +204,7 @@ lws_genhash_destroy(struct lws_genhash_ctx *ctx, void *result) } return 0; +#endif } #else @@ -249,10 +304,50 @@ lws_genhash_destroy(struct lws_genhash_ctx *ctx, void *result) #endif +#if defined(LWS_HAVE_MBEDTLS_V4) +static psa_algorithm_t +lws_genhmac_to_psa_alg(enum lws_genhmac_types type) +{ + switch (type) { + case LWS_GENHMAC_TYPE_SHA1: return PSA_ALG_SHA_1; + case LWS_GENHMAC_TYPE_SHA256: return PSA_ALG_SHA_256; + case LWS_GENHMAC_TYPE_SHA384: return PSA_ALG_SHA_384; + case LWS_GENHMAC_TYPE_SHA512: return PSA_ALG_SHA_512; + default: return 0; + } +} +#endif + int lws_genhmac_init(struct lws_genhmac_ctx *ctx, enum lws_genhmac_types type, const uint8_t *key, size_t key_len) { +#if defined(LWS_HAVE_MBEDTLS_V4) + psa_algorithm_t alg, hmac_alg; + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + + ctx->type = (uint8_t)type; + alg = lws_genhmac_to_psa_alg(type); + if (!alg) + return -1; + + hmac_alg = PSA_ALG_HMAC(alg); + + psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_SIGN_HASH | PSA_KEY_USAGE_VERIFY_HASH | PSA_KEY_USAGE_SIGN_MESSAGE | PSA_KEY_USAGE_VERIFY_MESSAGE); + psa_set_key_algorithm(&attributes, hmac_alg); + psa_set_key_type(&attributes, PSA_KEY_TYPE_HMAC); + + if (psa_import_key(&attributes, key, key_len, &ctx->key_id) != PSA_SUCCESS) + return -1; + + ctx->mac_ctx = psa_mac_operation_init(); + if (psa_mac_sign_setup(&ctx->mac_ctx, ctx->key_id, hmac_alg) != PSA_SUCCESS) { + psa_destroy_key(ctx->key_id); + return -1; + } + + return 0; +#else int t; ctx->type = (uint8_t)type; @@ -298,11 +393,21 @@ lws_genhmac_init(struct lws_genhmac_ctx *ctx, enum lws_genhmac_types type, } return 0; +#endif } int lws_genhmac_update(struct lws_genhmac_ctx *ctx, const void *in, size_t len) { +#if defined(LWS_HAVE_MBEDTLS_V4) + if (!len) + return 0; + + if (psa_mac_update(&ctx->mac_ctx, in, len) != PSA_SUCCESS) + return -1; + + return 0; +#else if (!len) return 0; @@ -310,11 +415,27 @@ lws_genhmac_update(struct lws_genhmac_ctx *ctx, const void *in, size_t len) return -1; return 0; +#endif } int lws_genhmac_destroy(struct lws_genhmac_ctx *ctx, void *result) { +#if defined(LWS_HAVE_MBEDTLS_V4) + size_t mac_len; + int ret = 0; + + if (result) { + if (psa_mac_sign_finish(&ctx->mac_ctx, result, lws_genhmac_size(ctx->type), &mac_len) != PSA_SUCCESS) + ret = -1; + } else { + psa_mac_abort(&ctx->mac_ctx); + } + + psa_destroy_key(ctx->key_id); + + return ret; +#else int n = 0; if (result) @@ -326,4 +447,5 @@ lws_genhmac_destroy(struct lws_genhmac_ctx *ctx, void *result) return -1; return 0; +#endif } diff --git a/lib/tls/mbedtls/lws-genrsa.c b/lib/tls/mbedtls/lws-genrsa.c index 292b1ac1a7..9a3bec8592 100644 --- a/lib/tls/mbedtls/lws-genrsa.c +++ b/lib/tls/mbedtls/lws-genrsa.c @@ -25,6 +25,7 @@ * same whether you are using openssl or mbedtls crypto functions underneath. */ #include "private-lib-core.h" +#if !defined(LWS_HAVE_MBEDTLS_V4) #include "private-lib-tls-mbedtls.h" #include @@ -562,3 +563,306 @@ lws_genrsa_destroy(struct lws_genrsa_ctx *ctx) lws_free(ctx->ctx); ctx->ctx = NULL; } +#else /* LWS_HAVE_MBEDTLS_V4 */ + +#include "private-lib-tls-mbedtls.h" +#include + +void +lws_genrsa_destroy_elements(struct lws_gencrypto_keyelem *el) +{ + int n; + for (n = 0; n < LWS_GENCRYPTO_RSA_KEYEL_COUNT; n++) + if (el[n].buf) + lws_free_set_NULL(el[n].buf); +} + +static int write_asn1_integer(uint8_t **p, uint8_t *end, const struct lws_gencrypto_keyelem *el) +{ + size_t len = el->len; + int leading_zero = 0; + if (!len || !el->buf) { + if (*p + 3 > end) return -1; + *(*p)++ = 0x02; + *(*p)++ = 0x01; + *(*p)++ = 0x00; + return 0; + } + if (el->buf[0] & 0x80) { + leading_zero = 1; + len++; + } + if (*p + 2 + (len >= 128 ? (len >= 256 ? 3 : 2) : 1) + len > end) + return -1; + + *(*p)++ = 0x02; + if (len < 128) { + *(*p)++ = (uint8_t)len; + } else if (len < 256) { + *(*p)++ = 0x81; + *(*p)++ = (uint8_t)len; + } else { + *(*p)++ = 0x82; + *(*p)++ = (uint8_t)(len >> 8); + *(*p)++ = (uint8_t)(len & 0xFF); + } + if (leading_zero) + *(*p)++ = 0x00; + memcpy(*p, el->buf, el->len); + *p += el->len; + return 0; +} + +static int write_asn1_len(uint8_t **p, uint8_t *end, size_t len) +{ + if (len < 128) { + if (*p + 1 > end) return -1; + *(*p)++ = (uint8_t)len; + } else if (len < 256) { + if (*p + 2 > end) return -1; + *(*p)++ = 0x81; + *(*p)++ = (uint8_t)len; + } else { + if (*p + 3 > end) return -1; + *(*p)++ = 0x82; + *(*p)++ = (uint8_t)(len >> 8); + *(*p)++ = (uint8_t)(len & 0xFF); + } + return 0; +} + +int +lws_genrsa_create(struct lws_genrsa_ctx *ctx, + const struct lws_gencrypto_keyelem *el, + struct lws_context *context, enum enum_genrsa_mode mode, + enum lws_genhash_types oaep_hashid) +{ + uint8_t der[4096]; + uint8_t *p = der; + uint8_t *end = der + sizeof(der); + size_t payload_len = 0; + int i; + struct lws_gencrypto_keyelem zero = { NULL, 0 }; + struct lws_gencrypto_keyelem version = { (uint8_t *)"\0", 1 }; + psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT; + + memset(ctx, 0, sizeof(*ctx)); + ctx->context = context; + ctx->mode = mode; + + /* Calculate payload size */ + for (i = -1; i < LWS_GENCRYPTO_RSA_KEYEL_COUNT; i++) { + const struct lws_gencrypto_keyelem *e = (i == -1) ? &version : &el[i]; + size_t len = e->len; + if (len && (e->buf[0] & 0x80)) len++; + payload_len += 1 + (size_t)(len >= 128 ? (len >= 256 ? 3 : 2) : 1) + len; + if (!len && i != -1) payload_len += 3; /* zero int */ + } + + if (p + 1 > end) return -1; + *p++ = 0x30; /* SEQUENCE */ + if (write_asn1_len(&p, end, payload_len)) return -1; + + if (write_asn1_integer(&p, end, &version)) return -1; + for (i = 0; i < LWS_GENCRYPTO_RSA_KEYEL_COUNT; i++) { + if (write_asn1_integer(&p, end, el[i].len ? &el[i] : &zero)) return -1; + } + + psa_set_key_type(&attr, PSA_KEY_TYPE_RSA_KEY_PAIR); + psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_SIGN_HASH | PSA_KEY_USAGE_VERIFY_HASH | + PSA_KEY_USAGE_DECRYPT | PSA_KEY_USAGE_ENCRYPT); + + /* Determine algorithm based on mode */ + if (mode == LGRSAM_PKCS1_1_5) { + psa_set_key_algorithm(&attr, PSA_ALG_RSA_PKCS1V15_SIGN_RAW); + } else if (mode == LGRSAM_PKCS1_OAEP_PSS) { + psa_set_key_algorithm(&attr, PSA_ALG_RSA_PSS_ANY_SALT(PSA_ALG_ANY_HASH)); + } + + if (psa_import_key(&attr, der, (size_t)(p - der), &ctx->key_id) != PSA_SUCCESS) + return -1; + + return 0; +} + +int +lws_genrsa_new_keypair(struct lws_context *context, struct lws_genrsa_ctx *ctx, + enum enum_genrsa_mode mode, struct lws_gencrypto_keyelem *el, + int bits) +{ + psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT; + uint8_t der[4096]; + size_t der_len; + + memset(ctx, 0, sizeof(*ctx)); + ctx->context = context; + ctx->mode = mode; + + psa_set_key_type(&attr, PSA_KEY_TYPE_RSA_KEY_PAIR); + psa_set_key_bits(&attr, (size_t)bits); + psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_SIGN_HASH | PSA_KEY_USAGE_VERIFY_HASH | + PSA_KEY_USAGE_DECRYPT | PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_EXPORT); + + if (mode == LGRSAM_PKCS1_1_5) { + psa_set_key_algorithm(&attr, PSA_ALG_RSA_PKCS1V15_SIGN_RAW); + } else if (mode == LGRSAM_PKCS1_OAEP_PSS) { + psa_set_key_algorithm(&attr, PSA_ALG_RSA_PSS_ANY_SALT(PSA_ALG_ANY_HASH)); + } + + if (psa_generate_key(&attr, &ctx->key_id) != PSA_SUCCESS) + return -1; + + if (psa_export_key(ctx->key_id, der, sizeof(der), &der_len) != PSA_SUCCESS) + return -1; + + { + uint8_t *p = der; + uint8_t *end = der + der_len; + int i; + + if (p >= end || *p++ != 0x30) return -1; + /* Skip length */ + if (p >= end) return -1; + if (*p & 0x80) { + int l = *p++ & 0x7F; + p += l; + } else { + p++; + } + + /* Skip version integer */ + if (p >= end || *p++ != 0x02) return -1; + if (p >= end) return -1; + if (*p & 0x80) { + int l = *p++ & 0x7F; + p += l; + } else { + p += 1 + *p; + } + + for (i = 0; i < LWS_GENCRYPTO_RSA_KEYEL_COUNT; i++) { + int len; + if (p >= end || *p++ != 0x02) goto cleanup_der; + if (p >= end) goto cleanup_der; + if (*p & 0x80) { + int l = *p++ & 0x7F; + len = 0; + while (l--) len = (len << 8) | *p++; + } else { + len = *p++; + } + if (p + len > end) goto cleanup_der; + /* Skip leading zero if present */ + if (len > 1 && p[0] == 0x00) { + p++; + len--; + } + el[i].buf = lws_malloc((size_t)len, "genrsakey"); + if (!el[i].buf) goto cleanup_der; + memcpy(el[i].buf, p, (size_t)len); + el[i].len = (uint32_t)len; + p += len; + } + } + + return 0; + +cleanup_der: + lws_genrsa_destroy_elements(el); + return -1; +} + +int +lws_genrsa_public_decrypt(struct lws_genrsa_ctx *ctx, const uint8_t *in, + size_t in_len, uint8_t *out, size_t out_max) +{ + size_t olen; + if (psa_asymmetric_decrypt(ctx->key_id, PSA_ALG_RSA_PKCS1V15_CRYPT, + in, in_len, NULL, 0, out, out_max, &olen) != PSA_SUCCESS) + return -1; + return (int)olen; +} + +int +lws_genrsa_private_decrypt(struct lws_genrsa_ctx *ctx, const uint8_t *in, + size_t in_len, uint8_t *out, size_t out_max) +{ + size_t olen; + if (psa_asymmetric_decrypt(ctx->key_id, PSA_ALG_RSA_PKCS1V15_CRYPT, + in, in_len, NULL, 0, out, out_max, &olen) != PSA_SUCCESS) + return -1; + return (int)olen; +} + +int +lws_genrsa_public_encrypt(struct lws_genrsa_ctx *ctx, const uint8_t *in, + size_t in_len, uint8_t *out) +{ + size_t olen; + if (psa_asymmetric_encrypt(ctx->key_id, PSA_ALG_RSA_PKCS1V15_CRYPT, + in, in_len, NULL, 0, out, 4096, &olen) != PSA_SUCCESS) + return -1; + return (int)olen; +} + +int +lws_genrsa_private_encrypt(struct lws_genrsa_ctx *ctx, const uint8_t *in, + size_t in_len, uint8_t *out) +{ + size_t olen; + if (psa_asymmetric_encrypt(ctx->key_id, PSA_ALG_RSA_PKCS1V15_CRYPT, + in, in_len, NULL, 0, out, 4096, &olen) != PSA_SUCCESS) + return -1; + return (int)olen; +} + +int +lws_genrsa_hash_sig_verify(struct lws_genrsa_ctx *ctx, const uint8_t *in, + enum lws_genhash_types hash_type, const uint8_t *sig, + size_t sig_len) +{ + psa_algorithm_t alg; + if (ctx->mode == LGRSAM_PKCS1_1_5) { + alg = PSA_ALG_RSA_PKCS1V15_SIGN(PSA_ALG_ANY_HASH); /* We'll use specific if needed */ + } else { + alg = PSA_ALG_RSA_PSS_ANY_SALT(PSA_ALG_ANY_HASH); + } + if (psa_verify_hash(ctx->key_id, alg, in, lws_genhash_size(hash_type), sig, sig_len) != PSA_SUCCESS) + return -1; + return 0; +} + +int +lws_genrsa_hash_sign(struct lws_genrsa_ctx *ctx, const uint8_t *in, + enum lws_genhash_types hash_type, uint8_t *sig, + size_t sig_len) +{ + size_t olen; + psa_algorithm_t alg; + if (ctx->mode == LGRSAM_PKCS1_1_5) { + alg = PSA_ALG_RSA_PKCS1V15_SIGN(PSA_ALG_ANY_HASH); + } else { + alg = PSA_ALG_RSA_PSS_ANY_SALT(PSA_ALG_ANY_HASH); + } + if (psa_sign_hash(ctx->key_id, alg, in, lws_genhash_size(hash_type), sig, sig_len, &olen) != PSA_SUCCESS) + return -1; + return (int)olen; +} + +int +lws_genrsa_render_pkey_asn1(struct lws_genrsa_ctx *ctx, int _private, + uint8_t *pkey_asn1, size_t pkey_asn1_len) +{ + size_t olen; + if (psa_export_key(ctx->key_id, pkey_asn1, pkey_asn1_len, &olen) != PSA_SUCCESS) + return -1; + return (int)olen; +} + +void +lws_genrsa_destroy(struct lws_genrsa_ctx *ctx) +{ + psa_destroy_key(ctx->key_id); +} + +#endif diff --git a/lib/tls/mbedtls/mbedtls-client.c b/lib/tls/mbedtls/mbedtls-client.c index f64f3468c5..cc904eea77 100644 --- a/lib/tls/mbedtls/mbedtls-client.c +++ b/lib/tls/mbedtls/mbedtls-client.c @@ -416,7 +416,9 @@ lws_tls_client_create_vhost_context(struct lws_vhost *vh, return 1; } +#if !defined(LWS_HAVE_MBEDTLS_V4) vh->tls.ssl_client_ctx->rngctx = &vh->context->mcdc; +#endif if (!ca_filepath && (!ca_mem || !ca_mem_len)) { #if defined(LWS_HAVE_SSL_CTX_load_verify_dir) if (!SSL_CTX_load_verify_dir( diff --git a/lib/tls/mbedtls/mbedtls-extensions.c b/lib/tls/mbedtls/mbedtls-extensions.c index 912fa667a6..466c9b930b 100644 --- a/lib/tls/mbedtls/mbedtls-extensions.c +++ b/lib/tls/mbedtls/mbedtls-extensions.c @@ -59,7 +59,14 @@ */ typedef struct { - mbedtls_oid_descriptor_t descriptor; + const char *asn1; + size_t asn1_len; + const char *name; + const char *description; +} lws_oid_descriptor_t; + +typedef struct { + lws_oid_descriptor_t descriptor; int ext_type; } oid_x509_ext_t; @@ -97,26 +104,26 @@ static const oid_x509_ext_t oid_x509_ext[] = { #define FN_OID_TYPED_FROM_ASN1( TYPE_T, NAME, LIST ) \ static const TYPE_T * oid_ ## NAME ## _from_asn1( \ - const mbedtls_asn1_buf *oid ) \ + const mbedtls_x509_buf *oid ) \ { \ const TYPE_T *p = (LIST); \ - const mbedtls_oid_descriptor_t *cur = \ - (const mbedtls_oid_descriptor_t *) p; \ + const lws_oid_descriptor_t *cur = \ + (const lws_oid_descriptor_t *) p; \ if( p == NULL || oid == NULL ) return( NULL ); \ - while( cur->MBEDTLS_PRIVATE(asn1) != NULL ) { \ - if( cur->MBEDTLS_PRIVATE(asn1_len) == oid->MBEDTLS_PRIVATE_V30_ONLY(len) && \ - memcmp( cur->MBEDTLS_PRIVATE(asn1), oid->MBEDTLS_PRIVATE_V30_ONLY(p), oid->MBEDTLS_PRIVATE_V30_ONLY(len) ) == 0 ) { \ + while( cur->asn1 != NULL ) { \ + if( cur->asn1_len == oid->MBEDTLS_PRIVATE_V30_ONLY(len) && \ + memcmp( cur->asn1, oid->MBEDTLS_PRIVATE_V30_ONLY(p), oid->MBEDTLS_PRIVATE_V30_ONLY(len) ) == 0 ) { \ return( p ); \ } \ p++; \ - cur = (const mbedtls_oid_descriptor_t *) p; \ + cur = (const lws_oid_descriptor_t *) p; \ } \ return( NULL ); \ } #define FN_OID_GET_ATTR1(FN_NAME, TYPE_T, TYPE_NAME, ATTR1_TYPE, ATTR1) \ -int FN_NAME( const mbedtls_asn1_buf *oid, ATTR1_TYPE * ATTR1 ) \ +int FN_NAME( const mbedtls_x509_buf *oid, ATTR1_TYPE * ATTR1 ) \ { \ const TYPE_T *data = oid_ ## TYPE_NAME ## _from_asn1( oid ); \ if (!data) return 1; \ @@ -281,8 +288,8 @@ static int lws_x509_get_general_names(uint8_t **p, const uint8_t *end, mbedtls_x509_sequence *name ) { - mbedtls_asn1_sequence *cur = name; - mbedtls_asn1_buf *buf; + mbedtls_x509_sequence *cur = name; + mbedtls_x509_buf *buf; size_t len, tag_len; unsigned char tag; int r; diff --git a/lib/tls/mbedtls/mbedtls-quic.c b/lib/tls/mbedtls/mbedtls-quic.c index 0ace99234f..fad1c72961 100644 --- a/lib/tls/mbedtls/mbedtls-quic.c +++ b/lib/tls/mbedtls/mbedtls-quic.c @@ -27,6 +27,8 @@ #if defined(LWS_ROLE_QUIC) && defined(LWS_WITH_TLS) && defined(LWS_WITH_MBEDTLS) +#if defined(LWS_HAVE_mbedtls_ssl_set_export_keys_cb) + static void mbedtls_quic_export_keys_cb(void *p_expkey, mbedtls_ssl_key_export_type type, @@ -294,4 +296,64 @@ lws_tls_quic_api_test(void) return 0; } +int +lws_tls_quic_migrate_wsi(struct lws *old_wsi, struct lws *new_wsi) +{ + mbedtls_ssl_context *msc; + + if (!new_wsi || !new_wsi->tls.ssl) + return -1; + + msc = SSL_mbedtls_ssl_context_from_SSL(new_wsi->tls.ssl); + if (!msc) + return -1; + + mbedtls_ssl_set_export_keys_cb(msc, mbedtls_quic_export_keys_cb, new_wsi); + + return 0; +} + +#else + +int +lws_tls_quic_init(struct lws *wsi, lws_tls_quic_secret_cb cb) +{ + lwsl_err("%s: MbedTLS version too old for QUIC support\n", __func__); + return -1; +} + +int +lws_tls_quic_advance_handshake(struct lws *wsi, int level, + const uint8_t *in, size_t in_len, + uint8_t *out, size_t *out_len) +{ + return -1; +} + +int +lws_tls_quic_set_transport_parameters(struct lws *wsi, const uint8_t *tp, size_t tp_len) +{ + return -1; +} + +int +lws_tls_quic_get_transport_parameters(struct lws *wsi, const uint8_t **tp, size_t *tp_len) +{ + return -1; +} + +int +lws_tls_quic_api_test(void) +{ + return 0; +} + +int +lws_tls_quic_migrate_wsi(struct lws *old_wsi, struct lws *new_wsi) +{ + return -1; +} + +#endif /* LWS_HAVE_mbedtls_ssl_set_export_keys_cb */ + #endif diff --git a/lib/tls/mbedtls/mbedtls-server.c b/lib/tls/mbedtls/mbedtls-server.c index e93d0305c4..0717b09741 100644 --- a/lib/tls/mbedtls/mbedtls-server.c +++ b/lib/tls/mbedtls/mbedtls-server.c @@ -248,7 +248,9 @@ lws_tls_vhost_backend_create_ctx(struct lws_vhost *vhost) return 1; } +#if !defined(LWS_HAVE_MBEDTLS_V4) tls->ssl_ctx->rngctx = &vhost->context->mcdc; +#endif if (tls->cfg_ssl_ca_filepath) { lwsl_notice("%s: vh %s: loading CA filepath %s\n", __func__, diff --git a/lib/tls/mbedtls/mbedtls-session.c b/lib/tls/mbedtls/mbedtls-session.c index f7d0820fd1..71fdbed33e 100644 --- a/lib/tls/mbedtls/mbedtls-session.c +++ b/lib/tls/mbedtls/mbedtls-session.c @@ -206,7 +206,8 @@ lws_tls_session_new_mbedtls(struct lws *wsi) #endif vh = wsi->a.vhost; - if (vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE) + if ((vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE) || + vh->being_destroyed) return 0; if (lws_tls_session_tag_from_wsi(wsi, buf, sizeof(buf))) diff --git a/lib/tls/mbedtls/mbedtls-x509.c b/lib/tls/mbedtls/mbedtls-x509.c index 144f7039cb..8f209a92f4 100644 --- a/lib/tls/mbedtls/mbedtls-x509.c +++ b/lib/tls/mbedtls/mbedtls-x509.c @@ -25,7 +25,15 @@ #include "private-lib-core.h" #include "private-lib-tls-mbedtls.h" #include +#if !defined(LWS_HAVE_MBEDTLS_V4) #include +#endif +#include +#if defined(LWS_HAVE_MBEDTLS_V4) +#include +#include +#include +#endif #if defined(LWS_PLAT_OPTEE) || defined(OPTEE_DEV_KIT) struct tm { @@ -133,6 +141,10 @@ lws_tls_mbedtls_cert_info(mbedtls_x509_crt *x509, enum lws_tls_cert_info type, case LWS_TLS_CERT_INFO_OPAQUE_PUBLIC_KEY: { +#if defined(LWS_HAVE_MBEDTLS_V4) + lwsl_err("LWS_TLS_CERT_INFO_OPAQUE_PUBLIC_KEY not yet implemented for MbedTLS v4\n"); + return -1; +#else char *p = buf->ns.name; size_t r = len, u; @@ -178,6 +190,7 @@ lws_tls_mbedtls_cert_info(mbedtls_x509_crt *x509, enum lws_tls_cert_info type, return -1; } break; +#endif } case LWS_TLS_CERT_INFO_DER_RAW: @@ -420,6 +433,106 @@ int lws_x509_public_to_jwk(struct lws_jwk *jwk, struct lws_x509_cert *x509, const char *curves, int rsa_min_bits) { +#if defined(LWS_HAVE_MBEDTLS_V4) + int ret = -1; + unsigned char der[4096]; + int der_len = mbedtls_pk_write_pubkey_der(&x509->cert.pk, der, sizeof(der)); + if (der_len < 0) { + lwsl_err("%s: write pubkey der failed\n", __func__); + return -1; + } + unsigned char *p = der + sizeof(der) - der_len; + const unsigned char *end = der + sizeof(der); + size_t asn1_len; + + psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT; + mbedtls_pk_get_psa_attributes(&x509->cert.pk, PSA_KEY_USAGE_VERIFY_HASH, &attr); + psa_key_type_t type = psa_get_key_type(&attr); + + memset(jwk, 0, sizeof(*jwk)); + + /* SubjectPublicKeyInfo ::= SEQUENCE */ + if (mbedtls_asn1_get_tag(&p, end, &asn1_len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) goto bail; + /* algorithm AlgorithmIdentifier ::= SEQUENCE */ + if (mbedtls_asn1_get_tag(&p, end, &asn1_len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) goto bail; + p += asn1_len; /* skip algorithm details */ + + /* subjectPublicKey BIT STRING */ + if (mbedtls_asn1_get_tag(&p, end, &asn1_len, MBEDTLS_ASN1_BIT_STRING)) goto bail; + if (*p == 0x00) { p++; asn1_len--; } /* Skip unused bits */ + + if (PSA_KEY_TYPE_IS_RSA(type)) { + jwk->kty = LWS_GENCRYPTO_KTY_RSA; + /* RSAPublicKey ::= SEQUENCE */ + if (mbedtls_asn1_get_tag(&p, end, &asn1_len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) goto bail; + /* Modulus N */ + if (mbedtls_asn1_get_tag(&p, end, &asn1_len, MBEDTLS_ASN1_INTEGER)) goto bail; + if (*p == 0x00) { p++; asn1_len--; } + jwk->e[LWS_GENCRYPTO_RSA_KEYEL_N].buf = lws_malloc(asn1_len, "jwk_N"); + if (!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_N].buf) goto bail; + jwk->e[LWS_GENCRYPTO_RSA_KEYEL_N].len = (uint32_t)asn1_len; + memcpy(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_N].buf, p, asn1_len); + p += asn1_len; + + /* Exponent E */ + if (mbedtls_asn1_get_tag(&p, end, &asn1_len, MBEDTLS_ASN1_INTEGER)) goto bail; + if (*p == 0x00) { p++; asn1_len--; } + jwk->e[LWS_GENCRYPTO_RSA_KEYEL_E].buf = lws_malloc(asn1_len, "jwk_E"); + if (!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_E].buf) goto bail; + jwk->e[LWS_GENCRYPTO_RSA_KEYEL_E].len = (uint32_t)asn1_len; + memcpy(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_E].buf, p, asn1_len); + } else if (PSA_KEY_TYPE_IS_ECC(type)) { + jwk->kty = LWS_GENCRYPTO_KTY_EC; + + if (asn1_len < 1 || *p != 0x04) { + lwsl_err("Only uncompressed EC points supported\n"); + goto bail; + } + p++; asn1_len--; + if (asn1_len % 2 != 0) goto bail; + size_t coord_len = asn1_len / 2; + + jwk->e[LWS_GENCRYPTO_EC_KEYEL_X].buf = lws_malloc(coord_len, "jwk_X"); + if (!jwk->e[LWS_GENCRYPTO_EC_KEYEL_X].buf) goto bail; + jwk->e[LWS_GENCRYPTO_EC_KEYEL_X].len = (uint32_t)coord_len; + memcpy(jwk->e[LWS_GENCRYPTO_EC_KEYEL_X].buf, p, coord_len); + p += coord_len; + + jwk->e[LWS_GENCRYPTO_EC_KEYEL_Y].buf = lws_malloc(coord_len, "jwk_Y"); + if (!jwk->e[LWS_GENCRYPTO_EC_KEYEL_Y].buf) goto bail; + jwk->e[LWS_GENCRYPTO_EC_KEYEL_Y].len = (uint32_t)coord_len; + memcpy(jwk->e[LWS_GENCRYPTO_EC_KEYEL_Y].buf, p, coord_len); + + psa_ecc_family_t family = PSA_KEY_TYPE_ECC_GET_FAMILY(type); + size_t bits = psa_get_key_bits(&attr); + const char *crv = NULL; + + if (family == PSA_ECC_FAMILY_SECP_R1) { + if (bits == 256) crv = "P-256"; + else if (bits == 384) crv = "P-384"; + else if (bits == 521) crv = "P-521"; + } + + if (!crv) { + lwsl_err("Unsupported curve family=%d bits=%u\n", (int)family, (unsigned)bits); + goto bail; + } + + jwk->e[LWS_GENCRYPTO_EC_KEYEL_CRV].buf = lws_malloc(strlen(crv) + 1, "jwk_crv"); + if (!jwk->e[LWS_GENCRYPTO_EC_KEYEL_CRV].buf) goto bail; + jwk->e[LWS_GENCRYPTO_EC_KEYEL_CRV].len = (uint32_t)strlen(crv); + memcpy(jwk->e[LWS_GENCRYPTO_EC_KEYEL_CRV].buf, crv, strlen(crv) + 1); + } else { + lwsl_err("%s: key type %d not supported\n", __func__, (int)type); + return -1; + } + + ret = 0; + +bail: + if (ret) lws_jwk_destroy(jwk); + return ret; +#else int kt = (int)mbedtls_pk_get_type(&x509->cert.MBEDTLS_PRIVATE_V30_ONLY(pk)), n, count = 0, ret = -1; mbedtls_rsa_context *rsactx; @@ -488,12 +601,145 @@ lws_x509_public_to_jwk(struct lws_jwk *jwk, struct lws_x509_cert *x509, lws_jwk_destroy(jwk); return ret; +#endif } int lws_x509_jwk_privkey_pem(struct lws_context *cx, struct lws_jwk *jwk, void *pem, size_t len, const char *passphrase) { +#if defined(LWS_HAVE_MBEDTLS_V4) + mbedtls_pk_context pk; + int n, ret = -1; + unsigned char der[4096]; + int der_len; + + mbedtls_pk_init(&pk); + n = passphrase ? (int)strlen(passphrase) : 0; + n = mbedtls_pk_parse_key(&pk, pem, len, (uint8_t *)passphrase, (size_t)n); + if (n) { + lwsl_err("%s: parse PEM key failed: -0x%x\n", __func__, -n); + return -1; + } + + der_len = mbedtls_pk_write_key_der(&pk, der, sizeof(der)); + if (der_len < 0) { + lwsl_err("%s: write key der failed\n", __func__); + goto bail; + } + + psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT; + mbedtls_pk_get_psa_attributes(&pk, PSA_KEY_USAGE_SIGN_HASH, &attr); + psa_key_type_t type = psa_get_key_type(&attr); + + unsigned char *p = der + sizeof(der) - der_len; + const unsigned char *end = der + sizeof(der); + size_t asn1_len; + + if (PSA_KEY_TYPE_IS_RSA(type)) { + if (jwk->kty != LWS_GENCRYPTO_KTY_RSA) { + lwsl_err("%s: RSA privkey, non-RSA jwk\n", __func__); + goto bail; + } + + /* RSAPrivateKey ::= SEQUENCE */ + if (mbedtls_asn1_get_tag(&p, end, &asn1_len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) goto bail; + /* version Version */ + if (mbedtls_asn1_get_tag(&p, end, &asn1_len, MBEDTLS_ASN1_INTEGER)) goto bail; + p += asn1_len; + + /* Modulus N */ + if (mbedtls_asn1_get_tag(&p, end, &asn1_len, MBEDTLS_ASN1_INTEGER)) goto bail; + p += asn1_len; + /* Exponent E */ + if (mbedtls_asn1_get_tag(&p, end, &asn1_len, MBEDTLS_ASN1_INTEGER)) goto bail; + p += asn1_len; + + /* Exponent D */ + if (mbedtls_asn1_get_tag(&p, end, &asn1_len, MBEDTLS_ASN1_INTEGER)) goto bail; + if (*p == 0x00) { p++; asn1_len--; } + jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf = lws_malloc(asn1_len, "jwk_D"); + if (!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf) goto bail; + jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].len = (uint32_t)asn1_len; + memcpy(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf, p, asn1_len); + p += asn1_len; + + /* Prime P */ + if (mbedtls_asn1_get_tag(&p, end, &asn1_len, MBEDTLS_ASN1_INTEGER)) goto bail; + if (*p == 0x00) { p++; asn1_len--; } + jwk->e[LWS_GENCRYPTO_RSA_KEYEL_P].buf = lws_malloc(asn1_len, "jwk_P"); + if (!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_P].buf) goto bail; + jwk->e[LWS_GENCRYPTO_RSA_KEYEL_P].len = (uint32_t)asn1_len; + memcpy(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_P].buf, p, asn1_len); + p += asn1_len; + + /* Prime Q */ + if (mbedtls_asn1_get_tag(&p, end, &asn1_len, MBEDTLS_ASN1_INTEGER)) goto bail; + if (*p == 0x00) { p++; asn1_len--; } + jwk->e[LWS_GENCRYPTO_RSA_KEYEL_Q].buf = lws_malloc(asn1_len, "jwk_Q"); + if (!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_Q].buf) goto bail; + jwk->e[LWS_GENCRYPTO_RSA_KEYEL_Q].len = (uint32_t)asn1_len; + memcpy(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_Q].buf, p, asn1_len); + p += asn1_len; + + /* Exponent DP */ + if (mbedtls_asn1_get_tag(&p, end, &asn1_len, MBEDTLS_ASN1_INTEGER)) goto bail; + if (*p == 0x00) { p++; asn1_len--; } + jwk->e[LWS_GENCRYPTO_RSA_KEYEL_DP].buf = lws_malloc(asn1_len, "jwk_DP"); + if (!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_DP].buf) goto bail; + jwk->e[LWS_GENCRYPTO_RSA_KEYEL_DP].len = (uint32_t)asn1_len; + memcpy(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_DP].buf, p, asn1_len); + p += asn1_len; + + /* Exponent DQ */ + if (mbedtls_asn1_get_tag(&p, end, &asn1_len, MBEDTLS_ASN1_INTEGER)) goto bail; + if (*p == 0x00) { p++; asn1_len--; } + jwk->e[LWS_GENCRYPTO_RSA_KEYEL_DQ].buf = lws_malloc(asn1_len, "jwk_DQ"); + if (!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_DQ].buf) goto bail; + jwk->e[LWS_GENCRYPTO_RSA_KEYEL_DQ].len = (uint32_t)asn1_len; + memcpy(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_DQ].buf, p, asn1_len); + p += asn1_len; + + /* Coefficient QI */ + if (mbedtls_asn1_get_tag(&p, end, &asn1_len, MBEDTLS_ASN1_INTEGER)) goto bail; + if (*p == 0x00) { p++; asn1_len--; } + jwk->e[LWS_GENCRYPTO_RSA_KEYEL_QI].buf = lws_malloc(asn1_len, "jwk_QI"); + if (!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_QI].buf) goto bail; + jwk->e[LWS_GENCRYPTO_RSA_KEYEL_QI].len = (uint32_t)asn1_len; + memcpy(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_QI].buf, p, asn1_len); + p += asn1_len; + + } else if (PSA_KEY_TYPE_IS_ECC(type)) { + if (jwk->kty != LWS_GENCRYPTO_KTY_EC) { + lwsl_err("%s: EC privkey, non-EC jwk\n", __func__); + goto bail; + } + + /* ECPrivateKey ::= SEQUENCE */ + if (mbedtls_asn1_get_tag(&p, end, &asn1_len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) goto bail; + /* version Version */ + if (mbedtls_asn1_get_tag(&p, end, &asn1_len, MBEDTLS_ASN1_INTEGER)) goto bail; + p += asn1_len; + /* privateKey OCTET STRING */ + if (mbedtls_asn1_get_tag(&p, end, &asn1_len, MBEDTLS_ASN1_OCTET_STRING)) goto bail; + + jwk->e[LWS_GENCRYPTO_EC_KEYEL_D].buf = lws_malloc(asn1_len, "jwk_D"); + if (!jwk->e[LWS_GENCRYPTO_EC_KEYEL_D].buf) goto bail; + jwk->e[LWS_GENCRYPTO_EC_KEYEL_D].len = (uint32_t)asn1_len; + memcpy(jwk->e[LWS_GENCRYPTO_EC_KEYEL_D].buf, p, asn1_len); + p += asn1_len; + + } else { + lwsl_err("%s: key type %d not supported\n", __func__, (int)type); + goto bail; + } + + ret = 0; + +bail: + mbedtls_pk_free(&pk); + return ret; +#else mbedtls_rsa_context *rsactx; mbedtls_ecp_keypair *ecpctx; mbedtls_pk_context pk; @@ -506,7 +752,7 @@ lws_x509_jwk_privkey_pem(struct lws_context *cx, struct lws_jwk *jwk, if (passphrase) n = (int)strlen(passphrase); n = mbedtls_pk_parse_key(&pk, pem, len, (uint8_t *)passphrase, (unsigned int)n -#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 0x03000000 +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 0x03000000 && !defined(LWS_HAVE_MBEDTLS_V4) , mbedtls_ctr_drbg_random, &cx->mcdc #endif ); @@ -565,6 +811,7 @@ lws_x509_jwk_privkey_pem(struct lws_context *cx, struct lws_jwk *jwk, mbedtls_pk_free(&pk); return ret; +#endif } #endif @@ -585,6 +832,11 @@ lws_x509_create_cert(struct lws_context *context, uint8_t **key_buf, size_t *key_len, const struct lws_x509_cert_gen_info *info) { + int ret = 1; +#if defined(LWS_HAVE_MBEDTLS_V4) + lwsl_err("Self-signed cert generation not yet implemented for MbedTLS v4\n"); + return ret; +#else mbedtls_x509write_cert crt; mbedtls_pk_context key; mbedtls_mpi serial; @@ -593,7 +845,6 @@ lws_x509_create_cert(struct lws_context *context, mbedtls_ctr_drbg_context *pdrbg = &ctr_drbg; mbedtls_x509_crt issuer_crt; mbedtls_pk_context issuer_key; - int ret = 1; unsigned char buf[4096]; char name[128]; int len; @@ -784,6 +1035,7 @@ lws_x509_create_cert(struct lws_context *context, } return ret; +#endif } int diff --git a/lib/tls/mbedtls/private-lib-tls-mbedtls.h b/lib/tls/mbedtls/private-lib-tls-mbedtls.h index 12cbd4ce72..57c99fa7e6 100644 --- a/lib/tls/mbedtls/private-lib-tls-mbedtls.h +++ b/lib/tls/mbedtls/private-lib-tls-mbedtls.h @@ -58,7 +58,35 @@ int lws_x509_get_crt_ext(mbedtls_x509_crt *crt, mbedtls_x509_buf *skid, lws_mbedtls_x509_authority *akid); -#if (MBEDTLS_VERSION_MAJOR == 3) && (MBEDTLS_VERSION_MINOR >= 5) +#if defined(LWS_HAVE_MBEDTLS_V4) +#define MBEDTLS_ASN1_BOOLEAN 0x01 +#define MBEDTLS_ASN1_INTEGER 0x02 +#define MBEDTLS_ASN1_BIT_STRING 0x03 +#define MBEDTLS_ASN1_OCTET_STRING 0x04 +#define MBEDTLS_ASN1_NULL 0x05 +#define MBEDTLS_ASN1_OID 0x06 +#define MBEDTLS_ASN1_UTF8_STRING 0x0C +#define MBEDTLS_ASN1_SEQUENCE 0x10 +#define MBEDTLS_ASN1_SET 0x11 +#define MBEDTLS_ASN1_PRINTABLE_STRING 0x13 +#define MBEDTLS_ASN1_T61_STRING 0x14 +#define MBEDTLS_ASN1_IA5_STRING 0x16 +#define MBEDTLS_ASN1_UTC_TIME 0x17 +#define MBEDTLS_ASN1_GENERALIZED_TIME 0x18 +#define MBEDTLS_ASN1_UNIVERSAL_STRING 0x1C +#define MBEDTLS_ASN1_BMP_STRING 0x1E +#define MBEDTLS_ASN1_PRIMITIVE 0x00 +#define MBEDTLS_ASN1_CONSTRUCTED 0x20 +#define MBEDTLS_ASN1_CONTEXT_SPECIFIC 0x80 + +int mbedtls_asn1_get_len(unsigned char **p, const unsigned char *end, size_t *len); +int mbedtls_asn1_get_tag(unsigned char **p, const unsigned char *end, size_t *len, int tag); +int mbedtls_asn1_get_bool(unsigned char **p, const unsigned char *end, int *val); +int mbedtls_asn1_get_int(unsigned char **p, const unsigned char *end, int *val); +int mbedtls_asn1_get_bitstring_null(unsigned char **p, const unsigned char *end, size_t *len); +#endif + +#if defined(LWS_HAVE_MBEDTLS_V4) || ((MBEDTLS_VERSION_MAJOR == 3) && (MBEDTLS_VERSION_MINOR >= 5)) int mbedtls_x509_get_name(unsigned char **p, const unsigned char *end, mbedtls_x509_name *cur); #endif diff --git a/lib/tls/mbedtls/wrapper/platform/ssl_pm.c b/lib/tls/mbedtls/wrapper/platform/ssl_pm.c index f9e2bcc89f..874069f0a3 100755 --- a/lib/tls/mbedtls/wrapper/platform/ssl_pm.c +++ b/lib/tls/mbedtls/wrapper/platform/ssl_pm.c @@ -26,8 +26,10 @@ #include "mbedtls/net.h" #endif #include "mbedtls/debug.h" +#if !defined(LWS_HAVE_MBEDTLS_V4) #include "mbedtls/entropy.h" #include "mbedtls/ctr_drbg.h" +#endif #include "mbedtls/error.h" #define X509_INFO_STRING_LENGTH 8192 @@ -41,11 +43,15 @@ struct ssl_pm mbedtls_ssl_config conf; +#if !defined(LWS_HAVE_MBEDTLS_V4) mbedtls_ctr_drbg_context ctr_drbg; +#endif mbedtls_ssl_context ssl; +#if !defined(LWS_HAVE_MBEDTLS_V4) mbedtls_entropy_context entropy; +#endif SSL *owner; }; @@ -114,8 +120,10 @@ int ssl_pm_new(SSL *ssl) struct ssl_pm *ssl_pm; int ret; +#if !defined(LWS_HAVE_MBEDTLS_V4) const unsigned char pers[] = "OpenSSL PM"; size_t pers_len = sizeof(pers); +#endif int endpoint; //int version; @@ -140,8 +148,10 @@ int ssl_pm_new(SSL *ssl) mbedtls_net_init(&ssl_pm->cl_fd); mbedtls_ssl_config_init(&ssl_pm->conf); +#if !defined(LWS_HAVE_MBEDTLS_V4) mbedtls_ctr_drbg_init(&ssl_pm->ctr_drbg); mbedtls_entropy_init(&ssl_pm->entropy); +#endif mbedtls_ssl_init(&ssl_pm->ssl); #if defined(LWS_HAVE_mbedtls_ssl_set_verify) @@ -150,11 +160,13 @@ int ssl_pm_new(SSL *ssl) mbedtls_ssl_conf_verify(&ssl_pm->conf, lws_mbedtls_f_vrfy, ssl_pm); #endif +#if !defined(LWS_HAVE_MBEDTLS_V4) ret = mbedtls_ctr_drbg_seed(&ssl_pm->ctr_drbg, mbedtls_entropy_func, &ssl_pm->entropy, pers, pers_len); if (ret) { lwsl_notice("%s: mbedtls_ctr_drbg_seed() return -0x%x", __func__, -ret); //goto mbedtls_err1; } +#endif if (method->endpoint) { endpoint = MBEDTLS_SSL_IS_SERVER; @@ -191,7 +203,9 @@ int ssl_pm_new(SSL *ssl) } #endif // 0 +#if !defined(LWS_HAVE_MBEDTLS_V4) mbedtls_ssl_conf_rng(&ssl_pm->conf, mbedtls_ctr_drbg_random, &ssl_pm->ctr_drbg); +#endif //#ifdef CONFIG_OPENSSL_LOWLEVEL_DEBUG // mbedtls_debug_set_threshold(MBEDTLS_DEBUG_LEVEL); @@ -218,9 +232,11 @@ int ssl_pm_new(SSL *ssl) mbedtls_err2: mbedtls_ssl_config_free(&ssl_pm->conf); +#if !defined(LWS_HAVE_MBEDTLS_V4) mbedtls_ctr_drbg_free(&ssl_pm->ctr_drbg); //mbedtls_err1: mbedtls_entropy_free(&ssl_pm->entropy); +#endif ssl_mem_free(ssl_pm); no_mem: return -1; @@ -233,8 +249,10 @@ void ssl_pm_free(SSL *ssl) { struct ssl_pm *ssl_pm = (struct ssl_pm *)ssl->ssl_pm; +#if !defined(LWS_HAVE_MBEDTLS_V4) mbedtls_ctr_drbg_free(&ssl_pm->ctr_drbg); mbedtls_entropy_free(&ssl_pm->entropy); +#endif mbedtls_ssl_config_free(&ssl_pm->conf); mbedtls_ssl_free(&ssl_pm->ssl); @@ -455,7 +473,15 @@ int ssl_pm_read(SSL *ssl, void *buffer, int len) int ret; struct ssl_pm *ssl_pm = (struct ssl_pm *)ssl->ssl_pm; - ret = mbedtls_ssl_read(&ssl_pm->ssl, buffer, (size_t)len); + do { + ret = mbedtls_ssl_read(&ssl_pm->ssl, buffer, (size_t)len); + } while ( +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 0x03000000 + ret == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET +#else + 0 +#endif + ); if (ret < 0) { // lwsl_notice("%s: mbedtls_ssl_read says -0x%x\n", __func__, -ret); SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "mbedtls_ssl_read() return -0x%x", -ret); @@ -496,7 +522,15 @@ int ssl_pm_send(SSL *ssl, const void *buffer, int len) int ret; struct ssl_pm *ssl_pm = (struct ssl_pm *)ssl->ssl_pm; - ret = mbedtls_ssl_write(&ssl_pm->ssl, buffer, (size_t)len); + do { + ret = mbedtls_ssl_write(&ssl_pm->ssl, buffer, (size_t)len); + } while ( +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 0x03000000 + ret == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET +#else + 0 +#endif + ); /* * We can get a positive number, which may be less than len... that * much was sent successfully and you can call again to send more. @@ -852,7 +886,9 @@ int pkey_pm_load(EVP_PKEY *pk, const unsigned char *buffer, int len) int ret; unsigned char *load_buf; struct pkey_pm *pkey_pm = (struct pkey_pm *)pk->pkey_pm; +#if !defined(LWS_HAVE_MBEDTLS_V4) mbedtls_ctr_drbg_context ctr_drbg; +#endif if (pkey_pm->pkey) mbedtls_pk_free(pkey_pm->pkey); @@ -875,9 +911,11 @@ int pkey_pm_load(EVP_PKEY *pk, const unsigned char *buffer, int len) load_buf[len] = '\0'; mbedtls_pk_init(pkey_pm->pkey); +#if !defined(LWS_HAVE_MBEDTLS_V4) mbedtls_ctr_drbg_init(&ctr_drbg); +#endif -#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 0x03000000 +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 0x03000000 && !defined(LWS_HAVE_MBEDTLS_V4) #if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 0x03050000 ret = mbedtls_pk_parse_key(pkey_pm->pkey, load_buf, (unsigned int)len, NULL, 0, mbedtls_ctr_drbg_random, &ctr_drbg); @@ -885,6 +923,8 @@ int pkey_pm_load(EVP_PKEY *pk, const unsigned char *buffer, int len) ret = mbedtls_pk_parse_key(pkey_pm->pkey, load_buf, (unsigned int)len + 1, NULL, 0, mbedtls_ctr_drbg_random, &ctr_drbg); #endif +#elif defined(LWS_HAVE_MBEDTLS_V4) + ret = mbedtls_pk_parse_key(pkey_pm->pkey, load_buf, (unsigned int)len, NULL, 0); #else ret = mbedtls_pk_parse_key(pkey_pm->pkey, load_buf, (unsigned int)len + 1, NULL, 0); #endif @@ -895,12 +935,16 @@ int pkey_pm_load(EVP_PKEY *pk, const unsigned char *buffer, int len) goto failed; } +#if !defined(LWS_HAVE_MBEDTLS_V4) mbedtls_ctr_drbg_free(&ctr_drbg); +#endif return 0; failed: +#if !defined(LWS_HAVE_MBEDTLS_V4) mbedtls_ctr_drbg_free(&ctr_drbg); +#endif mbedtls_pk_free(pkey_pm->pkey); ssl_mem_free(pkey_pm->pkey); pkey_pm->pkey = NULL; diff --git a/lib/tls/openssl/lws-genaes.c b/lib/tls/openssl/lws-genaes.c index 182eb41ba6..73e7ce973f 100644 --- a/lib/tls/openssl/lws-genaes.c +++ b/lib/tls/openssl/lws-genaes.c @@ -85,7 +85,7 @@ lws_genaes_create(struct lws_genaes_ctx *ctx, enum enum_aes_operation op, ctx->cipher = EVP_aes_128_ctr(); break; #endif -#if defined(LWS_HAVE_EVP_aes_128_ecb) +#if defined(LWS_HAVE_EVP_aes_128_ecb) || defined(OPENSSL_IS_AWSLC) case LWS_GAESM_ECB: ctx->cipher = EVP_aes_128_ecb(); break; @@ -140,7 +140,7 @@ lws_genaes_create(struct lws_genaes_ctx *ctx, enum enum_aes_operation op, ctx->cipher = EVP_aes_192_ctr(); break; #endif -#if defined(LWS_HAVE_EVP_aes_128_ecb) +#if defined(LWS_HAVE_EVP_aes_128_ecb) || defined(OPENSSL_IS_AWSLC) case LWS_GAESM_ECB: ctx->cipher = EVP_aes_192_ecb(); break; @@ -194,7 +194,7 @@ lws_genaes_create(struct lws_genaes_ctx *ctx, enum enum_aes_operation op, ctx->cipher = EVP_aes_256_ctr(); break; #endif -#if defined(LWS_HAVE_EVP_aes_128_ecb) +#if defined(LWS_HAVE_EVP_aes_128_ecb) || defined(OPENSSL_IS_AWSLC) case LWS_GAESM_ECB: ctx->cipher = EVP_aes_256_ecb(); break; diff --git a/lib/tls/openssl/lws-genec.c b/lib/tls/openssl/lws-genec.c index e69c13bccf..c880d6e0a8 100644 --- a/lib/tls/openssl/lws-genec.c +++ b/lib/tls/openssl/lws-genec.c @@ -50,8 +50,7 @@ EVP_PKEY * EVP_PKEY_CTX_get0_pkey(EVP_PKEY_CTX *p) } #endif -#if !defined(LWS_HAVE_ECDSA_SIG_set0) -static void +#if !defined(LWS_HAVE_ECDSA_SIG_set0) && !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC) ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps) { if (pr != NULL) @@ -73,25 +72,25 @@ ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s) return 1; } #endif -#if !defined(LWS_HAVE_BN_bn2binpad) +#if !defined(LWS_HAVE_BN_bn2binpad) && !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC) int BN_bn2binpad(const BIGNUM *a, unsigned char *to, int tolen) { int i; -#if !defined(USE_WOLFSSL) +#if !defined(USE_WOLFSSL) && !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC) BN_ULONG l; #endif -#if !defined(LIBRESSL_VERSION_NUMBER) && !defined(USE_WOLFSSL) +#if !defined(LIBRESSL_VERSION_NUMBER) && !defined(USE_WOLFSSL) && !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC) bn_check_top(a); #endif - i = BN_num_bytes(a); + i = (int)BN_num_bytes(a); /* Add leading zeroes if necessary */ if (tolen > i) { memset(to, 0, (size_t)(tolen - i)); to += tolen - i; } -#if defined(USE_WOLFSSL) +#if defined(USE_WOLFSSL) || defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) BN_bn2bin(a, to); #else while (i--) { diff --git a/lib/tls/openssl/lws-genhash.c b/lib/tls/openssl/lws-genhash.c index cd0298dc03..3afe948e91 100644 --- a/lib/tls/openssl/lws-genhash.c +++ b/lib/tls/openssl/lws-genhash.c @@ -183,7 +183,7 @@ int lws_genhmac_init(struct lws_genhmac_ctx *ctx, enum lws_genhmac_types type, const uint8_t *key, size_t key_len) { -#if defined(LWS_HAVE_HMAC_CTX_new) +#if defined(LWS_HAVE_HMAC_CTX_new) || defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) ctx->ctx = HMAC_CTX_new(); if (!ctx->ctx) return -1; @@ -212,7 +212,7 @@ lws_genhmac_init(struct lws_genhmac_ctx *ctx, enum lws_genhmac_types type, goto bail; } -#if defined(LWS_HAVE_HMAC_CTX_new) +#if defined(LWS_HAVE_HMAC_CTX_new) || defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) if (HMAC_Init_ex(ctx->ctx, key, SSL_SIZE_T_CAST(key_len), ctx->evp_type, NULL) != 1) #else @@ -223,7 +223,7 @@ lws_genhmac_init(struct lws_genhmac_ctx *ctx, enum lws_genhmac_types type, return 0; bail: -#if defined(LWS_HAVE_HMAC_CTX_new) +#if defined(LWS_HAVE_HMAC_CTX_new) || defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) HMAC_CTX_free(ctx->ctx); #endif @@ -233,7 +233,7 @@ lws_genhmac_init(struct lws_genhmac_ctx *ctx, enum lws_genhmac_types type, int lws_genhmac_update(struct lws_genhmac_ctx *ctx, const void *in, size_t len) { -#if defined(LWS_HAVE_HMAC_CTX_new) +#if defined(LWS_HAVE_HMAC_CTX_new) || defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) #if defined(LIBRESSL_VERSION_NUMBER) if (HMAC_Update(ctx->ctx, in, len) != 1) #else @@ -251,7 +251,7 @@ int lws_genhmac_destroy(struct lws_genhmac_ctx *ctx, void *result) { unsigned int size = (unsigned int)lws_genhmac_size(ctx->type); -#if defined(LWS_HAVE_HMAC_CTX_new) +#if defined(LWS_HAVE_HMAC_CTX_new) || defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) int n = HMAC_Final(ctx->ctx, result, &size); HMAC_CTX_free(ctx->ctx); diff --git a/lib/tls/openssl/lws-genrsa.c b/lib/tls/openssl/lws-genrsa.c index 18f9dd4996..3bb8e88362 100644 --- a/lib/tls/openssl/lws-genrsa.c +++ b/lib/tls/openssl/lws-genrsa.c @@ -112,7 +112,7 @@ lws_genrsa_create(struct lws_genrsa_ctx *ctx, goto bail; } -#if defined(LWS_HAVE_RSA_SET0_KEY) && !defined(USE_WOLFSSL) +#if (defined(LWS_HAVE_RSA_SET0_KEY) || defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)) && !defined(USE_WOLFSSL) if (RSA_set0_key(ctx->rsa, ctx->bn[LWS_GENCRYPTO_RSA_KEYEL_N], ctx->bn[LWS_GENCRYPTO_RSA_KEYEL_E], ctx->bn[LWS_GENCRYPTO_RSA_KEYEL_D]) != 1) { @@ -178,7 +178,7 @@ lws_genrsa_new_keypair(struct lws_context *context, struct lws_genrsa_ctx *ctx, if (n != 1) goto cleanup_1; -#if defined(LWS_HAVE_RSA_SET0_KEY) && !defined(USE_WOLFSSL) +#if (defined(LWS_HAVE_RSA_SET0_KEY) || defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)) && !defined(USE_WOLFSSL) { const BIGNUM *mpi[5]; @@ -302,13 +302,13 @@ lws_genrsa_hash_sig_verify(struct lws_genrsa_ctx *ctx, const uint8_t *in, if (!md) return -1; -#if defined(LWS_HAVE_RSA_verify_pss_mgf1) +#if defined(LWS_HAVE_RSA_verify_pss_mgf1) || defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) n = RSA_verify_pss_mgf1(ctx->rsa, in, SSL_SIZE_T_CAST(h), md, NULL, -1, - (uint8_t *)sig, + (uint8_t *)sig, (size_t)sig_len); #else n = RSA_verify_PKCS1_PSS(ctx->rsa, in, md, (uint8_t *)sig, + (int)sig_len); #endif - SSL_SIZE_T_CAST(sig_len)); break; default: return -1; diff --git a/lib/tls/openssl/openssl-client.c b/lib/tls/openssl/openssl-client.c index dffd7e603e..dfd808ba72 100644 --- a/lib/tls/openssl/openssl-client.c +++ b/lib/tls/openssl/openssl-client.c @@ -251,8 +251,8 @@ int lws_ssl_client_bio_create(struct lws *wsi) { char hostname[128], *p; -#if defined(LWS_HAVE_SSL_set_alpn_protos) && \ - defined(LWS_HAVE_SSL_get0_alpn_selected) +#if (defined(LWS_HAVE_SSL_set_alpn_protos) || defined(OPENSSL_IS_AWSLC)) && \ + (defined(LWS_HAVE_SSL_get0_alpn_selected) || defined(OPENSSL_IS_AWSLC)) uint8_t openssl_alpn[40]; const char *alpn_comma = wsi->a.context->tls.alpn_default; int n; @@ -260,8 +260,8 @@ lws_ssl_client_bio_create(struct lws *wsi) if (wsi->stash) { lws_strncpy(hostname, wsi->stash->cis[CIS_HOST], sizeof(hostname)); -#if defined(LWS_HAVE_SSL_set_alpn_protos) && \ - defined(LWS_HAVE_SSL_get0_alpn_selected) +#if (defined(LWS_HAVE_SSL_set_alpn_protos) || defined(OPENSSL_IS_AWSLC)) && \ + (defined(LWS_HAVE_SSL_get0_alpn_selected) || defined(OPENSSL_IS_AWSLC)) alpn_comma = wsi->stash->cis[CIS_ALPN]; #endif } else { @@ -397,8 +397,8 @@ lws_ssl_client_bio_create(struct lws *wsi) BIO_set_nbio(wsi->tls.client_bio, 1); /* nonblocking */ #endif -#if defined(LWS_HAVE_SSL_set_alpn_protos) && \ - defined(LWS_HAVE_SSL_get0_alpn_selected) +#if (defined(LWS_HAVE_SSL_set_alpn_protos) || defined(OPENSSL_IS_AWSLC)) && \ + (defined(LWS_HAVE_SSL_get0_alpn_selected) || defined(OPENSSL_IS_AWSLC)) if (wsi->a.vhost->tls.alpn) alpn_comma = wsi->a.vhost->tls.alpn; if (wsi->stash) { @@ -495,8 +495,8 @@ lws_ssl_client_bio_create(struct lws *wsi) enum lws_ssl_capable_status lws_tls_client_connect(struct lws *wsi, char *errbuf, size_t elen) { -#if defined(LWS_HAVE_SSL_set_alpn_protos) && \ - defined(LWS_HAVE_SSL_get0_alpn_selected) +#if (defined(LWS_HAVE_SSL_set_alpn_protos) || defined(OPENSSL_IS_AWSLC)) && \ + (defined(LWS_HAVE_SSL_get0_alpn_selected) || defined(OPENSSL_IS_AWSLC)) const unsigned char *prot; char a[32]; unsigned int len; @@ -555,8 +555,8 @@ lws_tls_client_connect(struct lws *wsi, char *errbuf, size_t elen) return LWS_SSL_CAPABLE_MORE_SERVICE_WRITE; if (n == 1 || m == SSL_ERROR_SYSCALL) { -#if defined(LWS_HAVE_SSL_set_alpn_protos) && \ - defined(LWS_HAVE_SSL_get0_alpn_selected) +#if (defined(LWS_HAVE_SSL_set_alpn_protos) || defined(OPENSSL_IS_AWSLC)) && \ + (defined(LWS_HAVE_SSL_get0_alpn_selected) || defined(OPENSSL_IS_AWSLC)) SSL_get0_alpn_selected(wsi->tls.ssl, &prot, &len); if (len >= sizeof(a)) diff --git a/lib/tls/openssl/openssl-quic.c b/lib/tls/openssl/openssl-quic.c index cdf1dd6cf7..24ec45f9ea 100644 --- a/lib/tls/openssl/openssl-quic.c +++ b/lib/tls/openssl/openssl-quic.c @@ -27,7 +27,7 @@ #if defined(LWS_ROLE_QUIC) && defined(LWS_WITH_TLS) && !defined(LWS_WITH_MBEDTLS) && !defined(LWS_WITH_SCHANNEL) -#if defined(LWS_WITH_BORINGSSL) || defined(LWS_WITH_AWSLC) || defined(USE_WOLFSSL) || defined(LIBRESSL_VERSION_NUMBER) +#if defined(LWS_WITH_BORINGSSL) || defined(LWS_WITH_AWSLC) || defined(LWS_WITH_AWSLC) || defined(USE_WOLFSSL) || defined(LIBRESSL_VERSION_NUMBER) #define LWS_HAVE_BORINGSSL_QUIC_API #endif @@ -44,6 +44,9 @@ set_encryption_secrets(WOLFSSL *ssl, enum wolfssl_encryption_level_t level, struct lws *wsi = (struct lws *)SSL_get_app_data((SSL *)ssl); enum lws_tls_quic_secret_type rt, wt; + if (!wsi) + return 1; + switch (level) { case wolfssl_encryption_early_data: rt = LWS_TLS_QUIC_SECRET_CLIENT_EARLY; @@ -81,6 +84,9 @@ add_handshake_data(WOLFSSL *ssl, enum wolfssl_encryption_level_t level, struct lws *wsi = (struct lws *)SSL_get_app_data((SSL *)ssl); int lws_level; + if (!wsi) + return 1; + switch (level) { case wolfssl_encryption_initial: lws_level = 0; break; case wolfssl_encryption_early_data: lws_level = 1; break; @@ -118,6 +124,9 @@ flush_flight(WOLFSSL *ssl) static int send_alert(WOLFSSL *ssl, enum wolfssl_encryption_level_t level, uint8_t alert) { + struct lws *wsi = (struct lws *)SSL_get_app_data((SSL *)ssl); + if (wsi) + wsi->tls.quic_alert = alert; return 1; } @@ -138,6 +147,9 @@ set_read_secret(SSL *ssl, enum ssl_encryption_level_t level, struct lws *wsi = (struct lws *)SSL_get_app_data(ssl); enum lws_tls_quic_secret_type t; + if (!wsi) + return 1; + switch (level) { case ssl_encryption_early_data: t = LWS_TLS_QUIC_SECRET_CLIENT_EARLY; @@ -166,6 +178,9 @@ set_write_secret(SSL *ssl, enum ssl_encryption_level_t level, struct lws *wsi = (struct lws *)SSL_get_app_data(ssl); enum lws_tls_quic_secret_type t; + if (!wsi) + return 1; + switch (level) { case ssl_encryption_early_data: t = LWS_TLS_QUIC_SECRET_CLIENT_EARLY; @@ -197,6 +212,9 @@ add_handshake_data(SSL *ssl, enum ssl_encryption_level_t level, struct lws *wsi = (struct lws *)SSL_get_app_data(ssl); int lws_level; + if (!wsi) + return 1; + switch (level) { case ssl_encryption_initial: lws_level = 0; break; case ssl_encryption_early_data: lws_level = 1; break; @@ -234,6 +252,10 @@ flush_flight(SSL *ssl) static int send_alert(SSL *ssl, enum ssl_encryption_level_t level, uint8_t alert) { + struct lws *wsi = (struct lws *)SSL_get_app_data(ssl); + lwsl_err("send_alert called with alert %d, wsi %p\n", alert, wsi); + if (wsi) + wsi->tls.quic_alert = alert; return 1; } @@ -268,10 +290,21 @@ lws_tls_quic_init(struct lws *wsi, lws_tls_quic_secret_cb cb) SSL_set_quic_method(wsi->tls.ssl, &quic_method); #endif - if (lwsi_role_client(wsi)) + if (lwsi_role_client(wsi)) { + if (wsi->flags & LCCSCF_ALLOW_EARLY_DATA) { +#if !defined(USE_WOLFSSL) && !defined(LWS_WITH_MBEDTLS) + SSL_set_early_data_enabled(wsi->tls.ssl, 1); +#endif + } SSL_set_connect_state(wsi->tls.ssl); - else + } else { + if (wsi->a.vhost && (wsi->a.vhost->options & LWS_SERVER_OPTION_ALLOW_EARLY_DATA)) { +#if !defined(USE_WOLFSSL) && !defined(LWS_WITH_MBEDTLS) + SSL_set_early_data_enabled(wsi->tls.ssl, 1); +#endif + } SSL_set_accept_state(wsi->tls.ssl); + } if (!wsi->tls.quic_tp_send) { const uint8_t dummy_tp[] = { @@ -281,6 +314,12 @@ lws_tls_quic_init(struct lws *wsi, lws_tls_quic_secret_cb cb) wolfSSL_set_quic_transport_params(wsi->tls.ssl, dummy_tp, sizeof(dummy_tp)); #else SSL_set_quic_transport_params(wsi->tls.ssl, dummy_tp, sizeof(dummy_tp)); +#endif + } else { +#if defined(USE_WOLFSSL) + wolfSSL_set_quic_transport_params(wsi->tls.ssl, wsi->tls.quic_tp_send, wsi->tls.quic_tp_send_len); +#else + SSL_set_quic_transport_params(wsi->tls.ssl, wsi->tls.quic_tp_send, wsi->tls.quic_tp_send_len); #endif } @@ -392,6 +431,12 @@ lws_tls_quic_advance_handshake(struct lws *wsi, int level, int lws_tls_quic_set_transport_parameters(struct lws *wsi, const uint8_t *tp, size_t tp_len) { + wsi->tls.quic_tp_send = tp; + wsi->tls.quic_tp_send_len = tp_len; + + if (!wsi->tls.ssl) + return 0; + #if defined(USE_WOLFSSL) if (wolfSSL_set_quic_transport_params(wsi->tls.ssl, tp, tp_len) != 1) return -1; @@ -466,6 +511,8 @@ openssl_quic_ext_parse_cb(SSL *ssl, unsigned int ext_type, if (!wsi) return 1; + lwsl_wsi_notice(wsi, "openssl_quic_ext_parse_cb: ext_type %u, inlen %zu", ext_type, inlen); + wsi->tls.quic_tp_recv = lws_malloc(inlen, "quic_tp_recv"); if (!wsi->tls.quic_tp_recv) { *al = SSL_AD_INTERNAL_ERROR; @@ -560,10 +607,21 @@ lws_tls_quic_init(struct lws *wsi, lws_tls_quic_secret_cb cb) wsi->tls.quic_secret_cb = cb; SSL_set_app_data(wsi->tls.ssl, wsi); - if (lwsi_role_client(wsi)) + if (lwsi_role_client(wsi)) { + if (wsi->flags & LCCSCF_ALLOW_EARLY_DATA) { +#if !defined(USE_WOLFSSL) && !defined(LWS_WITH_MBEDTLS) + SSL_set_early_data_enabled(wsi->tls.ssl, 1); +#endif + } SSL_set_connect_state(wsi->tls.ssl); - else + } else { + if (wsi->a.vhost && (wsi->a.vhost->options & LWS_SERVER_OPTION_ALLOW_EARLY_DATA)) { +#if !defined(USE_WOLFSSL) && !defined(LWS_WITH_MBEDTLS) + SSL_set_early_data_enabled(wsi->tls.ssl, 1); +#endif + } SSL_set_accept_state(wsi->tls.ssl); + } SSL_CTX_set_keylog_callback(ctx, openssl_quic_keylog_cb); @@ -804,4 +862,15 @@ lws_tls_quic_api_test(void) return -1; } +int +lws_tls_quic_migrate_wsi(struct lws *old_wsi, struct lws *new_wsi) +{ + if (!new_wsi || !new_wsi->tls.ssl) + return -1; + + SSL_set_app_data(new_wsi->tls.ssl, new_wsi); + + return 0; +} + #endif diff --git a/lib/tls/openssl/openssl-session.c b/lib/tls/openssl/openssl-session.c index e052b9b978..00c281d0f9 100644 --- a/lib/tls/openssl/openssl-session.c +++ b/lib/tls/openssl/openssl-session.c @@ -233,7 +233,8 @@ lws_tls_session_new_cb(SSL *ssl, SSL_SESSION *sess) } vh = wsi->a.vhost; - if (vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE) + if ((vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE) || + vh->being_destroyed) return 0; if (lws_tls_session_tag_from_wsi(wsi, tag, sizeof(tag))) diff --git a/lib/tls/openssl/openssl-x509.c b/lib/tls/openssl/openssl-x509.c index ef83e03f43..4e7c3f661b 100644 --- a/lib/tls/openssl/openssl-x509.c +++ b/lib/tls/openssl/openssl-x509.c @@ -290,7 +290,7 @@ lws_tls_openssl_cert_info(X509 *x509, enum lws_tls_cert_info type, { const X509V3_EXT_METHOD* method = X509V3_EXT_get(CAST_X509_EXTENSION(ext)); STACK_OF(CONF_VALUE) *cv; - #if defined(LWS_WITH_BORINGSSL) || defined(LWS_WITH_AWSLC) + #if defined(LWS_WITH_BORINGSSL) || defined(OPENSSL_IS_AWSLC) size_t j; #else int j; diff --git a/lib/tls/private-lib-tls.h b/lib/tls/private-lib-tls.h index f62fbc5f52..f4be4b5c85 100644 --- a/lib/tls/private-lib-tls.h +++ b/lib/tls/private-lib-tls.h @@ -75,12 +75,18 @@ #else /* not esp32 */ #if defined(LWS_WITH_MBEDTLS) #include +#if !defined(LWS_HAVE_MBEDTLS_V4) #include #include +#endif #include #include +#if !defined(LWS_HAVE_MBEDTLS_V4) #include #include +#else + #include +#endif #if defined(LWS_AMAZON_LINUX) #include "ssl.h" /* wrapper !!!! */ #else diff --git a/lib/tls/private-network.h b/lib/tls/private-network.h index 49a43cd102..8eb2020fef 100644 --- a/lib/tls/private-network.h +++ b/lib/tls/private-network.h @@ -125,6 +125,7 @@ struct lws_lws_tls { const uint8_t *quic_tp_send; size_t quic_tp_send_len; lws_tls_quic_secret_cb quic_secret_cb; + int quic_alert; unsigned int use_ssl; unsigned int redirect_to_https:1; diff --git a/lib/tls/schannel/schannel-quic.c b/lib/tls/schannel/schannel-quic.c index b4c34a0398..9aa7064873 100644 --- a/lib/tls/schannel/schannel-quic.c +++ b/lib/tls/schannel/schannel-quic.c @@ -262,7 +262,7 @@ lws_tls_quic_advance_handshake(struct lws *wsi, int level, sub->Subscriptions[0].HandshakeType = LWS_SCH_QUIC_TP_HS_TYPE_ENCRYPTED_EXT; in_bufs[num_in_bufs].BufferType = SECBUFFER_SUBSCRIBE_GENERIC_TLS_EXTENSION; - in_bufs[num_in_bufs].cbBuffer = sizeof(*sub); + in_bufs[num_in_bufs].cbBuffer = (unsigned long)(offsetof(SUBSCRIBE_GENERIC_TLS_EXTENSION, Subscriptions) + sizeof(sub->Subscriptions[0])); in_bufs[num_in_bufs].pvBuffer = sub; num_in_bufs++; } @@ -285,8 +285,8 @@ lws_tls_quic_advance_handshake(struct lws *wsi, int level, if (conn->rx_len > 0 && lwsi_role_client(wsi) && !wsi->tls.quic_tp_recv) { out_bufs[out_desc.cBuffers].BufferType = SECBUFFER_SUBSCRIBE_GENERIC_TLS_EXTENSION; - out_bufs[out_desc.cBuffers].cbBuffer = (unsigned long)conn->rx_len; - out_bufs[out_desc.cBuffers].pvBuffer = (void *)conn->rx_buf; + out_bufs[out_desc.cBuffers].cbBuffer = 0; + out_bufs[out_desc.cBuffers].pvBuffer = NULL; out_desc.cBuffers++; } @@ -353,14 +353,26 @@ lws_tls_quic_advance_handshake(struct lws *wsi, int level, status == SEC_I_CONTINUE_NEEDED_MESSAGE_OK) for (unsigned int j = 0; j < out_desc.cBuffers; j++) if (out_bufs[j].BufferType == SECBUFFER_SUBSCRIBE_GENERIC_TLS_EXTENSION && - out_bufs[j].cbBuffer > 0 && out_bufs[j].pvBuffer && !wsi->tls.quic_tp_recv) { - wsi->tls.quic_tp_recv = lws_malloc(out_bufs[j].cbBuffer, "quic_tp_recv"); - if (wsi->tls.quic_tp_recv) { - memcpy((void *)wsi->tls.quic_tp_recv, out_bufs[j].pvBuffer, out_bufs[j].cbBuffer); - wsi->tls.quic_tp_recv_len = out_bufs[j].cbBuffer; + + size_t ext_len = out_bufs[j].cbBuffer; + uint8_t *ext_data = (uint8_t *)out_bufs[j].pvBuffer; + + lwsl_notice("SChannel returned ext_len %zu", ext_len); + lwsl_hexdump_notice(ext_data, ext_len < 32 ? ext_len : 32); + + if (ext_len > 4) { + ext_len -= 4; + ext_data += 4; + + wsi->tls.quic_tp_recv = lws_malloc(ext_len, "quic_tp_recv"); + if (wsi->tls.quic_tp_recv) { + memcpy((void *)wsi->tls.quic_tp_recv, ext_data, ext_len); + wsi->tls.quic_tp_recv_len = ext_len; + } } - } + FreeContextBuffer(out_bufs[j].pvBuffer); + } if (status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED && @@ -633,3 +645,11 @@ lws_tls_quic_api_test(void) return secrets_found > 0 ? 0 : 1; #endif } + +int +lws_tls_quic_migrate_wsi(struct lws *old_wsi, struct lws *new_wsi) +{ + (void)old_wsi; + (void)new_wsi; + return 0; +} diff --git a/lib/tls/schannel/schannel-session.c b/lib/tls/schannel/schannel-session.c index f751e9d3c9..94aab4ceaf 100644 --- a/lib/tls/schannel/schannel-session.c +++ b/lib/tls/schannel/schannel-session.c @@ -23,6 +23,7 @@ */ #include "private-lib-core.h" +#include "private.h" void lws_tls_session_vh_destroy(struct lws_vhost *vh) @@ -34,8 +35,26 @@ int lws_tls_client_vhost_extra_cert_mem(struct lws_vhost *vh, const uint8_t *der, size_t der_len) { - /* TBD */ - return 1; + struct lws_tls_schannel_ctx *ctx = vh->tls.ssl_client_ctx; + + if (!ctx) + return 1; + + if (!ctx->store) { + ctx->store = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, 0, NULL); + if (!ctx->store) { + lwsl_vhost_err(vh, "CertOpenStore failed: 0x%x", (unsigned int)GetLastError()); + return 1; + } + } + + if (!CertAddEncodedCertificateToStore(ctx->store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + der, (DWORD)der_len, CERT_STORE_ADD_REPLACE_EXISTING, NULL)) { + lwsl_vhost_err(vh, "CertAddEncodedCertificateToStore failed: 0x%x", (unsigned int)GetLastError()); + return 1; + } + + return 0; } diff --git a/lib/tls/schannel/schannel-ssl.c b/lib/tls/schannel/schannel-ssl.c index a95d59ec30..58f0f29a38 100644 --- a/lib/tls/schannel/schannel-ssl.c +++ b/lib/tls/schannel/schannel-ssl.c @@ -80,7 +80,9 @@ lws_ssl_client_bio_create(struct lws *wsi) /* ALPN */ #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) - if (wsi->a.vhost->tls.alpn) + if (wsi->alpn[0]) + lws_strncpy(conn->alpn, wsi->alpn, sizeof(conn->alpn)); + else if (wsi->a.vhost->tls.alpn) lws_strncpy(conn->alpn, wsi->a.vhost->tls.alpn, sizeof(conn->alpn)); #endif @@ -156,11 +158,11 @@ lws_tls_client_connect(struct lws *wsi, char *errbuf, size_t len) unsigned char ProtocolList[ANYSIZE_ARRAY]; */ - uint32_t status = 0; - uint32_t proto_id_type = 1; /* ALPN */ - uint32_t list_size = 0; + uint32_t proto_lists_size = 0; + uint32_t proto_id_type = 2; /* SecApplicationProtocolNegotiationExt_ALPN */ + uint16_t list_size = 0; - uint8_t *pData = alpn_buf + 12; /* Skip 12 bytes header */ + uint8_t *pData = alpn_buf + 10; /* Skip 10 bytes header */ /* Parse comma separated list */ char temp[64]; @@ -185,11 +187,12 @@ lws_tls_client_connect(struct lws *wsi, char *errbuf, size_t len) else break; } - list_size = (uint32_t)(pData - (alpn_buf + 12)); + list_size = (uint16_t)(pData - (alpn_buf + 10)); + proto_lists_size = 6 + list_size; - memcpy(alpn_buf, &status, 4); + memcpy(alpn_buf, &proto_lists_size, 4); memcpy(alpn_buf + 4, &proto_id_type, 4); - memcpy(alpn_buf + 8, &list_size, 4); + memcpy(alpn_buf + 8, &list_size, 2); in_bufs[0].BufferType = SECBUFFER_APPLICATION_PROTOCOLS; in_bufs[0].pvBuffer = alpn_buf; @@ -562,7 +565,13 @@ lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, size_t len) return lws_ssl_capable_read(wsi, buf, len); } - if (status == SEC_E_OK || status == SEC_I_RENEGOTIATE) { + if (status != SEC_E_OK && status != SEC_I_RENEGOTIATE && status != SEC_I_CONTEXT_EXPIRED) { + lwsl_err("%s: DecryptMessage failed 0x%x\n", __func__, (int)status); + return LWS_SSL_CAPABLE_ERROR; + } + + if (status == SEC_E_OK || status == SEC_I_RENEGOTIATE || + status == SEC_I_CONTEXT_EXPIRED) { int i; uint8_t *dec_data = NULL; size_t dec_len = 0; @@ -620,11 +629,6 @@ lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, size_t len) return LWS_SSL_CAPABLE_ERROR; check_pending: - if (n != (ssize_t)len) { - lws_ssl_remove_wsi_from_buffered_list(wsi); - return (int)n; - } - if (lws_ssl_pending(wsi)) { struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi]; if (lws_dll2_is_detached(&wsi->tls.dll_pending_tls)) @@ -703,6 +707,7 @@ lws_ssl_capable_write(struct lws *wsi, unsigned char *buf, size_t len) status = EncryptMessage(&conn->ctxt, 0, &msg_desc, 0); if (status != SEC_E_OK) { + lwsl_err("%s: EncryptMessage failed 0x%x\n", __func__, (int)status); lws_free(alloc_buf); return LWS_SSL_CAPABLE_ERROR; } @@ -751,6 +756,14 @@ lws_ssl_pending(struct lws *wsi) return 1; } + if (conn && conn->rx_len >= 5) { + size_t record_len = (((size_t)conn->rx_buf[3]) << 8) | conn->rx_buf[4]; + if (conn->rx_len >= 5 + record_len) { + lwsl_wsi_debug(wsi, "pending rx_buf complete record %d", (int)record_len); + return 1; + } + } + return 0; } @@ -799,6 +812,7 @@ __lws_tls_shutdown(struct lws *wsi) lws_tls_client_confirm_peer_cert(struct lws *wsi, char *ebuf, size_t ebuf_len) { struct lws_tls_schannel_conn *conn = wsi->tls.ssl; + struct lws_tls_schannel_ctx *ctx = wsi->a.vhost->tls.ssl_client_ctx; PCCERT_CONTEXT pCert = NULL; SECURITY_STATUS status; int ret = -1; @@ -817,7 +831,7 @@ lws_tls_client_confirm_peer_cert(struct lws *wsi, char *ebuf, size_t ebuf_len) memset(&ChainPara, 0, sizeof(ChainPara)); ChainPara.cbSize = sizeof(ChainPara); - if (CertGetCertificateChain(NULL, pCert, NULL, NULL, &ChainPara, 0, NULL, &pChainContext)) { + if (CertGetCertificateChain(NULL, pCert, NULL, ctx ? ctx->store : NULL, &ChainPara, 0, NULL, &pChainContext)) { HTTPSPolicyCallbackData polHttps; memset(&polHttps, 0, sizeof(HTTPSPolicyCallbackData)); @@ -852,6 +866,22 @@ lws_tls_client_confirm_peer_cert(struct lws *wsi, char *ebuf, size_t ebuf_len) } } + if (ret != 0 && PolicyStatus.dwError == CERT_E_UNTRUSTEDROOT) { + /* Check if the root of the resolved chain is explicitly trusted in our custom store */ + if (ctx && ctx->store && pChainContext->cChain > 0) { + PCERT_SIMPLE_CHAIN pSimpleChain = pChainContext->rgpChain[0]; + if (pSimpleChain->cElement > 0) { + PCERT_CHAIN_ELEMENT pRootElement = pSimpleChain->rgpElement[pSimpleChain->cElement - 1]; + PCCERT_CONTEXT pFound = CertFindCertificateInStore(ctx->store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_EXISTING, pRootElement->pCertContext, NULL); + if (pFound) { + /* The root is explicitly trusted */ + ret = 0; + CertFreeCertificateContext(pFound); + } + } + } + } + if (ret != 0) { lws_snprintf(ebuf, ebuf_len, "Certificate validation failed: 0x%x", (unsigned int)PolicyStatus.dwError); } diff --git a/lib/tls/tls-server.c b/lib/tls/tls-server.c index b41ca495eb..0fc10574ad 100644 --- a/lib/tls/tls-server.c +++ b/lib/tls/tls-server.c @@ -340,9 +340,13 @@ lws_server_socket_service_ssl(struct lws *wsi, lws_sockfd_type accept_fd, char f goto notls_accepted; } + char ipbuf[64]; + + lws_get_peer_simple(wsi, ipbuf, sizeof(ipbuf)); lwsl_notice("%s: client did not send a valid " - "tls hello (default vhost %s)\n", - __func__, wsi->a.vhost->name); + "tls hello (default vhost %s) from %s\n", + __func__, wsi->a.vhost->name, ipbuf); + lwsl_hexdump_notice(pt->serv_buf, s > 256 ? 256 : (size_t)s); goto fail; } if (!s) { diff --git a/lib/tls/tls.c b/lib/tls/tls.c index eb9913002a..3290a363da 100644 --- a/lib/tls/tls.c +++ b/lib/tls/tls.c @@ -292,7 +292,21 @@ lws_tls_server_conn_alpn(struct lws *wsi) lwsl_info("%s: negotiated '%s' using ALPN\n", __func__, cstr); wsi->tls.use_ssl |= LCCSCF_USE_SSL; +#if defined(LWS_WITH_CLIENT) + /* record the successful ALPN in the cache */ + if (wsi->cli_hostname_copy && wsi->a.context->alpn_cache && wsi->c_port) { + char key[256]; + void *p; + lws_snprintf(key, sizeof(key), "alpn_%s_%u", wsi->cli_hostname_copy, wsi->c_port); + /* cache it with a TTL, e.g. 1 hour (3600 seconds) */ + lws_cache_write_through(wsi->a.context->alpn_cache, key, (const uint8_t *)cstr, len + 1, + lws_now_usecs() + (lws_usec_t)(3600ULL * 1000000ULL), &p); + lwsl_wsi_notice(wsi, "wrote ALPN %s to cache for %s", cstr, key); + } +#endif + return lws_role_call_alpn_negotiated(wsi, (const char *)cstr); + #else lwsl_err("%s: openssl/gnutls too old\n", __func__); #endif diff --git a/minimal-examples-lowlevel/abstract/protocols/smtp-client/main.c b/minimal-examples-lowlevel/abstract/protocols/smtp-client/main.c index 57b0ae72ed..673a5e79c8 100644 --- a/minimal-examples-lowlevel/abstract/protocols/smtp-client/main.c +++ b/minimal-examples-lowlevel/abstract/protocols/smtp-client/main.c @@ -56,7 +56,7 @@ done_cb(struct lws_smtp_email *email, void *buf, size_t len) int main(int argc, const char **argv) { - int n = 1, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int n = 1; struct lws_context_creation_info info; lws_smtp_sequencer_args_t ss_args; struct lws_context *context; @@ -77,8 +77,6 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); p = lws_cmdline_option(argc, argv, switches[LWS_SW_R].sw); if (!p) { @@ -87,10 +85,10 @@ int main(int argc, const char **argv) } recip = p; - lws_set_log_level(logs, NULL); lwsl_user("LWS API selftest: SMTP client\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = CONTEXT_PORT_NO_LISTEN; info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS; diff --git a/minimal-examples-lowlevel/api-tests/api-test-async-dns/main.c b/minimal-examples-lowlevel/api-tests/api-test-async-dns/main.c index a942304bbe..10ea49079e 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-async-dns/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-async-dns/main.c @@ -506,12 +506,7 @@ main(int argc, const char **argv) uint8_t mac[6]; int n = 1; - fixup(0); - fixup(5); - fixup(6); - - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); /* the normal lws init */ (void)switches; @@ -541,6 +536,10 @@ main(int argc, const char **argv) return 1; } + fixup(0); + fixup(5); + fixup(6); + { lws_sockaddr46 sa46; int index = 0; diff --git a/minimal-examples-lowlevel/api-tests/api-test-auth-dns/main.c b/minimal-examples-lowlevel/api-tests/api-test-auth-dns/main.c index bbbd82cec9..b3feb52cdf 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-auth-dns/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-auth-dns/main.c @@ -26,18 +26,22 @@ #include #include #include +#if defined(WIN32) || defined(_WIN32) +#include +#else #include +#endif int main(int argc, const char **argv) { struct lws_context_creation_info cx_info; struct lws_auth_dns_sign_info info; struct lws_context *cx; - int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int res = 1; - lws_set_log_level(logs, NULL); + lws_context_info_defaults(&cx_info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &cx_info); - memset(&cx_info, 0, sizeof(cx_info)); cx_info.port = CONTEXT_PORT_NO_LISTEN; cx = lws_create_context(&cx_info); if (!cx) diff --git a/minimal-examples-lowlevel/api-tests/api-test-auth-dns/test.zone.in b/minimal-examples-lowlevel/api-tests/api-test-auth-dns/test.zone.in index 9cfba8d372..6ac8aff488 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-auth-dns/test.zone.in +++ b/minimal-examples-lowlevel/api-tests/api-test-auth-dns/test.zone.in @@ -24,3 +24,7 @@ mail IN AAAA 2001:bc8:6010:213:208:a2ff:fe0c:72ce @ IN TXT "v=spf1 mx a -all" mx 60 IN A ${MHWC_DYNAMIC} mx 60 IN AAAA 2001:bc8:6010:213:208:a2ff:fe0c:72ce + +@ 1200 IN HTTPS 1 . alpn="h3,h2" +www 1200 IN TYPE65 \# 13 00010000010006026833026832 + diff --git a/minimal-examples-lowlevel/api-tests/api-test-backtrace/main.c b/minimal-examples-lowlevel/api-tests/api-test-backtrace/main.c index aab5c9db1c..55a1c3b7a9 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-backtrace/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-backtrace/main.c @@ -34,7 +34,7 @@ main(int argc, const char **argv) struct lws_context_creation_info info; struct lws_context *context; const char *p; - int result = 1, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE, n; + int result = 1, n; uint8_t ib[2048], ob[1536], *eib = ib; lws_backtrace_info_t si; unsigned int m; @@ -50,10 +50,7 @@ main(int argc, const char **argv) } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_STDIN].sw))) { fdin = open(p, LWS_O_RDONLY, 0); @@ -66,7 +63,8 @@ main(int argc, const char **argv) lwsl_user("LWS Compressed Backtrace Decoder\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); #if defined(LWS_WITH_NETWORK) info.port = CONTEXT_PORT_NO_LISTEN; #endif diff --git a/minimal-examples-lowlevel/api-tests/api-test-base32/main.c b/minimal-examples-lowlevel/api-tests/api-test-base32/main.c index b38db6ea8a..b5a66ac483 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-base32/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-base32/main.c @@ -25,14 +25,10 @@ static const struct tests tests[] = { int main(int argc, const char **argv) { - int n, m, fails = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int n, m, fails = 0; char enc[64], dec[64]; - const char *p; - if ((p = lws_cmdline_option(argc, argv, "-d"))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS API selftest: base32\n"); for (n = 0; n < (int)LWS_ARRAY_SIZE(tests); n++) { diff --git a/minimal-examples-lowlevel/api-tests/api-test-cose/main.c b/minimal-examples-lowlevel/api-tests/api-test-cose/main.c index 5471ccc84e..c2a4a3f935 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-cose/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-cose/main.c @@ -29,8 +29,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int result = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int result = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -38,14 +37,11 @@ int main(int argc, const char **argv) return 0; } - - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - - lws_set_log_level(logs, NULL); lwsl_user("LWS COSE api tests\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); + #if defined(LWS_WITH_NETWORK) info.port = CONTEXT_PORT_NO_LISTEN; #endif diff --git a/minimal-examples-lowlevel/api-tests/api-test-cose/sign.c b/minimal-examples-lowlevel/api-tests/api-test-cose/sign.c index 6c8c50b652..88255a98c3 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-cose/sign.c +++ b/minimal-examples-lowlevel/api-tests/api-test-cose/sign.c @@ -677,7 +677,6 @@ test_cose_sign(struct lws_context *context) lws_dll2_owner_t *o; int n; - memset(&info, 0, sizeof(info)); info.cx = context; info.keyset = &set; diff --git a/minimal-examples-lowlevel/api-tests/api-test-dhcpc/main.c b/minimal-examples-lowlevel/api-tests/api-test-dhcpc/main.c index 70aa5e307a..31f1c283c7 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-dhcpc/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-dhcpc/main.c @@ -77,8 +77,7 @@ main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); lwsl_user("LWS API selftest: DHCP Client\n"); info.port = CONTEXT_PORT_NO_LISTEN; diff --git a/minimal-examples-lowlevel/api-tests/api-test-dir/main.c b/minimal-examples-lowlevel/api-tests/api-test-dir/main.c index 2cbe916a30..d34a050a56 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-dir/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-dir/main.c @@ -47,12 +47,11 @@ create_file(const char *path, size_t size) int main(int argc, const char **argv) { - int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + lws_dir_du_t du; int result = 0; lwsl_user("lws-api-test-dir\n"); - lws_set_log_level(logs, NULL); lwsl_user("LWS API selftest: lws_dir du\n"); /* Create test directory structure */ diff --git a/minimal-examples-lowlevel/api-tests/api-test-extip/main.c b/minimal-examples-lowlevel/api-tests/api-test-extip/main.c index 73eeedc6a1..fa022ac8da 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-extip/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-extip/main.c @@ -90,8 +90,7 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); - memset(&ops, 0, sizeof ops); + lws_context_info_defaults(&info, NULL);memset(&ops, 0, sizeof ops); lws_cmdline_option_handle_builtin(argc, argv, &info); diff --git a/minimal-examples-lowlevel/api-tests/api-test-fts/main.c b/minimal-examples-lowlevel/api-tests/api-test-fts/main.c index d21a19b2f2..7b07d1d602 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-fts/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-fts/main.c @@ -18,7 +18,6 @@ static struct option options[] = { { "help", no_argument, NULL, 'h' }, { "createindex", no_argument, NULL, 'c' }, { "index", required_argument, NULL, 'i' }, - { "debug", required_argument, NULL, 'd' }, { "file", required_argument, NULL, 'f' }, { "lines", required_argument, NULL, 'l' }, { NULL, 0, 0, 0 } @@ -28,15 +27,19 @@ static struct option options[] = { static const char *index_filepath = "/tmp/lws-fts-test-index"; static char filepath[256]; -int main(int argc, char **argv) +int main(int argc, char * const * argv) { - int n, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; int fd, fi, ft, createindex = 0, flags = LWSFTS_F_QUERY_AUTOCOMPLETE; + struct lws_context_creation_info cx_info; struct lws_fts_search_params params; struct lws_fts_result *result; struct lws_fts_file *jtf; struct lws_fts *t; char buf[16384]; + int n; + + lws_context_info_defaults(&cx_info, NULL); + lws_cmdline_option_handle_builtin(argc, (const char **)argv, &cx_info); do { #if defined(LWS_HAS_GETOPT_LONG) || defined(WIN32) @@ -52,9 +55,6 @@ int main(int argc, char **argv) filepath[sizeof(filepath) - 1] = '\0'; index_filepath = filepath; break; - case 'd': - logs = atoi(optarg); - break; case 'c': createindex = 1; break; @@ -76,7 +76,6 @@ int main(int argc, char **argv) } } while (n >= 0); - lws_set_log_level(logs, NULL); lwsl_user("LWS API selftest: full-text search\n"); if (createindex) { diff --git a/minimal-examples-lowlevel/api-tests/api-test-gencrypto/lws-genaes.c b/minimal-examples-lowlevel/api-tests/api-test-gencrypto/lws-genaes.c index 83a3cf02e6..65a4af65e7 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-gencrypto/lws-genaes.c +++ b/minimal-examples-lowlevel/api-tests/api-test-gencrypto/lws-genaes.c @@ -207,6 +207,7 @@ test_genaes_cfb128(void) (defined(LWS_WITH_MBEDTLS) && (!defined(MBEDTLS_CONFIG_H) || defined(MBEDTLS_CIPHER_MODE_CFB))) || \ (!defined(LWS_WITH_MBEDTLS) && defined(LWS_HAVE_EVP_aes_128_cfb8)) +#if !defined(LWS_HAVE_MBEDTLS_V4) static const uint8_t /* * produced with (plaintext.txt contains "test plaintext\0\0") @@ -228,7 +229,9 @@ cfb8_enc[] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, }; +#endif +#if !defined(LWS_HAVE_MBEDTLS_V4) static int test_genaes_cfb8(void) { @@ -291,6 +294,7 @@ test_genaes_cfb8(void) return -1; } #endif +#endif #if (defined(LWS_WITH_MBEDTLS) && (!defined(MBEDTLS_CONFIG_H) || defined(MBEDTLS_CIPHER_MODE_CTR))) || \ (!defined(LWS_WITH_MBEDTLS) && defined(LWS_HAVE_EVP_aes_128_ctr)) @@ -597,6 +601,7 @@ test_genaes_ofb(void) #if (defined(LWS_WITH_MBEDTLS) && (!defined(MBEDTLS_CONFIG_H) || defined(MBEDTLS_CIPHER_MODE_XTS))) || \ (!defined(LWS_WITH_MBEDTLS) && defined(LWS_HAVE_EVP_aes_128_xts)) +#if !defined(LWS_HAVE_MBEDTLS_V4) static const uint8_t /* * Fedora openssl tool doesn't support xts... this data produced @@ -620,6 +625,8 @@ static const uint8_t 0x5f, 0x31, 0x9e, 0xcd, 0x33, 0x08, 0xa0, 0x44 } ; +#endif +#if !defined(LWS_HAVE_MBEDTLS_V4) static int test_genaes_xts(void) { @@ -690,6 +697,7 @@ test_genaes_xts(void) return -1; } #endif +#endif //#if !defined(LWS_WITH_SCHANNEL) static const uint8_t @@ -832,9 +840,11 @@ test_genaes(struct lws_context *context) #if defined(LWS_WITH_SCHANNEL) || \ (defined(LWS_WITH_MBEDTLS) && (!defined(MBEDTLS_CONFIG_H) || defined(MBEDTLS_CIPHER_MODE_CFB))) || \ (!defined(LWS_WITH_MBEDTLS) && defined(LWS_HAVE_EVP_aes_128_cfb8)) +#if !defined(LWS_HAVE_MBEDTLS_V4) if (test_genaes_cfb8()) goto bail; #endif +#endif #if (defined(LWS_WITH_MBEDTLS) && (!defined(MBEDTLS_CONFIG_H) || defined(MBEDTLS_CIPHER_MODE_CTR))) || \ (!defined(LWS_WITH_MBEDTLS) && defined(LWS_HAVE_EVP_aes_128_ctr)) if (test_genaes_ctr()) @@ -853,9 +863,11 @@ test_genaes(struct lws_context *context) #endif #if (defined(LWS_WITH_MBEDTLS) && (!defined(MBEDTLS_CONFIG_H) || defined(MBEDTLS_CIPHER_MODE_XTS))) || \ (!defined(LWS_WITH_MBEDTLS) && defined(LWS_HAVE_EVP_aes_128_xts)) +#if !defined(LWS_HAVE_MBEDTLS_V4) if (test_genaes_xts()) goto bail; #endif +#endif //#if !defined(LWS_WITH_SCHANNEL) if (test_genaes_gcm()) diff --git a/minimal-examples-lowlevel/api-tests/api-test-gencrypto/main.c b/minimal-examples-lowlevel/api-tests/api-test-gencrypto/main.c index f49660058e..cf0cfce164 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-gencrypto/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-gencrypto/main.c @@ -38,8 +38,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int result = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int result = 0; (void)switches; if (lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -48,13 +47,11 @@ int main(int argc, const char **argv) } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS gencrypto apis tests\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); #if defined(LWS_WITH_NETWORK) info.port = CONTEXT_PORT_NO_LISTEN; #endif diff --git a/minimal-examples-lowlevel/api-tests/api-test-gunzip/main.c b/minimal-examples-lowlevel/api-tests/api-test-gunzip/main.c index 8a8453decc..1ff9d12631 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-gunzip/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-gunzip/main.c @@ -35,7 +35,7 @@ int fdin = 0, fdout = 1; int main(int argc, const char **argv) { - int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int result = 0, more = 1; const char *p; lws_stateful_ret_t r = LWS_SRET_WANT_INPUT; @@ -50,10 +50,7 @@ main(int argc, const char **argv) } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS API selftest: gunzip\n"); if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_STDIN].sw))) { diff --git a/minimal-examples-lowlevel/api-tests/api-test-jose/main.c b/minimal-examples-lowlevel/api-tests/api-test-jose/main.c index 26649cf0db..bd6708aa2f 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-jose/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-jose/main.c @@ -31,8 +31,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int result = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int result = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -41,13 +40,11 @@ int main(int argc, const char **argv) } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS JOSE api tests\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); #if defined(LWS_WITH_NETWORK) info.port = CONTEXT_PORT_NO_LISTEN; #endif diff --git a/minimal-examples-lowlevel/api-tests/api-test-jpeg/main.c b/minimal-examples-lowlevel/api-tests/api-test-jpeg/main.c index 2be13dc4a3..dcf296f167 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-jpeg/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-jpeg/main.c @@ -33,7 +33,7 @@ int fdin = 0, fdout = 1; int main(int argc, const char **argv) { - int result = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int result = 0; lws_stateful_ret_t r = LWS_SRET_WANT_INPUT; const char *p; lws_jpeg_t *j; @@ -46,10 +46,7 @@ main(int argc, const char **argv) } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS JPEG test tool\n"); if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_STDIN].sw))) { diff --git a/minimal-examples-lowlevel/api-tests/api-test-jrpc/main.c b/minimal-examples-lowlevel/api-tests/api-test-jrpc/main.c index 4e9c963142..475cb418cc 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-jrpc/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-jrpc/main.c @@ -209,10 +209,9 @@ static const lws_jrpc_method_t methods[] = { int main(int argc, const char **argv) { - int n, m, e = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int n, m, e = 0; struct lws_jrpc_obj *req; struct lws_jrpc *jrpc; - const char *p; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -221,10 +220,7 @@ int main(int argc, const char **argv) } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS API selftest: JSON-RPC\n"); for (m = 0; m < (int)LWS_ARRAY_SIZE(jrpc_request_tests); m++) { diff --git a/minimal-examples-lowlevel/api-tests/api-test-lecp/main.c b/minimal-examples-lowlevel/api-tests/api-test-lecp/main.c index bc1086dd03..78deaa5596 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-lecp/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-lecp/main.c @@ -4629,7 +4629,6 @@ int main(int argc, const char **argv) return 0; } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) logs = atoi(p); diff --git a/minimal-examples-lowlevel/api-tests/api-test-lejp/main.c b/minimal-examples-lowlevel/api-tests/api-test-lejp/main.c index f49bf0611a..50e16017b0 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-lejp/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-lejp/main.c @@ -802,9 +802,8 @@ test_cb(struct lejp_ctx *ctx, char reason) int main(int argc, const char **argv) { - int n, e = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int n, e = 0; struct lejp_ctx ctx; - const char *p; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -815,10 +814,7 @@ int main(int argc, const char **argv) memset(&ctx, 0, sizeof(ctx)); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS API selftest: lws_struct JSON\n"); for (m = 0; m < (int)LWS_ARRAY_SIZE(json_tests); m++) { diff --git a/minimal-examples-lowlevel/api-tests/api-test-lhp-dlo/main.c b/minimal-examples-lowlevel/api-tests/api-test-lhp-dlo/main.c index 5b3264b3f7..821acfe3c8 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-lhp-dlo/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-lhp-dlo/main.c @@ -265,8 +265,7 @@ main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); lwsl_user("LWS LHP DLO test tool - %s https://site.com [--bmp file.bmp]\n", argv[0]); diff --git a/minimal-examples-lowlevel/api-tests/api-test-lhp/main.c b/minimal-examples-lowlevel/api-tests/api-test-lhp/main.c index c291e47522..994ea1ef2d 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-lhp/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-lhp/main.c @@ -274,11 +274,10 @@ static lws_displaylist_t displaylist; int main(int argc, const char **argv) { - int e = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int e = 0; lws_stateful_ret_t n; lws_dl_rend_t drt; lhp_ctx_t ctx; - const char *p; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -290,10 +289,7 @@ main(int argc, const char **argv) memset(&ctx, 0, sizeof(ctx)); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS API selftest: lhp HTML5 parser\n"); for (m = 0; m < (int)LWS_ARRAY_SIZE(html_tests); m++) { diff --git a/minimal-examples-lowlevel/api-tests/api-test-lws_cache/main.c b/minimal-examples-lowlevel/api-tests/api-test-lws_cache/main.c index 64835fff89..dfbbb3145a 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-lws_cache/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-lws_cache/main.c @@ -478,8 +478,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; - memset(&info, 0, sizeof info); - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); info.fd_limit_per_thread = 1 + 6 + 1; info.port = CONTEXT_PORT_NO_LISTEN; diff --git a/minimal-examples-lowlevel/api-tests/api-test-lws_dsh/main.c b/minimal-examples-lowlevel/api-tests/api-test-lws_dsh/main.c index bbd60ec5c5..cb20a652ee 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-lws_dsh/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-lws_dsh/main.c @@ -423,9 +423,8 @@ test6(void) int main(int argc, const char **argv) { - int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int ret = 0, n; - const char *p; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -434,10 +433,7 @@ int main(int argc, const char **argv) } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS API selftest: lws_dsh\n"); n = test1(); diff --git a/minimal-examples-lowlevel/api-tests/api-test-lws_map/main.c b/minimal-examples-lowlevel/api-tests/api-test-lws_map/main.c index 5be25205e6..c5b6f47bbb 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-lws_map/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-lws_map/main.c @@ -12,15 +12,7 @@ #include -enum { - LWS_SW_D, - LWS_SW_HELP, -}; -static const struct lws_switches switches[] = { - [LWS_SW_D] = { "-d", "Debug logs (e.g. -d 15)" }, - [LWS_SW_HELP] = { "--help", "Show this help information" }, -}; typedef struct lws_map_item lws_map_item_t; @@ -41,26 +33,21 @@ compare_mykey_t(const lws_map_key_t key1, size_t kl1, int main(int argc, const char **argv) { - int e = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE, + int e = 0, expected = 4, pass = 0; mykey_t k1 = { .key = 123 }, k2 = { .key = 234 }, k3 = { .key = 999 }; struct lwsac *ac = NULL; lws_map_item_t *item; lws_map_info_t info; lws_map_t *map; - const char *p; - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS API selftest: lws_map\n"); /* Test 1: string keys */ lwsl_user("%s: test1\n", __func__); - memset(&info, 0, sizeof(info)); - map = lws_map_create(&info); + memset(&info, 0, sizeof(info));map = lws_map_create(&info); if (!map) { e++; goto end_t1; @@ -231,8 +218,7 @@ int main(int argc, const char **argv) /* Test 4: same key items */ lwsl_user("%s: test4\n", __func__); - memset(&info, 0, sizeof(info)); - map = lws_map_create(&info); + memset(&info, 0, sizeof(info));map = lws_map_create(&info); if (!map) { e++; goto end_t4; diff --git a/minimal-examples-lowlevel/api-tests/api-test-lws_smd/main.c b/minimal-examples-lowlevel/api-tests/api-test-lws_smd/main.c index be9796518e..33bcd4092e 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-lws_smd/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-lws_smd/main.c @@ -200,7 +200,7 @@ main(int argc, const char **argv) lws_state_notify_link_t notifier = { { NULL, NULL, NULL }, system_notify_cb, "app" }; lws_state_notify_link_t *na[] = { ¬ifier, NULL }; - int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + struct lws_context_creation_info info; struct lws_smd_peer *userreg; const char *p; @@ -210,8 +210,6 @@ main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_COUNT].sw))) how_many_msg = (unsigned int)atol(p); @@ -221,11 +219,11 @@ main(int argc, const char **argv) if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_INTERVAL].sw))) usec_interval = (unsigned int)atol(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS API selftest: lws_smd: %u msgs at %uus interval\n", how_many_msg, usec_interval); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = CONTEXT_PORT_NO_LISTEN; info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; info.register_notifier_list = na; diff --git a/minimal-examples-lowlevel/api-tests/api-test-lws_struct-json/main.c b/minimal-examples-lowlevel/api-tests/api-test-lws_struct-json/main.c index d3e399b589..5d306e4d84 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-lws_struct-json/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-lws_struct-json/main.c @@ -542,7 +542,6 @@ int main(int argc, const char **argv) return 0; } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) logs = atoi(p); diff --git a/minimal-examples-lowlevel/api-tests/api-test-lws_struct_sqlite/main.c b/minimal-examples-lowlevel/api-tests/api-test-lws_struct_sqlite/main.c index e522a54415..e1faeb2d4a 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-lws_struct_sqlite/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-lws_struct_sqlite/main.c @@ -82,13 +82,12 @@ static const char *test_string = int main(int argc, const char **argv) { - int e = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int e = 0; struct lws_context_creation_info info; struct lws_context *context; struct lwsac *ac = NULL; lws_dll2_owner_t resown; teststruct_t ts, *pts; - const char *p; sqlite3 *db; (void)switches; @@ -98,13 +97,11 @@ int main(int argc, const char **argv) } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS API selftest: lws_struct SQLite\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); #if defined(LWS_WITH_NETWORK) info.port = CONTEXT_PORT_NO_LISTEN; #endif diff --git a/minimal-examples-lowlevel/api-tests/api-test-lws_stub/main.c b/minimal-examples-lowlevel/api-tests/api-test-lws_stub/main.c index 3e6353e403..6259f62d60 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-lws_stub/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-lws_stub/main.c @@ -206,12 +206,10 @@ int main(int argc, const char **argv) struct lws_context_creation_info info; struct lws_context *cx; const char *p; - int result = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int result = 0; - if ((p = lws_cmdline_option(argc, argv, "-d"))) - logs = atoi(p); if (lws_cmdline_option(argc, argv, "-h") || lws_cmdline_option(argc, argv, "--help")) { @@ -225,7 +223,6 @@ int main(int argc, const char **argv) setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); - lws_set_log_level(logs, NULL); signal(SIGINT, sigint_handler); lws_context_info_defaults(&info, NULL); diff --git a/minimal-examples-lowlevel/api-tests/api-test-lws_tokenize/main.c b/minimal-examples-lowlevel/api-tests/api-test-lws_tokenize/main.c index 31be57a670..9ea8c65ebb 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-lws_tokenize/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-lws_tokenize/main.c @@ -345,13 +345,7 @@ int main(int argc, const char **argv) lws_tokenize_elem e; const char *p; char b1[22]; - int n, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n; int fail = 0, ok = 0, flags = 0; char dotstar[512]; (void)switches; @@ -362,17 +356,15 @@ int main(int argc, const char **argv) } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS API selftest: lws_tokenize\n"); if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_F].sw))) flags = atoi(p); - memset(&info, 0, sizeof info); + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | LWS_SERVER_OPTION_H2_JUST_FIX_WINDOW_UPDATE_OVERFLOW; diff --git a/minimal-examples-lowlevel/api-tests/api-test-lwsac/main.c b/minimal-examples-lowlevel/api-tests/api-test-lwsac/main.c index 85e7a40d59..043348b606 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-lwsac/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-lwsac/main.c @@ -32,11 +32,10 @@ struct mytest { int main(int argc, const char **argv) { - int n, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE, acc; + int n, acc; lws_list_ptr list_head = NULL, iter; struct lwsac *lwsac = NULL; struct mytest *m; - const char *p; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -45,10 +44,7 @@ int main(int argc, const char **argv) } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS API selftest: lwsac\n"); /* diff --git a/minimal-examples-lowlevel/api-tests/api-test-mnemonic/main.c b/minimal-examples-lowlevel/api-tests/api-test-mnemonic/main.c index 53e1924ae6..a0b2697058 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-mnemonic/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-mnemonic/main.c @@ -63,7 +63,8 @@ int main(int argc, const char **argv) char buf[256]; uint8_t entropy[16]; - memset(&info, 0, sizeof(info)); + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; info.port = CONTEXT_PORT_NO_LISTEN; diff --git a/minimal-examples-lowlevel/api-tests/api-test-qpack/CMakeLists.txt b/minimal-examples-lowlevel/api-tests/api-test-qpack/CMakeLists.txt index 4972f77876..e4fa558bca 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-qpack/CMakeLists.txt +++ b/minimal-examples-lowlevel/api-tests/api-test-qpack/CMakeLists.txt @@ -61,6 +61,7 @@ set(SRCS main.c) set(requirements 1) require_lws_config(LWS_WITH_HTTP3 1 requirements) +require_lws_config(LWS_WITH_NETWORK 1 requirements) if (requirements) if (LWS_WITH_LS_QPACK) diff --git a/minimal-examples-lowlevel/api-tests/api-test-qpack/main.c b/minimal-examples-lowlevel/api-tests/api-test-qpack/main.c index abe4ea4c1b..8253d97299 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-qpack/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-qpack/main.c @@ -10,7 +10,11 @@ #include #include #include +#if defined(WIN32) || defined(_WIN32) +#include +#else #include +#endif #if defined(LWS_WITH_LS_QPACK) @@ -18,7 +22,7 @@ #include #endif -#include + #include void lws_free(void *p); @@ -70,11 +74,16 @@ static int test_qpack_encoder(struct lws_context *ctx) if (lws_finalize_http_header(wsi, &p, end)) { lwsl_err("finalize fail\n"); fails++; } /* Decode the generated encoder stream */ - if (tx_enc.enc_ptr) { - lwsl_user("Encoded %d bytes of encoder stream.\n", (int)tx_enc.enc_ptr); - if (lws_qpack_decode_encoder_stream(&state, &qctx, tx_enc.enc_buf, tx_enc.enc_ptr)) { - lwsl_err("Failed to decode encoder stream\n"); - fails++; + { + size_t len = lws_buflist_total_len(&tx_enc.tx_bl); + if (len) { + uint8_t enc_buf[1024]; + lws_buflist_linear_copy(&tx_enc.tx_bl, 0, enc_buf, len); + lwsl_user("Encoded %d bytes of encoder stream.\n", (int)len); + if (lws_qpack_decode_encoder_stream(&state, &qctx, enc_buf, len)) { + lwsl_err("Failed to decode encoder stream\n"); + fails++; + } } } @@ -119,7 +128,7 @@ test_qif_roundtrip_cb(void *user, int name_idx, const char *name, size_t name_le const char *n2 = exp_name ? exp_name : ""; if (!name && name_idx >= 0) { - const unsigned char *name_str = lws_token_to_string(name_idx); + const unsigned char *name_str = lws_token_to_string((enum lws_token_indexes)name_idx); char clean_name[128] = ""; if (name_str) { size_t len = strlen((const char *)name_str); @@ -207,12 +216,18 @@ test_qif_roundtrip(struct lws_context *ctx, const char *filepath) } /* Decode encoder stream */ - if (tx_enc.enc_ptr > 0) { - if (lws_qpack_decode_encoder_stream(&enc_state, &qctx, tx_enc.enc_buf, tx_enc.enc_ptr)) { - lwsl_err("Encoder stream decode failed in %s\n", filepath); - fails++; + { + size_t len = lws_buflist_total_len(&tx_enc.tx_bl); + if (len > 0) { + uint8_t enc_buf[1024]; + if (len > sizeof(enc_buf)) len = sizeof(enc_buf); + lws_buflist_linear_copy(&tx_enc.tx_bl, 0, enc_buf, len); + if (lws_qpack_decode_encoder_stream(&enc_state, &qctx, enc_buf, len)) { + lwsl_err("Encoder stream decode failed in %s\n", filepath); + fails++; + } + lws_buflist_destroy_all_segments(&tx_enc.tx_bl); } - tx_enc.enc_ptr = 0; } /* Decode header block */ @@ -392,9 +407,39 @@ test_qif_file(const char *filepath) return fails; } +struct qif_test_ctx { + struct lws_context *cx; + int fails; + int is_roundtrip; +}; + +static int +qif_dir_cb(const char *dirpath, void *user, struct lws_dir_entry *lde) +{ + struct qif_test_ctx *qctx = (struct qif_test_ctx *)user; + char path[512]; + + if (lde->type != LDOT_FILE) + return 0; + + if (qctx->is_roundtrip) { + if (strstr(lde->name, ".qif")) { + snprintf(path, sizeof(path), "%s/%s", dirpath, lde->name); + qctx->fails += test_qif_roundtrip(qctx->cx, path); + } + } else { + if (strstr(lde->name, ".out")) { + snprintf(path, sizeof(path), "%s/%s", dirpath, lde->name); + qctx->fails += test_qif_file(path); + } + } + + return 0; +} + int main(int argc, const char **argv) { - int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + struct lws_context_creation_info info; struct lws_context *context; int tok, fails = 0; @@ -402,10 +447,10 @@ int main(int argc, const char **argv) unsigned char buf[256]; int len; - lws_set_log_level(logs, NULL); lwsl_user("LWS QPACK API tests\n"); - memset(&info, 0, sizeof info); + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = CONTEXT_PORT_NO_LISTEN; info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; @@ -566,37 +611,18 @@ int main(int argc, const char **argv) /* 7. Run against ALL QIF Interop Outputs */ { - DIR *d = opendir("../minimal-examples-lowlevel/api-tests/api-test-qpack/qifs/encoded/qpack-06/ls-qpack"); - struct dirent *dir; + struct qif_test_ctx qctx = { context, 0, 0 }; lwsl_user("\n--- 7. QIF Interop Decoder Test (ALL datasets) ---\n"); - if (d) { - while ((dir = readdir(d)) != NULL) { - if (strstr(dir->d_name, ".out")) { - char path[512]; - snprintf(path, sizeof(path), "../minimal-examples-lowlevel/api-tests/api-test-qpack/qifs/encoded/qpack-06/ls-qpack/%s", dir->d_name); - fails += test_qif_file(path); - } - } - closedir(d); - } + lws_dir("../minimal-examples-lowlevel/api-tests/api-test-qpack/qifs/encoded/qpack-06/ls-qpack", &qctx, qif_dir_cb); + fails += qctx.fails; } /* 8. Run LWS Encoder Roundtrip against ALL QIF Plaintext Datasets */ { - DIR *d = opendir("../minimal-examples-lowlevel/api-tests/api-test-qpack/qifs/qifs"); - struct dirent *dir; + struct qif_test_ctx qctx = { context, 0, 1 }; lwsl_user("\n--- 8. QIF Interop Encoder Roundtrip Test (ALL datasets) ---\n"); - if (d) { - while ((dir = readdir(d)) != NULL) { - if (strstr(dir->d_name, ".qif")) { - char path[512]; - snprintf(path, sizeof(path), "../minimal-examples-lowlevel/api-tests/api-test-qpack/qifs/qifs/%s", dir->d_name); - /* lwsl_user("Roundtripping %s\n", path); */ - fails += test_qif_roundtrip(context, path); - } - } - closedir(d); - } + lws_dir("../minimal-examples-lowlevel/api-tests/api-test-qpack/qifs/qifs", &qctx, qif_dir_cb); + fails += qctx.fails; } fails += test_qpack_encoder(context); diff --git a/minimal-examples-lowlevel/api-tests/api-test-quic-tls/CMakeLists.txt b/minimal-examples-lowlevel/api-tests/api-test-quic-tls/CMakeLists.txt index 0362a886b2..00a1d43765 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-quic-tls/CMakeLists.txt +++ b/minimal-examples-lowlevel/api-tests/api-test-quic-tls/CMakeLists.txt @@ -11,6 +11,7 @@ set(SRCS main.c) set(requirements 1) require_lws_config(LWS_WITH_TLS 1 requirements) require_lws_config(LWS_ROLE_QUIC 1 requirements) +require_lws_config(LWS_WITH_NETWORK 1 requirements) if (requirements) diff --git a/minimal-examples-lowlevel/api-tests/api-test-quic-tls/main.c b/minimal-examples-lowlevel/api-tests/api-test-quic-tls/main.c index e5febc5fd7..e0039e3b75 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-quic-tls/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-quic-tls/main.c @@ -3,10 +3,9 @@ int main(int argc, const char **argv) { - int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int res; - lws_set_log_level(logs, NULL); lwsl_user("api-test-quic-tls\n"); res = lws_tls_quic_api_test(); diff --git a/minimal-examples-lowlevel/api-tests/api-test-secure-streams/main.c b/minimal-examples-lowlevel/api-tests/api-test-secure-streams/main.c index b0a4511b78..34e37a020a 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-secure-streams/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-secure-streams/main.c @@ -401,8 +401,7 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); lwsl_user("LWS secure streams test client [-d]\n"); diff --git a/minimal-examples-lowlevel/api-tests/api-test-smtp_client/main.c b/minimal-examples-lowlevel/api-tests/api-test-smtp_client/main.c index 35ab08a696..67e4cb7204 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-smtp_client/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-smtp_client/main.c @@ -190,13 +190,12 @@ tests_completion_cb(const void *cb_user) int main(int argc, const char **argv) { - int n = 1, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int n = 1; struct lws_context_creation_info info; lws_test_sequencer_args_t args; struct lws_context *context; lws_abs_t *abs = NULL; struct lws_vhost *vh; - const char *p; /* the normal lws init */ (void)switches; @@ -209,13 +208,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS API selftest: SMTP client unit tests\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = CONTEXT_PORT_NO_LISTEN; info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS; diff --git a/minimal-examples-lowlevel/api-tests/api-test-ssjpeg/main.c b/minimal-examples-lowlevel/api-tests/api-test-ssjpeg/main.c index fe9143b2c7..bf10fcf993 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-ssjpeg/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-ssjpeg/main.c @@ -223,8 +223,7 @@ main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_STDOUT].sw))) { fdout = open(p, LWS_O_WRONLY | LWS_O_CREAT | LWS_O_TRUNC, 0600); diff --git a/minimal-examples-lowlevel/api-tests/api-test-upng/main.c b/minimal-examples-lowlevel/api-tests/api-test-upng/main.c index 04e0b0c065..4ab6bd8910 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-upng/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-upng/main.c @@ -33,7 +33,7 @@ int fdin = 0, fdout = 1; int main(int argc, const char **argv) { - int result = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int result = 0; lws_stateful_ret_t r = LWS_SRET_WANT_INPUT; const char *p; lws_upng_t *u; @@ -45,10 +45,7 @@ main(int argc, const char **argv) } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS UPNG test tool\n"); if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_STDIN].sw))) { diff --git a/minimal-examples-lowlevel/api-tests/api-test-x509/main.c b/minimal-examples-lowlevel/api-tests/api-test-x509/main.c index 6e45605fd5..4ac872dbd4 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-x509/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-x509/main.c @@ -28,19 +28,34 @@ static const uint8_t expected_spki[] = { int main(int argc, const char **argv) { - int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + struct lws_x509_cert *x509 = NULL; char big[1024]; union lws_tls_cert_info_results *res = (union lws_tls_cert_info_results *)big; int ret = 0; size_t big_len = sizeof(big) - sizeof(*res) + sizeof(res->ns.name); - lws_set_log_level(logs, NULL); + struct lws_context_creation_info info; + struct lws_context *context; + lwsl_user("LWS x509 API test\n"); + lws_context_info_defaults(&info, NULL); +#if defined(LWS_WITH_NETWORK) + info.port = CONTEXT_PORT_NO_LISTEN; +#endif + info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + if (lws_x509_create(&x509)) { lwsl_err("lws_x509_create failed\n"); - return 1; + ret = 1; + goto bail_ctx; } if (lws_x509_parse_from_pem(x509, test_cert, strlen(test_cert) + 1)) { @@ -72,5 +87,7 @@ int main(int argc, const char **argv) bail: lws_x509_destroy(&x509); +bail_ctx: + lws_context_destroy(context); return ret; } diff --git a/minimal-examples-lowlevel/client-server/minimal-ws-proxy/minimal-ws-proxy.c b/minimal-examples-lowlevel/client-server/minimal-ws-proxy/minimal-ws-proxy.c index 03d8deca7c..f12c96c75f 100644 --- a/minimal-examples-lowlevel/client-server/minimal-ws-proxy/minimal-ws-proxy.c +++ b/minimal-examples-lowlevel/client-server/minimal-ws-proxy/minimal-ws-proxy.c @@ -60,14 +60,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -78,13 +71,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal ws proxy | visit http://localhost:7681\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE; info.port = 7681; diff --git a/minimal-examples-lowlevel/crypto/minimal-crypto-cose-key/main.c b/minimal-examples-lowlevel/crypto/minimal-crypto-cose-key/main.c index d01295acbd..73bf2e0b7d 100644 --- a/minimal-examples-lowlevel/crypto/minimal-crypto-cose-key/main.c +++ b/minimal-examples-lowlevel/crypto/minimal-crypto-cose-key/main.c @@ -135,8 +135,7 @@ cose_key_dump(const struct lws_cose_key *ck) int main(int argc, const char **argv) { uint8_t *kid = NULL, ktmp[4096], set_temp[32 * 1024], temp[256]; - int result = 1, bits = 0, - logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int result = 1, bits = 0; struct lws_context_creation_info info; size_t kid_len = 0, stp = 0; struct lws_context *context; @@ -153,14 +152,12 @@ int main(int argc, const char **argv) } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS cose-key example tool -k keyset [-s alg-name kid ]\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); #if defined(LWS_WITH_NETWORK) info.port = CONTEXT_PORT_NO_LISTEN; #endif diff --git a/minimal-examples-lowlevel/crypto/minimal-crypto-cose-sign/CMakeLists.txt b/minimal-examples-lowlevel/crypto/minimal-crypto-cose-sign/CMakeLists.txt index 75d2f9a59a..2dd6008a06 100644 --- a/minimal-examples-lowlevel/crypto/minimal-crypto-cose-sign/CMakeLists.txt +++ b/minimal-examples-lowlevel/crypto/minimal-crypto-cose-sign/CMakeLists.txt @@ -56,22 +56,27 @@ if (requirements) set_tests_properties(crypto-cose-sign-6 PROPERTIES DEPENDS crypto-cose-sign-3) - # RSA 4096 signing - - add_test(NAME crypto-cose-sign-7 - COMMAND lws-crypto-cose-sign -s -k rsa-4096.ck - --alg RS512 --cose-sign - --stdin payload.txt - --stdout ctest-sig-rs512.sig) - - # RSA 4096 validation - - add_test(NAME crypto-cose-sign-8 - COMMAND lws-crypto-cose-sign -k rsa-4096.ck --cose-sign - --stdout r8.txt - --stdin ctest-sig-rs512.sig) - set_tests_properties(crypto-cose-sign-8 PROPERTIES - DEPENDS crypto-cose-sign-7) + if (NOT LWS_WITH_GNUTLS) + # RSA 4096 signing + add_test(NAME crypto-cose-sign-7 + COMMAND lws-crypto-cose-sign -s -k rsa-4096.ck + --alg RS512 --cose-sign + --stdin payload.txt + --stdout ctest-sig-rs512.sig) + + # RSA 4096 validation + add_test(NAME crypto-cose-sign-8 + COMMAND lws-crypto-cose-sign -k rsa-4096.ck --cose-sign + --stdout r8.txt + --stdin ctest-sig-rs512.sig) + set_tests_properties(crypto-cose-sign-8 PROPERTIES + DEPENDS crypto-cose-sign-7) + set_tests_properties(crypto-cose-sign-7 crypto-cose-sign-8 + PROPERTIES + WORKING_DIRECTORY + ${CMAKE_SOURCE_DIR}/minimal-examples-lowlevel/crypto/minimal-crypto-cose-sign + TIMEOUT 5) + endif() # HMAC signing, cose-mac @@ -186,8 +191,6 @@ if (requirements) crypto-cose-sign-4 crypto-cose-sign-5 crypto-cose-sign-6 - crypto-cose-sign-7 - crypto-cose-sign-8 # crypto-cose-sign-9 # crypto-cose-sign-10 # crypto-cose-sign-11 diff --git a/minimal-examples-lowlevel/crypto/minimal-crypto-cose-sign/main.c b/minimal-examples-lowlevel/crypto/minimal-crypto-cose-sign/main.c index a9c5ccedd6..c828dd5e63 100644 --- a/minimal-examples-lowlevel/crypto/minimal-crypto-cose-sign/main.c +++ b/minimal-examples-lowlevel/crypto/minimal-crypto-cose-sign/main.c @@ -127,8 +127,7 @@ pay_cb(struct lws_cose_validate_context *cps, void *opaque, int main(int argc, const char **argv) { uint8_t *ks, temp[256], *kid = NULL, ktmp[4096], sbuf[512]; - int n, m, sign = 0, result = 1, - logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int n, m, sign = 0, result = 1; enum lws_cose_sig_types sigtype = SIGTYPE_UNKNOWN; struct lws_cose_validate_context *cps = NULL; struct lws_cose_sign_context *csc = NULL; @@ -152,14 +151,12 @@ int main(int argc, const char **argv) } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS cose-sign example tool -k keyset [-s alg-name kid ]\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); #if defined(LWS_WITH_NETWORK) info.port = CONTEXT_PORT_NO_LISTEN; #endif diff --git a/minimal-examples-lowlevel/crypto/minimal-crypto-dnssec/main.c b/minimal-examples-lowlevel/crypto/minimal-crypto-dnssec/main.c index 654aba8676..940057dc3c 100644 --- a/minimal-examples-lowlevel/crypto/minimal-crypto-dnssec/main.c +++ b/minimal-examples-lowlevel/crypto/minimal-crypto-dnssec/main.c @@ -94,7 +94,7 @@ smd_cb(void *opaque, lws_smd_class_t c, lws_usec_t ts, void *buf, size_t len) int main(int argc, const char **argv) { - int result = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int result = 0; struct lws_context_creation_info info; struct lws_context *context; const char *p; @@ -102,10 +102,7 @@ int main(int argc, const char **argv) const struct lws_dht_dnssec_ops *ops; struct lws_vhost *vh; - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); if ((argc == 1) || lws_cmdline_option(argc, argv, "-h") || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { lwsl_user("Usage: %s [args...]\n\n", argv[0]); @@ -132,7 +129,8 @@ int main(int argc, const char **argv) static const char * dynamic_pdirs[3]; - memset(&info, 0, sizeof info); + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); #if defined(LWS_WITH_NETWORK) info.port = CONTEXT_PORT_NO_LISTEN; #endif diff --git a/minimal-examples-lowlevel/crypto/minimal-crypto-jwe/main.c b/minimal-examples-lowlevel/crypto/minimal-crypto-jwe/main.c index e9cd281f79..cdb9beef88 100644 --- a/minimal-examples-lowlevel/crypto/minimal-crypto-jwe/main.c +++ b/minimal-examples-lowlevel/crypto/minimal-crypto-jwe/main.c @@ -101,8 +101,7 @@ format_c(const char *key) int main(int argc, const char **argv) { - int n, enc = 0, result = 0, - logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int n, enc = 0, result = 0; char *in; struct lws_context_creation_info info; int temp_len = sizeof(temp); @@ -117,13 +116,11 @@ int main(int argc, const char **argv) } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS JWE example tool\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); #if defined(LWS_WITH_NETWORK) info.port = CONTEXT_PORT_NO_LISTEN; #endif diff --git a/minimal-examples-lowlevel/crypto/minimal-crypto-jwk/main.c b/minimal-examples-lowlevel/crypto/minimal-crypto-jwk/main.c index f0dd8104f3..24578edbe6 100644 --- a/minimal-examples-lowlevel/crypto/minimal-crypto-jwk/main.c +++ b/minimal-examples-lowlevel/crypto/minimal-crypto-jwk/main.c @@ -98,7 +98,7 @@ format_c(int fd, const char *key) int main(int argc, const char **argv) { - int result = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int result = 0; enum lws_gencrypto_kty kty = LWS_GENCRYPTO_KTY_RSA; struct lws_context_creation_info info; const char *curve = "P-256", *p; @@ -115,10 +115,7 @@ int main(int argc, const char **argv) } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS JWK example\n"); if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_B].sw))) @@ -144,7 +141,8 @@ int main(int argc, const char **argv) } } - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); #if defined(LWS_WITH_NETWORK) info.port = CONTEXT_PORT_NO_LISTEN; #endif diff --git a/minimal-examples-lowlevel/crypto/minimal-crypto-jws/main.c b/minimal-examples-lowlevel/crypto/minimal-crypto-jws/main.c index 18d65dae15..d2491a9474 100644 --- a/minimal-examples-lowlevel/crypto/minimal-crypto-jws/main.c +++ b/minimal-examples-lowlevel/crypto/minimal-crypto-jws/main.c @@ -33,8 +33,7 @@ char temp[MAX_SIZE], compact[MAX_SIZE]; int main(int argc, const char **argv) { - int n, sign = 0, result = 0, - logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int n, sign = 0, result = 0; char *in; struct lws_context_creation_info info; struct lws_jws_map map; @@ -52,13 +51,11 @@ int main(int argc, const char **argv) } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS JWS example tool\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); #if defined(LWS_WITH_NETWORK) info.port = CONTEXT_PORT_NO_LISTEN; #endif diff --git a/minimal-examples-lowlevel/crypto/minimal-crypto-susvalid/main.c b/minimal-examples-lowlevel/crypto/minimal-crypto-susvalid/main.c index 274e710eff..5b68666f68 100644 --- a/minimal-examples-lowlevel/crypto/minimal-crypto-susvalid/main.c +++ b/minimal-examples-lowlevel/crypto/minimal-crypto-susvalid/main.c @@ -240,17 +240,13 @@ process_byte(struct parse_state *s, uint8_t b) int main(int argc, const char **argv) { - int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + struct parse_state s; - const char *p; uint8_t buf[1024]; ssize_t n; int fd; - if ((p = lws_cmdline_option(argc, argv, "-d"))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS Suspicious Unicode Validator\n"); if (argc < 2 || lws_cmdline_option(argc, argv, "-h")) { diff --git a/minimal-examples-lowlevel/crypto/minimal-crypto-x509/main.c b/minimal-examples-lowlevel/crypto/minimal-crypto-x509/main.c index 8e9d532717..50c60befe0 100644 --- a/minimal-examples-lowlevel/crypto/minimal-crypto-x509/main.c +++ b/minimal-examples-lowlevel/crypto/minimal-crypto-x509/main.c @@ -75,7 +75,7 @@ read_pem_c509_cert(struct lws_x509_cert **x509, const char *filename, int main(int argc, const char **argv) { - int n, result = 1, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int n, result = 1; struct lws_x509_cert *x509 = NULL, *x509_trusted = NULL; struct lws_context_creation_info info; struct lws_context *context; @@ -92,13 +92,11 @@ int main(int argc, const char **argv) memset(&jwk, 0, sizeof(jwk)); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS X509 api example\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); #if defined(LWS_WITH_NETWORK) info.port = CONTEXT_PORT_NO_LISTEN; #endif diff --git a/minimal-examples-lowlevel/dbus-client/minimal-dbus-client/minimal-dbus-client.c b/minimal-examples-lowlevel/dbus-client/minimal-dbus-client/minimal-dbus-client.c index 1fcada1745..0371e20309 100644 --- a/minimal-examples-lowlevel/dbus-client/minimal-dbus-client/minimal-dbus-client.c +++ b/minimal-examples-lowlevel/dbus-client/minimal-dbus-client/minimal-dbus-client.c @@ -231,14 +231,7 @@ int main(int argc, const char **argv) { struct lws_vhost *vh; struct lws_context_creation_info info; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */ /* | LLL_THREAD */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -249,13 +242,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal DBUS client\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS; context = lws_create_context(&info); if (!context) { diff --git a/minimal-examples-lowlevel/dbus-client/minimal-dbus-ws-proxy-testclient/minimal-dbus-ws-proxy-testclient.c b/minimal-examples-lowlevel/dbus-client/minimal-dbus-ws-proxy-testclient/minimal-dbus-ws-proxy-testclient.c index 1c48cc1793..1176a293a7 100644 --- a/minimal-examples-lowlevel/dbus-client/minimal-dbus-ws-proxy-testclient/minimal-dbus-ws-proxy-testclient.c +++ b/minimal-examples-lowlevel/dbus-client/minimal-dbus-ws-proxy-testclient/minimal-dbus-ws-proxy-testclient.c @@ -383,13 +383,7 @@ int main(int argc, const char **argv) struct lws_vhost *vh; struct lws_context_creation_info info; const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */ /* | LLL_THREAD */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -400,16 +394,14 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_X].sw))) autoexit_budget = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal DBUS ws proxy testclient\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS; context = lws_create_context(&info); if (!context) { diff --git a/minimal-examples-lowlevel/dbus-server/minimal-dbus-server/main.c b/minimal-examples-lowlevel/dbus-server/minimal-dbus-server/main.c index b1cd50833a..62c904b34b 100644 --- a/minimal-examples-lowlevel/dbus-server/minimal-dbus-server/main.c +++ b/minimal-examples-lowlevel/dbus-server/minimal-dbus-server/main.c @@ -470,14 +470,7 @@ void sigint_handler(int sig) int main(int argc, const char **argv) { struct lws_context_creation_info info; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */ /* | LLL_THREAD */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -488,13 +481,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal DBUS server\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS; context = lws_create_context(&info); if (!context) { diff --git a/minimal-examples-lowlevel/dbus-server/minimal-dbus-ws-proxy/main.c b/minimal-examples-lowlevel/dbus-server/minimal-dbus-ws-proxy/main.c index 885ab7dda4..c9b8a9a2a8 100644 --- a/minimal-examples-lowlevel/dbus-server/minimal-dbus-ws-proxy/main.c +++ b/minimal-examples-lowlevel/dbus-server/minimal-dbus-ws-proxy/main.c @@ -69,14 +69,7 @@ int main(int argc, const char **argv) { static struct lws_context *context; struct lws_context_creation_info info; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */ /* | LLL_THREAD */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -87,13 +80,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS DBUS ws client proxy\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE; info.port = CONTEXT_PORT_NO_LISTEN; diff --git a/minimal-examples-lowlevel/gtk/minimal-gtk/main.c b/minimal-examples-lowlevel/gtk/minimal-gtk/main.c index 56f234cd02..dc25b5ca0e 100644 --- a/minimal-examples-lowlevel/gtk/minimal-gtk/main.c +++ b/minimal-examples-lowlevel/gtk/minimal-gtk/main.c @@ -148,7 +148,8 @@ t1_main (gpointer user_data) /* attach our lws activities to the main loop of this thread */ lws_set_log_level(LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE, NULL); - memset(&info, 0, sizeof info); + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = CONTEXT_PORT_NO_LISTEN; info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | LWS_SERVER_OPTION_GLIB; diff --git a/minimal-examples-lowlevel/http-client/minimal-http-client-attach/minimal-http-client-attach.c b/minimal-examples-lowlevel/http-client/minimal-http-client-attach/minimal-http-client-attach.c index 40c8d714f6..73877f0576 100644 --- a/minimal-examples-lowlevel/http-client/minimal-http-client-attach/minimal-http-client-attach.c +++ b/minimal-examples-lowlevel/http-client/minimal-http-client-attach/minimal-http-client-attach.c @@ -193,7 +193,8 @@ lws_create(void *d) lwsl_user("%s: tid %p\n", __func__, (void *)(intptr_t)pthread_self()); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + info.port = CONTEXT_PORT_NO_LISTEN; info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; info.system_ops = &ops; @@ -221,8 +222,7 @@ lws_create(void *d) int main(int argc, const char **argv) { - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; - const char *p; + int n = 0; void *retval; (void)switches; @@ -234,10 +234,7 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http client attach\n"); pthread_mutex_init(&lock, NULL); diff --git a/minimal-examples-lowlevel/http-client/minimal-http-client-captive-portal/minimal-http-client-captive-portal.c b/minimal-examples-lowlevel/http-client/minimal-http-client-captive-portal/minimal-http-client-captive-portal.c index 5036176846..385de048c7 100644 --- a/minimal-examples-lowlevel/http-client/minimal-http-client-captive-portal/minimal-http-client-captive-portal.c +++ b/minimal-examples-lowlevel/http-client/minimal-http-client-captive-portal/minimal-http-client-captive-portal.c @@ -290,9 +290,8 @@ static lws_state_notify_link_t * const app_notifier_list[] = { int main(int argc, const char **argv) { - int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + struct lws_context_creation_info info; - const char *p; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -303,13 +302,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http client captive portal detect\n"); - memset(&info, 0, sizeof info); + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = CONTEXT_PORT_NO_LISTEN; info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; info.system_ops = &ops; diff --git a/minimal-examples-lowlevel/http-client/minimal-http-client-certinfo/minimal-http-client-certinfo.c b/minimal-examples-lowlevel/http-client/minimal-http-client-certinfo/minimal-http-client-certinfo.c index 0279093441..c714ddf44d 100644 --- a/minimal-examples-lowlevel/http-client/minimal-http-client-certinfo/minimal-http-client-certinfo.c +++ b/minimal-examples-lowlevel/http-client/minimal-http-client-certinfo/minimal-http-client-certinfo.c @@ -23,7 +23,6 @@ enum { }; static const struct lws_switches switches[] = { - [LWS_SW_H1] = { "--h1", "Enable --h1 feature" }, [LWS_SW_D] = { "-d", "Debug logs (e.g. -d 15)" }, [LWS_SW_L] = { "-l", "Enable -l feature" }, [LWS_SW_S] = { "-s", "Use TLS / https" }, @@ -189,15 +188,7 @@ int main(int argc, const char **argv) struct lws_client_connect_info i; struct lws_context *context; const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* - * For LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE - * - * | LLL_INFO | LLL_PARSER | LLL_HEADER | LLL_EXT | - * LLL_CLIENT | LLL_LATENCY | LLL_DEBUG - */ ; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -208,13 +199,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); - lwsl_user("LWS minimal http client [<-d ] [-l] [--h1]\n"); + lwsl_user("LWS minimal http client [-d ] [-l]\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ info.protocols = protocols; @@ -261,10 +250,6 @@ int main(int argc, const char **argv) i.host = i.address; i.origin = i.address; - /* force h1 even if h2 available */ - if (lws_cmdline_option(argc, argv, switches[LWS_SW_H1].sw)) - i.alpn = "http/1.1"; - i.method = "GET"; i.protocol = protocols[0].name; diff --git a/minimal-examples-lowlevel/http-client/minimal-http-client-custom-headers/minimal-http-client-custom-headers.c b/minimal-examples-lowlevel/http-client/minimal-http-client-custom-headers/minimal-http-client-custom-headers.c index c8b74682bf..027767b524 100644 --- a/minimal-examples-lowlevel/http-client/minimal-http-client-custom-headers/minimal-http-client-custom-headers.c +++ b/minimal-examples-lowlevel/http-client/minimal-http-client-custom-headers/minimal-http-client-custom-headers.c @@ -159,16 +159,7 @@ int main(int argc, const char **argv) struct lws_context_creation_info info; struct lws_client_connect_info i; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* - * For LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE - * - * | LLL_INFO | LLL_PARSER | LLL_HEADER | LLL_EXT | - * LLL_CLIENT | LLL_LATENCY | LLL_DEBUG - */ ; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -179,13 +170,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http client Custom Headers [-d] [-l] [--h1]\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ info.protocols = protocols; diff --git a/minimal-examples-lowlevel/http-client/minimal-http-client-h2-rxflow/minimal-http-client.c b/minimal-examples-lowlevel/http-client/minimal-http-client-h2-rxflow/minimal-http-client.c index dbd04b8450..8245f4be1f 100644 --- a/minimal-examples-lowlevel/http-client/minimal-http-client-h2-rxflow/minimal-http-client.c +++ b/minimal-examples-lowlevel/http-client/minimal-http-client-h2-rxflow/minimal-http-client.c @@ -182,8 +182,6 @@ system_notify_cb(lws_state_manager_t *mgr, lws_state_notify_link_t *link, LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM; i.alpn = "h2"; - if (lws_cmdline_option(a->argc, a->argv, "--h1")) - i.alpn = "http/1.1"; if ((p = lws_cmdline_option(a->argc, a->argv, "-p"))) i.port = atoi(p); @@ -249,10 +247,9 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); - lwsl_user("LWS minimal http client [-d] [-l] [--h1]\n"); + lwsl_user("LWS minimal http client [-d] [-l]\n"); info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ diff --git a/minimal-examples-lowlevel/http-client/minimal-http-client-hugeurl/minimal-http-client-hugeurl.c b/minimal-examples-lowlevel/http-client/minimal-http-client-hugeurl/minimal-http-client-hugeurl.c index ee20bc43cc..f75efee0aa 100644 --- a/minimal-examples-lowlevel/http-client/minimal-http-client-hugeurl/minimal-http-client-hugeurl.c +++ b/minimal-examples-lowlevel/http-client/minimal-http-client-hugeurl/minimal-http-client-hugeurl.c @@ -15,13 +15,11 @@ #include enum { - LWS_SW_H1, LWS_SW_L, LWS_SW_HELP, }; static const struct lws_switches switches[] = { - [LWS_SW_H1] = { "--h1", "Enable --h1 feature" }, [LWS_SW_L] = { "-l", "Enable -l feature" }, [LWS_SW_HELP] = { "--help", "Show this help information" }, }; @@ -172,10 +170,9 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); - lwsl_user("LWS minimal http client hugeurl [-d ] [-l] [--h1]\n"); + lwsl_user("LWS minimal http client hugeurl [-d ] [-l]\n"); info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ @@ -219,9 +216,6 @@ int main(int argc, const char **argv) i.address = "warmcat.com"; } - if (lws_cmdline_option(argc, argv, switches[LWS_SW_H1].sw)) - i.alpn = "http/1.1"; - i.path = uri; i.host = i.address; i.origin = i.address; diff --git a/minimal-examples-lowlevel/http-client/minimal-http-client-jit-trust/minimal-http-client.c b/minimal-examples-lowlevel/http-client/minimal-http-client-jit-trust/minimal-http-client.c index a91c733310..3fd6e59a20 100644 --- a/minimal-examples-lowlevel/http-client/minimal-http-client-jit-trust/minimal-http-client.c +++ b/minimal-examples-lowlevel/http-client/minimal-http-client-jit-trust/minimal-http-client.c @@ -130,8 +130,6 @@ try_connect(struct lws_context *cx) LCCSCF_ACCEPT_TLS_DOWNGRADE_REDIRECTS; i.alpn = "h2,http/1.1"; - if (lws_cmdline_option(a->argc, a->argv, "--h1")) - i.alpn = "http/1.1"; if (lws_cmdline_option(a->argc, a->argv, "--h2-prior-knowledge")) i.ssl_connection |= LCCSCF_H2_PRIOR_KNOWLEDGE; @@ -432,10 +430,9 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); - lwsl_user("LWS minimal http client JIT Trust [-d] [-l] [--h1]\n"); + lwsl_user("LWS minimal http client JIT Trust [-d] [-l]\n"); info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | /* we start off not trusting anything */ diff --git a/minimal-examples-lowlevel/http-client/minimal-http-client-multi/CMakeLists.txt b/minimal-examples-lowlevel/http-client/minimal-http-client-multi/CMakeLists.txt index 2bd8eae9e6..f27f8d1d12 100644 --- a/minimal-examples-lowlevel/http-client/minimal-http-client-multi/CMakeLists.txt +++ b/minimal-examples-lowlevel/http-client/minimal-http-client-multi/CMakeLists.txt @@ -26,7 +26,12 @@ require_lws_config(LWS_WITH_MBEDTLS 1 MBEDTLS) if (requirements) add_executable(${SAMP} ${SRCS}) - find_program(VALGRIND "valgrind") + option(USE_VALGRIND_IF_AVAILABLE "Use valgrind for tests if available" OFF) + if (USE_VALGRIND_IF_AVAILABLE) + find_program(VALGRIND "valgrind") + else() + set(VALGRIND "") + endif() # # instantiate the server per sai builder instance, they are running in the same diff --git a/minimal-examples-lowlevel/http-client/minimal-http-client-multi/minimal-http-client-multi.c b/minimal-examples-lowlevel/http-client/minimal-http-client-multi/minimal-http-client-multi.c index 06bfb2d2eb..faf4eaa0ed 100644 --- a/minimal-examples-lowlevel/http-client/minimal-http-client-multi/minimal-http-client-multi.c +++ b/minimal-examples-lowlevel/http-client/minimal-http-client-multi/minimal-http-client-multi.c @@ -41,7 +41,6 @@ enum { LWS_SW_EV, LWS_SW_EVENT, LWS_SW_GLIB, - LWS_SW_H1, LWS_SW_LIMIT, LWS_SW_NO_TLS, LWS_SW_NO_TLS_SESSION_REUSE, @@ -64,7 +63,6 @@ static const struct lws_switches switches[] = { [LWS_SW_EV] = { "--ev", "Enable --ev feature" }, [LWS_SW_EVENT] = { "--event", "Enable --event feature" }, [LWS_SW_GLIB] = { "--glib", "Enable --glib feature" }, - [LWS_SW_H1] = { "--h1", "Enable --h1 feature" }, [LWS_SW_LIMIT] = { "--limit", "Enable --limit feature" }, [LWS_SW_NO_TLS] = { "--no-tls", "Enable --no-tls feature" }, [LWS_SW_NO_TLS_SESSION_REUSE] = { "--no-tls-session-reuse", "Enable --no-tls-session-reuse feature" }, @@ -106,6 +104,7 @@ static int completed, failed, numbered, stagger_idx, posting, count = COUNT, static lws_sorted_usec_list_t sul_stagger; static struct lws_client_connect_info i; static struct lws *client_wsi[COUNT]; +static char conn_state[COUNT]; static char urlpath[64], intr; static struct lws_context *context; @@ -244,6 +243,9 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, lwsl_err("CLIENT_CONNECTION_ERROR: %s\n", in ? (char *)in : "(null)"); client_wsi[idx] = NULL; + if (conn_state[idx] == 2) + break; + conn_state[idx] = 2; failed++; #if defined(LWS_WITH_CONMON) @@ -287,22 +289,26 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, lwsl_user("LWS_CALLBACK_COMPLETED_CLIENT_HTTP %s: idx %d\n", lws_wsi_tag(wsi), idx); client_wsi[idx] = NULL; + if (conn_state[idx] == 2) + break; + conn_state[idx] = 2; goto finished; case LWS_CALLBACK_CLOSED_CLIENT_HTTP: - lwsl_info("%s: closed: %s\n", __func__, lws_wsi_tag(client_wsi[idx])); + lwsl_info("%s: closed: %s\n", __func__, lws_wsi_tag(wsi)); #if defined(LWS_WITH_CONMON) dump_conmon_data(wsi); #endif - if (client_wsi[idx]) { + if (conn_state[idx] != 2) { /* * If it completed normally, it will have been set to - * NULL then already. So we are dealing with an + * 2 then already. So we are dealing with an * abnormal, failing, close */ client_wsi[idx] = NULL; + conn_state[idx] = 2; failed++; goto finished; } @@ -440,15 +446,21 @@ lws_try_client_connection(struct lws_client_connect_info *ii, int m) ii->opaque_user_data = (void *)(intptr_t)m; if (!lws_client_connect_via_info(ii)) { - failed++; - lwsl_user("%s: failed: conn idx %d\n", __func__, m); - if (++completed == count) { - lwsl_user("Done: failed: %d\n", failed); - lws_context_destroy(context); + if (conn_state[m] != 2) { + conn_state[m] = 2; + failed++; + lwsl_user("%s: failed: conn idx %d\n", __func__, m); + if (++completed == count) { + lwsl_user("Done: failed: %d\n", failed); + lws_context_destroy(context); + } } - } else + } else { + if (conn_state[m] != 2) + conn_state[m] = 1; lwsl_user("started connection %s: idx %d (%s)\n", lws_wsi_tag(client_wsi[m]), m, ii->path); + } } @@ -572,8 +584,7 @@ int main(int argc, const char **argv) int pl = 0; #endif - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ - memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL);memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */ lws_cmdline_option_handle_builtin(argc, argv, &info); @@ -597,7 +608,7 @@ int main(int argc, const char **argv) staggered = !!lws_cmdline_option(argc, argv, switches[LWS_SW_S].sw); lwsl_user("LWS minimal http client [-s (staggered)] [-p (pipeline)]\n"); - lwsl_user(" [--h1 (http/1 only)] [-l (localhost)] [-d ]\n"); + lwsl_user(" [-l (localhost)] [-d ]\n"); lwsl_user(" [-n (numbered)] [--post]\n"); info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ @@ -678,10 +689,6 @@ int main(int argc, const char **argv) i.ssl_connection |= LCCSCF_CONMON; #endif - /* force h1 even if h2 available */ - if (lws_cmdline_option(argc, argv, switches[LWS_SW_H1].sw)) - i.alpn = "http/1.1"; - strcpy(urlpath, "/"); if (lws_cmdline_option(argc, argv, switches[LWS_SW_L].sw)) { @@ -690,6 +697,10 @@ int main(int argc, const char **argv) i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED; if (posting) strcpy(urlpath, "/formtest"); + } else if (lws_cmdline_option(argc, argv, "--h3")) { + i.port = 443; + i.address = "cloudflare-quic.com"; + strcpy(urlpath, "/"); } else { i.port = 443; i.address = "libwebsockets.org"; diff --git a/minimal-examples-lowlevel/http-client/minimal-http-client-post-form/minimal-http-client-post-form.c b/minimal-examples-lowlevel/http-client/minimal-http-client-post-form/minimal-http-client-post-form.c index 99249e4ebf..8c600d1359 100644 --- a/minimal-examples-lowlevel/http-client/minimal-http-client-post-form/minimal-http-client-post-form.c +++ b/minimal-examples-lowlevel/http-client/minimal-http-client-post-form/minimal-http-client-post-form.c @@ -311,9 +311,6 @@ sul_connect_cb(lws_sorted_usec_list_t *sul) i.origin = i.address; i.method = "POST"; - if (lws_cmdline_option_cx(cx, "--h1")) - i.alpn = "http/1.1"; - i.protocol = protocols[0].name; i.pwsi = &client_wsi; @@ -375,9 +372,8 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ - lws_cmdline_option_handle_builtin(argc, argv, &info); - lwsl_user("LWS minimal http client form - POST [-d] [-l] [--h1] https://libwebsockets.org/testserver/formtest\n"); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); + lwsl_user("LWS minimal http client form - POST [-d] [-l] https://libwebsockets.org/testserver/formtest\n"); info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | LWS_SERVER_OPTION_H2_JUST_FIX_WINDOW_UPDATE_OVERFLOW; diff --git a/minimal-examples-lowlevel/http-client/minimal-http-client-post/minimal-http-client-post.c b/minimal-examples-lowlevel/http-client/minimal-http-client-post/minimal-http-client-post.c index 4158d0cc26..82e6485d0d 100644 --- a/minimal-examples-lowlevel/http-client/minimal-http-client-post/minimal-http-client-post.c +++ b/minimal-examples-lowlevel/http-client/minimal-http-client-post/minimal-http-client-post.c @@ -18,12 +18,14 @@ enum { LWS_SW_L, LWS_SW_M, + LWS_SW_H3, LWS_SW_HELP, }; static const struct lws_switches switches[] = { [LWS_SW_L] = { "-l", "Enable -l feature" }, [LWS_SW_M] = { "-m", "Enable -m feature" }, + [LWS_SW_H3] = { "--h3", "Use HTTP/3" }, [LWS_SW_HELP] = { "--help", "Show this help information" }, }; @@ -220,6 +222,15 @@ app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, if (p) url = p; +#if defined(LWS_ROLE_H3) + if (lws_cmdline_option_cx(cx, "--h3")) { + i.alpn = "h3"; + /* cloudflare quic test server */ + if (!p) + url = "https://cloudflare-quic.com/"; + } +#endif + { lws_parse_uri_t *puri; @@ -244,10 +255,6 @@ app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, i.origin = i.address; i.method = "POST"; - /* force h1 even if h2 available */ - if (lws_cmdline_option_cx(cx, "--h1")) - i.alpn = "http/1.1"; - i.protocol = protocols[0].name; for (n = 0; n < count_clients; n++) { @@ -291,14 +298,13 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ - lws_cmdline_option_handle_builtin(argc, argv, &info); - lwsl_user("LWS minimal http client - POST [-d] [-l] [--h1] https://libwebsockets.org/testserver/formtest\n"); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); + lwsl_user("LWS minimal http client - POST [-d] [-l] https://libwebsockets.org/testserver/formtest\n"); if (lws_cmdline_option(argc, argv, switches[LWS_SW_M].sw)) count_clients = LWS_ARRAY_SIZE(client_wsi); - info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; info.port = CONTEXT_PORT_NO_LISTEN; info.protocols = protocols; diff --git a/minimal-examples-lowlevel/http-client/minimal-http-client/CMakeLists.txt b/minimal-examples-lowlevel/http-client/minimal-http-client/CMakeLists.txt index bbb7fa16b6..e38d5ea92d 100644 --- a/minimal-examples-lowlevel/http-client/minimal-http-client/CMakeLists.txt +++ b/minimal-examples-lowlevel/http-client/minimal-http-client/CMakeLists.txt @@ -10,6 +10,7 @@ set(SRCS minimal-http-client.c) set(has_fault_injection 1) set(has_h2 1) +set(has_h3 1) set(has_plugins 1) set(has_ss_policy_parse 1) set(has_no_system_vhost 1) @@ -24,6 +25,10 @@ require_lws_config(LWS_WITH_SYS_STATE 1 requirements) require_lws_config(LWS_WITH_TLS 1 requirements) require_lws_config(LWS_ROLE_H2 1 has_h2) +require_lws_config(LWS_ROLE_H3 1 has_h3) +if (LWS_WITH_MBEDTLS) + set(has_h3 0) +endif() require_lws_config(LWS_WITH_SYS_FAULT_INJECTION 1 has_fault_injection) require_lws_config(LWS_WITH_EVLIB_PLUGINS 1 has_plugins) require_lws_config(LWS_WITH_EVENT_LIBS 1 has_plugins) @@ -56,6 +61,12 @@ if (requirements) add_test(NAME http-client-warmcat-h1 COMMAND lws-minimal-http-client --h1) + + if (has_h3) + add_test(NAME http-client-h3 COMMAND lws-minimal-http-client --h3 https://cloudflare-quic.com/ -d 1039) + set_tests_properties(http-client-h3 PROPERTIES TIMEOUT 10) + list(APPEND mytests http-client-h3) + endif() if (has_fault_injection) @@ -131,7 +142,7 @@ if (requirements) if (has_async_dns) list(APPEND mytests http-client-fi-connfail) - add_test(NAME http-client-fi-connfail COMMAND lws-minimal-http-client --expected-exit 2 --fault-injection "wsi=user/connfail") + add_test(NAME http-client-fi-connfail COMMAND lws-minimal-http-client --expected-exit 3 --fault-injection "wsi=user/connfail") else() list(APPEND mytests http-client-fi-connfail) add_test(NAME http-client-fi-connfail COMMAND lws-minimal-http-client --expected-exit 2 --fault-injection "wsi=user/connfail") diff --git a/minimal-examples-lowlevel/http-client/minimal-http-client/minimal-http-client.c b/minimal-examples-lowlevel/http-client/minimal-http-client/minimal-http-client.c index 31c1362039..539a390dd3 100644 --- a/minimal-examples-lowlevel/http-client/minimal-http-client/minimal-http-client.c +++ b/minimal-examples-lowlevel/http-client/minimal-http-client/minimal-http-client.c @@ -110,6 +110,7 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, lws_get_peer_simple(wsi, buf, sizeof(buf)); status = (int)lws_http_client_http_response(wsi); + lwsl_user("Connected with server response: %d\n", status); #if defined(LWS_WITH_ALLOC_METADATA_LWS) _lws_alloc_metadata_dump_lws(lws_alloc_metadata_dump_stdout, NULL); @@ -200,6 +201,7 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, break; case LWS_CALLBACK_CLOSED_CLIENT_HTTP: + lwsl_user("LWS_CALLBACK_CLOSED_CLIENT_HTTP\n"); interrupted = 1; bad = status != 200; lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */ @@ -280,12 +282,9 @@ system_notify_cb(lws_state_manager_t *mgr, lws_state_notify_link_t *link, LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM; i.alpn = "h2,http/1.1"; - if (lws_cmdline_option(a->argc, a->argv, "--h1")) - i.alpn = "http/1.1"; if (lws_cmdline_option(a->argc, a->argv, "--h3")) { - i.alpn = "h3"; i.method = "QUIC"; - i.address = "google.com"; + i.address = "cloudflare-quic.com"; i.port = 443; } @@ -365,6 +364,25 @@ system_notify_cb(lws_state_manager_t *mgr, lws_state_notify_link_t *link, return 0; } +#if defined(WIN32) && defined(LWS_WITH_SCHANNEL) +#include +static int is_quic_supported_on_os(void) { + OSVERSIONINFOEXW osvi = { sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0 }; + DWORDLONG const dwlConditionMask = VerSetConditionMask( + VerSetConditionMask( + VerSetConditionMask( + 0, VER_MAJORVERSION, VER_GREATER_EQUAL), + VER_MINORVERSION, VER_GREATER_EQUAL), + VER_BUILDNUMBER, VER_GREATER_EQUAL); + + osvi.dwMajorVersion = 10; + osvi.dwMinorVersion = 0; + osvi.dwBuildNumber = 20348; /* Windows Server 2022 */ + + return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER, dwlConditionMask) != FALSE; +} +#endif + int main(int argc, const char **argv) { lws_state_notify_link_t notifier = { { NULL, NULL, NULL }, @@ -382,12 +400,21 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); + +#if defined(WIN32) && defined(LWS_WITH_SCHANNEL) + if (lws_cmdline_option(argc, argv, "--h3")) { + if (!is_quic_supported_on_os()) { + lwsl_user("SChannel QUIC requires Windows 11+ / Server 2022+\n"); + return 0; + } + } +#endif - lwsl_user("LWS minimal http client [-d] [-l] [--h1]\n"); + lwsl_user("LWS minimal http client [-d] [-l]\n"); - info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | + info.options &= ~((uint64_t)LWS_SERVER_OPTION_EXPLICIT_VHOSTS); + info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | LWS_SERVER_OPTION_H2_JUST_FIX_WINDOW_UPDATE_OVERFLOW; info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ info.protocols = protocols; @@ -434,6 +461,7 @@ int main(int argc, const char **argv) memcert[info.client_ssl_ca_mem_len++] = '\0'; } #endif + lwsl_user("CA FILEPATH: %s\n", info.client_ssl_ca_filepath); context = lws_create_context(&info); if (!context) { lwsl_err("lws init failed\n"); diff --git a/minimal-examples-lowlevel/http-server/minimal-http-server-basicauth/minimal-http-server-basicauth.c b/minimal-examples-lowlevel/http-server/minimal-http-server-basicauth/minimal-http-server-basicauth.c index bf76664472..79385efbc2 100644 --- a/minimal-examples-lowlevel/http-server/minimal-http-server-basicauth/minimal-http-server-basicauth.c +++ b/minimal-examples-lowlevel/http-server/minimal-http-server-basicauth/minimal-http-server-basicauth.c @@ -64,14 +64,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -82,13 +75,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http server basic auth | visit http://localhost:7681\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.mounts = &mount; info.options = diff --git a/minimal-examples-lowlevel/http-server/minimal-http-server-cgi/minimal-http-server.c b/minimal-examples-lowlevel/http-server/minimal-http-server-cgi/minimal-http-server.c index f78ae644e3..d9bdf46426 100644 --- a/minimal-examples-lowlevel/http-server/minimal-http-server-cgi/minimal-http-server.c +++ b/minimal-examples-lowlevel/http-server/minimal-http-server-cgi/minimal-http-server.c @@ -50,14 +50,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -68,10 +61,7 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http server | visit http://localhost:7681\n"); { @@ -83,7 +73,8 @@ int main(int argc, const char **argv) "%s/my-cgi-script.sh", cwd); } - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.mounts = &mount; info.error_document_404 = "/404.html"; diff --git a/minimal-examples-lowlevel/http-server/minimal-http-server-custom-headers/minimal-http-server-custom-headers.c b/minimal-examples-lowlevel/http-server/minimal-http-server-custom-headers/minimal-http-server-custom-headers.c index 197c3a7fe6..2427da0f91 100644 --- a/minimal-examples-lowlevel/http-server/minimal-http-server-custom-headers/minimal-http-server-custom-headers.c +++ b/minimal-examples-lowlevel/http-server/minimal-http-server-custom-headers/minimal-http-server-custom-headers.c @@ -149,8 +149,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -161,13 +160,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http server custom headers | visit http://localhost:7681\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | LWS_SERVER_OPTION_EXPLICIT_VHOSTS | LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE; diff --git a/minimal-examples-lowlevel/http-server/minimal-http-server-deaddrop/minimal-http-server-deaddrop.c b/minimal-examples-lowlevel/http-server/minimal-http-server-deaddrop/minimal-http-server-deaddrop.c index 36ad06ff57..d590de16d3 100644 --- a/minimal-examples-lowlevel/http-server/minimal-http-server-deaddrop/minimal-http-server-deaddrop.c +++ b/minimal-examples-lowlevel/http-server/minimal-http-server-deaddrop/minimal-http-server-deaddrop.c @@ -114,8 +114,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -126,13 +125,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http server deaddrop | visit https://localhost:7681\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.mounts = &mount; info.pvo = &pvo; diff --git a/minimal-examples-lowlevel/http-server/minimal-http-server-dynamic/minimal-http-server-dynamic.c b/minimal-examples-lowlevel/http-server/minimal-http-server-dynamic/minimal-http-server-dynamic.c index 36de8711dd..1de5a4a7f7 100644 --- a/minimal-examples-lowlevel/http-server/minimal-http-server-dynamic/minimal-http-server-dynamic.c +++ b/minimal-examples-lowlevel/http-server/minimal-http-server-dynamic/minimal-http-server-dynamic.c @@ -263,14 +263,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -281,13 +274,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http server dynamic | visit http://localhost:7681\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | LWS_SERVER_OPTION_EXPLICIT_VHOSTS | LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE; diff --git a/minimal-examples-lowlevel/http-server/minimal-http-server-eventlib-custom/minimal-http-server.c b/minimal-examples-lowlevel/http-server/minimal-http-server-eventlib-custom/minimal-http-server.c index 2210a3e0c6..3d4d925b76 100644 --- a/minimal-examples-lowlevel/http-server/minimal-http-server-eventlib-custom/minimal-http-server.c +++ b/minimal-examples-lowlevel/http-server/minimal-http-server-eventlib-custom/minimal-http-server.c @@ -400,8 +400,7 @@ void sigint_handler(int sig) int main(int argc, const char **argv) { struct lws_context_creation_info info; - const char *p; - int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + void *foreign_loops[1]; (void)switches; @@ -413,18 +412,16 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); /* * init the existing custom event loop here if anything to do, don't * run it yet. In our example, no init required. */ - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http server | visit http://localhost:7681\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.mounts = &mount; info.error_document_404 = "/404.html"; diff --git a/minimal-examples-lowlevel/http-server/minimal-http-server-eventlib-demos/minimal-http-server-eventlib-demos.c b/minimal-examples-lowlevel/http-server/minimal-http-server-eventlib-demos/minimal-http-server-eventlib-demos.c index 03346f62f8..8f9b9d32e8 100644 --- a/minimal-examples-lowlevel/http-server/minimal-http-server-eventlib-demos/minimal-http-server-eventlib-demos.c +++ b/minimal-examples-lowlevel/http-server/minimal-http-server-eventlib-demos/minimal-http-server-eventlib-demos.c @@ -110,14 +110,7 @@ void sigint_handler(int sig) int main(int argc, const char **argv) { struct lws_context_creation_info info; - const char *p; - int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -126,14 +119,12 @@ int main(int argc, const char **argv) } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http server eventlib | visit http://localhost:7681\n"); lwsl_user(" [-s (ssl)] [--uv (libuv)] [--ev (libev)] [--event (libevent)]\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.mounts = &mount; info.error_document_404 = "/404.html"; diff --git a/minimal-examples-lowlevel/http-server/minimal-http-server-eventlib-foreign/minimal-http-server-eventlib-foreign.c b/minimal-examples-lowlevel/http-server/minimal-http-server-eventlib-foreign/minimal-http-server-eventlib-foreign.c index 51c7b0708c..a7d9c64abc 100644 --- a/minimal-examples-lowlevel/http-server/minimal-http-server-eventlib-foreign/minimal-http-server-eventlib-foreign.c +++ b/minimal-examples-lowlevel/http-server/minimal-http-server-eventlib-foreign/minimal-http-server-eventlib-foreign.c @@ -237,13 +237,7 @@ foreign_timer_service(void *foreign_loop) int main(int argc, const char **argv) { const char *p; - int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -252,10 +246,7 @@ int main(int argc, const char **argv) } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http server eventlib + foreign loop |" " visit http://localhost:7681\n"); @@ -265,7 +256,8 @@ int main(int argc, const char **argv) * and lws. */ - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_P].sw))) info.port = atoi(p); diff --git a/minimal-examples-lowlevel/http-server/minimal-http-server-eventlib-smp/minimal-http-server-eventlib-smp.c b/minimal-examples-lowlevel/http-server/minimal-http-server-eventlib-smp/minimal-http-server-eventlib-smp.c index ff41c81b72..2a3d246c25 100644 --- a/minimal-examples-lowlevel/http-server/minimal-http-server-eventlib-smp/minimal-http-server-eventlib-smp.c +++ b/minimal-examples-lowlevel/http-server/minimal-http-server-eventlib-smp/minimal-http-server-eventlib-smp.c @@ -101,13 +101,7 @@ int main(int argc, const char **argv) struct lws_context_creation_info info; const char *p; void *retval; - int n, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -116,15 +110,13 @@ int main(int argc, const char **argv) } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http server eventlib SMP | visit http://localhost:7681\n"); lwsl_user(" [-s (ssl)] [--uv (libuv)] [--ev (libev)] [--event (libevent)]\n"); lwsl_user("WARNING: Not stable, under development!\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.mounts = &mount; info.error_document_404 = "/404.html"; diff --git a/minimal-examples-lowlevel/http-server/minimal-http-server-eventlib/minimal-http-server-eventlib.c b/minimal-examples-lowlevel/http-server/minimal-http-server-eventlib/minimal-http-server-eventlib.c index 1ee74aa7cd..b745ca3773 100644 --- a/minimal-examples-lowlevel/http-server/minimal-http-server-eventlib/minimal-http-server-eventlib.c +++ b/minimal-examples-lowlevel/http-server/minimal-http-server-eventlib/minimal-http-server-eventlib.c @@ -70,14 +70,7 @@ void sigint_handler(int sig) int main(int argc, const char **argv) { struct lws_context_creation_info info; - const char *p; - int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -86,14 +79,12 @@ int main(int argc, const char **argv) } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http server eventlib | visit http://localhost:7681\n"); lwsl_user(" [-s (ssl)] [--uv (libuv)] [--ev (libev)] [--event (libevent)]\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.mounts = &mount; info.error_document_404 = "/404.html"; diff --git a/minimal-examples-lowlevel/http-server/minimal-http-server-form-get/minimal-http-server-form-get.c b/minimal-examples-lowlevel/http-server/minimal-http-server-form-get/minimal-http-server-form-get.c index 26b267586d..9473b4ae53 100644 --- a/minimal-examples-lowlevel/http-server/minimal-http-server-form-get/minimal-http-server-form-get.c +++ b/minimal-examples-lowlevel/http-server/minimal-http-server-form-get/minimal-http-server-form-get.c @@ -107,14 +107,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -125,13 +118,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http server GET | visit http://localhost:7681\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.protocols = protocols; info.mounts = &mount; diff --git a/minimal-examples-lowlevel/http-server/minimal-http-server-form-post-file/minimal-http-server-form-post-file.c b/minimal-examples-lowlevel/http-server/minimal-http-server-form-post-file/minimal-http-server-form-post-file.c index a05e8dd37c..b6a05f377a 100644 --- a/minimal-examples-lowlevel/http-server/minimal-http-server-form-post-file/minimal-http-server-form-post-file.c +++ b/minimal-examples-lowlevel/http-server/minimal-http-server-form-post-file/minimal-http-server-form-post-file.c @@ -222,14 +222,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -240,13 +233,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http server POST file | visit http://localhost:7681\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.protocols = protocols; info.mounts = &mount; diff --git a/minimal-examples-lowlevel/http-server/minimal-http-server-form-post-lwsac/minimal-http-server-form-post.c b/minimal-examples-lowlevel/http-server/minimal-http-server-form-post-lwsac/minimal-http-server-form-post.c index 7ad1d42e85..48410c02d3 100644 --- a/minimal-examples-lowlevel/http-server/minimal-http-server-form-post-lwsac/minimal-http-server-form-post.c +++ b/minimal-examples-lowlevel/http-server/minimal-http-server-form-post-lwsac/minimal-http-server-form-post.c @@ -176,14 +176,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -194,13 +187,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http server POST | visit http://localhost:7681\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.protocols = protocols; info.mounts = &mount; diff --git a/minimal-examples-lowlevel/http-server/minimal-http-server-form-post/minimal-http-server-form-post.c b/minimal-examples-lowlevel/http-server/minimal-http-server-form-post/minimal-http-server-form-post.c index 184c32418b..b0b10c3925 100644 --- a/minimal-examples-lowlevel/http-server/minimal-http-server-form-post/minimal-http-server-form-post.c +++ b/minimal-examples-lowlevel/http-server/minimal-http-server-form-post/minimal-http-server-form-post.c @@ -175,13 +175,7 @@ int main(int argc, const char **argv) struct lws_context_creation_info info; struct lws_context *context; const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -192,13 +186,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http server POST | visit http://localhost:7681\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.protocols = protocols; info.mounts = &mount; diff --git a/minimal-examples-lowlevel/http-server/minimal-http-server-fulltext-search/minimal-http-server.c b/minimal-examples-lowlevel/http-server/minimal-http-server-fulltext-search/minimal-http-server.c index cc0b148a5a..18da397f04 100644 --- a/minimal-examples-lowlevel/http-server/minimal-http-server-fulltext-search/minimal-http-server.c +++ b/minimal-examples-lowlevel/http-server/minimal-http-server-fulltext-search/minimal-http-server.c @@ -74,10 +74,9 @@ void sigint_handler(int sig) int main(int argc, const char **argv) { - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int n = 0; struct lws_context_creation_info info; struct lws_context *context; - const char *p; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -88,14 +87,12 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http server fulltext search | " "visit http://localhost:7681\n"); - memset(&info, 0, sizeof info); + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.mounts = &mount; info.protocols = protocols; diff --git a/minimal-examples-lowlevel/http-server/minimal-http-server-h2-long-poll/minimal-http-server.c b/minimal-examples-lowlevel/http-server/minimal-http-server-h2-long-poll/minimal-http-server.c index 082705f713..99a764686b 100644 --- a/minimal-examples-lowlevel/http-server/minimal-http-server-h2-long-poll/minimal-http-server.c +++ b/minimal-examples-lowlevel/http-server/minimal-http-server-h2-long-poll/minimal-http-server.c @@ -128,8 +128,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -140,13 +139,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http server h2 long poll\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; #if defined(LWS_WITH_TLS) info.ssl_cert_filepath = "localhost-100y.cert"; diff --git a/minimal-examples-lowlevel/http-server/minimal-http-server-mimetypes/minimal-http-server-mimetypes.c b/minimal-examples-lowlevel/http-server/minimal-http-server-mimetypes/minimal-http-server-mimetypes.c index 8584602c6f..a6c87ce1c6 100644 --- a/minimal-examples-lowlevel/http-server/minimal-http-server-mimetypes/minimal-http-server-mimetypes.c +++ b/minimal-examples-lowlevel/http-server/minimal-http-server-mimetypes/minimal-http-server-mimetypes.c @@ -55,14 +55,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -73,13 +66,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http server | visit http://localhost:7681\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.mounts = &mount; info.error_document_404 = "/404.html"; diff --git a/minimal-examples-lowlevel/http-server/minimal-http-server-multivhost/minimal-http-server.c b/minimal-examples-lowlevel/http-server/minimal-http-server-multivhost/minimal-http-server.c index 2b4b07c169..6af5000f60 100644 --- a/minimal-examples-lowlevel/http-server/minimal-http-server-multivhost/minimal-http-server.c +++ b/minimal-examples-lowlevel/http-server/minimal-http-server-multivhost/minimal-http-server.c @@ -69,14 +69,7 @@ int main(int argc, const char **argv) struct lws_context_creation_info info; struct lws_context *context; struct lws_vhost *new_vhost; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -85,15 +78,13 @@ int main(int argc, const char **argv) } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http server-multivhost | visit http://localhost:7681 / 7682\n"); signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE; diff --git a/minimal-examples-lowlevel/http-server/minimal-http-server-proxy/minimal-http-server-proxy.c b/minimal-examples-lowlevel/http-server/minimal-http-server-proxy/minimal-http-server-proxy.c index 4224b78e0a..541b670f93 100644 --- a/minimal-examples-lowlevel/http-server/minimal-http-server-proxy/minimal-http-server-proxy.c +++ b/minimal-examples-lowlevel/http-server/minimal-http-server-proxy/minimal-http-server-proxy.c @@ -42,14 +42,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -58,15 +51,13 @@ int main(int argc, const char **argv) } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http server proxy | visit https://localhost:7681\n"); signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.mounts = &mount; info.error_document_404 = "/404.html"; diff --git a/minimal-examples-lowlevel/http-server/minimal-http-server-smp/minimal-http-server-smp.c b/minimal-examples-lowlevel/http-server/minimal-http-server-smp/minimal-http-server-smp.c index 6d154997ef..6b320ee446 100644 --- a/minimal-examples-lowlevel/http-server/minimal-http-server-smp/minimal-http-server-smp.c +++ b/minimal-examples-lowlevel/http-server/minimal-http-server-smp/minimal-http-server-smp.c @@ -81,13 +81,7 @@ int main(int argc, const char **argv) struct lws_context_creation_info info; void *retval; const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -96,15 +90,13 @@ int main(int argc, const char **argv) } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http server SMP | visit http://127.0.0.1:7681\n"); signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.mounts = &mount; info.options = diff --git a/minimal-examples-lowlevel/http-server/minimal-http-server-sse-ring/minimal-http-server-sse-ring.c b/minimal-examples-lowlevel/http-server/minimal-http-server-sse-ring/minimal-http-server-sse-ring.c index 0db3e96070..4706d105ce 100644 --- a/minimal-examples-lowlevel/http-server/minimal-http-server-sse-ring/minimal-http-server-sse-ring.c +++ b/minimal-examples-lowlevel/http-server/minimal-http-server-sse-ring/minimal-http-server-sse-ring.c @@ -351,14 +351,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -369,13 +362,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http Server-Side Events + ring | visit http://localhost:7681\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.protocols = protocols; info.mounts = &mount; diff --git a/minimal-examples-lowlevel/http-server/minimal-http-server-sse/minimal-http-server-sse.c b/minimal-examples-lowlevel/http-server/minimal-http-server-sse/minimal-http-server-sse.c index abfc2f920f..802b545b83 100644 --- a/minimal-examples-lowlevel/http-server/minimal-http-server-sse/minimal-http-server-sse.c +++ b/minimal-examples-lowlevel/http-server/minimal-http-server-sse/minimal-http-server-sse.c @@ -173,14 +173,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -191,14 +184,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http Server-Side Events | visit http://localhost:7681\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ - + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.protocols = protocols; info.mounts = &mount; info.options = diff --git a/minimal-examples-lowlevel/http-server/minimal-http-server-tls-80/minimal-http-server-tls-80.c b/minimal-examples-lowlevel/http-server/minimal-http-server-tls-80/minimal-http-server-tls-80.c index 49f295424a..20c3e86dbd 100644 --- a/minimal-examples-lowlevel/http-server/minimal-http-server-tls-80/minimal-http-server-tls-80.c +++ b/minimal-examples-lowlevel/http-server/minimal-http-server-tls-80/minimal-http-server-tls-80.c @@ -61,14 +61,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -77,17 +70,14 @@ int main(int argc, const char **argv) } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http server TLS + 80 | visit https://localhost\n"); lwsl_user(" Run as ROOT so can listen on 443\n"); signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ - + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | LWS_SERVER_OPTION_EXPLICIT_VHOSTS | LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE; diff --git a/minimal-examples-lowlevel/http-server/minimal-http-server-tls-mem/minimal-http-server-tls-mem.c b/minimal-examples-lowlevel/http-server/minimal-http-server-tls-mem/minimal-http-server-tls-mem.c index 8388942d1e..a5a8983a99 100644 --- a/minimal-examples-lowlevel/http-server/minimal-http-server-tls-mem/minimal-http-server-tls-mem.c +++ b/minimal-examples-lowlevel/http-server/minimal-http-server-tls-mem/minimal-http-server-tls-mem.c @@ -394,14 +394,8 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */, ret = 1; + int n, ret = 0; + n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -410,16 +404,13 @@ int main(int argc, const char **argv) } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http server TLS | visit https://localhost:7681\n"); signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ - + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | LWS_SERVER_OPTION_EXPLICIT_VHOSTS | LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE; diff --git a/minimal-examples-lowlevel/http-server/minimal-http-server-tls/minimal-http-server-tls.c b/minimal-examples-lowlevel/http-server/minimal-http-server-tls/minimal-http-server-tls.c index 03dbca9fa0..98999134fd 100644 --- a/minimal-examples-lowlevel/http-server/minimal-http-server-tls/minimal-http-server-tls.c +++ b/minimal-examples-lowlevel/http-server/minimal-http-server-tls/minimal-http-server-tls.c @@ -110,8 +110,9 @@ int main(int argc, const char **argv) #else signal(SIGINT, sigint_handler); #endif - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); lws_cmdline_option_handle_builtin(argc, argv, &info); + info.fd_limit_per_thread = 0; lwsl_user("LWS minimal http server TLS | visit https://localhost:7681\n"); info.port = 7681; diff --git a/minimal-examples-lowlevel/http-server/minimal-http-server/minimal-http-server.c b/minimal-examples-lowlevel/http-server/minimal-http-server/minimal-http-server.c index e5b34d1942..00f9300ffd 100644 --- a/minimal-examples-lowlevel/http-server/minimal-http-server/minimal-http-server.c +++ b/minimal-examples-lowlevel/http-server/minimal-http-server/minimal-http-server.c @@ -49,14 +49,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -67,13 +60,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http server | visit http://localhost:7681\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.mounts = &mount; info.error_document_404 = "/404.html"; diff --git a/minimal-examples-lowlevel/mqtt-client/minimal-mqtt-client-multi/minimal-mqtt-client-multi.c b/minimal-examples-lowlevel/mqtt-client/minimal-mqtt-client-multi/minimal-mqtt-client-multi.c index 244a50516a..c64e2ce4fb 100644 --- a/minimal-examples-lowlevel/mqtt-client/minimal-mqtt-client-multi/minimal-mqtt-client-multi.c +++ b/minimal-examples-lowlevel/mqtt-client/minimal-mqtt-client-multi/minimal-mqtt-client-multi.c @@ -408,8 +408,7 @@ int main(int argc, const char **argv) int n = 0; signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); do_ssl = !!lws_cmdline_option(argc, argv, switches[LWS_SW_S].sw); if (do_ssl) diff --git a/minimal-examples-lowlevel/mqtt-client/minimal-mqtt-client-qos2/minimal-mqtt-client-qos2.c b/minimal-examples-lowlevel/mqtt-client/minimal-mqtt-client-qos2/minimal-mqtt-client-qos2.c index ab9c545785..a3652c2b62 100644 --- a/minimal-examples-lowlevel/mqtt-client/minimal-mqtt-client-qos2/minimal-mqtt-client-qos2.c +++ b/minimal-examples-lowlevel/mqtt-client/minimal-mqtt-client-qos2/minimal-mqtt-client-qos2.c @@ -376,8 +376,7 @@ int main(int argc, const char **argv) int n = 0; signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); do_ssl = !!lws_cmdline_option(argc, argv, switches[LWS_SW_S].sw); do_fault_injection = !!lws_cmdline_option(argc, argv, switches[LWS_SW_F].sw); diff --git a/minimal-examples-lowlevel/mqtt-client/minimal-mqtt-client/minimal-mqtt-client.c b/minimal-examples-lowlevel/mqtt-client/minimal-mqtt-client/minimal-mqtt-client.c index 75f5817996..c2b95f1682 100644 --- a/minimal-examples-lowlevel/mqtt-client/minimal-mqtt-client/minimal-mqtt-client.c +++ b/minimal-examples-lowlevel/mqtt-client/minimal-mqtt-client/minimal-mqtt-client.c @@ -326,8 +326,7 @@ int main(int argc, const char **argv) int n = 0; signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); do_ssl = !!lws_cmdline_option(argc, argv, switches[LWS_SW_S].sw); if (do_ssl) diff --git a/minimal-examples-lowlevel/quic/minimal-quic-client-server/CMakeLists.txt b/minimal-examples-lowlevel/quic/minimal-quic-client-server/CMakeLists.txt index ab7ca1c0c6..f6d6057a4c 100644 --- a/minimal-examples-lowlevel/quic/minimal-quic-client-server/CMakeLists.txt +++ b/minimal-examples-lowlevel/quic/minimal-quic-client-server/CMakeLists.txt @@ -29,6 +29,32 @@ if (requirements) math(EXPR PORT_QCS "7681 + $ENV{SAI_INSTANCE_IDX}") endif() + find_program(H3SPEC_EXECUTABLE h3spec) + if (H3SPEC_EXECUTABLE) + add_test(NAME st_qcs_srv COMMAND + ${CMAKE_SOURCE_DIR}/scripts/ctest-background.sh + qcs_srv + $ + -s -p ${PORT_QCS} -d 1039 ) + add_test(NAME ki_qcs_srv COMMAND + ${CMAKE_SOURCE_DIR}/scripts/ctest-background-kill.sh qcs_srv + $ ) + + set_tests_properties(st_qcs_srv PROPERTIES + WORKING_DIRECTORY . + FIXTURES_SETUP qcs_srv + TIMEOUT 800) + set_tests_properties(ki_qcs_srv PROPERTIES + FIXTURES_CLEANUP qcs_srv) + + add_test(NAME h3spec COMMAND + ${H3SPEC_EXECUTABLE} 127.0.0.1 ${PORT_QCS} -n ) + + set_tests_properties(h3spec PROPERTIES + FIXTURES_REQUIRED "qcs_srv" + TIMEOUT 120) + endif() + add_test(NAME ${SAMP} COMMAND ${SAMP} -p ${PORT_QCS}) set_tests_properties(${SAMP} PROPERTIES WORKING_DIRECTORY . diff --git a/minimal-examples-lowlevel/quic/minimal-quic-client-server/main.c b/minimal-examples-lowlevel/quic/minimal-quic-client-server/main.c index 9b7ab0fcbf..3b2b420b96 100644 --- a/minimal-examples-lowlevel/quic/minimal-quic-client-server/main.c +++ b/minimal-examples-lowlevel/quic/minimal-quic-client-server/main.c @@ -17,6 +17,15 @@ static int interrupted; static int result = 1; /* 1 means failed/timeout, 0 means success */ +static int server_only = 0; + +static const struct lws_http_mount mount_redir = { + .mountpoint = "/", + .origin = "warmcat.com/git/blog", + .origin_protocol = LWSMPRO_REDIR_HTTPS, + .mountpoint_len = 1, + .exact_match = 1, +}; static const char * const test_cert = "-----BEGIN CERTIFICATE-----\n" @@ -131,6 +140,8 @@ simple_hash(uint32_t hash, const uint8_t *data, size_t len) return hash; } + + static void teardown_cb(lws_sorted_usec_list_t *sul) { @@ -145,7 +156,8 @@ check_test_completion(struct lws *wsi) if (client_done && server_done) { lwsl_notice("Test completed successfully! Both sides received full data. Waiting 1.5s for ACKs...\n"); result = 0; - lws_sul_schedule(lws_get_context(wsi), 0, &sul_teardown, teardown_cb, 1500 * 1000); + if (!server_only) + lws_sul_schedule(lws_get_context(wsi), 0, &sul_teardown, teardown_cb, 1500 * 1000); } } @@ -160,8 +172,10 @@ callback_quic_test(struct lws *wsi, enum lws_callback_reasons reason, case LWS_CALLBACK_PROTOCOL_INIT: { +#if (_LWS_ENABLED_LOGS & LLL_NOTICE) struct lws_vhost *vh = lws_get_vhost(wsi); lwsl_vhost_notice(vh, "Protocol init"); +#endif break; } @@ -186,8 +200,8 @@ callback_quic_test(struct lws *wsi, enum lws_callback_reasons reason, size_t to_send = TOTAL_DATA - client_sent; if (to_send > 1024) to_send = 1024; - memset(&buf[LWS_PRE], (client_sent & 0xff), to_send); + lwsl_notice("CLIENT WSI allowance=%d\n", (int)lws_get_peer_write_allowance(wsi)); int n = lws_write(wsi, &buf[LWS_PRE], to_send, LWS_WRITE_BINARY); if (n > 0) { client_sent += (size_t)n; @@ -196,22 +210,32 @@ callback_quic_test(struct lws *wsi, enum lws_callback_reasons reason, } break; } + case LWS_CALLBACK_ESTABLISHED: + lwsl_notice("SERVER ESTABLISHED!\n"); + lws_callback_on_writable(wsi); + break; case LWS_CALLBACK_SERVER_WRITEABLE: { uint8_t buf[LWS_PRE + 1024]; + if (server_only) + break; + if (server_sent >= TOTAL_DATA) break; size_t to_send = TOTAL_DATA - server_sent; if (to_send > 1024) to_send = 1024; memset(&buf[LWS_PRE], (server_sent & 0xff), to_send); + lwsl_notice("SERVER WSI allowance=%d\n", (int)lws_get_peer_write_allowance(wsi)); int n = lws_write(wsi, &buf[LWS_PRE], to_send, LWS_WRITE_BINARY); if (n > 0) { server_sent += (size_t)n; if (server_sent < TOTAL_DATA) lws_callback_on_writable(wsi); + } else { + lwsl_err("SERVER WROTE FAILED n=%d\n", n); } break; } @@ -265,14 +289,35 @@ enum { LWS_SW_HELP, LWS_SW_URL, LWS_SW_PORT, + LWS_SW_SERVER_ONLY, }; static const struct lws_switches switches[] = { [LWS_SW_HELP] = { "--help", "Show this help information" }, [LWS_SW_URL] = { "-u", "URL to connect to (if absent, acts as server too)" }, [LWS_SW_PORT] = { "-p", "Port to connect to / listen on (default 7681)" }, + [LWS_SW_SERVER_ONLY] = { "-s", "Server only mode (do not launch client, do not send data unprompted)" }, }; +#if defined(WIN32) && defined(LWS_WITH_SCHANNEL) +#include +static int is_quic_supported_on_os(void) { + OSVERSIONINFOEXW osvi = { sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0 }; + DWORDLONG const dwlConditionMask = VerSetConditionMask( + VerSetConditionMask( + VerSetConditionMask( + 0, VER_MAJORVERSION, VER_GREATER_EQUAL), + VER_MINORVERSION, VER_GREATER_EQUAL), + VER_BUILDNUMBER, VER_GREATER_EQUAL); + + osvi.dwMajorVersion = 10; + osvi.dwMinorVersion = 0; + osvi.dwBuildNumber = 20348; /* Windows Server 2022 */ + + return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER, dwlConditionMask) != FALSE; +} +#endif + int main(int argc, const char **argv) { struct lws_context_creation_info info; @@ -287,8 +332,16 @@ int main(int argc, const char **argv) int url_port = 0; lws_context_info_defaults(&info, NULL); + info.fd_limit_per_thread = 0; lws_cmdline_option_handle_builtin(argc, argv, &info); +#if defined(WIN32) && defined(LWS_WITH_SCHANNEL) + if (!is_quic_supported_on_os()) { + lwsl_user("SChannel QUIC requires Windows 11+ / Server 2022+\n"); + return 0; + } +#endif + if (lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { lws_switches_print_help(argv[0], switches, LWS_ARRAY_SIZE(switches)); return 0; @@ -298,6 +351,9 @@ int main(int argc, const char **argv) if (p) port = atoi(p); + if (lws_cmdline_option(argc, argv, "-s")) + server_only = 1; + p = lws_cmdline_option(argc, argv, "-u"); if (p) { lws_strncpy(url_buf, p, sizeof(url_buf)); @@ -330,13 +386,14 @@ int main(int argc, const char **argv) info.vhost_name = "quic-server"; info.listen_accept_role = "quic"; info.listen_accept_protocol = "quic-test-protocol"; - info.alpn = "lws-quic"; + info.alpn = "h3,lws-quic"; /* TLS 1.3 requires valid certificates for QUIC. Use our in-memory certs. */ info.server_ssl_cert_mem = test_cert; info.server_ssl_cert_mem_len = (unsigned int)strlen(test_cert); info.server_ssl_private_key_mem = test_key; info.server_ssl_private_key_mem_len = (unsigned int)strlen(test_key); + info.mounts = &mount_redir; } else { info.port = CONTEXT_PORT_NO_LISTEN; info.vhost_name = "quic-client"; @@ -358,44 +415,48 @@ int main(int argc, const char **argv) } } - /* - * Immediately launch the client. - */ - memset(&i, 0, sizeof(i)); - i.context = context; - i.port = port; - i.address = p ? address : "127.0.0.1"; - i.host = p ? address : "localhost"; - i.origin = i.address; - i.vhost = vh; - i.ssl_connection = LCCSCF_USE_SSL; - if (!p) /* by default, we use our canned selfsigned cert for local tests */ - i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED | - LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK; - i.protocol = "quic-test-protocol"; - i.alpn = "lws-quic"; - i.local_protocol_name = "quic-test-protocol"; - i.method = "QUIC"; - - client_wsi = lws_client_connect_via_info(&i); - if (!client_wsi) { - lwsl_err("Client connection failed\n"); - goto bail; + if (!server_only) { + /* + * Immediately launch the client. + */ + memset(&i, 0, sizeof(i)); + i.context = context; + i.port = port; + i.address = p ? address : "127.0.0.1"; + i.host = p ? address : "localhost"; + i.origin = i.address; + i.vhost = vh; + i.ssl_connection = LCCSCF_USE_SSL; + if (!p) /* by default, we use our canned selfsigned cert for local tests */ + i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED | + LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK; + i.protocol = "quic-test-protocol"; + i.alpn = "lws-quic"; + i.local_protocol_name = "quic-test-protocol"; + i.method = "QUIC"; + + client_wsi = lws_client_connect_via_info(&i); + if (!client_wsi) { + lwsl_err("Client connection failed\n"); + goto bail; + } } start_us = lws_now_usecs(); last_rx_us = start_us; while (lws_service(context, 0) >= 0 && !interrupted) { - if (lws_now_usecs() - start_us > 60000000) { - lwsl_err("Timeout waiting for QUIC transfer (60s absolute)\n"); - result = 1; - break; - } - if (lws_now_usecs() - last_rx_us > 15000000) { - lwsl_err("Timeout waiting for QUIC transfer (15s idle RX)\n"); - result = 1; - break; + if (!server_only) { + if (lws_now_usecs() - start_us > 60000000) { + lwsl_err("Timeout waiting for QUIC transfer (60s absolute)\n"); + result = 1; + break; + } + if (lws_now_usecs() - last_rx_us > 15000000) { + lwsl_err("Timeout waiting for QUIC transfer (15s idle RX)\n"); + result = 1; + break; + } } } diff --git a/minimal-examples-lowlevel/raw/minimal-raw-adopt-tcp/minimal-raw-adopt-tcp.c b/minimal-examples-lowlevel/raw/minimal-raw-adopt-tcp/minimal-raw-adopt-tcp.c index f057926bc4..60662f9631 100644 --- a/minimal-examples-lowlevel/raw/minimal-raw-adopt-tcp/minimal-raw-adopt-tcp.c +++ b/minimal-examples-lowlevel/raw/minimal-raw-adopt-tcp/minimal-raw-adopt-tcp.c @@ -105,14 +105,7 @@ int main(int argc, const char **argv) lws_sock_file_fd_type sock; struct addrinfo h, *r, *rp; struct lws_vhost *vhost; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -123,13 +116,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal raw adopt tcp\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS; context = lws_create_context(&info); diff --git a/minimal-examples-lowlevel/raw/minimal-raw-adopt-udp/minimal-raw-adopt-udp.c b/minimal-examples-lowlevel/raw/minimal-raw-adopt-udp/minimal-raw-adopt-udp.c index 0dbe0d9981..59190c9eee 100644 --- a/minimal-examples-lowlevel/raw/minimal-raw-adopt-udp/minimal-raw-adopt-udp.c +++ b/minimal-examples-lowlevel/raw/minimal-raw-adopt-udp/minimal-raw-adopt-udp.c @@ -154,14 +154,7 @@ int main(int argc, const char **argv) struct lws_context_creation_info info; struct lws_context *context; struct lws_vhost *vhost; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -172,13 +165,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal raw adopt udp | nc -u 127.0.0.1 7681\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS; context = lws_create_context(&info); diff --git a/minimal-examples-lowlevel/raw/minimal-raw-audio/audio.c b/minimal-examples-lowlevel/raw/minimal-raw-audio/audio.c index eca19e7a19..ed9769e5fb 100644 --- a/minimal-examples-lowlevel/raw/minimal-raw-audio/audio.c +++ b/minimal-examples-lowlevel/raw/minimal-raw-audio/audio.c @@ -200,8 +200,7 @@ int main(int argc, const char **argv) int n = 0; signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); lwsl_user("LWS minimal raw audio\n"); diff --git a/minimal-examples-lowlevel/raw/minimal-raw-client/main.c b/minimal-examples-lowlevel/raw/minimal-raw-client/main.c index f632678f0c..fa9204631a 100644 --- a/minimal-examples-lowlevel/raw/minimal-raw-client/main.c +++ b/minimal-examples-lowlevel/raw/minimal-raw-client/main.c @@ -11,15 +11,7 @@ #include -enum { - LWS_SW_D, - LWS_SW_HELP, -}; -static const struct lws_switches switches[] = { - [LWS_SW_D] = { "-d", "Debug logs (e.g. -d 15)" }, - [LWS_SW_HELP] = { "--help", "Show this help information" }, -}; #include #include @@ -187,22 +179,18 @@ void sigint_handler(int sig) int main(int argc, const char **argv) { struct lws_context_creation_info info; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int n = 0; lws_state_notify_link_t notifier = { { NULL, NULL, NULL }, system_notify_cb, "app" }; lws_state_notify_link_t *na[] = { ¬ifier, NULL }; signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal raw client\n"); - memset(&info, 0, sizeof info); - + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; info.port = CONTEXT_PORT_NO_LISTEN_SERVER; info.protocols = protocols; diff --git a/minimal-examples-lowlevel/raw/minimal-raw-dht-zone-client/minimal-raw-dht-zone-client.c b/minimal-examples-lowlevel/raw/minimal-raw-dht-zone-client/minimal-raw-dht-zone-client.c index 8afacf471b..71d8840ee6 100644 --- a/minimal-examples-lowlevel/raw/minimal-raw-dht-zone-client/minimal-raw-dht-zone-client.c +++ b/minimal-examples-lowlevel/raw/minimal-raw-dht-zone-client/minimal-raw-dht-zone-client.c @@ -145,8 +145,7 @@ app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, if (current == LWS_SYSTATE_OPERATIONAL) break; - memset(&info, 0, sizeof(info)); - info.vhost_name = "dht-client"; + lws_context_info_defaults(&info, NULL);info.vhost_name = "dht-client"; info.pvo = pvos; info.port = atoi(port_buf); info.protocols = app_protocols; diff --git a/minimal-examples-lowlevel/raw/minimal-raw-dht/CMakeLists.txt b/minimal-examples-lowlevel/raw/minimal-raw-dht/CMakeLists.txt index 569b5090ff..91edbfb085 100644 --- a/minimal-examples-lowlevel/raw/minimal-raw-dht/CMakeLists.txt +++ b/minimal-examples-lowlevel/raw/minimal-raw-dht/CMakeLists.txt @@ -51,7 +51,7 @@ if (requirements) WORKING_DIRECTORY . FIXTURES_SETUP raw_dht_srv TIMEOUT 800 - ENVIRONMENT "SAI_LIST_PORT=${PORT_DHT_SRV};LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/lib") + ENVIRONMENT "SAI_LIST_PORT=${PORT_DHT_SRV};SAI_LIST_IS_UDP=1;LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/lib") add_test(NAME ki_raw_dht_srv COMMAND ${CMAKE_SOURCE_DIR}/scripts/ctest-background-kill.sh diff --git a/minimal-examples-lowlevel/raw/minimal-raw-dht/minimal-raw-dht.c b/minimal-examples-lowlevel/raw/minimal-raw-dht/minimal-raw-dht.c index 846506c615..4e02b6a113 100644 --- a/minimal-examples-lowlevel/raw/minimal-raw-dht/minimal-raw-dht.c +++ b/minimal-examples-lowlevel/raw/minimal-raw-dht/minimal-raw-dht.c @@ -234,8 +234,7 @@ app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, NULL, NULL, "lws-dht-stats", "" }; - memset(&info, 0, sizeof(info)); - info.vhost_name = "http"; + lws_context_info_defaults(&info, NULL);info.vhost_name = "http"; info.port = atoi(port_buf) + 100; info.protocols = app_protocols; info.mounts = &mount_stats; @@ -252,8 +251,7 @@ app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, return 0; } - memset(&info, 0, sizeof(info)); - info.vhost_name = "dht"; + lws_context_info_defaults(&info, NULL);info.vhost_name = "dht"; info.pvo = pvos; info.port = atoi(port_buf); info.protocols = app_protocols; diff --git a/minimal-examples-lowlevel/raw/minimal-raw-fallback-http-server/minimal-raw-fallback-http-server.c b/minimal-examples-lowlevel/raw/minimal-raw-fallback-http-server/minimal-raw-fallback-http-server.c index 8d16e9749b..2c78c4e028 100644 --- a/minimal-examples-lowlevel/raw/minimal-raw-fallback-http-server/minimal-raw-fallback-http-server.c +++ b/minimal-examples-lowlevel/raw/minimal-raw-fallback-http-server/minimal-raw-fallback-http-server.c @@ -101,8 +101,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -113,14 +112,12 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal raw fallback http server | " "visit http://localhost:7681\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.protocols = protocols; info.mounts = &mount; diff --git a/minimal-examples-lowlevel/raw/minimal-raw-file/minimal-raw-file.c b/minimal-examples-lowlevel/raw/minimal-raw-file/minimal-raw-file.c index d753ac3643..e6c3548ddf 100644 --- a/minimal-examples-lowlevel/raw/minimal-raw-file/minimal-raw-file.c +++ b/minimal-examples-lowlevel/raw/minimal-raw-file/minimal-raw-file.c @@ -124,14 +124,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -142,10 +135,7 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal raw file\n"); if (argc < 2) { lwsl_user("Usage: %s " @@ -157,7 +147,8 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = CONTEXT_PORT_NO_LISTEN_SERVER; /* no listen socket for demo */ info.protocols = protocols; diff --git a/minimal-examples-lowlevel/raw/minimal-raw-netcat/minimal-raw-netcat.c b/minimal-examples-lowlevel/raw/minimal-raw-netcat/minimal-raw-netcat.c index 4c5538341f..6584272b29 100644 --- a/minimal-examples-lowlevel/raw/minimal-raw-netcat/minimal-raw-netcat.c +++ b/minimal-examples-lowlevel/raw/minimal-raw-netcat/minimal-raw-netcat.c @@ -162,7 +162,7 @@ int main(int argc, const char **argv) struct addrinfo h, *r, *rp; struct lws_vhost *vhost; const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -173,13 +173,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal raw netcat [--server ip] [--port port] [-w ms]\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS; context = lws_create_context(&info); diff --git a/minimal-examples-lowlevel/raw/minimal-raw-proxy-fallback/minimal-raw-proxy-fallback.c b/minimal-examples-lowlevel/raw/minimal-raw-proxy-fallback/minimal-raw-proxy-fallback.c index 33732724ee..5c84613827 100644 --- a/minimal-examples-lowlevel/raw/minimal-raw-proxy-fallback/minimal-raw-proxy-fallback.c +++ b/minimal-examples-lowlevel/raw/minimal-raw-proxy-fallback/minimal-raw-proxy-fallback.c @@ -81,7 +81,7 @@ static const struct lws_protocol_vhost_options pvo = { int main(int argc, const char **argv) { - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int n = 0; struct lws_context_creation_info info; struct lws_context *context; char outward[256]; @@ -96,10 +96,7 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal raw proxy fallback | visit http://localhost:7681\n"); if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_R].sw))) { @@ -107,7 +104,8 @@ int main(int argc, const char **argv) pvo1.value = outward; } - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.pvo = &pvo; info.mounts = &mount; diff --git a/minimal-examples-lowlevel/raw/minimal-raw-proxy/minimal-raw-proxy.c b/minimal-examples-lowlevel/raw/minimal-raw-proxy/minimal-raw-proxy.c index 192534c528..cbfac7d079 100644 --- a/minimal-examples-lowlevel/raw/minimal-raw-proxy/minimal-raw-proxy.c +++ b/minimal-examples-lowlevel/raw/minimal-raw-proxy/minimal-raw-proxy.c @@ -61,7 +61,7 @@ static const struct lws_protocol_vhost_options pvo = { int main(int argc, const char **argv) { - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int n = 0; struct lws_context_creation_info info; struct lws_context *context; char outward[256]; @@ -76,10 +76,7 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal raw proxy\n"); if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_R].sw))) { @@ -87,7 +84,8 @@ int main(int argc, const char **argv) pvo1.value = outward; } - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.pvo = &pvo; #if defined(LWS_WITH_PLUGINS) diff --git a/minimal-examples-lowlevel/raw/minimal-raw-serial/minimal-raw-file.c b/minimal-examples-lowlevel/raw/minimal-raw-serial/minimal-raw-file.c index dc54c81cc5..06387f8349 100644 --- a/minimal-examples-lowlevel/raw/minimal-raw-serial/minimal-raw-file.c +++ b/minimal-examples-lowlevel/raw/minimal-raw-serial/minimal-raw-file.c @@ -200,14 +200,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -218,10 +211,7 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal raw serial\n"); if (argc < 2) { lwsl_user("Usage: %s " @@ -232,7 +222,8 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = CONTEXT_PORT_NO_LISTEN_SERVER; /* no listen socket for demo */ info.protocols = protocols; diff --git a/minimal-examples-lowlevel/raw/minimal-raw-vhost/minimal-raw-vhost.c b/minimal-examples-lowlevel/raw/minimal-raw-vhost/minimal-raw-vhost.c index 09ac712c47..3db175e155 100644 --- a/minimal-examples-lowlevel/raw/minimal-raw-vhost/minimal-raw-vhost.c +++ b/minimal-examples-lowlevel/raw/minimal-raw-vhost/minimal-raw-vhost.c @@ -128,14 +128,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -146,13 +139,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal raw vhost | nc localhost 7681\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.protocols = protocols; info.options = LWS_SERVER_OPTION_ONLY_RAW; /* vhost accepts RAW */ diff --git a/minimal-examples-lowlevel/raw/minimal-raw-webrtc-webcam/minimal-raw-webrtc-webcam.c b/minimal-examples-lowlevel/raw/minimal-raw-webrtc-webcam/minimal-raw-webrtc-webcam.c index 7a249c789c..1891bbdae0 100644 --- a/minimal-examples-lowlevel/raw/minimal-raw-webrtc-webcam/minimal-raw-webrtc-webcam.c +++ b/minimal-examples-lowlevel/raw/minimal-raw-webrtc-webcam/minimal-raw-webrtc-webcam.c @@ -445,8 +445,7 @@ app_system_state_nf(lws_state_manager_t *mgr, break; lwsl_user("%s: OPERATIONAL->creating vhost\n", __func__); - memset(&info, 0, sizeof(info)); - info.vhost_name = "webrtc"; + lws_context_info_defaults(&info, NULL);info.vhost_name = "webrtc"; info.port = 7681; info.protocols = protocols; info.pvo = pvos; diff --git a/minimal-examples-lowlevel/raw/minimal-raw-webrtc-webcam/webcam-media.c b/minimal-examples-lowlevel/raw/minimal-raw-webrtc-webcam/webcam-media.c index f27a2d6152..666df193ae 100644 --- a/minimal-examples-lowlevel/raw/minimal-raw-webrtc-webcam/webcam-media.c +++ b/minimal-examples-lowlevel/raw/minimal-raw-webrtc-webcam/webcam-media.c @@ -28,7 +28,7 @@ media_update_scaler(struct per_vhost_data *vhd) lws_transcode_destroy(&vhd->tcc_enc); memset(&info, 0, sizeof(info)); - info.codec = LWS_TCC_H264; + info.codec = LWS_TCC_H264; info.width = vhd->target_width; info.height = vhd->target_height; info.fps = 30; diff --git a/minimal-examples-lowlevel/raw/minimal-raw-wol/minimal-raw-wol.c b/minimal-examples-lowlevel/raw/minimal-raw-wol/minimal-raw-wol.c index 922eacd4e4..110abd7b3d 100644 --- a/minimal-examples-lowlevel/raw/minimal-raw-wol/minimal-raw-wol.c +++ b/minimal-examples-lowlevel/raw/minimal-raw-wol/minimal-raw-wol.c @@ -39,8 +39,7 @@ int main(int argc, const char **argv) } - memset(&info, 0, sizeof info); - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_IP].sw))) ip = p; diff --git a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-alexa/main.c b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-alexa/main.c index 2586817593..fdfc26b849 100644 --- a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-alexa/main.c +++ b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-alexa/main.c @@ -378,8 +378,7 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); lwsl_user("LWS secure streams - Alexa voice test [-d]\n"); diff --git a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-avs/main-client.c b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-avs/main-client.c index 9bd124d766..2c539931c7 100644 --- a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-avs/main-client.c +++ b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-avs/main-client.c @@ -90,8 +90,7 @@ int main(int argc, const char **argv) int n = 0; signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); lwsl_user("LWS secure streams - AVS test client [-d]\n"); diff --git a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-avs/main.c b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-avs/main.c index 1dd1058fa8..586dc3b146 100644 --- a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-avs/main.c +++ b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-avs/main.c @@ -331,8 +331,7 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); lwsl_user("LWS secure streams - AVS test [-d]\n"); diff --git a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-binance/main.c b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-binance/main.c index e0863a87fc..ea7b70cb96 100644 --- a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-binance/main.c +++ b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-binance/main.c @@ -233,8 +233,7 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); lwsl_user("LWS minimal Secure Streams binance client\n"); diff --git a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-blob/minimal-secure-streams.c b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-blob/minimal-secure-streams.c index f74909bd61..69d881fa2f 100644 --- a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-blob/minimal-secure-streams.c +++ b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-blob/minimal-secure-streams.c @@ -547,8 +547,7 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); lwsl_user("LWS secure streams test client [-d]\n"); diff --git a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-client-tx/minimal-secure-streams-client-tx.c b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-client-tx/minimal-secure-streams-client-tx.c index 4bc0734111..14e53f2f63 100644 --- a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-client-tx/minimal-secure-streams-client-tx.c +++ b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-client-tx/minimal-secure-streams-client-tx.c @@ -159,7 +159,7 @@ static const lws_ss_info_t ssi = { int main(int argc, const char **argv) { - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int n = 0; struct lws_context_creation_info info; struct lws_context *context; const char *p; @@ -173,17 +173,14 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_C].sw))) reads = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS secure streams client TX [-d]\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ - + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; info.fd_limit_per_thread = 1 + 6 + 1; info.port = CONTEXT_PORT_NO_LISTEN; diff --git a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-hugeurl/minimal-secure-streams.c b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-hugeurl/minimal-secure-streams.c index 569dbb0730..d5342b7eae 100644 --- a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-hugeurl/minimal-secure-streams.c +++ b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-hugeurl/minimal-secure-streams.c @@ -13,7 +13,6 @@ #include enum { - LWS_SW_H1, LWS_SW_A, LWS_SW_H, LWS_SW_I, @@ -22,7 +21,6 @@ enum { }; static const struct lws_switches switches[] = { - [LWS_SW_H1] = { "--h1", "Enable --h1 feature" }, [LWS_SW_A] = { "-a", "Enable -a feature" }, [LWS_SW_H] = { "-h", "Strict Host Check / Help" }, [LWS_SW_I] = { "-i", "Interface to bind to" }, @@ -34,7 +32,7 @@ static const struct lws_switches switches[] = { #include static unsigned int timeout_ms = 6000; -static int interrupted, bad = 1, h1; +static int interrupted, bad = 1; static lws_state_notify_link_t nl; static size_t hugeurl_size = 4000; @@ -346,9 +344,6 @@ app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, if (current != LWS_SYSTATE_OPERATIONAL) return 0; - if (h1) - ssi.streamtype = "lws_anything_h1"; - if (!lws_ss_create(context, 0, &ssi, NULL, NULL, NULL, NULL)) return 0; @@ -383,8 +378,7 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); lwsl_user("LWS secure streams hugeurl test client [-d][-h ]\n"); @@ -416,9 +410,6 @@ int main(int argc, const char **argv) info.connect_timeout_secs = 15; info.timeout_secs = 10; - if (lws_cmdline_option(argc, argv, switches[LWS_SW_H1].sw)) - h1 = 1; - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_H].sw))) hugeurl_size = (size_t)atol(p); diff --git a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-metadata/minimal-secure-streams.c b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-metadata/minimal-secure-streams.c index 575aec784b..33b8bb45e6 100644 --- a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-metadata/minimal-secure-streams.c +++ b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-metadata/minimal-secure-streams.c @@ -316,8 +316,7 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); lwsl_user("LWS secure streams test client [-d]\n"); diff --git a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-mqtt/minimal-secure-streams-mqtt.c b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-mqtt/minimal-secure-streams-mqtt.c index c3e54d47fe..0f8c13adc8 100644 --- a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-mqtt/minimal-secure-streams-mqtt.c +++ b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-mqtt/minimal-secure-streams-mqtt.c @@ -491,8 +491,7 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); lwsl_user("LWS secure streams mqtt test client [-d]\n"); diff --git a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-perf/minimal-secure-streams.c b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-perf/minimal-secure-streams.c index a9d9f9b2cb..4e7d7b850f 100644 --- a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-perf/minimal-secure-streams.c +++ b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-perf/minimal-secure-streams.c @@ -486,8 +486,7 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); lwsl_user("LWS secure streams test client PERF [-d]\n"); diff --git a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-policy2c/minimal-secure-streams.c b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-policy2c/minimal-secure-streams.c index 4801640d6c..1abdc8faa9 100644 --- a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-policy2c/minimal-secure-streams.c +++ b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-policy2c/minimal-secure-streams.c @@ -83,8 +83,7 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); lwsl_user("LWS secure streams policy2c [-d]\n"); @@ -380,6 +379,7 @@ int main(int argc, const char **argv) switch (pol->protocol) { case LWSSSP_H1: case LWSSSP_H2: + case LWSSSP_H3: case LWSSSP_WS: if (!pol->u.http.count_respmap) @@ -500,6 +500,7 @@ int main(int argc, const char **argv) switch (pol->protocol) { case LWSSSP_H1: case LWSSSP_H2: + case LWSSSP_H3: case LWSSSP_WS: printf("\t.u = {\n\t\t.http = {\n"); diff --git a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-post/minimal-secure-streams-post.c b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-post/minimal-secure-streams-post.c index 296ca33045..9666102c1a 100644 --- a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-post/minimal-secure-streams-post.c +++ b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-post/minimal-secure-streams-post.c @@ -515,8 +515,7 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); lwsl_user("LWS secure streams test client [-d]\n"); diff --git a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-proxy/main.c b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-proxy/main.c index be574a1617..d0ff613ca1 100644 --- a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-proxy/main.c +++ b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-proxy/main.c @@ -493,8 +493,7 @@ int main(int argc, const char **argv) } - memset(&info, 0, sizeof info); - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); signal(SIGINT, sigint_handler); diff --git a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-server-raw/main.c b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-server-raw/main.c index fa1ee57ec4..abcfaba0e9 100644 --- a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-server-raw/main.c +++ b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-server-raw/main.c @@ -72,8 +72,7 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); lwsl_user("LWS Secure Streams Server Raw\n"); info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | diff --git a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-server/main.c b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-server/main.c index 40f3179ed0..8a10893889 100644 --- a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-server/main.c +++ b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-server/main.c @@ -303,8 +303,7 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); if (lws_cmdline_option(argc, argv, switches[LWS_SW_M].sw)) multipart = 1; diff --git a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-sigv4/ss-s3-main.c b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-sigv4/ss-s3-main.c index 4c5949987a..0d75ff4980 100644 --- a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-sigv4/ss-s3-main.c +++ b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-sigv4/ss-s3-main.c @@ -230,7 +230,7 @@ sigint_handler(int sig) int main(int argc, const char **argv) { - int logs = LLL_USER | LLL_ERR | LLL_WARN /* | LLL_NOTICE */ ; + struct lws_context_creation_info info; struct lws_context *context; int n = 0; @@ -243,10 +243,8 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - lws_set_log_level(logs, NULL); - memset(&info, 0, sizeof info); - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); lwsl_user("LWS minimal secure streams sigv4 \n"); diff --git a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-smd/minimal-secure-streams-smd.c b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-smd/minimal-secure-streams-smd.c index 0b94073367..6d936ad097 100644 --- a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-smd/minimal-secure-streams-smd.c +++ b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-smd/minimal-secure-streams-smd.c @@ -289,8 +289,7 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); - + lws_context_info_defaults(&info, NULL); #if defined(LWS_SS_USE_SSPC) if (lws_cmdline_option(argc, argv, switches[LWS_SW_MULTI].sw)) return smd_ss_multi_test(argc, argv); diff --git a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-smd/multi.c b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-smd/multi.c index 08687600da..d10bc9d29c 100644 --- a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-smd/multi.c +++ b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-smd/multi.c @@ -365,8 +365,7 @@ smd_ss_multi_test(int argc, const char **argv) /* the original process */ n = -1; /* so original ends up with context.user as 0 below */ - memset(&info, 0, sizeof info); - memset(&sul_timeout, 0, sizeof sul_timeout); + lws_context_info_defaults(&info, NULL);memset(&sul_timeout, 0, sizeof sul_timeout); lws_cmdline_option_handle_builtin(argc, argv, &info); diff --git a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-staticpolicy/minimal-secure-streams.c b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-staticpolicy/minimal-secure-streams.c index 359415f53f..44b19741ec 100644 --- a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-staticpolicy/minimal-secure-streams.c +++ b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-staticpolicy/minimal-secure-streams.c @@ -218,8 +218,7 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); lwsl_user("LWS secure streams static policy test client [-d]\n"); diff --git a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-stress/minimal-secure-streams.c b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-stress/minimal-secure-streams.c index 6c1128c6ce..4d8d27bbbf 100644 --- a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-stress/minimal-secure-streams.c +++ b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-stress/minimal-secure-streams.c @@ -621,8 +621,7 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_C].sw))) concurrent = atoi(p); diff --git a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-testsfail/minimal-secure-streams-testsfail.c b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-testsfail/minimal-secure-streams-testsfail.c index 6a7ed52ec8..9d1796d24b 100644 --- a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-testsfail/minimal-secure-streams-testsfail.c +++ b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-testsfail/minimal-secure-streams-testsfail.c @@ -923,8 +923,7 @@ main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); if ((pp = lws_cmdline_option(argc, argv, switches[LWS_SW_AMOUNT].sw))) amount = (size_t)atoi(pp); diff --git a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-threads/minimal-secure-streams-threads.c b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-threads/minimal-secure-streams-threads.c index 810bcade21..23bd6663fc 100644 --- a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-threads/minimal-secure-streams-threads.c +++ b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams-threads/minimal-secure-streams-threads.c @@ -238,9 +238,7 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); - - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); lwsl_user("LWS Secure Streams threads test client [-d]\n"); diff --git a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams/minimal-secure-streams.c b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams/minimal-secure-streams.c index 08e998fe4a..1f0b439a40 100644 --- a/minimal-examples-lowlevel/secure-streams/minimal-secure-streams/minimal-secure-streams.c +++ b/minimal-examples-lowlevel/secure-streams/minimal-secure-streams/minimal-secure-streams.c @@ -588,8 +588,7 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); //lws_set_log_level(LLL_USER | LLL_ERR | LLL_DEBUG | LLL_NOTICE | LLL_INFO, NULL); diff --git a/minimal-examples-lowlevel/webtransport/minimal-webtransport-client/CMakeLists.txt b/minimal-examples-lowlevel/webtransport/minimal-webtransport-client/CMakeLists.txt new file mode 100644 index 0000000000..4ebdf82e57 --- /dev/null +++ b/minimal-examples-lowlevel/webtransport/minimal-webtransport-client/CMakeLists.txt @@ -0,0 +1,25 @@ +project(lws-minimal-webtransport-client C) +cmake_minimum_required(VERSION 3.10) +find_package(libwebsockets CONFIG REQUIRED) +list(APPEND CMAKE_MODULE_PATH ${LWS_CMAKE_DIR}) +include(CheckCSourceCompiles) +include(LwsCheckRequirements) + +set(SAMP lws-minimal-webtransport-client) +set(SRCS minimal-webtransport-client.c) + +set(requirements 1) +require_lws_config(LWS_ROLE_WT 1 requirements) +require_lws_config(LWS_WITH_CLIENT 1 requirements) +require_lws_config(LWS_WITH_HTTP3 1 requirements) + +if (requirements) + add_executable(${SAMP} ${SRCS}) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared ${LIBWEBSOCKETS_DEP_LIBS}) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets ${LIBWEBSOCKETS_DEP_LIBS}) + endif() +endif() diff --git a/minimal-examples-lowlevel/webtransport/minimal-webtransport-client/minimal-webtransport-client.c b/minimal-examples-lowlevel/webtransport/minimal-webtransport-client/minimal-webtransport-client.c new file mode 100644 index 0000000000..e7581e0856 --- /dev/null +++ b/minimal-examples-lowlevel/webtransport/minimal-webtransport-client/minimal-webtransport-client.c @@ -0,0 +1,135 @@ +/* + * lws-minimal-webtransport-client + * + * Written in 2010-2026 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * This demonstrates a WebTransport client. + */ + +#include +#include +#include +#include + +static struct lws_context *context; +static int interrupted; + +static int +callback_minimal(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + switch (reason) { + + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + lwsl_err("CLIENT_CONNECTION_ERROR: %s\n", + in ? (char *)in : "(null)"); + interrupted = 1; + break; + + case LWS_CALLBACK_CLIENT_ESTABLISHED: + lwsl_user("LWS_CALLBACK_CLIENT_ESTABLISHED (wsi: %p)\n", wsi); + if (lws_wt_is_session(wsi)) { + lwsl_user(" WebTransport Session Established. Spawning bidi stream.\n"); + struct lws *cwsi = lws_wt_create_stream(wsi, 0); + if (cwsi) { + lwsl_user(" Created Bidi Stream: %p\n", cwsi); + /* request to write some data */ + lws_callback_on_writable(cwsi); + } + } else { + lwsl_user(" WebTransport Stream Established\n"); + lws_callback_on_writable(wsi); + } + break; + + case LWS_CALLBACK_CLIENT_WRITEABLE: + if (!lws_wt_is_session(wsi)) { + uint8_t buf[LWS_PRE + 32]; + uint8_t *p = &buf[LWS_PRE]; + int n = lws_snprintf((char *)p, 32, "Hello from WebTransport Stream!"); + lws_write(wsi, p, (unsigned int)n, LWS_WRITE_BINARY); + lwsl_user(" Sent message on stream %p\n", wsi); + } else { + /* We could write datagrams here */ + } + break; + + case LWS_CALLBACK_CLIENT_RECEIVE: + lwsl_user("LWS_CALLBACK_CLIENT_RECEIVE (wsi: %p, len: %zu)\n", wsi, len); + lwsl_hexdump_notice(in, len); + break; + + case LWS_CALLBACK_CLIENT_CLOSED: + lwsl_user("LWS_CALLBACK_CLIENT_CLOSED\n"); + if (lws_wt_is_session(wsi)) { + interrupted = 1; + } + break; + + default: + break; + } + + return 0; +} + +static const struct lws_protocols protocols[] = { + { "webtransport", callback_minimal, 0, 1024, 0, NULL, 0 }, + LWS_PROTOCOL_LIST_TERM +}; + +static void +sigint_handler(int sig) +{ + interrupted = 1; +} + +int main(int argc, const char **argv) +{ + struct lws_context_creation_info info; + struct lws_client_connect_info i; + int n = 0; + + signal(SIGINT, sigint_handler); + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); + + lwsl_user("LWS minimal WebTransport client\n"); + + info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + info.port = CONTEXT_PORT_NO_LISTEN; + info.protocols = protocols; + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + memset(&i, 0, sizeof(i)); + i.context = context; + i.port = 7681; + i.address = "localhost"; + i.path = "/"; + i.host = i.address; + i.origin = i.address; + i.ssl_connection = LCCSCF_USE_SSL | LCCSCF_ALLOW_SELFSIGNED | LCCSCF_ALLOW_INSECURE; + i.protocol = "webtransport"; + i.alpn = "h3"; /* Force HTTP/3 */ + + if (!lws_client_connect_via_info(&i)) { + lwsl_err("Client connect failed\n"); + interrupted = 1; + } + + while (n >= 0 && !interrupted) + n = lws_service(context, 0); + + lws_context_destroy(context); + lwsl_user("Completed\n"); + + return 0; +} diff --git a/minimal-examples-lowlevel/webtransport/minimal-webtransport-server/CMakeLists.txt b/minimal-examples-lowlevel/webtransport/minimal-webtransport-server/CMakeLists.txt new file mode 100644 index 0000000000..23c3a6d024 --- /dev/null +++ b/minimal-examples-lowlevel/webtransport/minimal-webtransport-server/CMakeLists.txt @@ -0,0 +1,25 @@ +project(lws-minimal-webtransport-server C) +cmake_minimum_required(VERSION 3.10) +find_package(libwebsockets CONFIG REQUIRED) +list(APPEND CMAKE_MODULE_PATH ${LWS_CMAKE_DIR}) +include(CheckCSourceCompiles) +include(LwsCheckRequirements) + +set(SAMP lws-minimal-webtransport-server) +set(SRCS minimal-webtransport-server.c) + +set(requirements 1) +require_lws_config(LWS_ROLE_WT 1 requirements) +require_lws_config(LWS_WITH_SERVER 1 requirements) +require_lws_config(LWS_WITH_HTTP3 1 requirements) + +if (requirements) + add_executable(${SAMP} ${SRCS}) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared ${LIBWEBSOCKETS_DEP_LIBS}) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets ${LIBWEBSOCKETS_DEP_LIBS}) + endif() +endif() diff --git a/minimal-examples-lowlevel/webtransport/minimal-webtransport-server/minimal-webtransport-server.c b/minimal-examples-lowlevel/webtransport/minimal-webtransport-server/minimal-webtransport-server.c new file mode 100644 index 0000000000..3d0e23585f --- /dev/null +++ b/minimal-examples-lowlevel/webtransport/minimal-webtransport-server/minimal-webtransport-server.c @@ -0,0 +1,103 @@ +/* + * lws-minimal-webtransport-server + * + * Written in 2010-2026 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * This demonstrates a minimal WebTransport server. + */ + +#include +#include +#include +#include +#include + +static int interrupted; + +static int +callback_webtransport(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + switch (reason) { + + case LWS_CALLBACK_ESTABLISHED: + lwsl_user("LWS_CALLBACK_ESTABLISHED (wsi: %p)\n", wsi); + /* If it's a session WSI, we can create streams */ + if (lws_wt_is_session(wsi)) { + lwsl_user(" WebTransport Session Established\n"); + /* Example: create a bidi stream */ + struct lws *cwsi = lws_wt_create_stream(wsi, 0); + if (cwsi) { + lwsl_user(" Created Bidi Stream: %p\n", cwsi); + } + } else { + lwsl_user(" WebTransport Stream Established\n"); + } + break; + + case LWS_CALLBACK_RECEIVE: + lwsl_user("LWS_CALLBACK_RECEIVE (wsi: %p, len: %zu)\n", wsi, len); + lwsl_hexdump_notice(in, len); + /* We can bounce it back if we want, or just print it */ + break; + + case LWS_CALLBACK_CLOSED: + lwsl_user("LWS_CALLBACK_CLOSED (wsi: %p)\n", wsi); + break; + + default: + break; + } + + return 0; +} + +static struct lws_protocols protocols[] = { + { "webtransport", callback_webtransport, 0, 1024, 0, NULL, 0 }, + { NULL, NULL, 0, 0, 0, NULL, 0 } /* terminator */ +}; + +static void +sigint_handler(int sig) +{ + interrupted = 1; +} + +int main(int argc, const char **argv) +{ + struct lws_context_creation_info info; + struct lws_context *context; + int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + + signal(SIGINT, sigint_handler); + + lws_set_log_level(logs, NULL); + lwsl_user("LWS minimal WebTransport server\n"); + + memset(&info, 0, sizeof info); + info.port = 7681; + info.protocols = protocols; + info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | + LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT; + + info.ssl_cert_filepath = "localhost-100y.cert"; + info.ssl_private_key_filepath = "localhost-100y.key"; + info.alpn = "h3"; /* We only support HTTP/3 for WebTransport */ + + /* generate localhost-100y.cert and key if missing using lws provided script */ + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + while (n >= 0 && !interrupted) + n = lws_service(context, 0); + + lws_context_destroy(context); + return 0; +} diff --git a/minimal-examples-lowlevel/webtransport/minimal-webtransport-test-server/CMakeLists.txt b/minimal-examples-lowlevel/webtransport/minimal-webtransport-test-server/CMakeLists.txt new file mode 100644 index 0000000000..e97b2245e0 --- /dev/null +++ b/minimal-examples-lowlevel/webtransport/minimal-webtransport-test-server/CMakeLists.txt @@ -0,0 +1,25 @@ +project(lws-minimal-webtransport-test-server C) +cmake_minimum_required(VERSION 3.10) +find_package(libwebsockets CONFIG REQUIRED) +list(APPEND CMAKE_MODULE_PATH ${LWS_CMAKE_DIR}) +include(CheckCSourceCompiles) +include(LwsCheckRequirements) + +set(SAMP lws-minimal-webtransport-test-server) +set(SRCS minimal-webtransport-test-server.c ../../../../plugins/protocol_webtransport_test/protocol_webtransport_test.c) + +set(requirements 1) +require_lws_config(LWS_ROLE_WT 1 requirements) +require_lws_config(LWS_WITH_SERVER 1 requirements) +require_lws_config(LWS_WITH_HTTP3 1 requirements) + + +if (requirements) + add_executable(${SAMP} ${SRCS}) + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared ${LIBWEBSOCKETS_DEP_LIBS}) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets ${LIBWEBSOCKETS_DEP_LIBS}) + endif() +endif() diff --git a/minimal-examples-lowlevel/webtransport/minimal-webtransport-test-server/minimal-webtransport-test-server.c b/minimal-examples-lowlevel/webtransport/minimal-webtransport-test-server/minimal-webtransport-test-server.c new file mode 100644 index 0000000000..0471f5b1f9 --- /dev/null +++ b/minimal-examples-lowlevel/webtransport/minimal-webtransport-test-server/minimal-webtransport-test-server.c @@ -0,0 +1,78 @@ +/* + * lws-minimal-webtransport-test-server + * + * Written in 2010-2026 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * This server mounts the assets for the WebTransport test UI and loads the + * `protocol_webtransport_test` plugin. + */ + +#include +#include +#include + +static int interrupted; + +static const struct lws_http_mount mount = { + .mount_next = NULL, + .mountpoint = "/", + .origin = "../../../../plugins/protocol_webtransport_test/assets", + .def = "index.html", + .origin_protocol = LWSMPRO_FILE, + .mountpoint_len = 1, +}; + +extern const lws_plugin_protocol_t webtransport_test; + +static void +sigint_handler(int sig) +{ + interrupted = 1; +} + +int main(int argc, const char **argv) +{ + struct lws_context_creation_info info; + struct lws_context *context; + const char *p; + int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + + signal(SIGINT, sigint_handler); + + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); + + lws_set_log_level(logs, NULL); + lwsl_user("LWS minimal webtransport test server | visit https://localhost:7681\n"); + + memset(&info, 0, sizeof info); + info.port = 7681; + info.mounts = &mount; + info.error_document_404 = "/404.html"; + info.options = + LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | + LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE; + + info.pvo = NULL; + info.protocols = webtransport_test.protocols; + + info.ssl_cert_filepath = "localhost-100y.cert"; + info.ssl_private_key_filepath = "localhost-100y.key"; + info.alpn = "h3,h2,http/1.1"; + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + while (n >= 0 && !interrupted) + n = lws_service(context, 0); + + lws_context_destroy(context); + + return 0; +} diff --git a/minimal-examples-lowlevel/ws-client/minimal-ws-client-binance/main.c b/minimal-examples-lowlevel/ws-client/minimal-ws-client-binance/main.c index 4bbf8171c3..4e8fa5d22f 100644 --- a/minimal-examples-lowlevel/ws-client/minimal-ws-client-binance/main.c +++ b/minimal-examples-lowlevel/ws-client/minimal-ws-client-binance/main.c @@ -342,8 +342,7 @@ int main(int argc, const char **argv) int n = 0; signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); lwsl_user("LWS minimal binance client\n"); diff --git a/minimal-examples-lowlevel/ws-client/minimal-ws-client-echo/minimal-ws-client-echo.c b/minimal-examples-lowlevel/ws-client/minimal-ws-client-echo/minimal-ws-client-echo.c index 06684240bf..7041290f63 100644 --- a/minimal-examples-lowlevel/ws-client/minimal-ws-client-echo/minimal-ws-client-echo.c +++ b/minimal-examples-lowlevel/ws-client/minimal-ws-client-echo/minimal-ws-client-echo.c @@ -123,13 +123,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; const char *p; - int n, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -138,10 +132,7 @@ int main(int argc, const char **argv) } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal ws client echo + permessage-deflate + multifragment bulk message\n"); lwsl_user(" lws-minimal-ws-client-echo [-n (no exts)] [-u url] [-p port] [-o (once)]\n"); @@ -165,7 +156,8 @@ int main(int argc, const char **argv) lwsl_user("options %d, ads %s\n", options, ads); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = CONTEXT_PORT_NO_LISTEN; info.protocols = protocols; info.pvo = &pvo; diff --git a/minimal-examples-lowlevel/ws-client/minimal-ws-client-ping/minimal-ws-client-ping.c b/minimal-examples-lowlevel/ws-client/minimal-ws-client-ping/minimal-ws-client-ping.c index eb3b8071db..14f31fef56 100644 --- a/minimal-examples-lowlevel/ws-client/minimal-ws-client-ping/minimal-ws-client-ping.c +++ b/minimal-examples-lowlevel/ws-client/minimal-ws-client-ping/minimal-ws-client-ping.c @@ -119,13 +119,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -136,13 +130,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal ws client PING\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ info.protocols = protocols; diff --git a/minimal-examples-lowlevel/ws-client/minimal-ws-client-pmd-bulk/minimal-ws-client-pmd-bulk.c b/minimal-examples-lowlevel/ws-client/minimal-ws-client-pmd-bulk/minimal-ws-client-pmd-bulk.c index 65183e51c5..635d0ee0a5 100644 --- a/minimal-examples-lowlevel/ws-client/minimal-ws-client-pmd-bulk/minimal-ws-client-pmd-bulk.c +++ b/minimal-examples-lowlevel/ws-client/minimal-ws-client-pmd-bulk/minimal-ws-client-pmd-bulk.c @@ -91,14 +91,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -109,15 +102,13 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal ws client + permessage-deflate + multifragment bulk message\n"); lwsl_user(" needs minimal-ws-server-pmd-bulk running to communicate with\n"); lwsl_user(" %s [-n (no exts)] [-c (compressible)]\n", argv[0]); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = CONTEXT_PORT_NO_LISTEN; info.protocols = protocols; info.pvo = &pvo; diff --git a/minimal-examples-lowlevel/ws-client/minimal-ws-client-rx/minimal-ws-client.c b/minimal-examples-lowlevel/ws-client/minimal-ws-client-rx/minimal-ws-client.c index 8975b9e443..19fb79f8dc 100644 --- a/minimal-examples-lowlevel/ws-client/minimal-ws-client-rx/minimal-ws-client.c +++ b/minimal-examples-lowlevel/ws-client/minimal-ws-client-rx/minimal-ws-client.c @@ -17,14 +17,12 @@ #include enum { - LWS_SW_H2, LWS_SW_D, LWS_SW_T, LWS_SW_HELP, }; static const struct lws_switches switches[] = { - [LWS_SW_H2] = { "--h2", "Enable --h2 feature" }, [LWS_SW_D] = { "-d", "Debug logs (e.g. -d 15)" }, [LWS_SW_T] = { "-t", "Test flag" }, [LWS_SW_HELP] = { "--help", "Show this help information" }, @@ -91,14 +89,7 @@ int main(int argc, const char **argv) struct lws_context_creation_info info; struct lws_client_connect_info i; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, lws - * must have been configured with -DCMAKE_BUILD_TYPE=DEBUG - * instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -108,15 +99,13 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); test = !!lws_cmdline_option(argc, argv, switches[LWS_SW_T].sw); - lws_set_log_level(logs, NULL); - lwsl_user("LWS minimal ws client rx [-d ] [--h2] [-t (test)]\n"); + lwsl_user("LWS minimal ws client rx [-d ] [-t (test)]\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ info.protocols = protocols; @@ -156,9 +145,6 @@ int main(int argc, const char **argv) i.protocol = protocols[0].name; /* "dumb-increment-protocol" */ i.pwsi = &client_wsi; - if (lws_cmdline_option(argc, argv, switches[LWS_SW_H2].sw)) - i.alpn = "h2"; - lws_client_connect_via_info(&i); while (n >= 0 && client_wsi && !interrupted) diff --git a/minimal-examples-lowlevel/ws-client/minimal-ws-client-spam-tx-rx/minimal-ws-client.c b/minimal-examples-lowlevel/ws-client/minimal-ws-client-spam-tx-rx/minimal-ws-client.c index b3f0495d79..8283e6576b 100644 --- a/minimal-examples-lowlevel/ws-client/minimal-ws-client-spam-tx-rx/minimal-ws-client.c +++ b/minimal-examples-lowlevel/ws-client/minimal-ws-client-spam-tx-rx/minimal-ws-client.c @@ -165,8 +165,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; const char *p; - int n = 0, logs = - LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int n = 0; #ifndef WIN32 srandom((unsigned int)time(0)); #endif @@ -182,12 +181,10 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if (lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw)) - logs |= LLL_INFO | LLL_DEBUG; - lws_set_log_level(logs, NULL); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ info.protocols = protocols; diff --git a/minimal-examples-lowlevel/ws-client/minimal-ws-client-spam/minimal-ws-client-spam.c b/minimal-examples-lowlevel/ws-client/minimal-ws-client-spam/minimal-ws-client-spam.c index 75b6a60772..16b8c5c94c 100644 --- a/minimal-examples-lowlevel/ws-client/minimal-ws-client-spam/minimal-ws-client-spam.c +++ b/minimal-examples-lowlevel/ws-client/minimal-ws-client-spam/minimal-ws-client-spam.c @@ -211,13 +211,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -228,13 +222,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal ws client SPAM\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ info.protocols = protocols; diff --git a/minimal-examples-lowlevel/ws-client/minimal-ws-client-tx/minimal-ws-client.c b/minimal-examples-lowlevel/ws-client/minimal-ws-client-tx/minimal-ws-client.c index e56c0b96d5..cd515a1d42 100644 --- a/minimal-examples-lowlevel/ws-client/minimal-ws-client-tx/minimal-ws-client.c +++ b/minimal-examples-lowlevel/ws-client/minimal-ws-client-tx/minimal-ws-client.c @@ -310,14 +310,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -328,14 +321,12 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal ws client tx\n"); lwsl_user(" Run minimal-ws-broker and browse to that\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ info.protocols = protocols; /* diff --git a/minimal-examples-lowlevel/ws-client/minimal-ws-client/minimal-ws-client.c b/minimal-examples-lowlevel/ws-client/minimal-ws-client/minimal-ws-client.c index 034b6d44bf..4ae80ad2be 100644 --- a/minimal-examples-lowlevel/ws-client/minimal-ws-client/minimal-ws-client.c +++ b/minimal-examples-lowlevel/ws-client/minimal-ws-client/minimal-ws-client.c @@ -97,6 +97,12 @@ connect_client(lws_sorted_usec_list_t *sul) i.retry_and_idle_policy = &retry; i.userdata = m; +#if defined(LWS_ROLE_H3) + if (lws_cmdline_option_cx(context, "--h3")) { + i.alpn = "h3"; + } +#endif + if (!lws_client_connect_via_info(&i)) /* * Failed... schedule a retry... we can't use the _retry_wsi() @@ -186,8 +192,7 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - memset(&info, 0, sizeof info); - lws_cmdline_option_handle_builtin(argc, argv, &info); + lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info); lwsl_user("LWS minimal ws client\n"); diff --git a/minimal-examples-lowlevel/ws-server/minimal-ws-broker/minimal-ws-broker.c b/minimal-examples-lowlevel/ws-server/minimal-ws-broker/minimal-ws-broker.c index 08057cf9c4..d2dbebb0a3 100644 --- a/minimal-examples-lowlevel/ws-server/minimal-ws-broker/minimal-ws-broker.c +++ b/minimal-examples-lowlevel/ws-server/minimal-ws-broker/minimal-ws-broker.c @@ -57,14 +57,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -75,13 +68,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal ws broker | visit http://localhost:7681\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.mounts = &mount; info.protocols = protocols; diff --git a/minimal-examples-lowlevel/ws-server/minimal-ws-raw-proxy/minimal-ws-raw-proxy.c b/minimal-examples-lowlevel/ws-server/minimal-ws-raw-proxy/minimal-ws-raw-proxy.c index 48a750f330..945139eba0 100644 --- a/minimal-examples-lowlevel/ws-server/minimal-ws-raw-proxy/minimal-ws-raw-proxy.c +++ b/minimal-examples-lowlevel/ws-server/minimal-ws-raw-proxy/minimal-ws-raw-proxy.c @@ -409,14 +409,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -427,13 +420,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal ws-raw proxy | visit http://localhost:7681 (-s = use TLS / https)\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.mounts = &mount; info.protocols = protocols; diff --git a/minimal-examples-lowlevel/ws-server/minimal-ws-server-echo/minimal-ws-server-echo.c b/minimal-examples-lowlevel/ws-server/minimal-ws-server-echo/minimal-ws-server-echo.c index e4a61333c9..9f83198d92 100644 --- a/minimal-examples-lowlevel/ws-server/minimal-ws-server-echo/minimal-ws-server-echo.c +++ b/minimal-examples-lowlevel/ws-server/minimal-ws-server-echo/minimal-ws-server-echo.c @@ -84,13 +84,7 @@ int main(int argc, const char **argv) struct lws_context_creation_info info; struct lws_context *context; const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -101,10 +95,7 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal ws client echo + permessage-deflate + multifragment bulk message\n"); lwsl_user(" lws-minimal-ws-client-echo [-n (no exts)] [-p port] [-o (once)]\n"); @@ -115,7 +106,8 @@ int main(int argc, const char **argv) if (lws_cmdline_option(argc, argv, switches[LWS_SW_O].sw)) options |= 1; - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = port; info.protocols = protocols; info.pvo = &pvo; diff --git a/minimal-examples-lowlevel/ws-server/minimal-ws-server-pmd-bulk/minimal-ws-server-pmd-bulk.c b/minimal-examples-lowlevel/ws-server/minimal-ws-server-pmd-bulk/minimal-ws-server-pmd-bulk.c index 4c832fe479..5f5a2f3c37 100644 --- a/minimal-examples-lowlevel/ws-server/minimal-ws-server-pmd-bulk/minimal-ws-server-pmd-bulk.c +++ b/minimal-examples-lowlevel/ws-server/minimal-ws-server-pmd-bulk/minimal-ws-server-pmd-bulk.c @@ -96,14 +96,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -114,14 +107,12 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal ws server + permessage-deflate | visit http://localhost:7681\n"); lwsl_user(" %s [-n (no exts)] [-c (compressible)] [-b (blob)]\n", argv[0]); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.mounts = &mount; info.protocols = protocols; diff --git a/minimal-examples-lowlevel/ws-server/minimal-ws-server-pmd-corner/minimal-ws-server-pmd-corner.c b/minimal-examples-lowlevel/ws-server/minimal-ws-server-pmd-corner/minimal-ws-server-pmd-corner.c index 72f3ca716c..93d75f34e4 100644 --- a/minimal-examples-lowlevel/ws-server/minimal-ws-server-pmd-corner/minimal-ws-server-pmd-corner.c +++ b/minimal-examples-lowlevel/ws-server/minimal-ws-server-pmd-corner/minimal-ws-server-pmd-corner.c @@ -67,14 +67,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -85,13 +78,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal ws server + permessage-deflate Corner Cases | visit http://localhost:7681\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.mounts = &mount; info.protocols = protocols; diff --git a/minimal-examples-lowlevel/ws-server/minimal-ws-server-pmd/minimal-ws-server-pmd.c b/minimal-examples-lowlevel/ws-server/minimal-ws-server-pmd/minimal-ws-server-pmd.c index 96343e97db..a198073040 100644 --- a/minimal-examples-lowlevel/ws-server/minimal-ws-server-pmd/minimal-ws-server-pmd.c +++ b/minimal-examples-lowlevel/ws-server/minimal-ws-server-pmd/minimal-ws-server-pmd.c @@ -67,14 +67,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -85,13 +78,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal ws server + permessage-deflate | visit http://localhost:7681\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.mounts = &mount; info.protocols = protocols; diff --git a/minimal-examples-lowlevel/ws-server/minimal-ws-server-ring/minimal-ws-server-ring.c b/minimal-examples-lowlevel/ws-server/minimal-ws-server-ring/minimal-ws-server-ring.c index 1eac5f6fb9..5f091cb779 100644 --- a/minimal-examples-lowlevel/ws-server/minimal-ws-server-ring/minimal-ws-server-ring.c +++ b/minimal-examples-lowlevel/ws-server/minimal-ws-server-ring/minimal-ws-server-ring.c @@ -57,14 +57,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -75,13 +68,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal ws server (lws_ring) | visit http://localhost:7681\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.mounts = &mount; info.protocols = protocols; diff --git a/minimal-examples-lowlevel/ws-server/minimal-ws-server-threadpool/minimal-ws-server-threadpool.c b/minimal-examples-lowlevel/ws-server/minimal-ws-server-threadpool/minimal-ws-server-threadpool.c index a90b16c29c..e3b8246a74 100644 --- a/minimal-examples-lowlevel/ws-server/minimal-ws-server-threadpool/minimal-ws-server-threadpool.c +++ b/minimal-examples-lowlevel/ws-server/minimal-ws-server-threadpool/minimal-ws-server-threadpool.c @@ -91,14 +91,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -109,13 +102,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal ws server + threadpool | visit http://localhost:7681\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.mounts = &mount; info.protocols = protocols; diff --git a/minimal-examples-lowlevel/ws-server/minimal-ws-server-threads-foreign-libuv-smp/minimal-ws-server.c b/minimal-examples-lowlevel/ws-server/minimal-ws-server-threads-foreign-libuv-smp/minimal-ws-server.c index dbfbdf8cfe..348131628e 100644 --- a/minimal-examples-lowlevel/ws-server/minimal-ws-server-threads-foreign-libuv-smp/minimal-ws-server.c +++ b/minimal-examples-lowlevel/ws-server/minimal-ws-server-threads-foreign-libuv-smp/minimal-ws-server.c @@ -133,12 +133,11 @@ signal_cb(uv_signal_t *watcher, int signum) int main(int argc, const char **argv) { - int n, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + int n; pthread_t pthread_service[COUNT_THREADS]; struct lws_context_creation_info info; void *foreign_loops[COUNT_THREADS]; int actual_threads; - const char *p; void *retval; (void)switches; @@ -148,10 +147,7 @@ int main(int argc, const char **argv) } - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal ws server + threads + smp | visit http://localhost:7681\n"); for (n = 0; n < COUNT_THREADS; n++) { @@ -164,7 +160,8 @@ int main(int argc, const char **argv) foreign_loops[n] = &loop[n]; } - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.mounts = &mount; info.pcontext = &context; diff --git a/minimal-examples-lowlevel/ws-server/minimal-ws-server-threads-smp/minimal-ws-server.c b/minimal-examples-lowlevel/ws-server/minimal-ws-server-threads-smp/minimal-ws-server.c index d88bf52f36..c610ac10c2 100644 --- a/minimal-examples-lowlevel/ws-server/minimal-ws-server-threads-smp/minimal-ws-server.c +++ b/minimal-examples-lowlevel/ws-server/minimal-ws-server-threads-smp/minimal-ws-server.c @@ -146,9 +146,8 @@ void sigint_handler(int sig) int main(int argc, const char **argv) { - int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + struct lws_context_creation_info info; - const char *p; int n = 0; (void)switches; @@ -160,13 +159,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal ws server + threads + smp | visit http://localhost:7681\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.mounts = &mount; info.protocols = protocols; diff --git a/minimal-examples-lowlevel/ws-server/minimal-ws-server-threads/minimal-ws-server.c b/minimal-examples-lowlevel/ws-server/minimal-ws-server-threads/minimal-ws-server.c index 5b5f8c4b24..ebecba5040 100644 --- a/minimal-examples-lowlevel/ws-server/minimal-ws-server-threads/minimal-ws-server.c +++ b/minimal-examples-lowlevel/ws-server/minimal-ws-server-threads/minimal-ws-server.c @@ -91,14 +91,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -109,13 +102,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal ws server + threads | visit http://localhost:7681\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.mounts = &mount; info.protocols = protocols; diff --git a/minimal-examples-lowlevel/ws-server/minimal-ws-server-timer/minimal-ws-server.c b/minimal-examples-lowlevel/ws-server/minimal-ws-server-timer/minimal-ws-server.c index fd270cfd09..23f238fe9c 100644 --- a/minimal-examples-lowlevel/ws-server/minimal-ws-server-timer/minimal-ws-server.c +++ b/minimal-examples-lowlevel/ws-server/minimal-ws-server-timer/minimal-ws-server.c @@ -94,14 +94,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -112,13 +105,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal ws server | visit http://localhost:7681 (-s = use TLS / https)\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.mounts = &mount; info.protocols = protocols; diff --git a/minimal-examples-lowlevel/ws-server/minimal-ws-server/minimal-ws-server.c b/minimal-examples-lowlevel/ws-server/minimal-ws-server/minimal-ws-server.c index 60313586b3..574f14539b 100644 --- a/minimal-examples-lowlevel/ws-server/minimal-ws-server/minimal-ws-server.c +++ b/minimal-examples-lowlevel/ws-server/minimal-ws-server/minimal-ws-server.c @@ -73,14 +73,7 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; - const char *p; - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */; + int n = 0; (void)switches; if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) { @@ -91,13 +84,11 @@ int main(int argc, const char **argv) signal(SIGINT, sigint_handler); - if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_D].sw))) - logs = atoi(p); - lws_set_log_level(logs, NULL); lwsl_user("LWS minimal ws server | visit http://localhost:7681 (-s = use TLS / https)\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_context_info_defaults(&info, NULL); + lws_cmdline_option_handle_builtin(argc, argv, &info); info.port = 7681; info.mounts = &mount; info.protocols = protocols; diff --git a/plugins/protocol_deaddrop/assets/drop.svg b/plugins/protocol_deaddrop/assets/drop.svg index f413cf054e..ec46a5f61d 100644 --- a/plugins/protocol_deaddrop/assets/drop.svg +++ b/plugins/protocol_deaddrop/assets/drop.svg @@ -48,7 +48,7 @@ - + @@ -58,7 +58,7 @@ - + @@ -72,7 +72,7 @@ - + diff --git a/plugins/protocol_lws_auth_server/assets/admin.css b/plugins/protocol_lws_auth_server/assets/admin.css index b31ddc74eb..58dc78d299 100644 --- a/plugins/protocol_lws_auth_server/assets/admin.css +++ b/plugins/protocol_lws_auth_server/assets/admin.css @@ -64,3 +64,13 @@ input[type="text"] { .form-group.spaced.bottom { margin-bottom: 1.5rem; } .form-label { display: block; } .form-input { width: 100%; padding: 0.5rem; margin-top: 0.3rem; box-sizing: border-box; } + +.redirect-uri-cell { + word-break: break-all; + font-size: 0.9em; +} + +.protected-admin-label { + color: var(--text-muted); + font-style: italic; +} diff --git a/plugins/protocol_lws_auth_server/assets/admin.js b/plugins/protocol_lws_auth_server/assets/admin.js index c7f5882085..e9d2d13c8e 100644 --- a/plugins/protocol_lws_auth_server/assets/admin.js +++ b/plugins/protocol_lws_auth_server/assets/admin.js @@ -28,7 +28,7 @@ function renderClientsTable(clients) { ${c.client_id} ${c.name} - ${c.redirect_uris} + ${c.redirect_uris} @@ -73,7 +73,7 @@ function renderTable(users) { const isGod = u.grants && u.grants['*'] !== undefined; const actionsHtml = isGod ? - `Protected Administrator` : + `Protected Administrator` : ` `; diff --git a/plugins/protocol_lws_auth_server/assets/auth.css b/plugins/protocol_lws_auth_server/assets/auth.css index 2687baae89..2eeef9189f 100644 --- a/plugins/protocol_lws_auth_server/assets/auth.css +++ b/plugins/protocol_lws_auth_server/assets/auth.css @@ -306,3 +306,34 @@ input:focus { .strike-overlay.show-strike { opacity: 1; } .strike-overlay img { width: 100%; height: 100%; display: block; } .strike-count { position: absolute; bottom: 10px; right: 0px; font-weight: bold; font-size: 20px; color: #ef4444; background-color: rgba(0, 0, 0, 0.7); padding: 4px 8px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.5); border: 1px solid rgba(255,255,255,0.1); } + +/* Strict CSP Helpers */ +.text-center { text-align: center; } +.js-req-title { color: #f8fafc; font-size: 1.1rem; margin-bottom: 10px; } +.forgot-btn-wrap { margin-top: 5px; } +.forgot-btn { font-size: 0.9em; opacity: 0.8; } +.qr-hint { font-size: 0.9em; margin-top: -10px; opacity: 0.8; } +.auth-divider { opacity: 0.2; margin: 20px 0; border: 0; border-top: 1px solid #fff; } + +/* Backup codes container styles */ +.backup-codes-container { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin: 20px 0; background: rgba(0,0,0,0.1); padding: 15px; border-radius: 8px; } +.backup-code { font-family: monospace; font-size: 1.2em; letter-spacing: 2px; text-align: center; background: rgba(255,255,255,0.05); padding: 5px; border-radius: 4px; } +.warning-text { color: #ff6b6b; font-size: 0.9em; margin-bottom: 20px; } + +.redirect-whitelist-error { + font-size: 0.8rem; + color: #94a3b8; + text-align: center; + margin-top: 10px; +} + +.mb-0 { + margin-bottom: 0 !important; +} + +.auto-redirect-msg { + text-align: center; + color: var(--text-muted); + font-size: 0.9rem; + margin-top: 15px; +} diff --git a/plugins/protocol_lws_auth_server/assets/auth.js b/plugins/protocol_lws_auth_server/assets/auth.js index e417196d31..1660038fd2 100644 --- a/plugins/protocol_lws_auth_server/assets/auth.js +++ b/plugins/protocol_lws_auth_server/assets/auth.js @@ -195,7 +195,7 @@ document.addEventListener('DOMContentLoaded', () => { Security Violation

${errData.error || 'Untrusted Redirect URI'}

-

The specified redirection target is not whitelisted by the network administrator.

+

The specified redirection target is not whitelisted by the network administrator.

`; subtitle.innerText = "Access Blocked"; showNotif('error', errData.error || 'Untrusted Redirect URI'); @@ -643,11 +643,11 @@ document.addEventListener('DOMContentLoaded', () => { if (response.ok) { showNotif('success', 'Device authorized successfully!'); deviceAuthBox.innerHTML = ` -
+
Device Authorized
-

+

You may now close this tab. The device will connect automatically.

`; diff --git a/plugins/protocol_lws_auth_server/assets/index.html b/plugins/protocol_lws_auth_server/assets/index.html index aad000876b..fbacfe53d6 100644 --- a/plugins/protocol_lws_auth_server/assets/index.html +++ b/plugins/protocol_lws_auth_server/assets/index.html @@ -28,11 +28,9 @@

Authentication Server

- + @@ -99,7 +97,7 @@

Setup Authenticator