From 8fd4febfe642af52f206724819177301cd2ea289 Mon Sep 17 00:00:00 2001 From: Vikram Dattu Date: Wed, 8 Jul 2026 20:03:00 +0530 Subject: [PATCH] feat(lws): add LWS_WITH_CUSTOM_HEADERS option and UTC timegm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two ESP-IDF usability additions for libwebsockets consumers such as the AWS KVS WebRTC signaling client: - LWS_WITH_CUSTOM_HEADERS Kconfig option (off by default). When set, the websockets target gets LWS_WITH_CUSTOM_HEADERS / LWS_WITH_CUSTOM_HTTP_HEADERS as PUBLIC compile definitions, so lws_hdr_custom_copy() is available to lws and its consumers. lws's own option(LWS_WITH_CUSTOM_HEADERS) is gated 'H1 only' and is dropped on the FreeRTOS/minimal profile, so the compile-def is the reliable route. Used to read service-specific response headers (e.g. x-amzn-RequestId). - UTC timegm(). ESP picolibc ships timegm() but gates its prototype behind BSD/GNU visibility and the symbol isn't reliably linkable, so lws's CHECK_FUNCTION_EXISTS(timegm) fails and lws_http_date_parse_unix() falls back to mktime() (local TZ) — misreading a server 'GMT' Date header by the TZ offset, which breaks HTTP clock-skew handling (e.g. AWS SigV4). Force LWS_HAVE_TIMEGM and provide a dependency-free UTC timegm() in port/, prototype force-included. Validated on an ESP32-P4 esp-rainmaker camera (CONFIG_LWS_WITH_CUSTOM_HEADERS=y + CONFIG_LWS_WITH_THREADPOOL=y): KVS WebRTC signaling links and streams end-to-end, SigV4 clock-skew correct. --- components/libwebsockets/CMakeLists.txt | 26 ++++++++ components/libwebsockets/Kconfig | 10 +++ components/libwebsockets/port/lws_timegm.c | 62 +++++++++++++++++++ .../libwebsockets/port/lws_timegm_shim.h | 21 +++++++ 4 files changed, 119 insertions(+) create mode 100644 components/libwebsockets/port/lws_timegm.c create mode 100644 components/libwebsockets/port/lws_timegm_shim.h diff --git a/components/libwebsockets/CMakeLists.txt b/components/libwebsockets/CMakeLists.txt index f92298be8..6c802940f 100644 --- a/components/libwebsockets/CMakeLists.txt +++ b/components/libwebsockets/CMakeLists.txt @@ -25,6 +25,19 @@ else() set(LWS_WITH_THREADPOOL OFF CACHE INTERNAL "No worker thread pool") endif() +# Custom HTTP header support (lws_hdr_custom_*) is applied as a compile definition +# after add_subdirectory() below: lws's option(LWS_WITH_CUSTOM_HEADERS) is gated +# "H1 only" and gets dropped on the FreeRTOS/minimal profile, so the CMake-option +# route leaves it #undef in lws_config.h. A PUBLIC compile define is reliable. + +# ESP picolibc ships timegm() in libc but gates its prototype behind +# BSD/GNU visibility (and the symbol isn't reliably linkable), so lws's +# CHECK_FUNCTION_EXISTS(timegm) fails and lws_http_date_parse_unix() falls back to +# mktime() — which applies the local TZ and misreads a server "GMT" Date header by +# the TZ offset (breaks HTTP clock-skew handling, e.g. AWS SigV4). Force-enable and +# supply a UTC timegm() via the port (prototype shim force-included into lws). +set(LWS_HAVE_TIMEGM 1 CACHE INTERNAL "ESP port provides UTC timegm()" FORCE) + set(WRAP_FUNCTIONS mbedtls_ssl_handshake_step lws_adopt_descriptor_vhost) @@ -42,3 +55,16 @@ add_subdirectory(libwebsockets) # pollfd.c at this lws pin has an unused 'vpt' on FreeRTOS; drop after the next bump. target_compile_options(websockets PRIVATE -Wno-unused-variable) + +# Supply a UTC timegm() for lws (LWS_HAVE_TIMEGM set above). lws_http_date_parse_unix() +# calls timegm(); the port source defines it and the shim force-includes a prototype +# so date.c links against it instead of an implicit (int-returning) declaration. +target_sources(websockets PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/port/lws_timegm.c") +target_compile_options(websockets PRIVATE "-include${CMAKE_CURRENT_SOURCE_DIR}/port/lws_timegm_shim.h") + +# Enable custom HTTP header read APIs (lws_hdr_custom_*). PUBLIC so both lws's own +# sources and consumers (e.g. KVS WebRTC signaling) see the macro. Reliable where +# the lws CMake option is dropped on the FreeRTOS/minimal profile. +if(CONFIG_LWS_WITH_CUSTOM_HEADERS) + target_compile_definitions(websockets PUBLIC LWS_WITH_CUSTOM_HEADERS=1 LWS_WITH_CUSTOM_HTTP_HEADERS=1) +endif() diff --git a/components/libwebsockets/Kconfig b/components/libwebsockets/Kconfig index 195f9a2f7..ecc985966 100644 --- a/components/libwebsockets/Kconfig +++ b/components/libwebsockets/Kconfig @@ -32,4 +32,14 @@ menu "libwebsockets" or other work from the lws service thread (e.g. AWS KVS WebRTC signaling). Costs a few hundred bytes of static data. + config LWS_WITH_CUSTOM_HEADERS + bool "Build libwebsockets with custom HTTP header support" + default n + help + Enables the lws_hdr_custom_* APIs for reading non-standard + HTTP headers (sets LWS_WITH_CUSTOM_HEADERS and + LWS_WITH_CUSTOM_HTTP_HEADERS). Required by clients that must + read service-specific response headers — e.g. AWS KVS WebRTC + signaling reads the x-amzn-RequestId header over HTTP/1.1. + endmenu diff --git a/components/libwebsockets/port/lws_timegm.c b/components/libwebsockets/port/lws_timegm.c new file mode 100644 index 000000000..6bf33519f --- /dev/null +++ b/components/libwebsockets/port/lws_timegm.c @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * SPDX-License-Identifier: Apache-2.0 + */ + +/* UTC timegm() for the libwebsockets build on ESP-IDF. + * + * ESP's picolibc ships timegm() in libc but its prototype is gated + * behind BSD/GNU visibility, and the symbol is not reliably linkable in the + * IDF cross-build, so lws's CHECK_FUNCTION_EXISTS(timegm) fails. With + * LWS_HAVE_TIMEGM off, lws_http_date_parse_unix() falls back to mktime(), + * which interprets a struct tm in the LOCAL timezone — so a server "GMT" Date + * header is misread by the configured TZ offset, corrupting HTTP clock-skew + * handling (e.g. AWS SigV4 signing). Provide a dependency-free UTC conversion. + */ +#include + +static int lws_is_leap(int y) +{ + return ((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0); +} + +/* weak: if a future picolibc/newlib exports a linkable timegm(), the libc one + * wins and this definition is discarded — no multiple-definition error. */ +__attribute__((weak)) time_t timegm(struct tm *tm) +{ + static const int mdays_cum[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; + + int year = tm->tm_year + 1900; + int month = tm->tm_mon; + /* Normalize month into [0,11], carrying the remainder into the year. + * Works for negative tm_mon too (including exact multiples of -12), so + * mdays_cum[] is never indexed out of bounds. */ + year += month / 12; + month %= 12; + if (month < 0) { + month += 12; + year -= 1; + } + + long long days = 0; + if (year >= 1970) { + for (int y = 1970; y < year; ++y) { + days += 365 + lws_is_leap(y); + } + } else { + for (int y = year; y < 1970; ++y) { + days -= 365 + lws_is_leap(y); + } + } + + days += mdays_cum[month]; + if (month > 1 && lws_is_leap(year)) { + days += 1; + } + days += (tm->tm_mday - 1); + + return (time_t)(days * 86400LL + + (long long)tm->tm_hour * 3600 + + (long long)tm->tm_min * 60 + + (long long)tm->tm_sec); +} diff --git a/components/libwebsockets/port/lws_timegm_shim.h b/components/libwebsockets/port/lws_timegm_shim.h new file mode 100644 index 000000000..544a77c40 --- /dev/null +++ b/components/libwebsockets/port/lws_timegm_shim.h @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Prototype shim, force-included into the libwebsockets build. + * + * picolibc gates the timegm() prototype behind BSD/GNU visibility, so lws's + * date.c would otherwise see an implicit (int-returning) declaration and + * truncate the 64-bit time_t. This declares it so date.c links against the + * UTC timegm() provided in port/lws_timegm.c. See CMakeLists.txt. + */ +#pragma once +#include + +/* Only the ESP libc (picolibc/newlib) hides timegm()'s prototype behind BSD/GNU + * visibility; declare it for those. A libc that already declares it (e.g. glibc) + * needs no redeclaration. `#pragma once` already guards double-inclusion. */ +#if defined(__PICOLIBC__) || defined(__NEWLIB__) +time_t timegm(struct tm *__tp); +#endif