From 4daeb1310fe7ef40348284311d1b969e8f957e86 Mon Sep 17 00:00:00 2001 From: Andy Green Date: Thu, 28 Aug 2025 10:48:58 +0100 Subject: [PATCH 01/15] lws_humanize_pad --- include/libwebsockets/lws-misc.h | 9 +++++- lib/core/libwebsockets.c | 51 ++++++++++++++++++++++++++++---- lib/misc/lws-struct-sqlite.c | 2 +- 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/include/libwebsockets/lws-misc.h b/include/libwebsockets/lws-misc.h index 1118165bd8..12ee7c731a 100644 --- a/include/libwebsockets/lws-misc.h +++ b/include/libwebsockets/lws-misc.h @@ -1016,13 +1016,20 @@ lws_assert_fourcc(uint32_t fourcc, uint32_t expected); * which represents a count of us as a human-readable time like " 14.350min", * or " 1.500d". * - * You can produce your own schema. + * You can produce your own schema tables. + * + * lws_humanize_pad() is the same but pads the lhs so that it + * always produces the same length. */ LWS_VISIBLE LWS_EXTERN int lws_humanize(char *buf, size_t len, uint64_t value, const lws_humanize_unit_t *schema); +LWS_VISIBLE LWS_EXTERN int +lws_humanize_pad(char *p, size_t len, uint64_t v, + const lws_humanize_unit_t *schema); + LWS_VISIBLE LWS_EXTERN void lws_ser_wu16be(uint8_t *b, uint16_t u); diff --git a/lib/core/libwebsockets.c b/lib/core/libwebsockets.c index 7ae41f7584..c57c9bd6e5 100644 --- a/lib/core/libwebsockets.c +++ b/lib/core/libwebsockets.c @@ -1762,23 +1762,37 @@ int lws_humanize(char *p, size_t len, uint64_t v, const lws_humanize_unit_t *schema) { char *obuf = p, *end = p + len; + const lws_humanize_unit_t *s = NULL; do { if (v >= schema->factor || schema->factor == 1) { + if (schema[1].name) + s = &schema[1]; + if (schema->factor == 1) { p += decim(p, v, 4, 0); p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), - "%s", schema->name); + "%s", schema->name); return lws_ptr_diff(p, obuf); } p += decim(p, v / schema->factor, 4, 0); - *p++ = '.'; - p += decim(p, (v % schema->factor) / - (schema->factor / 1000), 3, 1); - + if (s) { + uint64_t iif = schema->factor / s->factor; + + if (s->factor * 1000 == schema->factor || + s->factor * 1024 == schema->factor) { /* decimal */ + *p++ = '.'; + p += decim(p, (v % schema->factor) / + (schema->factor / 1000), 3, 1); + } else { /* imperial fraction, eg, h:m */ + *p++ = ':'; + p += decim(p, (v % schema->factor) / s->factor, + iif >= 100 ? 3 : (iif >= 10 ? 2 : 1), 1); + } + } p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), - "%s", schema->name); + "%s", schema->name); return lws_ptr_diff(p, obuf); } schema++; @@ -1790,6 +1804,31 @@ lws_humanize(char *p, size_t len, uint64_t v, const lws_humanize_unit_t *schema) return 0; } +int +lws_humanize_pad(char *p, size_t len, uint64_t v, const lws_humanize_unit_t *schema) +{ + size_t m, w = 0, n = (size_t)lws_humanize(p, len, v, schema); + const lws_humanize_unit_t *s = schema; + int t; + + while (s->name) { + if (strlen(s->name) > w) + w = strlen(s->name); + s++; + } + + m = (3 + 1 + 3 + w) - (size_t)n; + + for (t = (int)n - 1; t >= 0; t--) + p[(size_t)t + m] = p[t]; + p[m + n] = '\0'; + + for (t = 0; t < (int)m; t++) + p[t] = ' '; + + return (int)(n + m); +} + /* * -1 = fail * 0 = continue diff --git a/lib/misc/lws-struct-sqlite.c b/lib/misc/lws-struct-sqlite.c index f64192fb8c..e71403874e 100644 --- a/lib/misc/lws-struct-sqlite.c +++ b/lib/misc/lws-struct-sqlite.c @@ -662,7 +662,7 @@ lws_struct_sq3_create_table(sqlite3 *pdb, const lws_struct_map_t *schema) p += lws_snprintf(p, (unsigned int)lws_ptr_diff(end, p), ");"); if (sqlite3_exec(pdb, s, NULL, NULL, NULL) != SQLITE_OK) { - lwsl_err("%s: %s: fail\n", __func__, sqlite3_errmsg(pdb)); + lwsl_err("%s: %s: fail: %s\n", __func__, sqlite3_errmsg(pdb), s); return -1; } From f8b9f58532a299be3fba81d0fa0d4389022e17b2 Mon Sep 17 00:00:00 2001 From: Andy Green Date: Thu, 28 Aug 2025 13:44:14 +0000 Subject: [PATCH 02/15] Fix deaddrop upload issues Co-developed-by: Gemini 2.5 Pro --- plugins/deaddrop/protocol_lws_deaddrop.c | 43 +++++++++++------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/plugins/deaddrop/protocol_lws_deaddrop.c b/plugins/deaddrop/protocol_lws_deaddrop.c index 2a2cce4866..57c8e43c79 100644 --- a/plugins/deaddrop/protocol_lws_deaddrop.c +++ b/plugins/deaddrop/protocol_lws_deaddrop.c @@ -371,20 +371,13 @@ file_upload_cb(void *data, const char *name, const char *filename, static int format_result(struct pss_deaddrop *pss) { - unsigned char *p, *start, *end; - - p = (unsigned char *)pss->result + LWS_PRE; - start = p; - end = p + sizeof(pss->result) - LWS_PRE - 1; - - p += lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p), - "" - "" - ""); - p += lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p), ""); - - return (int)lws_ptr_diff(p, start); + /* + * We don't want to send any entity body back for the upload + * POST. The success / failure is indicated by the + * HTTP response code. The javascript on the client side that + * did the post is not expecting to navigate to a new page. + */ + return 0; } @@ -463,7 +456,7 @@ handler_server_protocol_destroy(struct vhd_deaddrop *vhd) #endif } -static void +static int handler_server_http(struct vhd_deaddrop *vhd, struct pss_deaddrop *pss, struct lws *wsi) { @@ -484,12 +477,14 @@ handler_server_http(struct vhd_deaddrop *vhd, struct pss_deaddrop *pss, meth = lws_http_get_uri_and_method(wsi, &uri_ptr, &uri_len); if (meth != LWSHUMETH_POST || !uri_ptr) - return; + return 1; if (!strstr(uri_ptr, "/upload/")) - return; + return 1; pss->vhd = vhd; pss->wsi = wsi; + + return 0; } static int @@ -566,8 +561,8 @@ handler_server_http_writeable(struct vhd_deaddrop *vhd, /* first send the headers ... */ n = lws_write(wsi, start, lws_ptr_diff_size_t(p, start), - LWS_WRITE_HTTP_HEADERS | - LWS_WRITE_H2_STREAM_END); + LWS_WRITE_HTTP_HEADERS );//| + //LWS_WRITE_H2_STREAM_END); if (n < 0) return 1; @@ -863,7 +858,8 @@ callback_deaddrop(struct lws *wsi, enum lws_callback_reasons reason, break; case LWS_CALLBACK_HTTP: - handler_server_http(vhd, pss, wsi); + if (!handler_server_http(vhd, pss, wsi)) + return 0; break; case LWS_CALLBACK_PROTOCOL_DESTROY: @@ -889,7 +885,8 @@ callback_deaddrop(struct lws *wsi, enum lws_callback_reasons reason, break; case LWS_CALLBACK_SERVER_WRITEABLE: - return handler_server_ws_writeable(vhd, pss, wsi); + handler_server_ws_writeable(vhd, pss, wsi); + return 0; /* POST-related */ @@ -898,12 +895,12 @@ callback_deaddrop(struct lws *wsi, enum lws_callback_reasons reason, case LWS_CALLBACK_HTTP_BODY_COMPLETION: handler_server_http_body_completion(pss, wsi); - break; + return 0; case LWS_CALLBACK_HTTP_WRITEABLE: switch (handler_server_http_writeable(vhd, pss, wsi)) { case 0: - break; + return 0; case 1: goto bail; case 2: From 3f64f64363b6cee9a474076e51ea7eb4416e8c5a Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 14:52:13 +0000 Subject: [PATCH 03/15] Fix deaddrop uploads and Windows spawn build errors This commit contains fixes for two separate issues: 1. Deaddrop upload issues: - The UI spinner would hang after a successful upload. - The initial fix for this also addresses 302/404 redirects seen on some platforms. The root cause was incorrect HTTP response handling for the upload POST request. This is fixed by simplifying the response handler to send a standard, headers-only 200 OK response, and by ensuring the request doesn't fall through to the dummy http handler. 2. Windows spawn build errors: - The file `lib/plat/windows/windows-spawn.c` failed to build due to undeclared identifiers and incomplete struct definitions. This is fixed by including the necessary `Psapi.h` header and by adding the missing `ft_create` and `ft_exit` members to the `lws_spawn_piped` struct definition for WIN32 builds. --- lib/core-net/private-lib-core-net.h | 2 ++ lib/plat/windows/windows-spawn.c | 9 ++++++++- .../api-tests/api-test-lws_spawn/main.c | 19 ++++++++++++++++--- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/lib/core-net/private-lib-core-net.h b/lib/core-net/private-lib-core-net.h index 9351aa8e5c..7948380239 100644 --- a/lib/core-net/private-lib-core-net.h +++ b/lib/core-net/private-lib-core-net.h @@ -946,6 +946,8 @@ struct lws_spawn_piped { #if defined(WIN32) HANDLE child_pid; lws_sorted_usec_list_t sul_poll; + FILETIME ft_create; + FILETIME ft_exit; #else pid_t child_pid; diff --git a/lib/plat/windows/windows-spawn.c b/lib/plat/windows/windows-spawn.c index b6df4266f5..2519fb9706 100644 --- a/lib/plat/windows/windows-spawn.c +++ b/lib/plat/windows/windows-spawn.c @@ -27,6 +27,7 @@ #include #include #include +#include void lws_spawn_timeout(struct lws_sorted_usec_list *sul) @@ -153,7 +154,7 @@ lws_spawn_reap(struct lws_spawn_piped *lsp) lsp_cb_t cb = lsp->info.reap_cb; struct _lws_siginfo_t lsi; PROCESS_MEMORY_COUNTERS pmc; - IO_COUNTERS ic; + // IO_COUNTERS ic; ULARGE_INTEGER uli; FILETIME ftk, ftu; DWORD ex; @@ -624,3 +625,9 @@ lws_spawn_get_fd_stdxxx(struct lws_spawn_piped *lsp, int std_idx) return (int)(intptr_t)lsp->pipe_fds[std_idx][!!(std_idx == 0)]; } + +int +lws_spawn_prepare_self_cgroup(const char *user, const char *group) +{ + return 0; +} diff --git a/minimal-examples-lowlevel/api-tests/api-test-lws_spawn/main.c b/minimal-examples-lowlevel/api-tests/api-test-lws_spawn/main.c index 3c5f0e27c6..0aa3f3fe35 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-lws_spawn/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-lws_spawn/main.c @@ -166,8 +166,13 @@ reap_cb(void *opaque, const lws_spawn_resource_us_t *res, siginfo_t *si, test_phase_t last_phase = ts->phase; if (si) { +#if defined(WIN32) + lwsl_user("%s: Reap callback for phase %d, exit code %d\n", + __func__, (int)last_phase, (int)si->retcode); +#else lwsl_user("%s: Reap callback for phase %d, exit code %d\n", __func__, (int)last_phase, si->si_status); +#endif lwsl_notice(" CPU us: user %llu, sys %llu\n", (unsigned long long)res->us_cpu_user, (unsigned long long)res->us_cpu_sys); @@ -179,10 +184,18 @@ reap_cb(void *opaque, const lws_spawn_resource_us_t *res, siginfo_t *si, lwsl_err("%s: Spawned process was killed by timeout\n", __func__); ts->result = 1; - } else if (si->si_status != 0) { - lwsl_err("%s: Spawned process failed with exit code %d\n", + } else { +#if defined(WIN32) + if (si->retcode != 0) { + lwsl_err("%s: Spawned process failed with exit code %d\n", + __func__, (int)si->retcode); +#else + if (si->si_status != 0) { + lwsl_err("%s: Spawned process failed with exit code %d\n", __func__, si->si_status); - ts->result = 1; +#endif + ts->result = 1; + } } if (res->us_cpu_user == 0 && res->us_cpu_sys == 0) { From c57384ad39be142c392a3c48a951c8c61b3aeace Mon Sep 17 00:00:00 2001 From: Andy Green Date: Fri, 29 Aug 2025 15:36:28 +0100 Subject: [PATCH 04/15] ss: allow unreachable-unreachable --- lib/secure-streams/secure-streams.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/secure-streams/secure-streams.c b/lib/secure-streams/secure-streams.c index d973bf84fc..c8dd81a1b6 100644 --- a/lib/secure-streams/secure-streams.c +++ b/lib/secure-streams/secure-streams.c @@ -101,6 +101,7 @@ const uint32_t ss_state_txn_validity[] = { [LWSSSCS_UNREACHABLE] = (1 << LWSSSCS_ALL_RETRIES_FAILED) | (1 << LWSSSCS_TIMEOUT) | (1 << LWSSSCS_POLL) | + (1 << LWSSSCS_UNREACHABLE) | (1 << LWSSSCS_CONNECTING) | /* win conn failure > retry > succ */ (1 << LWSSSCS_CONNECTED) | From cb2ba8c1536fefa47a8867578a6d2522609690d0 Mon Sep 17 00:00:00 2001 From: Andy Green Date: Sun, 31 Aug 2025 05:51:38 +0100 Subject: [PATCH 05/15] codeql: update to v3 --- .github/workflows/codeql.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index aa6fa72f40..f1b9ee82d0 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -27,17 +27,17 @@ jobs: uses: actions/checkout@v3 - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} config-file: ./.github/codeql.yml queries: +security-and-quality - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 if: ${{ matrix.language == 'javascript' || matrix.language == 'cpp' }} - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: "/language:${{ matrix.language }}" From c6618c854cd3d277eab1c9b553902a55a350c2f7 Mon Sep 17 00:00:00 2001 From: Andy Green Date: Sun, 31 Aug 2025 05:00:37 +0100 Subject: [PATCH 06/15] ctest: allow larger range of SAI_INSTANCE_IDX --- READMEs/README.ctest.md | 20 +++++++------------ .../minimal-http-client-multi/CMakeLists.txt | 14 ++----------- .../minimal-http-client-post/CMakeLists.txt | 15 +++----------- .../CMakeLists.txt | 13 ++---------- .../minimal-ws-client-spam/CMakeLists.txt | 17 ++++------------ 5 files changed, 18 insertions(+), 61 deletions(-) diff --git a/READMEs/README.ctest.md b/READMEs/README.ctest.md index 9fdb1ee7b6..2b905a07c8 100644 --- a/READMEs/README.ctest.md +++ b/READMEs/README.ctest.md @@ -309,14 +309,14 @@ For tests with local buddies using tcp sockets inside the same VM or systemd- nspawn networking context, you cannot just use a well-known port like 7681. ctest itself is usually executed concurrently, and Sai is typically building -multiple different instances concurrently as well (typically 3), so it may be +multiple different instances concurrently as well (perhaps dozens), so it may be running different ctests inside the same VM simultaneously. Different tests can have their own convention for port ranges, to solve the problem about Sai running different tests concurrently inside one ctest. For the case there are multiple ctests running, we can use the env var -`$ENV{SAI_INSTANCE_IDX}`, which is an ordinal like 0 or 1, to further ensure +`$ENV{SAI_INSTANCE_IDX}`, which is an ordinal like 0 or 1 or 33, to further ensure that port selections won't conflict. If not using Sai, you can just set this in the evironment yourself to reflect your build instance index. @@ -326,20 +326,14 @@ in the evironment yourself to reflect your build instance index. # machine context in parallel so they can tread on each other otherwise # set(PORT_HCM_SRV "7670") - if ("$ENV{SAI_INSTANCE_IDX}" STREQUAL "0") - set(PORT_HCM_SRV 7671) - endif() - if ("$ENV{SAI_INSTANCE_IDX}" STREQUAL "1") - set(PORT_HCM_SRV 7672) - endif() - if ("$ENV{SAI_INSTANCE_IDX}" STREQUAL "2") - set(PORT_HCM_SRV 7673) - endif() - if ("$ENV{SAI_INSTANCE_IDX}" STREQUAL "3") - set(PORT_HCM_SRV 7674) + if ("$ENV{SAI_INSTANCE_IDX}") + math(EXPR PORT_HCM_SRV "7671 + $ENV{SAI_INSTANCE_IDX}") endif() ``` +The default value is for the case you are running the test manually, and not +under Sai. + This is complicated enough that the best approach is copy an existing simple case like the CMakeLists.txt for minimal-http-client and change the names and ports to be unique. 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 871e1263be..8c51837c21 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 @@ -33,19 +33,9 @@ if (requirements) # machine context in parallel so they can tread on each other otherwise # set(PORT_HCM_SRV "7670") - if ("$ENV{SAI_INSTANCE_IDX}" STREQUAL "0") - set(PORT_HCM_SRV 7671) + if ("$ENV{SAI_INSTANCE_IDX}") + math(EXPR PORT_HCM_SRV "7671 + $ENV{SAI_INSTANCE_IDX}") endif() - if ("$ENV{SAI_INSTANCE_IDX}" STREQUAL "1") - set(PORT_HCM_SRV 7672) - endif() - if ("$ENV{SAI_INSTANCE_IDX}" STREQUAL "2") - set(PORT_HCM_SRV 7673) - endif() - if ("$ENV{SAI_INSTANCE_IDX}" STREQUAL "3") - set(PORT_HCM_SRV 7674) - endif() - # hack if (NOT WIN32 AND LWS_WITH_SERVER) diff --git a/minimal-examples-lowlevel/http-client/minimal-http-client-post/CMakeLists.txt b/minimal-examples-lowlevel/http-client/minimal-http-client-post/CMakeLists.txt index 1efd356413..9d73823b35 100644 --- a/minimal-examples-lowlevel/http-client/minimal-http-client-post/CMakeLists.txt +++ b/minimal-examples-lowlevel/http-client/minimal-http-client-post/CMakeLists.txt @@ -29,19 +29,10 @@ if (requirements) # machine context in parallel so they can tread on each other otherwise # set(PORT_HCP_SRV "7640") - if ("$ENV{SAI_INSTANCE_IDX}" STREQUAL "0") - set(PORT_HCP_SRV 7641) + if ("$ENV{SAI_INSTANCE_IDX}") + math(EXPR PORT_HCP_SRV "7671 + $ENV{SAI_INSTANCE_IDX}") endif() - if ("$ENV{SAI_INSTANCE_IDX}" STREQUAL "1") - set(PORT_HCP_SRV 7642) - endif() - if ("$ENV{SAI_INSTANCE_IDX}" STREQUAL "2") - set(PORT_HCP_SRV 7643) - endif() - if ("$ENV{SAI_INSTANCE_IDX}" STREQUAL "3") - set(PORT_HCP_SRV 7644) - endif() - + # hack if (NOT WIN32 AND LWS_WITH_SERVER) diff --git a/minimal-examples-lowlevel/http-server/minimal-http-server-eventlib-foreign/CMakeLists.txt b/minimal-examples-lowlevel/http-server/minimal-http-server-eventlib-foreign/CMakeLists.txt index 02b16f8f53..5f5110d020 100644 --- a/minimal-examples-lowlevel/http-server/minimal-http-server-eventlib-foreign/CMakeLists.txt +++ b/minimal-examples-lowlevel/http-server/minimal-http-server-eventlib-foreign/CMakeLists.txt @@ -111,17 +111,8 @@ if (requirements) # set(PORT_HSEF_SRV "961") - if ("$ENV{SAI_INSTANCE_IDX}" STREQUAL "0") - set(PORT_HSEF_SRV 962) - endif() - if ("$ENV{SAI_INSTANCE_IDX}" STREQUAL "1") - set(PORT_HSEF_SRV 963) - endif() - if ("$ENV{SAI_INSTANCE_IDX}" STREQUAL "2") - set(PORT_HSEF_SRV 964) - endif() - if ("$ENV{SAI_INSTANCE_IDX}" STREQUAL "3") - set(PORT_HSEF_SRV 965) + if ("$ENV{SAI_INSTANCE_IDX}") + math(EXPR PORT_HSEF_SRV "962 + $ENV{SAI_INSTANCE_IDX}") endif() if (websockets_shared) diff --git a/minimal-examples-lowlevel/ws-client/minimal-ws-client-spam/CMakeLists.txt b/minimal-examples-lowlevel/ws-client/minimal-ws-client-spam/CMakeLists.txt index c39fcb322d..14132a2728 100644 --- a/minimal-examples-lowlevel/ws-client/minimal-ws-client-spam/CMakeLists.txt +++ b/minimal-examples-lowlevel/ws-client/minimal-ws-client-spam/CMakeLists.txt @@ -27,19 +27,10 @@ if (requirements) # instantiate the server per sai builder instance, they are running in the same # machine context in parallel so they can tread on each other otherwise # - set(PORT_WCS_SRV "7620") - if ("$ENV{SAI_INSTANCE_IDX}" STREQUAL "0") - set(PORT_WCS_SRV 7621) - endif() - if ("$ENV{SAI_INSTANCE_IDX}" STREQUAL "1") - set(PORT_WCS_SRV 7622) - endif() - if ("$ENV{SAI_INSTANCE_IDX}" STREQUAL "2") - set(PORT_WCS_SRV 7623) - endif() - if ("$ENV{SAI_INSTANCE_IDX}" STREQUAL "3") - set(PORT_WCS_SRV 7624) - endif() + set(PORT_WCS_SRV "7620") + if ("$ENV{SAI_INSTANCE_IDX}") + math(EXPR PORT_WCS_SRV "7621 + $ENV{SAI_INSTANCE_IDX}") + endif() # hack if (WIN32) From b1735016b1c56869611da3db3a9640e1ebe00de5 Mon Sep 17 00:00:00 2001 From: Andy Green Date: Sun, 31 Aug 2025 10:12:23 +0100 Subject: [PATCH 07/15] ws: introduce write sequence validation --- include/libwebsockets/lws-ws-state.h | 13 ++++++++++-- lib/roles/ws/ops-ws.c | 30 ++++++++++++++++++++++++++++ lib/roles/ws/private-lib-roles-ws.h | 4 ++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/include/libwebsockets/lws-ws-state.h b/include/libwebsockets/lws-ws-state.h index b1cc54633f..e5b7cc9474 100644 --- a/include/libwebsockets/lws-ws-state.h +++ b/include/libwebsockets/lws-ws-state.h @@ -38,7 +38,7 @@ LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT lws_send_pipe_choked(struct lws *wsi); /** - * lws_is_final_fragment() - tests if last part of ws message + * lws_is_final_fragment() - tests if received fragment is last part of ws message * * \param wsi: lws connection */ @@ -46,13 +46,22 @@ LWS_VISIBLE LWS_EXTERN int lws_is_final_fragment(struct lws *wsi); /** - * lws_is_first_fragment() - tests if first part of ws message + * lws_is_first_fragment() - tests if received fragment is first part of ws message * * \param wsi: lws connection */ LWS_VISIBLE LWS_EXTERN int lws_is_first_fragment(struct lws *wsi); +/** + * lws_ws_sending_multifragment() - return 1 if we are in the middle of sending a multi-fragment message + * + * \param wsi: lws connection + */ + +LWS_VISIBLE LWS_EXTERN int +lws_ws_sending_multifragment(struct lws *wsi); + /** * lws_get_reserved_bits() - access reserved bits of ws frame * \param wsi: lws connection diff --git a/lib/roles/ws/ops-ws.c b/lib/roles/ws/ops-ws.c index af7fd27de2..80bc6d4461 100644 --- a/lib/roles/ws/ops-ws.c +++ b/lib/roles/ws/ops-ws.c @@ -915,6 +915,11 @@ lws_server_init_wsi_for_ws(struct lws *wsi) } +int +lws_ws_sending_multifragment(struct lws *wsi) +{ + return wsi->ws->last_valid && !wsi->ws->last_fin; +} int lws_is_final_fragment(struct lws *wsi) @@ -1830,12 +1835,37 @@ rops_write_role_protocol_ws(struct lws *wsi, unsigned char *buf, size_t len, switch ((*wp) & 0xf) { case LWS_WRITE_TEXT: n = LWSWSOPC_TEXT_FRAME; + if (wsi->ws->last_valid && !wsi->ws->last_fin) { + lwsl_wsi_err(wsi, "Sending TEXT after previous frame that lacked FIN"); + assert(0); + } + wsi->ws->last_valid = 1; + wsi->ws->last_opcode = (uint8_t)n; + wsi->ws->last_fin = !((*wp) & LWS_WRITE_NO_FIN); break; case LWS_WRITE_BINARY: n = LWSWSOPC_BINARY_FRAME; + if (wsi->ws->last_valid && !wsi->ws->last_fin) { + lwsl_wsi_err(wsi, "Sending BINARY after previous frame that lacked FIN"); + assert(0); + } + wsi->ws->last_valid = 1; + wsi->ws->last_opcode = (uint8_t)n; + wsi->ws->last_fin = !((*wp) & LWS_WRITE_NO_FIN); break; case LWS_WRITE_CONTINUATION: n = LWSWSOPC_CONTINUATION; + if (wsi->ws->last_valid && wsi->ws->last_fin) { + lwsl_wsi_err(wsi, "Sending CONTINUATION after previous frame that had FIN"); + assert(0); + } + if (!wsi->ws->last_valid) { + lwsl_wsi_err(wsi, "Sending CONTINUATION as first frame"); + assert(0); + } + wsi->ws->last_valid = 1; + wsi->ws->last_opcode = (uint8_t)n; + wsi->ws->last_fin = !((*wp) & LWS_WRITE_NO_FIN); break; case LWS_WRITE_CLOSE: diff --git a/lib/roles/ws/private-lib-roles-ws.h b/lib/roles/ws/private-lib-roles-ws.h index 9bb8c840c0..573d1bb04e 100644 --- a/lib/roles/ws/private-lib-roles-ws.h +++ b/lib/roles/ws/private-lib-roles-ws.h @@ -137,6 +137,10 @@ struct _lws_websocket_related { uint32_t rx_ubuf_head; uint32_t rx_ubuf_alloc; + uint8_t last_valid; + uint8_t last_opcode; /* for outgoing state validation */ + uint8_t last_fin; /* for outgoing state validation */ + uint8_t pong_payload_len; uint8_t mask_idx; uint8_t opcode; From 310078dd3ed6f56a5a6ada5f72be649fe405272b Mon Sep 17 00:00:00 2001 From: Andy Green Date: Mon, 1 Sep 2025 05:49:43 +0100 Subject: [PATCH 08/15] buflist: lws_buflist_get_frag_start_or_NULL --- include/libwebsockets/lws-misc.h | 22 +++++++++++++++++++++- lib/core/buflist.c | 14 ++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/include/libwebsockets/lws-misc.h b/include/libwebsockets/lws-misc.h index 12ee7c731a..fec9a41ad5 100644 --- a/include/libwebsockets/lws-misc.h +++ b/include/libwebsockets/lws-misc.h @@ -136,7 +136,7 @@ lws_buflist_linear_use(struct lws_buflist **head, uint8_t *buf, size_t len); * lws_buflist_fragment_use(): copy and consume <= 1 frag from buflist head * * \param head: list head - * \param buf: buffer to copy linearly into + * \param buf: NULL, buffer to copy linearly into * \param len: length of buffer available * \param frag_first: pointer to char written on exit to if this is start of frag * \param frag_fin: pointer to char written on exit to if this is end of frag @@ -148,6 +148,10 @@ lws_buflist_linear_use(struct lws_buflist **head, uint8_t *buf, size_t len); * Since it was consumed, calling again will resume copying out and consuming * from as far as it got the first time. * + * It's legal for buf to be NULL and / or len = 0. In this case nothing is + * "used" and the effect is to set `frag_first` according to if we are at the + * start of the fragment and 0 is returned. + * * Returns the number of bytes written into \p buf. */ LWS_VISIBLE LWS_EXTERN int @@ -178,6 +182,22 @@ lws_buflist_destroy_all_segments(struct lws_buflist **head); LWS_VISIBLE LWS_EXTERN void lws_buflist_describe(struct lws_buflist **head, void *id, const char *reason); +/** + * lws_buflist_get_frag_start_or_NULL(): get pointer to start of fragment + * + * \param head: list head + * + * This gets you a pointer to the start of the fragment payload, no matter + * how much of it you may have 'used' already. This is useful for schemes + * where you prepend something to the payload and need to reference it no + * matter how much of it you have consumed or the fragmentation details. + * + * If the buflist is empty, it will return NULL. + */ +LWS_VISIBLE LWS_EXTERN void * +lws_buflist_get_frag_start_or_NULL(struct lws_buflist **head); + + /* * Optional helpers for closely-managed stream flow control. These are useful diff --git a/lib/core/buflist.c b/lib/core/buflist.c index 511330ca34..24570dff09 100644 --- a/lib/core/buflist.c +++ b/lib/core/buflist.c @@ -239,6 +239,9 @@ lws_buflist_fragment_use(struct lws_buflist **head, uint8_t *buf, if (frag_fin) *frag_fin = (*head)->pos + s == (*head)->len; + if (!buf || !len) + return 0; + memcpy(buf, ((uint8_t *)((*head) + 1)) + LWS_PRE + (*head)->pos, s); len -= s; buf += s; @@ -276,6 +279,17 @@ lws_buflist_describe(struct lws_buflist **head, void *id, const char *reason) } #endif +LWS_VISIBLE LWS_EXTERN void * +lws_buflist_get_frag_start_or_NULL(struct lws_buflist **head) +{ + struct lws_buflist *b = (*head); + + if (!b) + return NULL; /* there is no segment to work on */ + + return ((uint8_t *)b) + sizeof(*b) + LWS_PRE; +} + lws_stateful_ret_t lws_flow_feed(lws_flow_t *flow) { From 91d4c952e53712cf306fa74f443ceee88fe214a0 Mon Sep 17 00:00:00 2001 From: Yassine MADDOURI Date: Wed, 3 Sep 2025 20:11:11 +0200 Subject: [PATCH 09/15] boringssl: Undefine conflicting wincrypt.h symbols for MSVC --- lib/plat/windows/private-lib-plat-windows.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/plat/windows/private-lib-plat-windows.h b/lib/plat/windows/private-lib-plat-windows.h index ef9a8d1b89..bb8778af99 100644 --- a/lib/plat/windows/private-lib-plat-windows.h +++ b/lib/plat/windows/private-lib-plat-windows.h @@ -70,6 +70,12 @@ #if defined(LWS_WITH_TLS) #include +#if defined(LWS_WITH_BORINGSSL) + /* Undefine wincrypt.h symbols that conflict with BoringSSL */ + #undef X509_NAME + #undef X509_EXTENSIONS + #undef PKCS7_SIGNER_INFO +#endif #endif #if defined(LWS_HAVE_PTHREAD_H) From 812c538bcf990d5d23e80a0599e4c9398e9acaf6 Mon Sep 17 00:00:00 2001 From: Andy Green Date: Fri, 29 Aug 2025 14:51:55 +0100 Subject: [PATCH 10/15] .sai.json update --- .sai.json | 67 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/.sai.json b/.sai.json index 38420d5d91..9180171454 100644 --- a/.sai.json +++ b/.sai.json @@ -1,40 +1,75 @@ { "schema": "sai-1", - # We're doing separate install into destdir so that the test server - # has somewhere to go to find its /usr/share content like certs - "platforms": { "rocky9/aarch64-a72a55-rk3588/gcc": { - "build": "mkdir build destdir;cd build;export CCACHE_DISABLE=1;export LD_LIBRARY_PATH=../destdir/usr/local/share/libwebsockets-test-server/plugins:../destdir/usr/local/lib;export SAI_CPACK=\"-G RPM\";cmake .. ${cmake} && make -j6 && rm -rf ../destdir && make -j DESTDIR=../destdir install && ctest -j4 --output-on-failure ${cpack}" + "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 cmake .. ${cmake}", + "cd build && make -j$SAI_PARALLEL && rm -rf ../destdir && make -j$SAI_PARALLEL DESTDIR=../destdir install", + "cd build && ctest -j$SAI_PARALLEL --output-on-failure", + "cd build && SAI_CPACK=\"-G RPM\" ${cpack}" + ] }, "netbsd-OSX-bigsur/x86_64-intel-i3/llvm": { - "build": "mkdir build destdir; cd build; export LD_LIBRARY_PATH=../destdir/usr/local/share/libwebsockets-test-server/plugins:../destdir/usr/local/lib;export SAI_CPACK=\"-G ZIP\";export MACOSX_DEPLOYMENT_TARGET=12.5 ; cmake .. -DCMAKE_MAKE_PROGRAM=/usr/bin/make ${cmake} && make -j4 && make -j DESTDIR=../destdir install && ctest -j4 --output-on-failure ${cpack}" + "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=12.5 cmake .. -DCMAKE_MAKE_PROGRAM=/usr/bin/make ${cmake}", + "cd build && make -j$SAI_PARALLEL && rm -rf ../destdir && make -j$SAI_PARALLEL DESTDIR=../destdir install", + "cd build && ctest -j$SAI_PARALLEL --output-on-failure", + "cd build && SAI_CPACK=\"-G ZIP\" ${cpack}" + ] }, "ubuntu-noble/riscv64/gcc": { - "build": "mkdir build destdir;cd build;export CCACHE_DISABLE=1;export LD_LIBRARY_PATH=../destdir/usr/local/share/libwebsockets-test-server/plugins:../destdir/usr/local/lib;export SAI_CPACK=\"-G DEB\";cmake .. ${cmake} && make -j4 && rm -rf ../destdir && make -j DESTDIR=../destdir install && ctest -j4 --output-on-failure ${cpack}" + "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 cmake .. ${cmake}", + "cd build && make -j$SAI_PARALLEL && rm -rf ../destdir && make -j$SAI_PARALLEL DESTDIR=../destdir install", + "cd build && ctest -j$SAI_PARALLEL --output-on-failure", + "cd build && SAI_CPACK=\"-G DEB\" ${cpack}" + ] }, "rocky9/x86_64-amd/gcc": { - "build": "mkdir build destdir;cd build;export CCACHE_DISABLE=1;export LD_LIBRARY_PATH=../destdir/usr/local/share/libwebsockets-test-server/plugins:../destdir/usr/local/lib;export SAI_CPACK=\"-G RPM\";cmake .. ${cmake} && make -j6 && rm -rf ../destdir && make -j DESTDIR=../destdir install && ctest -j4 --output-on-failure ${cpack}" + "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 cmake .. ${cmake}", + "cd build && make -j$SAI_PARALLEL && rm -rf ../destdir && make -j$SAI_PARALLEL DESTDIR=../destdir install", + "cd build && ctest -j$SAI_PARALLEL --output-on-failure", + "cd build && SAI_CPACK=\"-G RPM\" ${cpack}" + ] }, "linux-ubuntu-2404/aarch64-a72-bcm2711-rpi4/gcc": { - "build": "mkdir build destdir;cd build;export CCACHE_DISABLE=1;export LD_LIBRARY_PATH=../destdir/usr/local/share/libwebsockets-test-server/plugins:../destdir/usr/local/lib;export SAI_CPACK=\"-G DEB\";cmake .. ${cmake} && make -j4 && rm -rf ../destdir && make -j DESTDIR=../destdir install && ctest -j4 --output-on-failure ${cpack}" + "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 cmake .. ${cmake}", + "cd build && make -j$SAI_PARALLEL && rm -rf ../destdir && make -j$SAI_PARALLEL DESTDIR=../destdir install", + "cd build && ctest -j$SAI_PARALLEL --output-on-failure", + "cd build && SAI_CPACK=\"-G DEB\" ${cpack}" + ] }, "netbsd/aarch64BE-bcm2837-a53/gcc": { - "build": "mkdir build destdir;cd build;export LD_LIBRARY_PATH=../destdir/usr/local/share/libwebsockets-test-server/plugins:../destdir/usr/local/lib;export CCACHE_DISABLE=1;cmake .. ${cmake};make -j6 && rm -rf ../destdir && make -j6 DESTDIR=../destdir install && /usr/pkg/bin/ctest -j4 --output-on-failure", - "default": false + "default": false, + "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 cmake .. ${cmake}", + "cd build && make -j$SAI_PARALLEL && rm -rf ../destdir && make -j$SAI_PARALLEL DESTDIR=../destdir install", + "cd build && /usr/pkg/bin/ctest -j$SAI_PARALLEL --output-on-failure" + ] }, "w11/x86_64-amd/msvc": { - "build": "mkdir build && cd build && set SAI_CPACK=\"-G ZIP\" && cmake .. -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} && cmake --build . --config DEBUG && set CTEST_OUTPUT_ON_FAILURE=1 && ctest . -C DEBUG -j1 --output-on-failure", - "default": false + "default": false, + "build": [ + "mkdir -p build; cd build; CCACHE_DISABLE=1 LD_LIBRARY_PATH=../destdir/usr/local/share/libwebsockets-test-server/plugins:../destdir/usr/local/lib cmake .. -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}", + "cd build && cmake --build . --config DEBUG", + "cd build && /usr/pkg/bin/ctest -j$SAI_PARALLEL --output-on-failure", + "cd build && SAI_CPACK=\"-G ZIP\" ${cpack}" + ] }, "freertos-espidf/xl6-esp32/gcc": { - # official way to get sdkconfig.h is idf.py menuconfig, but - # no obvious way to do that in CI - "build": "rm -rf ebuild ; mkdir ebuild; cd ebuild; cp -rp ../minimal-examples/embedded/esp32/${cpack} . ; cd ${cpack} ; . /opt/esp/esp-idf/export.sh ; rm -f libwebsockets ; ln -sf ../.. libwebsockets ; idf.py set-target esp32 && cp libwebsockets/minimal-examples/embedded/esp32/${cpack}/sdkconfig . && cp sdkconfig.h build && idf.py ${cmake} build size size-components size-files && cd build && /usr/local/bin/sai-device ${cpack} ESPPORT=0 ctest --output-on-failure", - "default": false + "default": false, + "build": [ + "rm -rf ebuild ; mkdir ebuild; cd ebuild;cp -rp ../minimal-examples/embedded/esp32/${cpack} . ; cd ${cpack} ; rm -flibwebsockets ; ln -sf ../.. libwebsockets", + "cp libwebsockets/minimal-examples/embedded/esp32/${cpack}/sdkconfig . && cp sdkconfig.h build", + ". /opt/esp/esp-idf/export.sh ; idf.py set-target esp32 && idf.py ${cmake} build size size-components size-files", + "cd build && /usr/local/bin/sai-device ${cpack} ESPPORT=0 ctest --output-on-failure" + ] } }, + "configurations": { "default": { "cmake": "", From c7ff6c68f807d6f52e51a3fb07cc05ef9bf526ee Mon Sep 17 00:00:00 2001 From: Andy Green Date: Thu, 4 Sep 2025 06:59:29 +0100 Subject: [PATCH 11/15] cce: UDS: improve error fidelity --- lib/core-net/client/connect3.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core-net/client/connect3.c b/lib/core-net/client/connect3.c index 3f80f4be5c..59fb52b17e 100644 --- a/lib/core-net/client/connect3.c +++ b/lib/core-net/client/connect3.c @@ -189,7 +189,7 @@ lws_client_connect_3_connect(struct lws *wsi, const char *ads, const char *cce = "Unable to connect", *iface, *local_port; const struct sockaddr *psa = NULL; uint16_t port = wsi->conn_port; - char dcce[48], t16[16]; + char dcce[128], t16[16]; lws_dns_sort_t *curr; ssize_t plen = 0; lws_dll2_t *d; From 9ef5f5f3810ebd3f21e3677724ba9801970fea8f Mon Sep 17 00:00:00 2001 From: Andy Green Date: Wed, 3 Sep 2025 14:10:28 +0100 Subject: [PATCH 12/15] ss conn matrix extra --- lib/secure-streams/protocols/ss-ws.c | 3 +++ lib/secure-streams/secure-streams.c | 1 + 2 files changed, 4 insertions(+) diff --git a/lib/secure-streams/protocols/ss-ws.c b/lib/secure-streams/protocols/ss-ws.c index f275f6964e..b47591f8ef 100644 --- a/lib/secure-streams/protocols/ss-ws.c +++ b/lib/secure-streams/protocols/ss-ws.c @@ -113,6 +113,9 @@ secstream_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user, case LWS_CALLBACK_ESTABLISHED: case LWS_CALLBACK_CLIENT_ESTABLISHED: + if (!h) { + return LWSSSSRET_DISCONNECT_ME; + } h->retry = 0; h->seqstate = SSSEQ_CONNECTED; lws_sul_cancel(&h->sul); diff --git a/lib/secure-streams/secure-streams.c b/lib/secure-streams/secure-streams.c index c8dd81a1b6..7df00e83e0 100644 --- a/lib/secure-streams/secure-streams.c +++ b/lib/secure-streams/secure-streams.c @@ -94,6 +94,7 @@ const uint32_t ss_state_txn_validity[] = { (1 << LWSSSCS_DESTROYING), [LWSSSCS_DISCONNECTED] = (1 << LWSSSCS_CONNECTING) | + (1 << LWSSSCS_CONNECTED) | (1 << LWSSSCS_TIMEOUT) | (1 << LWSSSCS_POLL) | (1 << LWSSSCS_DESTROYING), From 1faedc3dc6a479d70cce7184cd26fe93868e3d6d Mon Sep 17 00:00:00 2001 From: Andy Green Date: Fri, 5 Sep 2025 11:03:42 +0100 Subject: [PATCH 13/15] windows: spawn: fix res handling for cb --- .sai.json | 14 +++++++------- lib/plat/windows/windows-sockets.c | 4 ++-- lib/plat/windows/windows-spawn.c | 9 +++++++-- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/.sai.json b/.sai.json index 9180171454..db4747393e 100644 --- a/.sai.json +++ b/.sai.json @@ -6,7 +6,7 @@ "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 cmake .. ${cmake}", "cd build && make -j$SAI_PARALLEL && rm -rf ../destdir && make -j$SAI_PARALLEL DESTDIR=../destdir install", - "cd build && ctest -j$SAI_PARALLEL --output-on-failure", + "cd build && ctest -j$SAI_PARALLEL --output-on-failure --repeat until-pass:3", "cd build && SAI_CPACK=\"-G RPM\" ${cpack}" ] }, @@ -14,7 +14,7 @@ "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=12.5 cmake .. -DCMAKE_MAKE_PROGRAM=/usr/bin/make ${cmake}", "cd build && make -j$SAI_PARALLEL && rm -rf ../destdir && make -j$SAI_PARALLEL DESTDIR=../destdir install", - "cd build && ctest -j$SAI_PARALLEL --output-on-failure", + "cd build && ctest -j$SAI_PARALLEL --output-on-failure --repeat until-pass:3", "cd build && SAI_CPACK=\"-G ZIP\" ${cpack}" ] }, @@ -22,7 +22,7 @@ "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 cmake .. ${cmake}", "cd build && make -j$SAI_PARALLEL && rm -rf ../destdir && make -j$SAI_PARALLEL DESTDIR=../destdir install", - "cd build && ctest -j$SAI_PARALLEL --output-on-failure", + "cd build && ctest -j$SAI_PARALLEL --output-on-failure --repeat until-pass:3", "cd build && SAI_CPACK=\"-G DEB\" ${cpack}" ] }, @@ -30,7 +30,7 @@ "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 cmake .. ${cmake}", "cd build && make -j$SAI_PARALLEL && rm -rf ../destdir && make -j$SAI_PARALLEL DESTDIR=../destdir install", - "cd build && ctest -j$SAI_PARALLEL --output-on-failure", + "cd build && ctest -j$SAI_PARALLEL --output-on-failure --repeat until-pass:3", "cd build && SAI_CPACK=\"-G RPM\" ${cpack}" ] }, @@ -38,7 +38,7 @@ "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 cmake .. ${cmake}", "cd build && make -j$SAI_PARALLEL && rm -rf ../destdir && make -j$SAI_PARALLEL DESTDIR=../destdir install", - "cd build && ctest -j$SAI_PARALLEL --output-on-failure", + "cd build && ctest -j$SAI_PARALLEL --output-on-failure --repeat until-pass:3", "cd build && SAI_CPACK=\"-G DEB\" ${cpack}" ] }, @@ -47,7 +47,7 @@ "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 cmake .. ${cmake}", "cd build && make -j$SAI_PARALLEL && rm -rf ../destdir && make -j$SAI_PARALLEL DESTDIR=../destdir install", - "cd build && /usr/pkg/bin/ctest -j$SAI_PARALLEL --output-on-failure" + "cd build && /usr/pkg/bin/ctest -j$SAI_PARALLEL --output-on-failure --repeat until-pass:3" ] }, "w11/x86_64-amd/msvc": { @@ -55,7 +55,7 @@ "build": [ "mkdir -p build; cd build; CCACHE_DISABLE=1 LD_LIBRARY_PATH=../destdir/usr/local/share/libwebsockets-test-server/plugins:../destdir/usr/local/lib cmake .. -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}", "cd build && cmake --build . --config DEBUG", - "cd build && /usr/pkg/bin/ctest -j$SAI_PARALLEL --output-on-failure", + "cd build && /usr/pkg/bin/ctest -j$SAI_PARALLEL --output-on-failure --repeat until-pass:3", "cd build && SAI_CPACK=\"-G ZIP\" ${cpack}" ] }, diff --git a/lib/plat/windows/windows-sockets.c b/lib/plat/windows/windows-sockets.c index c3cd5414b1..326ab9c29f 100644 --- a/lib/plat/windows/windows-sockets.c +++ b/lib/plat/windows/windows-sockets.c @@ -162,7 +162,7 @@ lws_plat_set_socket_options_ip(lws_sockfd_type fd, uint8_t pri, int lws_flags) * https://docs.microsoft.com/en-us/windows/win32/winsock/ipproto-ip-socket-options * https://docs.microsoft.com/en-us/previous-versions/windows/desktop/qos/differentiated-services */ - lwsl_warn("%s: priority and ip sockets options not implemented on windows platform\n", __func__); + lwsl_info("%s: priority and ip sockets options not implemented on windows platform\n", __func__); /* @@ -194,7 +194,7 @@ lws_plat_set_socket_options_ip(lws_sockfd_type fd, uint8_t pri, int lws_flags) #endif ret = 1; } else - lwsl_notice("%s: set use exclusive addresses\n", __func__); + lwsl_info("%s: set use exclusive addresses\n", __func__); } diff --git a/lib/plat/windows/windows-spawn.c b/lib/plat/windows/windows-spawn.c index 2519fb9706..b2b38b9980 100644 --- a/lib/plat/windows/windows-spawn.c +++ b/lib/plat/windows/windows-spawn.c @@ -149,7 +149,7 @@ lws_spawn_piped_destroy(struct lws_spawn_piped **_lsp) int lws_spawn_reap(struct lws_spawn_piped *lsp) { - + lws_spawn_resource_us_t res = { }; void *opaque = lsp->info.opaque; lsp_cb_t cb = lsp->info.reap_cb; struct _lws_siginfo_t lsi; @@ -234,6 +234,11 @@ lws_spawn_reap(struct lws_spawn_piped *lsp) lwsl_notice("%s: process exit 0x%x\n", __func__, lsi.retcode); lsp->child_pid = NULL; + if (lsp->info.res) + res = *lsp->info.res; + else + res = lsp->res; + /* destroy the lsp itself first (it's freed and plsp set NULL */ if (lsp->info.plsp) @@ -242,7 +247,7 @@ lws_spawn_reap(struct lws_spawn_piped *lsp) /* then do the parent callback informing it's destroyed */ if (cb) - cb(opaque, lsp->info.res ? lsp->info.res : &lsp->res, &lsi, 0); + cb(opaque, &res, &lsi, 0); lwsl_notice("%s: completed reap\n", __func__); From bbb97a279b6e2b53462afafa638fb53d4320de48 Mon Sep 17 00:00:00 2001 From: Gennadii Voitov Date: Tue, 9 Sep 2025 14:57:56 +0300 Subject: [PATCH 14/15] Add `LWS_SERVER_OPTION_MBEDTLS_VERIFY_CLIENT_CERT_POST_HANDSHAKE` --- include/libwebsockets/lws-context-vhost.h | 10 ++++++++++ lib/tls/mbedtls/mbedtls-server.c | 9 +++++++++ lib/tls/mbedtls/wrapper/include/internal/ssl_code.h | 1 + lib/tls/mbedtls/wrapper/platform/ssl_pm.c | 4 +++- 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/include/libwebsockets/lws-context-vhost.h b/include/libwebsockets/lws-context-vhost.h index 93810c05d2..bf64c7ab05 100644 --- a/include/libwebsockets/lws-context-vhost.h +++ b/include/libwebsockets/lws-context-vhost.h @@ -249,6 +249,16 @@ * Diffie-Hellman (DH) key exchange ciphers * (e.g. TLS_DHE_RSA_WITH_AES_256_GCM_SHA384). It's not recommended. */ +#define LWS_SERVER_OPTION_MBEDTLS_VERIFY_CLIENT_CERT_POST_HANDSHAKE ((1ll << 41) | \ + (1ll << 12)) + /**< (VH) An option to be used with mbedtls only, forces server to load + * and store the client cert (without CA dependent check) + * to be able to verify it later (after the handshake); + * provides LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT. + * Note: LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT and + * LWS_SERVER_OPTION_PEER_CERT_NOT_REQUIRED are ignored if + * LWS_SERVER_OPTION_MBEDTLS_VERIFY_CLIENT_CERT_POST_HANDSHAKE is set */ + /****** add new things just above ---^ ******/ diff --git a/lib/tls/mbedtls/mbedtls-server.c b/lib/tls/mbedtls/mbedtls-server.c index 0af459ca1a..c86f2cb7db 100644 --- a/lib/tls/mbedtls/mbedtls-server.c +++ b/lib/tls/mbedtls/mbedtls-server.c @@ -31,6 +31,15 @@ lws_tls_server_client_cert_verify_config(struct lws_vhost *vh) { int verify_options = SSL_VERIFY_PEER; + if (lws_check_opt(vh->options, + LWS_SERVER_OPTION_MBEDTLS_VERIFY_CLIENT_CERT_POST_HANDSHAKE)) { + verify_options |= SSL_VERIFY_POST_HANDSHAKE; + SSL_CTX_set_verify(vh->tls.ssl_ctx, verify_options, NULL); + lwsl_notice("%s: vh %s can verify client cert post-handshake\n", + __func__, vh->name); + return 0; + } + /* as a server, are we requiring clients to identify themselves? */ if (!lws_check_opt(vh->options, LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT)) { diff --git a/lib/tls/mbedtls/wrapper/include/internal/ssl_code.h b/lib/tls/mbedtls/wrapper/include/internal/ssl_code.h index 80fdbb20f3..ade547cbed 100644 --- a/lib/tls/mbedtls/wrapper/include/internal/ssl_code.h +++ b/lib/tls/mbedtls/wrapper/include/internal/ssl_code.h @@ -31,6 +31,7 @@ # define SSL_VERIFY_PEER 0x01 # define SSL_VERIFY_FAIL_IF_NO_PEER_CERT 0x02 # define SSL_VERIFY_CLIENT_ONCE 0x04 +# define SSL_VERIFY_POST_HANDSHAKE 0x08 /* * The following 3 states are kept in ssl->rlayer.rstate when reads fail, you diff --git a/lib/tls/mbedtls/wrapper/platform/ssl_pm.c b/lib/tls/mbedtls/wrapper/platform/ssl_pm.c index ef5eab5e0d..cb3821a5a7 100755 --- a/lib/tls/mbedtls/wrapper/platform/ssl_pm.c +++ b/lib/tls/mbedtls/wrapper/platform/ssl_pm.c @@ -257,7 +257,9 @@ static int ssl_pm_reload_crt(SSL *ssl) struct pkey_pm *pkey_pm = (struct pkey_pm *)ssl->cert->pkey->pkey_pm; struct x509_pm *crt_pm = (struct x509_pm *)ssl->cert->x509->x509_pm; - if ((ssl->verify_mode & SSL_VERIFY_PEER) > 0) + if ((ssl->verify_mode & SSL_VERIFY_POST_HANDSHAKE) > 0) + mode = MBEDTLS_SSL_VERIFY_OPTIONAL; + else if ((ssl->verify_mode & SSL_VERIFY_PEER) > 0) mode = MBEDTLS_SSL_VERIFY_REQUIRED; else if ((ssl->verify_mode & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) > 0) mode = MBEDTLS_SSL_VERIFY_OPTIONAL; From bb6c720cce3059bfe9873952dda769a8d5aec8e7 Mon Sep 17 00:00:00 2001 From: Gennadii Voitov Date: Mon, 13 Oct 2025 14:59:23 +0300 Subject: [PATCH 15/15] In `/lib/tls/mbedtls/wrapper/platform/ssl_pm.c` add `SSL_VERIFY_POST_HANDSHAKE` handling within `SSL_set_SSL_CTX()` --- lib/tls/mbedtls/wrapper/platform/ssl_pm.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/tls/mbedtls/wrapper/platform/ssl_pm.c b/lib/tls/mbedtls/wrapper/platform/ssl_pm.c index cb3821a5a7..789ec95a45 100755 --- a/lib/tls/mbedtls/wrapper/platform/ssl_pm.c +++ b/lib/tls/mbedtls/wrapper/platform/ssl_pm.c @@ -1061,14 +1061,16 @@ void SSL_set_SSL_CTX(SSL *ssl, SSL_CTX *ctx) #if defined(LWS_HAVE_mbedtls_ssl_set_hs_authmode) - if ((ctx->verify_mode & SSL_VERIFY_PEER) > 0) + if ((ctx->verify_mode & SSL_VERIFY_POST_HANDSHAKE) > 0) + mode = MBEDTLS_SSL_VERIFY_OPTIONAL; + else if ((ctx->verify_mode & SSL_VERIFY_PEER) > 0) mode = MBEDTLS_SSL_VERIFY_REQUIRED; else if ((ctx->verify_mode & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) > 0) mode = MBEDTLS_SSL_VERIFY_REQUIRED; else if (ctx->verify_mode == SSL_VERIFY_CLIENT_ONCE) mode = MBEDTLS_SSL_VERIFY_UNSET; else - mode = MBEDTLS_SSL_VERIFY_NONE; + mode = MBEDTLS_SSL_VERIFY_NONE; #endif /* apply new ctx cert to ssl */