feat(lws): add LWS_WITH_CUSTOM_HEADERS option and UTC timegm

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 <time.h> 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.
This commit is contained in:
Vikram Dattu
2026-07-08 20:03:00 +05:30
committed by GitHub
parent 43f12ca7f8
commit 8fd4febfe6
4 changed files with 119 additions and 0 deletions
+26
View File
@@ -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 <time.h> 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()
+10
View File
@@ -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
@@ -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 <time.h> 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 <time.h>
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);
}
@@ -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 <time.h>
/* 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