[PATCH 1/5] mongoose: Move definition from interface to impl

35 views
Skip to first unread message

Bastian Germann

unread,
Jul 20, 2026, 4:27:06 PMJul 20
to swup...@googlegroups.com, Bastian Germann
The mongoose_interface.h contains a function definition that is only used
once. To minimize the interface, move it and drop the unused defs.

Signed-off-by: Bastian Germann <ba...@debian.org>
---
include/mongoose_interface.h | 12 ------------
mongoose/mongoose_interface.c | 2 ++
2 files changed, 2 insertions(+), 12 deletions(-)

diff --git a/include/mongoose_interface.h b/include/mongoose_interface.h
index 376063a8..49e033de 100644
--- a/include/mongoose_interface.h
+++ b/include/mongoose_interface.h
@@ -7,21 +7,9 @@

#pragma once

-#include <stddef.h>
-
-struct mg_connection;
-struct mg_str;
-
-/*
- * Max number of command line options
- * to be passed to the mongoose webserver
- */
-#define MAX_OPTIONS 40
/*
* This is used by swupdate to start the Webserver
*/
int start_mongoose(const char *cfgfname, int argc, char *argv[]);

void mongoose_print_help(void);
-void mongoose_upload_ok_reply(struct mg_connection *nc,
- const struct mg_str *filename, size_t len);
diff --git a/mongoose/mongoose_interface.c b/mongoose/mongoose_interface.c
index 093dc292..1ad3c3df 100644
--- a/mongoose/mongoose_interface.c
+++ b/mongoose/mongoose_interface.c
@@ -550,6 +550,8 @@ static void timer_ev_handler(void *fn_data)
/*
* Code common to V1 and V2
*/
+void mongoose_upload_ok_reply(struct mg_connection *nc,
+ const struct mg_str *filename, size_t len);
void mongoose_upload_ok_reply(struct mg_connection *nc,
const struct mg_str *filename, size_t len)
{

Bastian Germann

unread,
Jul 20, 2026, 4:27:06 PMJul 20
to swup...@googlegroups.com, Bastian Germann
The roadmap lists an alternative webserver implementation.
Add a new web server backend based on libwebsockets (lws) as an
alternative to mongoose.

Bastian Germann (5):
mongoose: Move definition from interface to impl
util: Extract utils from mongoose
test: util: Test functions from webserver
webserver: implement lws backend using libwebsockets
acceptance-tests: add webserver integration test

Kconfig | 36 +
Makefile | 2 +-
Makefile.flags | 5 +
core/swupdate.c | 2 +-
core/util.c | 52 ++
crypto/Makefile | 3 +
doc/source/improvement_proposals.rst | 4 -
doc/source/swupdate.rst | 5 +-
examples/configuration/swupdate.cfg | 20 +-
include/mongoose_interface.h | 12 -
include/util.h | 4 +
lws/Kconfig | 18 +
lws/Makefile | 5 +
lws/lws_interface.c | 881 +++++++++++++++++++++
mongoose/Kconfig | 33 -
mongoose/mongoose_interface.c | 58 +-
scripts/acceptance-tests/CheckImage.mk | 13 +-
scripts/acceptance-tests/test_webserver.sh | 145 ++++
test/test_util.c | 64 ++
19 files changed, 1243 insertions(+), 119 deletions(-)
create mode 100644 lws/Kconfig
create mode 100644 lws/Makefile
create mode 100644 lws/lws_interface.c
create mode 100755 scripts/acceptance-tests/test_webserver.sh

Bastian Germann

unread,
Jul 20, 2026, 4:27:08 PMJul 20
to swup...@googlegroups.com, Bastian Germann
Signed-off-by: Bastian Germann <ba...@debian.org>
---
test/test_util.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 64 insertions(+)

diff --git a/test/test_util.c b/test/test_util.c
index 3faa830f..e76365c8 100644
--- a/test/test_util.c
+++ b/test/test_util.c
@@ -7,6 +7,7 @@
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>
+#include "swupdate_status.h"
#include "util.h"

static int util_setup(void **state)
@@ -49,10 +50,73 @@ static void test_util_is_filename_valid(void **state)
assert_false(is_filename_valid("sub/../e.swu"));
}

+static void test_status_known_values(void **state)
+{
+ (void)state;
+ assert_string_equal(get_status_string(IDLE), "IDLE");
+ assert_string_equal(get_status_string(START), "START");
+ assert_string_equal(get_status_string(RUN), "RUN");
+ assert_string_equal(get_status_string(SUCCESS), "SUCCESS");
+ assert_string_equal(get_status_string(FAILURE), "FAILURE");
+ assert_string_equal(get_status_string(DOWNLOAD), "DOWNLOAD");
+ assert_string_equal(get_status_string(DONE), "DONE");
+ assert_string_equal(get_status_string(SUBPROCESS), "SUBPROCESS");
+}
+
+static void test_status_out_of_range(void **state)
+{
+ (void)state;
+ /* PROGRESS is intentionally absent from the string table */
+ assert_string_equal(get_status_string(PROGRESS), "UNKNOWN");
+ assert_string_equal(get_status_string(9999), "UNKNOWN");
+}
+
+static void test_source_known_values(void **state)
+{
+ (void)state;
+ assert_string_equal(get_source_string(SOURCE_UNKNOWN), "UNKNOWN");
+ assert_string_equal(get_source_string(SOURCE_WEBSERVER), "WEBSERVER");
+ assert_string_equal(get_source_string(SOURCE_SURICATTA), "SURICATTA");
+ assert_string_equal(get_source_string(SOURCE_DOWNLOADER), "DOWNLOADER");
+ assert_string_equal(get_source_string(SOURCE_LOCAL), "LOCAL");
+}
+
+static void test_source_out_of_range(void **state)
+{
+ (void)state;
+ /* SOURCE_CHUNKS_DOWNLOADER is intentionally absent from the string table */
+ assert_string_equal(get_source_string(SOURCE_CHUNKS_DOWNLOADER), "UNKNOWN");
+ assert_string_equal(get_source_string(9999), "UNKNOWN");
+}
+
+static void test_level_known(void **state)
+{
+ (void)state;
+ assert_int_equal(level_to_rfc_5424(ERRORLEVEL), 3);
+ assert_int_equal(level_to_rfc_5424(WARNLEVEL), 4);
+ assert_int_equal(level_to_rfc_5424(INFOLEVEL), 6);
+ assert_int_equal(level_to_rfc_5424(TRACELEVEL), 7);
+ assert_int_equal(level_to_rfc_5424(DEBUGLEVEL), 7);
+}
+
+static void test_level_unknown(void **state)
+{
+ (void)state;
+ assert_int_equal(level_to_rfc_5424(OFF), 7);
+ assert_int_equal(level_to_rfc_5424(-1), 7);
+ assert_int_equal(level_to_rfc_5424(9999), 7);
+}
+
int main(void)
{
int error_count = 0;
const struct CMUnitTest util_tests[] = {
+ cmocka_unit_test(test_level_known),
+ cmocka_unit_test(test_level_unknown),
+ cmocka_unit_test(test_source_known_values),
+ cmocka_unit_test(test_source_out_of_range),
+ cmocka_unit_test(test_status_known_values),
+ cmocka_unit_test(test_status_out_of_range),
cmocka_unit_test(test_util_ustrtoull),
cmocka_unit_test(test_util_size_delimiter_match),
cmocka_unit_test(test_util_is_filename_valid)

Bastian Germann

unread,
Jul 20, 2026, 4:27:09 PMJul 20
to swup...@googlegroups.com, Bastian Germann
Add a new web server backend based on libwebsockets (lws) as an
alternative to mongoose. The new backend provides the same HTTP API:

POST /upload - stream SWU images to the installer, supporting both
multipart/form-data and application/octet-stream
POST /restart - trigger the post-update action
GET * - static file serving from a configurable web root
GET (ws) - WebSocket status stream with the same JSON protocol
as the mongoose backend

Kconfig is restructured to introduce a WEBSERVER choice menu so that
mongoose and lws are mutually exclusive options. The config options
MONGOOSESSL and MONGOOSEIPV6 cover both backends while the name is kept.
Likewise, the mongoose_interface.h is implemented by lws but not renamed.

Three of the CLI/config file parameters that mongoose carries are not
implemented. Two of them configure HTTP digest authentication that is
implemented in libwebsockets for the client side only. The other one is
file listing, which would involve a HTML template.

Signed-off-by: Bastian Germann <ba...@debian.org>
---
Kconfig | 36 +
Makefile | 2 +-
Makefile.flags | 5 +
core/swupdate.c | 2 +-
crypto/Makefile | 3 +
doc/source/improvement_proposals.rst | 4 -
doc/source/swupdate.rst | 5 +-
examples/configuration/swupdate.cfg | 20 +-
lws/Kconfig | 18 +
lws/Makefile | 5 +
lws/lws_interface.c | 881 +++++++++++++++++++++++++
mongoose/Kconfig | 33 -
scripts/acceptance-tests/CheckImage.mk | 2 +-
13 files changed, 965 insertions(+), 51 deletions(-)
create mode 100644 lws/Kconfig
create mode 100644 lws/Makefile
create mode 100644 lws/lws_interface.c

diff --git a/Kconfig b/Kconfig
index 8b6139de..615a622a 100644
--- a/Kconfig
+++ b/Kconfig
@@ -399,8 +399,44 @@ config CHANNEL_CURL

source "suricatta/Kconfig"

+menuconfig WEBSERVER
+ bool "Web Server"
+ help
+ Enable update from remote using a web server on the target.
+
+if WEBSERVER
+choice
+ prompt "Web Server Type"
+ default MONGOOSE
+ help
+ Choose the web server
+
source "mongoose/Kconfig"

+source "lws/Kconfig"
+
+endchoice
+
+config MONGOOSEIPV6
+ bool "IPv6 support"
+ default y
+ help
+ Enable IPv6 support in the web server.
+
+config MONGOOSESSL
+ bool "SSL support"
+ depends on SSL_IMPL_OPENSSL || SSL_IMPL_WOLFSSL || SSL_IMPL_MBEDTLS || LWS
+ help
+ Enable TLS/SSL support in the web server.
+
+ For the lws backend, TLS is handled by the TLS backend that libwebsockets
+ was compiled against.
+
+comment "SSL support needs an SSL implementation"
+ depends on !SSL_IMPL_OPENSSL && !SSL_IMPL_WOLFSSL && !SSL_IMPL_MBEDTLS && !LWS
+
+endif
+
comment "Security"

source "crypto/Kconfig"
diff --git a/Makefile b/Makefile
index 58004474..7898985e 100644
--- a/Makefile
+++ b/Makefile
@@ -374,7 +374,7 @@ include $(srctree)/Makefile.flags

objs-y := core handlers bootloader suricatta
objs-$(CONFIG_SWUPDATE_CRYPTO) += crypto
-libs-y := corelib mongoose parser fs containers
+libs-y := corelib mongoose lws parser fs containers
bindings-y := bindings
tools-y := tools

diff --git a/Makefile.flags b/Makefile.flags
index 267d74ab..435bf3c8 100644
--- a/Makefile.flags
+++ b/Makefile.flags
@@ -301,6 +301,11 @@ ifneq ($(CONFIG_SWUFORWARDER_HANDLER),)
LDLIBS += websockets uriparser
endif

+# libwebsockets webserver
+ifneq ($(CONFIG_LWS),)
+LDLIBS += websockets
+endif
+
# Delta Update
ifneq ($(CONFIG_DELTA),)
LDLIBS += zck
diff --git a/core/swupdate.c b/core/swupdate.c
index a7ad57ae..c9c2b75c 100644
--- a/core/swupdate.c
+++ b/core/swupdate.c
@@ -1132,7 +1132,7 @@ int main(int argc, char **argv)
wait_threads_ready();

/* Start embedded web server */
-#if defined(CONFIG_MONGOOSE)
+#if defined(CONFIG_WEBSERVER)
if (opt_w) {
uid_t uid;
gid_t gid;
diff --git a/crypto/Makefile b/crypto/Makefile
index 5c775e7b..86d4590e 100644
--- a/crypto/Makefile
+++ b/crypto/Makefile
@@ -3,6 +3,7 @@
# SPDX-License-Identifier: GPL-2.0-only

ifeq ($(CONFIG_SSL_IMPL_OPENSSL),y)
+obj-$(CONFIG_LWS) += swupdate_HASH_openssl.o
obj-$(CONFIG_HASH_VERIFY) += swupdate_HASH_openssl.o
obj-$(CONFIG_SIGALG_RAWRSA) += swupdate_rsa_verify_openssl.o
obj-$(CONFIG_SIGALG_RSAPSS) += swupdate_rsa_verify_openssl.o
@@ -12,6 +13,7 @@ obj-$(CONFIG_SIGALG_ASYM_DEC_CMS) += swupdate_decrypt_openssl_cms.o
endif

ifeq ($(CONFIG_SSL_IMPL_WOLFSSL),y)
+obj-$(CONFIG_LWS) += swupdate_HASH_wolfssl.o
obj-$(CONFIG_HASH_VERIFY) += swupdate_HASH_wolfssl.o
obj-$(CONFIG_SIGALG_RAWRSA) += swupdate_rsa_verify_wolfssl.o
obj-$(CONFIG_SIGALG_RSAPSS) += swupdate_rsa_verify_wolfssl.o
@@ -20,6 +22,7 @@ obj-$(CONFIG_ENCRYPTED_IMAGES) += swupdate_decrypt_wolfssl.o
endif

ifeq ($(CONFIG_SSL_IMPL_MBEDTLS),y)
+obj-$(CONFIG_LWS) += swupdate_HASH_mbedtls.o
obj-$(CONFIG_HASH_VERIFY) += swupdate_HASH_mbedtls.o
obj-$(CONFIG_ENCRYPTED_IMAGES) += swupdate_decrypt_mbedtls.o
obj-$(CONFIG_SIGALG_RAWRSA) += swupdate_rsa_verify_mbedtls.o
diff --git a/doc/source/improvement_proposals.rst b/doc/source/improvement_proposals.rst
index 020bebe3..b790f3b9 100644
--- a/doc/source/improvement_proposals.rst
+++ b/doc/source/improvement_proposals.rst
@@ -246,10 +246,6 @@ Webserver that allows streaming.
Reported from ML there is this place for enhancement:

- use Mongoose as library
-- use alternative Webserver. Civetweb is a fork from Mongoose and could be integrated.
-- Webserver itself is not doing a lot - it just provides API to push a SWU and Websocket to report progress.
- If SWUpdate is behind a reverse-proxy, an easy own developed internal Webserver
- can be a solution.

* Status: Wait
* Request for Support : Sponsor
diff --git a/doc/source/swupdate.rst b/doc/source/swupdate.rst
index 62681c2a..df3e3202 100644
--- a/doc/source/swupdate.rst
+++ b/doc/source/swupdate.rst
@@ -674,6 +674,7 @@ Webserver command line parameters

Example: ``swupdate -w "-r /www -p 8080"``

+Arguments only available with Mongoose are marked with '\#'.
Mandatory arguments are marked with '\*':

+-------------------------+----------+--------------------------------------------+
@@ -697,10 +698,10 @@ Mandatory arguments are marked with '\*':
| | | clients stops to send data. If hit, an |
| | | update is aborted. Default=0 (unlimited) |
+-------------------------+----------+--------------------------------------------+
-| --auth-domain <string> | string | Set authentication domain |
+| --auth-domain <string> | string | \# Set authentication domain |
| | | Default: none |
+-------------------------+----------+--------------------------------------------+
-| --global-auth-file | string | Set authentication file if any |
+| --global-auth-file | string | \# Set authentication file if any |
| <string> | | Default: none |
+-------------------------+----------+--------------------------------------------+

diff --git a/examples/configuration/swupdate.cfg b/examples/configuration/swupdate.cfg
index 0bcf6ffc..121e86da 100644
--- a/examples/configuration/swupdate.cfg
+++ b/examples/configuration/swupdate.cfg
@@ -374,9 +374,7 @@ gservice :
# groupid : integer
# groupId for Webserver process
# listening_ports : integer
-# Webserver listening ports
-# enable_directory_listing : boolean
-# true to list directories
+# Webserver listening port
# ssl_certificate : string
# path to SSL certificat
# ssl_certificate_key : string
@@ -384,12 +382,6 @@ gservice :
# api : integer
# 1 = simple REST API for M2M communication
# 2 = WebApp with Websockets support
-# global-auth-file : string
-# path to the global authorization file, if any
-# default = none
-# auth-domain : string
-# path to auth-domain, if any
-# default = none
# run-postupdate : boolean (default true)
# run the postupdate command automatically after
# a successful update
@@ -397,6 +389,16 @@ gservice :
# when an update is started. If no data is received
# during this time, connection is closed by the Webserver
# and update is aborted.
+#
+# MONGOOSE-ONLY
+# enable_directory_listing : boolean
+# true to list directories
+# global-auth-file : string
+# path to the global authorization file, if any
+# default = none
+# auth-domain : string
+# path to auth-domain, if any
+# default = none

webserver :
{
diff --git a/lws/Kconfig b/lws/Kconfig
new file mode 100644
index 00000000..6405f8c4
--- /dev/null
+++ b/lws/Kconfig
@@ -0,0 +1,18 @@
+# SPDX-FileCopyrightText: 2026 Bastian Germann
+#
+# SPDX-License-Identifier: GPL-2.0-only
+
+config LWS
+ bool "libwebsockets"
+ depends on HAVE_LIBWEBSOCKETS
+ help
+ Enable a webserver built on libwebsockets.
+
+ Endpoints:
+ POST /upload - upload a SWU image
+ (multipart/form-data or application/octet-stream)
+ POST /restart - trigger the post-update action
+ GET * - static file serving from a configurable web root
+ (set via -r or the web-root config option)
+ GET (ws) - WebSocket status stream
+ (same JSON protocol as the mongoose webserver)
diff --git a/lws/Makefile b/lws/Makefile
new file mode 100644
index 00000000..1789ffdb
--- /dev/null
+++ b/lws/Makefile
@@ -0,0 +1,5 @@
+# SPDX-FileCopyrightText: 2026 Bastian Germann
+#
+# SPDX-License-Identifier: GPL-2.0-only
+
+lib-$(CONFIG_LWS) += lws_interface.o
diff --git a/lws/lws_interface.c b/lws/lws_interface.c
new file mode 100644
index 00000000..7dd41e9e
--- /dev/null
+++ b/lws/lws_interface.c
@@ -0,0 +1,881 @@
+/* SPDX-FileCopyrightText: 2026 Bastian Germann
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ */
+
+/*
+ * SWUpdate webserver based on libwebsockets.
+ *
+ * Provides in a single server:
+ * POST /upload - stream SWU images to the installer
+ * POST /restart - trigger the post-update action
+ * GET * - static file serving from a configurable web root
+ * GET (ws) - WebSocket status endpoint
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <errno.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <pthread.h>
+#include <sys/types.h>
+#include <time.h>
+
+#include <libwebsockets.h>
+
+#include "mongoose_interface.h"
+#include "network_ipc.h"
+#include "progress.h"
+#include "util.h"
+#include "parselib.h"
+#include "swupdate_settings.h"
+#include "pctl.h"
+
+/* -----------------------------------------------------------------------
+ * Constants
+ * --------------------------------------------------------------------- */
+
+#define LWS_DEFAULT_PORT 8080
+#define LWS_MAX_WS_SESSIONS 16
+
+/* -----------------------------------------------------------------------
+ * Message node (singly-linked list for the WebSocket broadcast queue)
+ * --------------------------------------------------------------------- */
+
+struct msg_node {
+ struct msg_node *next;
+ size_t len;
+ char json[]; /* flexible array – allocated inline */
+};
+
+static struct msg_node *msg_node_new(const char *json)
+{
+ size_t len = strlen(json);
+ struct msg_node *n = malloc(sizeof(*n) + len + 1);
+ if (!n)
+ return NULL;
+ n->next = NULL;
+ n->len = len;
+ memcpy(n->json, json, len + 1);
+ return n;
+}
+
+/* -----------------------------------------------------------------------
+ * Global state
+ * --------------------------------------------------------------------- */
+
+static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER;
+static struct msg_node *g_head;
+static struct msg_node *g_tail;
+static struct lws_context *g_ctx;
+static bool s_run_postupdate;
+static const char *s_document_root; /* set once before lws_create_context */
+static unsigned int s_watchdog_secs; /* 0 = disabled */
+
+static volatile sig_atomic_t s_signo;
+
+/* -----------------------------------------------------------------------
+ * Per-session state
+ * --------------------------------------------------------------------- */
+
+/* HTTP request routes */
+#define ROUTE_NONE 0
+#define ROUTE_UPLOAD 1
+
+/* Upload content types */
+#define CTYPE_NONE 0
+#define CTYPE_MULTIPART 1
+#define CTYPE_OCTET 2
+
+struct pss {
+ /* HTTP / upload */
+ int route;
+ int ctype;
+ int ipc_fd; /* -1 when not open */
+ struct lws_spa *spa; /* multipart parser; NULL if unused */
+ bool upload_error;
+ time_t last_io_time; /* for upload watchdog */
+
+ /* WebSocket */
+ struct msg_node *send_head;
+ struct msg_node *send_tail;
+ int slot; /* index in g_sessions[]; -1 if not WS */
+};
+
+/* -----------------------------------------------------------------------
+ * WebSocket session registry (lws service thread only – no lock needed)
+ * --------------------------------------------------------------------- */
+
+static struct pss *g_sessions[LWS_MAX_WS_SESSIONS];
+
+static void session_add(struct pss *pss)
+{
+ for (int i = 0; i < LWS_MAX_WS_SESSIONS; i++) {
+ if (!g_sessions[i]) {
+ g_sessions[i] = pss;
+ pss->slot = i;
+ return;
+ }
+ }
+ pss->slot = -1;
+ WARN("LWS: too many concurrent WebSocket sessions (max %d)",
+ LWS_MAX_WS_SESSIONS);
+}
+
+static void session_remove(struct pss *pss)
+{
+ if (pss->slot >= 0 && pss->slot < LWS_MAX_WS_SESSIONS)
+ g_sessions[pss->slot] = NULL;
+
+ struct msg_node *m = pss->send_head;
+ while (m) {
+ struct msg_node *nxt = m->next;
+ free(m);
+ m = nxt;
+ }
+ pss->send_head = pss->send_tail = NULL;
+}
+
+/* -----------------------------------------------------------------------
+ * WebSocket broadcast (called from IPC listener threads)
+ * --------------------------------------------------------------------- */
+
+static void ws_broadcast(const char *json)
+{
+ struct msg_node *n = msg_node_new(json);
+ if (!n)
+ return;
+
+ pthread_mutex_lock(&g_lock);
+ if (g_tail)
+ g_tail->next = n;
+ else
+ g_head = n;
+ g_tail = n;
+ pthread_mutex_unlock(&g_lock);
+
+ if (g_ctx)
+ lws_cancel_service(g_ctx);
+}
+
+/* -----------------------------------------------------------------------
+ * SPA (multipart/form-data) file upload callback
+ * --------------------------------------------------------------------- */
+
+static int spa_fileupload_cb(void *data, const char *name,
+ const char *filename, char *buf, int len,
+ enum lws_spa_fileupload_states state)
+{
+ struct pss *pss = data;
+
+ (void)name;
+
+ switch (state) {
+ case LWS_UFS_OPEN: {
+ struct swupdate_request req;
+ swupdate_prepare_req(&req);
+ req.source = SOURCE_WEBSERVER;
+ if (filename)
+ strncpy(req.info, filename, sizeof(req.info) - 1);
+ pss->ipc_fd = ipc_inst_start_ext(&req, sizeof(req));
+ if (pss->ipc_fd < 0) {
+ ERROR("LWS: upload: ipc_inst_start_ext failed");
+ pss->upload_error = true;
+ } else {
+ swupdate_download_update(0, 0);
+ }
+ break;
+ }
+
+ case LWS_UFS_CONTENT:
+ case LWS_UFS_FINAL_CONTENT:
+ if (!pss->upload_error && pss->ipc_fd >= 0 && buf && len > 0) {
+ if (write(pss->ipc_fd, buf, (size_t)len) < 0) {
+ ERROR("LWS: upload: IPC write: %s",
+ strerror(errno));
+ pss->upload_error = true;
+ }
+ }
+ if (state == LWS_UFS_FINAL_CONTENT && pss->ipc_fd >= 0) {
+ ipc_end(pss->ipc_fd);
+ pss->ipc_fd = -1;
+ }
+ break;
+
+ case LWS_UFS_CLOSE:
+ break;
+ }
+ return 0;
+}
+
+
+
+/* -----------------------------------------------------------------------
+ * Unified HTTP + WebSocket callback
+ * --------------------------------------------------------------------- */
+
+static const struct lws_protocols protocols[]; /* forward declaration */
+
+static int callback_swupdate(struct lws *wsi, enum lws_callback_reasons reason,
+ void *user, void *in, size_t len)
+{
+ struct pss *pss = (struct pss *)user;
+
+ switch (reason) {
+
+ /* ----------------------------------------------------------------
+ * Incoming HTTP request (headers complete)
+ * -------------------------------------------------------------- */
+ case LWS_CALLBACK_HTTP: {
+ char uri[256] = "";
+ bool is_post;
+
+ is_post = lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI) > 0;
+ if (is_post)
+ lws_hdr_copy(wsi, uri, sizeof(uri), WSI_TOKEN_POST_URI);
+ else
+ lws_hdr_copy(wsi, uri, sizeof(uri), WSI_TOKEN_GET_URI);
+
+ /* Initialise per-session HTTP state (pss is zero-filled by lws) */
+ pss->ipc_fd = -1;
+ pss->slot = -1;
+
+ if (is_post && strcmp(uri, "/upload") == 0) {
+ char ct[256] = "";
+ pss->route = ROUTE_UPLOAD;
+ lws_hdr_copy(wsi, ct, sizeof(ct),
+ WSI_TOKEN_HTTP_CONTENT_TYPE);
+
+ if (strncasecmp(ct, "multipart/form-data", 19) == 0) {
+ static const char * const fields[] =
+ { "file", NULL };
+ pss->ctype = CTYPE_MULTIPART;
+ pss->spa = lws_spa_create(wsi, fields, 1,
+ 4096,
+ spa_fileupload_cb,
+ pss);
+ if (!pss->spa) {
+ ERROR("LWS: lws_spa_create failed");
+ lws_return_http_status(wsi,
+ HTTP_STATUS_INTERNAL_SERVER_ERROR,
+ NULL);
+ goto done_http;
+ }
+ } else {
+ /* Treat anything else as raw octet-stream */
+ struct swupdate_request req;
+ pss->ctype = CTYPE_OCTET;
+ swupdate_prepare_req(&req);
+ req.source = SOURCE_WEBSERVER;
+ pss->ipc_fd = ipc_inst_start_ext(&req,
+ sizeof(req));
+ if (pss->ipc_fd < 0) {
+ ERROR("LWS: upload: ipc_inst_start_ext"
+ " failed");
+ lws_return_http_status(wsi,
+ HTTP_STATUS_INTERNAL_SERVER_ERROR,
+ NULL);
+ goto done_http;
+ }
+ swupdate_download_update(0, 0);
+ }
+ pss->last_io_time = time(NULL);
+ if (s_watchdog_secs)
+ lws_set_timer_usecs(wsi,
+ (lws_usec_t)s_watchdog_secs *
+ LWS_USEC_PER_SEC);
+ return 0;
+
+ } else if (is_post && strcmp(uri, "/restart") == 0) {
+ ipc_message msg = {};
+ int ret = ipc_postupdate(&msg);
+ lws_return_http_status(wsi,
+ (ret || msg.type != ACK)
+ ? HTTP_STATUS_INTERNAL_SERVER_ERROR
+ : HTTP_STATUS_OK,
+ NULL);
+ goto done_http;
+
+ } else if (!is_post) {
+ /* Static file serving */
+ if (s_document_root && *s_document_root) {
+ const char *p = (*uri == '/') ? uri : "/";
+ if (strcmp(p, "/") == 0)
+ p = "/index.html";
+
+ char filepath[512];
+ snprintf(filepath, sizeof(filepath), "%s%s",
+ s_document_root, p);
+
+ const char *mime = lws_get_mimetype(filepath, NULL);
+ if (!mime)
+ mime = "application/octet-stream";
+ int n = lws_serve_http_file(wsi, filepath, mime,
+ NULL, 0);
+ if (n < 0 || (n > 0 &&
+ lws_http_transaction_completed(wsi)))
+ return -1;
+ return 0;
+ }
+ }
+
+ lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL);
+done_http:
+ return lws_http_transaction_completed(wsi) ? -1 : 0;
+ }
+
+ /* ----------------------------------------------------------------
+ * Receiving POST body chunks
+ * -------------------------------------------------------------- */
+ case LWS_CALLBACK_HTTP_BODY:
+ if (pss->route == ROUTE_UPLOAD) {
+ pss->last_io_time = time(NULL);
+ if (pss->ctype == CTYPE_MULTIPART) {
+ if (pss->spa)
+ lws_spa_process(pss->spa, in, (int)len);
+ } else if (pss->ctype == CTYPE_OCTET) {
+ if (!pss->upload_error && pss->ipc_fd >= 0) {
+ if (write(pss->ipc_fd, in, len) < 0) {
+ ERROR("LWS: upload: IPC write:"
+ " %s", strerror(errno));
+ pss->upload_error = true;
+ }
+ }
+ }
+ }
+ break;
+
+ /* ----------------------------------------------------------------
+ * POST body fully received
+ * -------------------------------------------------------------- */
+ case LWS_CALLBACK_HTTP_BODY_COMPLETION:
+ if (pss->route == ROUTE_UPLOAD) {
+ if (pss->ctype == CTYPE_MULTIPART && pss->spa) {
+ lws_spa_finalize(pss->spa);
+ lws_spa_destroy(pss->spa);
+ pss->spa = NULL;
+ } else if (pss->ctype == CTYPE_OCTET &&
+ pss->ipc_fd >= 0) {
+ ipc_end(pss->ipc_fd);
+ pss->ipc_fd = -1;
+ }
+ lws_return_http_status(wsi,
+ pss->upload_error
+ ? HTTP_STATUS_INTERNAL_SERVER_ERROR
+ : HTTP_STATUS_OK,
+ NULL);
+ }
+ return lws_http_transaction_completed(wsi) ? -1 : 0;
+
+ /* ----------------------------------------------------------------
+ * Static file serving complete
+ * -------------------------------------------------------------- */
+ case LWS_CALLBACK_HTTP_FILE_COMPLETION:
+ return lws_http_transaction_completed(wsi) ? -1 : 0;
+
+ /* ----------------------------------------------------------------
+ * Connection closed (HTTP or mid-upload)
+ * -------------------------------------------------------------- */
+ case LWS_CALLBACK_CLOSED_HTTP:
+ if (pss->ipc_fd >= 0) {
+ close(pss->ipc_fd);
+ pss->ipc_fd = -1;
+ }
+ if (pss->spa) {
+ lws_spa_destroy(pss->spa);
+ pss->spa = NULL;
+ }
+ break;
+
+ /* ----------------------------------------------------------------
+ * WebSocket handshake complete
+ * -------------------------------------------------------------- */
+ case LWS_CALLBACK_ESTABLISHED:
+ pss->ipc_fd = -1;
+ pss->spa = NULL;
+ pss->send_head = NULL;
+ pss->send_tail = NULL;
+ pss->slot = -1;
+ session_add(pss);
+ break;
+
+ /* ----------------------------------------------------------------
+ * WebSocket connection closed
+ * -------------------------------------------------------------- */
+ case LWS_CALLBACK_CLOSED:
+ session_remove(pss);
+ break;
+
+ /* ----------------------------------------------------------------
+ * Send next pending WebSocket message
+ * -------------------------------------------------------------- */
+ case LWS_CALLBACK_SERVER_WRITEABLE: {
+ if (!pss->send_head)
+ break;
+
+ struct msg_node *msg = pss->send_head;
+ pss->send_head = msg->next;
+ if (!pss->send_head)
+ pss->send_tail = NULL;
+
+ uint8_t *buf = malloc(LWS_PRE + msg->len);
+ if (buf) {
+ memcpy(buf + LWS_PRE, msg->json, msg->len);
+ lws_write(wsi, buf + LWS_PRE, msg->len,
+ LWS_WRITE_TEXT);
+ free(buf);
+ }
+ free(msg);
+
+ if (pss->send_head)
+ lws_callback_on_writable(wsi);
+ break;
+ }
+
+ /* ----------------------------------------------------------------
+ * Broadcast queue woken by ws_broadcast() → lws_cancel_service()
+ * -------------------------------------------------------------- */
+ case LWS_CALLBACK_EVENT_WAIT_CANCELLED: {
+ pthread_mutex_lock(&g_lock);
+ struct msg_node *bcast = g_head;
+ g_head = g_tail = NULL;
+ pthread_mutex_unlock(&g_lock);
+
+ while (bcast) {
+ struct msg_node *nxt = bcast->next;
+ for (int i = 0; i < LWS_MAX_WS_SESSIONS; i++) {
+ struct pss *s = g_sessions[i];
+ if (!s)
+ continue;
+ struct msg_node *copy =
+ msg_node_new(bcast->json);
+ if (!copy)
+ continue;
+ if (s->send_tail)
+ s->send_tail->next = copy;
+ else
+ s->send_head = copy;
+ s->send_tail = copy;
+ }
+ free(bcast);
+ bcast = nxt;
+ }
+ lws_callback_on_writable_all_protocol(g_ctx, &protocols[0]);
+ break;
+ }
+
+ /* ----------------------------------------------------------------
+ * Upload watchdog timer
+ * -------------------------------------------------------------- */
+ case LWS_CALLBACK_TIMER:
+ if (pss->route == ROUTE_UPLOAD && s_watchdog_secs) {
+ time_t elapsed = time(NULL) - pss->last_io_time;
+ if ((unsigned int)elapsed >= s_watchdog_secs) {
+ ERROR("LWS: upload timeout after %ld seconds,"
+ " closing connection", (long)elapsed);
+ lws_return_http_status(wsi,
+ HTTP_STATUS_REQUEST_TIMEOUT, NULL);
+ return -1;
+ }
+ /* Not yet expired – reschedule */
+ lws_set_timer_usecs(wsi,
+ (lws_usec_t)s_watchdog_secs *
+ LWS_USEC_PER_SEC);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return lws_callback_http_dummy(wsi, reason, user, in, len);
+}
+
+static const struct lws_protocols protocols[] = {
+ {
+ .name = "swupdate",
+ .callback = callback_swupdate,
+ .per_session_data_size = sizeof(struct pss),
+ .rx_buffer_size = 4096,
+ },
+ { NULL, NULL, 0, 0, 0, NULL, 0 }
+};
+
+/* -----------------------------------------------------------------------
+ * IPC listener threads (same JSON protocol as mongoose_interface.c)
+ * --------------------------------------------------------------------- */
+
+static void *broadcast_message_thread(void __attribute__((__unused__)) *data)
+{
+ int fd = -1;
+
+ for (;;) {
+ ipc_message msg;
+ int ret;
+
+ if (fd < 0)
+ fd = ipc_notify_connect();
+ if (fd < 0) {
+ sleep(1);
+ continue;
+ }
+
+ ret = ipc_notify_receive(&fd, &msg);
+ if (ret != sizeof(msg))
+ break;
+
+ if (strlen(msg.data.notify.msg) != 0 &&
+ msg.data.status.current != PROGRESS) {
+ char text[4096];
+ char str[4160];
+
+ snescape(text, sizeof(text), msg.data.notify.msg);
+
+ snprintf(str, sizeof(str),
+ "{\r\n"
+ "\t\"type\": \"message\",\r\n"
+ "\t\"level\": \"%d\",\r\n"
+ "\t\"text\": \"%s\"\r\n"
+ "}\r\n",
+ level_to_rfc_5424(msg.data.notify.level),
+ text);
+ ws_broadcast(str);
+ }
+ }
+
+ return NULL;
+}
+
+static void *broadcast_progress_thread(void __attribute__((__unused__)) *data)
+{
+ RECOVERY_STATUS status = -1;
+ sourcetype source = -1;
+ unsigned int step = 0;
+ uint8_t percent = 0;
+ int fd = -1;
+
+ for (;;) {
+ struct progress_msg msg;
+ char str[1024];
+ char escaped[512];
+ int ret;
+
+ if (fd < 0)
+ fd = progress_ipc_connect(true);
+ if (fd < 0) {
+ sleep(1);
+ continue;
+ }
+
+ ret = progress_ipc_receive(&fd, &msg);
+ if (ret != sizeof(msg))
+ break;
+
+ if (msg.status != PROGRESS &&
+ (msg.status != status || msg.status == FAILURE)) {
+ status = msg.status;
+ snescape(escaped, sizeof(escaped),
+ get_status_string(msg.status));
+ snprintf(str, sizeof(str),
+ "{\r\n"
+ "\t\"type\": \"status\",\r\n"
+ "\t\"status\": \"%s\"\r\n"
+ "}\r\n",
+ escaped);
+ ws_broadcast(str);
+ }
+
+ if (msg.source != source) {
+ source = msg.source;
+ snprintf(str, sizeof(str),
+ "{\r\n"
+ "\t\"type\": \"source\",\r\n"
+ "\t\"source\": \"%s\"\r\n"
+ "}\r\n",
+ get_source_string(msg.source));
+ ws_broadcast(str);
+ }
+
+ if (msg.status == SUCCESS && msg.source == SOURCE_WEBSERVER &&
+ s_run_postupdate) {
+ ipc_message ipc = {};
+ ipc_postupdate(&ipc);
+ }
+
+ if (msg.infolen) {
+ snescape(escaped, sizeof(escaped), msg.info);
+ snprintf(str, sizeof(str),
+ "{\r\n"
+ "\t\"type\": \"info\",\r\n"
+ "\t\"source\": \"%s\"\r\n"
+ "}\r\n",
+ escaped);
+ ws_broadcast(str);
+ }
+
+ if ((msg.cur_step != step || msg.cur_percent != percent) &&
+ msg.cur_step) {
+ step = msg.cur_step;
+ percent = msg.cur_percent;
+ snescape(escaped, sizeof(escaped),
+ msg.cur_step ? msg.cur_image : "");
+ snprintf(str, sizeof(str),
+ "{\r\n"
+ "\t\"type\": \"step\",\r\n"
+ "\t\"number\": \"%d\",\r\n"
+ "\t\"step\": \"%d\",\r\n"
+ "\t\"name\": \"%s\",\r\n"
+ "\t\"percent\": \"%d\"\r\n"
+ "}\r\n",
+ msg.nsteps, msg.cur_step,
+ escaped, msg.cur_percent);
+ ws_broadcast(str);
+ }
+ }
+
+ return NULL;
+}
+
+/* -----------------------------------------------------------------------
+ * Configuration + entry point
+ * --------------------------------------------------------------------- */
+
+struct lws_opts {
+ int port;
+ char *document_root;
+ bool run_postupdate;
+ unsigned int watchdog_secs;
+#ifdef CONFIG_MONGOOSESSL
+ bool ssl;
+ char *ssl_cert;
+ char *ssl_key;
+#endif
+};
+
+static int lws_settings(void *elem, void __attribute__((__unused__)) *data)
+{
+ struct lws_opts *opts = data;
+ char tmp[256] = "";
+
+ GET_FIELD_INT(LIBCFG_PARSER, elem, "listening_ports", &opts->port);
+ GET_FIELD_STRING_RESET(LIBCFG_PARSER, elem, "document_root", tmp);
+ if (strlen(tmp)) {
+ free(opts->document_root);
+ opts->document_root = strdup(tmp);
+ }
+ GET_FIELD_BOOL(LIBCFG_PARSER, elem, "run-postupdate",
+ &opts->run_postupdate);
+ GET_FIELD_INT(LIBCFG_PARSER, elem, "timeout",
+ (int *)&opts->watchdog_secs);
+#ifdef CONFIG_MONGOOSESSL
+ GET_FIELD_STRING_RESET(LIBCFG_PARSER, elem, "ssl_certificate", tmp);
+ if (strlen(tmp)) {
+ free(opts->ssl_cert);
+ opts->ssl_cert = strdup(tmp);
+ }
+ GET_FIELD_STRING_RESET(LIBCFG_PARSER, elem, "ssl_certificate_key", tmp);
+ if (strlen(tmp)) {
+ free(opts->ssl_key);
+ opts->ssl_key = strdup(tmp);
+ }
+#endif
+ return 0;
+}
+
+static struct option long_options[] = {
+ { "port", required_argument, NULL, 'p' },
+ { "document-root", required_argument, NULL, 'r' },
+ { "timeout", required_argument, NULL, 't' },
+#ifdef CONFIG_MONGOOSESSL
+ { "ssl", no_argument, NULL, 's' },
+ { "ssl-cert", required_argument, NULL, 'C' },
+ { "ssl-key", required_argument, NULL, 'K' },
+#endif
+ { NULL, 0, NULL, 0 }
+};
+
+void mongoose_print_help(void)
+{
+ fprintf(
+ stdout,
+ "\tlws arguments:\n"
+ "\t -p, --port <port> : server port number (default: %d)\n"
+#ifdef CONFIG_MONGOOSESSL
+ "\t -s, --ssl : enable ssl support\n"
+ "\t -C, --ssl-cert <cert> : ssl certificate to present to clients\n"
+ "\t -K, --ssl-key <key> : key corresponding to the ssl certificate\n"
+#endif
+ "\t -r, --document-root <path> : path to document root directory\n"
+ "\t -t, --timeout : timeout to check if connection is lost (default: check disabled)\n",
+ LWS_DEFAULT_PORT);
+}
+
+static void signal_handler(int signo) {
+ s_signo = signo;
+}
+
+int start_mongoose(const char *cfgfname, int argc, char *argv[])
+{
+ struct lws_opts opts = {
+ .port = LWS_DEFAULT_PORT,
+ .document_root = NULL,
+ .run_postupdate = true,
+ .watchdog_secs = 0,
+#ifdef CONFIG_MONGOOSESSL
+ .ssl = false,
+ .ssl_cert = NULL,
+ .ssl_key = NULL,
+#endif
+ };
+
+ if (cfgfname) {
+ swupdate_cfg_handle handle;
+ swupdate_cfg_init(&handle);
+ if (swupdate_cfg_read_file(&handle, cfgfname) == 0)
+ read_module_settings(&handle, "webserver",
+ lws_settings, &opts);
+ swupdate_cfg_destroy(&handle);
+ }
+
+ optind = 1;
+ int choice;
+ while ((choice = getopt_long(argc, argv,
+#ifdef CONFIG_MONGOOSESSL
+ "p:r:t:sC:K:",
+#else
+ "p:r:t:",
+#endif
+ long_options, NULL)) != -1) {
+ switch (choice) {
+ case 'p':
+ opts.port = (int)strtol(optarg, NULL, 10);
+ break;
+ case 'r':
+ free(opts.document_root);
+ opts.document_root = strdup(optarg);
+ break;
+ case 't':
+ opts.watchdog_secs = (unsigned int)strtoul(optarg, NULL, 10);
+ break;
+#ifdef CONFIG_MONGOOSESSL
+ case 's':
+ opts.ssl = true;
+ break;
+ case 'C':
+ free(opts.ssl_cert);
+ opts.ssl_cert = strdup(optarg);
+ break;
+ case 'K':
+ free(opts.ssl_key);
+ opts.ssl_key = strdup(optarg);
+ break;
+#endif
+ default:
+ free(opts.document_root);
+ return -EINVAL;
+ }
+ }
+
+ if (optind < argc) {
+ ERROR("Non-option arguments given to lws, see --help.");
+ mongoose_print_help();
+ free(opts.document_root);
+#ifdef CONFIG_MONGOOSESSL
+ free(opts.ssl_cert);
+ free(opts.ssl_key);
+#endif
+ return -EINVAL;
+ }
+
+ s_document_root = opts.document_root ? opts.document_root : "";
+ s_run_postupdate = opts.run_postupdate;
+ s_watchdog_secs = opts.watchdog_secs;
+
+ signal(SIGINT, signal_handler);
+ signal(SIGTERM, signal_handler);
+
+ struct lws_context_creation_info info;
+ memset(&info, 0, sizeof(info));
+ info.port = opts.port;
+ info.protocols = protocols;
+ info.gid = (gid_t)-1;
+ info.uid = (uid_t)-1;
+#ifndef CONFIG_MONGOOSEIPV6
+ info.options |= LWS_SERVER_OPTION_DISABLE_IPV6;
+#endif
+#ifdef CONFIG_MONGOOSESSL
+ if (opts.ssl) {
+ if (opts.ssl_cert && opts.ssl_key) {
+ info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+ info.ssl_cert_filepath = opts.ssl_cert;
+ info.ssl_private_key_filepath = opts.ssl_key;
+ } else {
+ ERROR("LWS: TLS requires both --ssl-cert and --ssl-key");
+ free(opts.document_root);
+ free(opts.ssl_cert);
+ free(opts.ssl_key);
+ return -EINVAL;
+ }
+ }
+#endif
+
+ lws_set_log_level(LLL_ERR | LLL_WARN, NULL);
+
+ g_ctx = lws_create_context(&info);
+ if (!g_ctx) {
+ ERROR("LWS: lws_create_context failed");
+ free(opts.document_root);
+#ifdef CONFIG_MONGOOSESSL
+ free(opts.ssl_cert);
+ free(opts.ssl_key);
+#endif
+ return -1;
+ }
+
+ INFO("LWS webserver listening on port %d", opts.port);
+
+ pthread_t tid;
+
+ if (pthread_create(&tid, NULL, broadcast_message_thread, NULL) != 0) {
+ ERROR("LWS: cannot create message thread: %m");
+ lws_context_destroy(g_ctx);
+ free(opts.document_root);
+#ifdef CONFIG_MONGOOSESSL
+ free(opts.ssl_cert);
+ free(opts.ssl_key);
+#endif
+ return -1;
+ }
+ pthread_detach(tid);
+
+ if (pthread_create(&tid, NULL, broadcast_progress_thread, NULL) != 0) {
+ ERROR("LWS: cannot create progress thread: %m");
+ lws_context_destroy(g_ctx);
+ free(opts.document_root);
+#ifdef CONFIG_MONGOOSESSL
+ free(opts.ssl_cert);
+ free(opts.ssl_key);
+#endif
+ return -1;
+ }
+ pthread_detach(tid);
+
+ /* Run the lws event loop in this thread until a signal arrives */
+ while (s_signo == 0)
+ lws_service(g_ctx, 50);
+
+ lws_context_destroy(g_ctx);
+ g_ctx = NULL;
+ free(opts.document_root);
+#ifdef CONFIG_MONGOOSESSL
+ free(opts.ssl_cert);
+ free(opts.ssl_key);
+#endif
+ return 0;
+}
diff --git a/mongoose/Kconfig b/mongoose/Kconfig
index a1e1afe3..a65643eb 100644
--- a/mongoose/Kconfig
+++ b/mongoose/Kconfig
@@ -2,40 +2,7 @@
#
# SPDX-License-Identifier: GPL-2.0-only

-menuconfig WEBSERVER
- bool "Web Server"
- help
- Enable update from remote using a web server on the target.
-
-if WEBSERVER
-choice
- prompt "Web Server Type"
- default MONGOOSE
- help
- Choose the web server
-
config MONGOOSE
bool "mongoose"
help
Mongoose embeddded web server
-
-endchoice
-
-config MONGOOSEIPV6
- bool "IPv6 support"
- default y
- depends on MONGOOSE
- help
- It enables ipv6 support into mongoose
-
-config MONGOOSESSL
- bool "SSL support"
- depends on MONGOOSE
- depends on SSL_IMPL_OPENSSL || SSL_IMPL_WOLFSSL || SSL_IMPL_MBEDTLS
- help
- It enables SSL support into mongoose
-
-comment "SSL support needs an SSL implementation"
- depends on !SSL_IMPL_OPENSSL && !SSL_IMPL_WOLFSSL && !SSL_IMPL_MBEDTLS
-
-endif
diff --git a/scripts/acceptance-tests/CheckImage.mk b/scripts/acceptance-tests/CheckImage.mk
index 1f04f2c8..23f6aebd 100644
--- a/scripts/acceptance-tests/CheckImage.mk
+++ b/scripts/acceptance-tests/CheckImage.mk
@@ -41,7 +41,7 @@ ifeq ($(CONFIG_SIGNED_IMAGES),$(CONFIG_SIGALG_CMS))
tests-$(CONFIG_LIBCONFIG) += $(if $(CONFIG_RAW), ValidImageTest)
endif
tests-y += InvOptsNoImg
-tests-$(CONFIG_MONGOOSE) += InvOptsCheckWithWeb
+tests-$(CONFIG_WEBSERVER) += InvOptsCheckWithWeb
tests-$(CONFIG_SURICATTA) += InvOptsCheckWithSur
tests-$(CONFIG_SIGALG_CMS) += InvSigNameCheck
tests-$(CONFIG_SIGALG_CMS) += ValidSigNameCheck

Bastian Germann

unread,
Jul 20, 2026, 4:27:09 PMJul 20
to swup...@googlegroups.com, Bastian Germann
Add a curl-based integration test that exercises the HTTP layer of a
running swupdate process's webserver.

Wire the new test into CheckImage.mk as WebserverTest, guarded by
CONFIG_WEBSERVER, so it runs as part of the existing acceptance-test
suite.

The script is self-contained: it creates a temporary document root,
spawns swupdate in the background, polls until the port is ready, runs
all assertions, and cleans up unconditionally via a trap. Port 18080
is used by default to avoid colliding with the default server port.

Signed-off-by: Bastian Germann <ba...@debian.org>
---
scripts/acceptance-tests/CheckImage.mk | 11 ++
scripts/acceptance-tests/test_webserver.sh | 145 +++++++++++++++++++++
2 files changed, 156 insertions(+)
create mode 100755 scripts/acceptance-tests/test_webserver.sh

diff --git a/scripts/acceptance-tests/CheckImage.mk b/scripts/acceptance-tests/CheckImage.mk
index 23f6aebd..785946c3 100644
--- a/scripts/acceptance-tests/CheckImage.mk
+++ b/scripts/acceptance-tests/CheckImage.mk
@@ -42,6 +42,7 @@ tests-$(CONFIG_LIBCONFIG) += $(if $(CONFIG_RAW), ValidImageTest)
endif
tests-y += InvOptsNoImg
tests-$(CONFIG_WEBSERVER) += InvOptsCheckWithWeb
+tests-$(CONFIG_WEBSERVER) += WebserverTest
tests-$(CONFIG_SURICATTA) += InvOptsCheckWithSur
tests-$(CONFIG_SIGALG_CMS) += InvSigNameCheck
tests-$(CONFIG_SIGALG_CMS) += ValidSigNameCheck
@@ -158,6 +159,16 @@ PHONY += InvOptsCheckWithWeb
InvOptsCheckWithWeb: FORCE $(if $(CONFIG_SIGALG_CMS), $(obj)/cacert.pem)
$(call cmd,swu_check_inv_websrv)

+#
+# webserver integration test – exercises both mongoose and lws backends
+#
+quiet_cmd_websrv_test = RUN $@
+ cmd_websrv_test = $(srctree)/scripts/acceptance-tests/test_webserver.sh ./swupdate
+
+PHONY += WebserverTest
+WebserverTest: FORCE
+ $(call cmd,websrv_test)
+
#
# invalid option test, suricatta with check
#
diff --git a/scripts/acceptance-tests/test_webserver.sh b/scripts/acceptance-tests/test_webserver.sh
new file mode 100755
index 00000000..4f4f5f20
--- /dev/null
+++ b/scripts/acceptance-tests/test_webserver.sh
@@ -0,0 +1,145 @@
+#!/bin/sh
+# SPDX-FileCopyrightText: 2026 Bastian Germann
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Integration tests for the SWUpdate webserver.
+#
+# Usage: test_webserver.sh [path-to-swupdate] [port]
+
+SWUPDATE="${1:-./swupdate}"
+PORT="${2:-18080}"
+BASE_URL="http://127.0.0.1:$PORT"
+DOCROOT=
+SWUPDATE_PID=
+FAKE_SWU=
+pass=0
+fail=0
+
+cleanup() {
+ [ -n "$FAKE_SWU" ] && rm -f "$FAKE_SWU"
+ if [ -n "$SWUPDATE_PID" ]; then
+ pkill -TERM -P "$SWUPDATE_PID" 2>/dev/null || true
+ kill "$SWUPDATE_PID" 2>/dev/null || true
+ wait "$SWUPDATE_PID" 2>/dev/null || true
+ fi
+ [ -n "$DOCROOT" ] && rm -rf "$(dirname "$DOCROOT")"
+}
+trap cleanup EXIT INT TERM
+
+# -----------------------------------------------------------------------
+# Setup
+# -----------------------------------------------------------------------
+DOCROOT=$(mktemp -d)/doc
+mkdir "$DOCROOT"
+echo "hello webserver" > "$DOCROOT/index.html"
+echo "body { color: red; }" > "$DOCROOT/style.css"
+echo "canary" > "$(dirname "$DOCROOT")/passwd"
+
+FAKE_SWU=$(mktemp --suffix=.swu)
+echo "not a real swu" > "$FAKE_SWU"
+
+"$SWUPDATE" -l 3 -w "-p $PORT -r $DOCROOT" >/dev/null 2>&1 &
+SWUPDATE_PID=$!
+
+# Wait up to 10 s for the server to start accepting connections
+i=0
+while ! curl -sf --max-time 1 "$BASE_URL/index.html" >/dev/null 2>&1; do
+ i=$((i + 1))
+ if [ "$i" -ge 10 ]; then
+ echo "FAIL setup: webserver not ready after 10 s"
+ exit 1
+ fi
+ sleep 1
+done
+
+# -----------------------------------------------------------------------
+# Helpers
+# -----------------------------------------------------------------------
+assert_http() {
+ desc="$1"
+ expected="$2"
+ shift 2
+ actual=$(curl -s --max-time 5 -o /dev/null -w "%{http_code}" "$@")
+ if [ "$actual" = "$expected" ]; then
+ echo "PASS: $desc (HTTP $actual)"
+ pass=$((pass + 1))
+ else
+ echo "FAIL: $desc (expected HTTP $expected, got HTTP $actual)"
+ fail=$((fail + 1))
+ fi
+}
+
+assert_http_in() {
+ desc="$1"
+ expected_pat="$2"
+ shift 2
+ actual=$(curl -s --max-time 5 -o /dev/null -w "%{http_code}" "$@")
+ if echo "$actual" | grep -qE "^($expected_pat)$"; then
+ echo "PASS: $desc (HTTP $actual)"
+ pass=$((pass + 1))
+ else
+ echo "FAIL: $desc (expected HTTP in {$expected_pat}, got HTTP $actual)"
+ fail=$((fail + 1))
+ fi
+}
+
+assert_body() {
+ desc="$1"
+ pattern="$2"
+ shift 2
+ body=$(curl -s --max-time 5 "$@")
+ if echo "$body" | grep -q "$pattern"; then
+ echo "PASS: $desc"
+ pass=$((pass + 1))
+ else
+ echo "FAIL: $desc (pattern '$pattern' not found in response)"
+ fail=$((fail + 1))
+ fi
+}
+
+# -----------------------------------------------------------------------
+# Static file serving
+# -----------------------------------------------------------------------
+assert_http "GET /index.html returns 200" 200 "$BASE_URL/index.html"
+assert_http "GET /style.css returns 200" 200 "$BASE_URL/style.css"
+assert_http "GET / returns 200" 200 "$BASE_URL/"
+assert_body "GET /index.html body is correct" "hello webserver" \
+ "$BASE_URL/index.html"
+
+# -----------------------------------------------------------------------
+# 404 for unknown resources
+# -----------------------------------------------------------------------
+assert_http "GET unknown file returns 404" 404 "$BASE_URL/no-such-file.txt"
+
+# -----------------------------------------------------------------------
+# Path traversal prevention
+# -----------------------------------------------------------------------
+# The canary file next to the docroot is never served, confirming the
+# traversal is blocked. It must be a 40x error.
+assert_http_in "Path traversal attempt is blocked" "40[0-6]" \
+ --path-as-is "$BASE_URL/../passwd"
+
+# -----------------------------------------------------------------------
+# /restart endpoint
+# -----------------------------------------------------------------------
+# POST: ipc_postupdate may return ACK (HTTP 200/201) or fail (HTTP 500);
+# both are valid HTTP responses.
+assert_http_in "POST /restart returns valid HTTP response" "200|201|500" \
+ -X POST "$BASE_URL/restart"
+
+# -----------------------------------------------------------------------
+# /upload endpoint
+# -----------------------------------------------------------------------
+# Multipart POST: the swupdate installer is running in the parent process,
+# so ipc_inst_start_ext should succeed (HTTP 200). If the installer is
+# not yet ready the upload handler returns HTTP 500.
+assert_http_in "POST /upload multipart returns valid HTTP response" "200|500" \
+ -X POST -F "file=@$FAKE_SWU;filename=test.swu" \
+ "$BASE_URL/upload"
+
+# -----------------------------------------------------------------------
+# Summary
+# -----------------------------------------------------------------------
+echo ""
+echo "Results: $pass passed, $fail failed"
+[ "$fail" -eq 0 ]

Stefano Babic

unread,
Jul 21, 2026, 10:30:31 AMJul 21
to Bastian Germann, swup...@googlegroups.com
Hi Bastian,

On 7/20/26 22:26, Bastian Germann wrote:
> The roadmap lists an alternative webserver implementation.
> Add a new web server backend based on libwebsockets (lws) as an
> alternative to mongoose.

lws was an alternative I had in mind - many thanks for this !

Best regards,
Stefano
--
_______________________________________________________________________
Nabla Software Engineering GmbH
Hirschstr. 111A | 86156 Augsburg | Tel: +49 821 45592596
Geschäftsführer : Stefano Babic | HRB 40522 Augsburg
E-Mail: sba...@nabladev.com

Stefano Babic

unread,
Jul 21, 2026, 10:31:14 AMJul 21
to Bastian Germann, swup...@googlegroups.com
On 7/20/26 22:26, Bastian Germann wrote:
Reviewed-by: Stefano Babic <stefan...@swupdate.org>

Stefano Babic

unread,
Jul 21, 2026, 10:47:33 AMJul 21
to Bastian Germann, swup...@googlegroups.com
Hi Bastian,

On 7/20/26 22:26, Bastian Germann wrote:
> Add a new web server backend based on libwebsockets (lws) as an
> alternative to mongoose. The new backend provides the same HTTP API:
>
> POST /upload - stream SWU images to the installer, supporting both
> multipart/form-data and application/octet-stream
> POST /restart - trigger the post-update action
> GET * - static file serving from a configurable web root
> GET (ws) - WebSocket status stream with the same JSON protocol
> as the mongoose backend
>

Fine.

> Kconfig is restructured to introduce a WEBSERVER choice menu so that
> mongoose and lws are mutually exclusive options. The config options
> MONGOOSESSL and MONGOOSEIPV6 cover both backends while the name is kept.
> Likewise, the mongoose_interface.h is implemented by lws but not renamed.

Just a note: the only reason for mongoose_interface is to add prototype
as start_mongoose(), but they can be also renamed like
start_webserver(). But this can be easy done with a follow up patch, no
need to change here.

>
> Three of the CLI/config file parameters that mongoose carries are not
> implemented. Two of them configure HTTP digest authentication that is
> implemented in libwebsockets for the client side only. The other one is
> file listing, which would involve a HTML template.

That is ok.
They can be also renamed, they are not used directly by mongoose code.
The related Kconfig translate them as MG_ENABLE_IPV6=1 and MG_TLS
(mapped to one of the crypto engine).
This is not very clear. We force a Webserver setup (that has nothing to
do here) to build here.

What about to set CONFIG_HASH_VERIFY in Webserver's Kconfig if SSL is set ?
What about a wrong post, that is someone (maybe an attacker) start
pushing an image with POST but without body content. From code, I assume
that LWS_UFS_CLOSE is called without passing through
LWS_UFS_FINAL_CONTENT, and the ipc remains open, waiting forever for the
next chunk of the SWU. What do you mind ?
Best regards,
Stefano

Bastian Germann

unread,
Jul 27, 2026, 6:03:52 PM (9 days ago) Jul 27
to swup...@googlegroups.com, Bastian Germann, Stefano Babic
The mongoose_interface.h contains a function definition that is only used
once. To minimize the interface, move it and drop the unused defs.

Signed-off-by: Bastian Germann <ba...@debian.org>
Reviewed-by: Stefano Babic <stefan...@swupdate.org>
--
2.47.3

Bastian Germann

unread,
Jul 27, 2026, 6:03:52 PM (9 days ago) Jul 27
to swup...@googlegroups.com, Bastian Germann
The roadmap lists an alternative webserver implementation.
Add a new web server backend based on libwebsockets (lws) as an
alternative to mongoose.

v2:
* Remove the crypto/Makefile changes which were leftovers from an unsent
iteration
* Address the multipart upload LWS_UFS_CLOSE comment, freeing resources

Bastian Germann (5):
mongoose: Move definition from interface to impl
util: Extract utils from mongoose
test: util: Test functions from webserver
webserver: implement lws backend using libwebsockets
acceptance-tests: add webserver integration test

Kconfig | 36 +
Makefile | 2 +-
Makefile.flags | 5 +
core/swupdate.c | 2 +-
core/util.c | 52 ++
doc/source/improvement_proposals.rst | 4 -
doc/source/swupdate.rst | 5 +-
examples/configuration/swupdate.cfg | 20 +-
include/mongoose_interface.h | 12 -
include/util.h | 4 +
lws/Kconfig | 18 +
lws/Makefile | 5 +
lws/lws_interface.c | 886 +++++++++++++++++++++
mongoose/Kconfig | 33 -
mongoose/mongoose_interface.c | 58 +-
scripts/acceptance-tests/CheckImage.mk | 13 +-
scripts/acceptance-tests/test_webserver.sh | 145 ++++
test/test_util.c | 64 ++
18 files changed, 1245 insertions(+), 119 deletions(-)
create mode 100644 lws/Kconfig
create mode 100644 lws/Makefile
create mode 100644 lws/lws_interface.c
create mode 100755 scripts/acceptance-tests/test_webserver.sh

--
2.47.3

Bastian Germann

unread,
Jul 27, 2026, 6:03:53 PM (9 days ago) Jul 27
to swup...@googlegroups.com, Bastian Germann
Add a curl-based integration test that exercises the HTTP layer of a
running swupdate process's webserver.

Wire the new test into CheckImage.mk as WebserverTest, guarded by
CONFIG_WEBSERVER, so it runs as part of the existing acceptance-test
suite.

The script is self-contained: it creates a temporary document root,
spawns swupdate in the background, polls until the port is ready, runs
all assertions, and cleans up unconditionally via a trap. Port 18080
is used by default to avoid colliding with the default server port.

Signed-off-by: Bastian Germann <ba...@debian.org>
---
scripts/acceptance-tests/CheckImage.mk | 11 ++
scripts/acceptance-tests/test_webserver.sh | 145 +++++++++++++++++++++
2 files changed, 156 insertions(+)
create mode 100755 scripts/acceptance-tests/test_webserver.sh

diff --git a/scripts/acceptance-tests/CheckImage.mk b/scripts/acceptance-tests/CheckImage.mk
index 23f6aebd..785946c3 100644
--- a/scripts/acceptance-tests/CheckImage.mk
+++ b/scripts/acceptance-tests/CheckImage.mk
@@ -42,6 +42,7 @@ tests-$(CONFIG_LIBCONFIG) += $(if $(CONFIG_RAW), ValidImageTest)
endif
tests-y += InvOptsNoImg
tests-$(CONFIG_WEBSERVER) += InvOptsCheckWithWeb
+tests-$(CONFIG_WEBSERVER) += WebserverTest
tests-$(CONFIG_SURICATTA) += InvOptsCheckWithSur
tests-$(CONFIG_SIGALG_CMS) += InvSigNameCheck
tests-$(CONFIG_SIGALG_CMS) += ValidSigNameCheck
@@ -158,6 +159,16 @@ PHONY += InvOptsCheckWithWeb
InvOptsCheckWithWeb: FORCE $(if $(CONFIG_SIGALG_CMS), $(obj)/cacert.pem)
$(call cmd,swu_check_inv_websrv)

+#
+# webserver integration test – exercises both mongoose and lws backends
+#
+quiet_cmd_websrv_test = RUN $@
+ cmd_websrv_test = $(srctree)/scripts/acceptance-tests/test_webserver.sh ./swupdate
+
+PHONY += WebserverTest
+WebserverTest: FORCE
+ $(call cmd,websrv_test)
+
#
# invalid option test, suricatta with check
#
diff --git a/scripts/acceptance-tests/test_webserver.sh b/scripts/acceptance-tests/test_webserver.sh
new file mode 100755
index 00000000..4f4f5f20
--- /dev/null
+++ b/scripts/acceptance-tests/test_webserver.sh
@@ -0,0 +1,145 @@
+#!/bin/sh
+# SPDX-FileCopyrightText: 2026 Bastian Germann
--
2.47.3

Bastian Germann

unread,
Jul 27, 2026, 6:03:53 PM (9 days ago) Jul 27
to swup...@googlegroups.com, Bastian Germann, Stefano Babic
Split out utility functions that come in handy for another webserver
implementation. Capitalize the macros.

Signed-off-by: Bastian Germann <ba...@debian.org>
Reviewed-by: Stefano Babic <stefan...@swupdate.org>
---
core/util.c | 52 ++++++++++++++++++++++++++++++++
include/util.h | 4 +++
mongoose/mongoose_interface.c | 56 -----------------------------------
3 files changed, 56 insertions(+), 56 deletions(-)

diff --git a/core/util.c b/core/util.c
index c52f1e4b..eb1fefbe 100644
--- a/core/util.c
+++ b/core/util.c
@@ -1368,3 +1368,55 @@ bool is_filename_valid (const char *file_name)
{
return file_name != NULL && file_name[0] != '/' && strstr(file_name, "../") == NULL;
}
+#define ENUM_STRING(x) [x] = #x
+const char *get_status_string(unsigned int status)
+{
+ static const char * const str[] = {
+ ENUM_STRING(IDLE),
+ ENUM_STRING(START),
+ ENUM_STRING(RUN),
+ ENUM_STRING(SUCCESS),
+ ENUM_STRING(FAILURE),
+ ENUM_STRING(DOWNLOAD),
+ ENUM_STRING(DONE),
+ ENUM_STRING(SUBPROCESS),
+ };
+
+ if (status >= ARRAY_SIZE(str))
+ return "UNKNOWN";
+
+ return str[status];
+}
+
+#define ENUM_SOURCE_STRING(x) [SOURCE_##x] = #x
+const char *get_source_string(unsigned int source)
+{
+ static const char * const str[] = {
+ ENUM_SOURCE_STRING(UNKNOWN),
+ ENUM_SOURCE_STRING(WEBSERVER),
+ ENUM_SOURCE_STRING(SURICATTA),
+ ENUM_SOURCE_STRING(DOWNLOADER),
+ ENUM_SOURCE_STRING(LOCAL),
+ };
+
+ if (source >= ARRAY_SIZE(str))
+ return "UNKNOWN";
+
+ return str[source];
+}
+
+int level_to_rfc_5424(int level)
+{
+ switch (level) {
+ case ERRORLEVEL:
+ return 3;
+ case WARNLEVEL:
+ return 4;
+ case INFOLEVEL:
+ return 6;
+ case TRACELEVEL:
+ case DEBUGLEVEL:
+ default:
+ return 7;
+ }
+}
diff --git a/include/util.h b/include/util.h
index cb90917f..f3f0cfc0 100644
--- a/include/util.h
+++ b/include/util.h
@@ -364,3 +364,7 @@ static inline bool is_enabled_or_disabled(const char *s) {
static inline bool is_enabled(const char *s) {
return (!strcmp(s, "enabled"));
}
+
+const char *get_status_string(unsigned int status);
+const char *get_source_string(unsigned int source);
+int level_to_rfc_5424(int level);
\ No newline at end of file
diff --git a/mongoose/mongoose_interface.c b/mongoose/mongoose_interface.c
index 1ad3c3df..a673fcf8 100644
--- a/mongoose/mongoose_interface.c
+++ b/mongoose/mongoose_interface.c
@@ -304,46 +304,6 @@ static void mg_http_send_digest_auth_request(struct mg_connection *c, const char
c->is_draining = 1;
}

-/*
- * These functions are for V2 of the protocol
- */
-#define enum_string(x) [x] = #x
-static const char *get_status_string(unsigned int status)
-{
- const char * const str[] = {
- enum_string(IDLE),
- enum_string(START),
- enum_string(RUN),
- enum_string(SUCCESS),
- enum_string(FAILURE),
- enum_string(DOWNLOAD),
- enum_string(DONE),
- enum_string(SUBPROCESS)
- };
-
- if (status >= ARRAY_SIZE(str))
- return "UNKNOWN";
-
- return str[status];
-}
-
-#define enum_source_string(x) [SOURCE_##x] = #x
-static const char *get_source_string(unsigned int source)
-{
- const char * const str[] = {
- enum_source_string(UNKNOWN),
- enum_source_string(WEBSERVER),
- enum_source_string(SURICATTA),
- enum_source_string(DOWNLOADER),
- enum_source_string(LOCAL)
- };
-
- if (source >= ARRAY_SIZE(str))
- return "UNKNOWN";
-
- return str[source];
-}
-
static void restart_handler(struct mg_connection *nc, void *ev_data)
{
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
@@ -363,22 +323,6 @@ static void restart_handler(struct mg_connection *nc, void *ev_data)
mg_http_reply(nc, 201, "", "%s", "Device will reboot now.\n");
}

-static int level_to_rfc_5424(int level)
-{
- switch(level) {
- case ERRORLEVEL:
- return 3;
- case WARNLEVEL:
- return 4;
- case INFOLEVEL:
- return 6;
- case TRACELEVEL:
- case DEBUGLEVEL:
- default:
- return 7;
- }
-}
-
static void broadcast(char *str)
{
if (conn_info.mgr && conn_info.id) {
--
2.47.3

Bastian Germann

unread,
Jul 27, 2026, 6:03:53 PM (9 days ago) Jul 27
to swup...@googlegroups.com, Bastian Germann
Add a new web server backend based on libwebsockets (lws) as an
alternative to mongoose. The new backend provides the same HTTP API:

POST /upload - stream SWU images to the installer, supporting both
multipart/form-data and application/octet-stream
POST /restart - trigger the post-update action
GET * - static file serving from a configurable web root
GET (ws) - WebSocket status stream with the same JSON protocol
as the mongoose backend

Kconfig is restructured to introduce a WEBSERVER choice menu so that
mongoose and lws are mutually exclusive options. The config options
MONGOOSESSL and MONGOOSEIPV6 cover both backends while the name is kept.
Likewise, the mongoose_interface.h is implemented by lws but not renamed.

Three of the CLI/config file parameters that mongoose carries are not
implemented. Two of them configure HTTP digest authentication that is
implemented in libwebsockets for the client side only. The other one is
file listing, which would involve a HTML template.

Signed-off-by: Bastian Germann <ba...@debian.org>
---
Kconfig | 36 +
Makefile | 2 +-
Makefile.flags | 5 +
core/swupdate.c | 2 +-
doc/source/improvement_proposals.rst | 4 -
doc/source/swupdate.rst | 5 +-
examples/configuration/swupdate.cfg | 20 +-
lws/Kconfig | 18 +
lws/Makefile | 5 +
lws/lws_interface.c | 886 +++++++++++++++++++++++++
mongoose/Kconfig | 33 -
scripts/acceptance-tests/CheckImage.mk | 2 +-
12 files changed, 967 insertions(+), 51 deletions(-)
create mode 100644 lws/Kconfig
create mode 100644 lws/Makefile
create mode 100644 lws/lws_interface.c

+# SPDX-FileCopyrightText: 2026 Bastian Germann
+#
+# SPDX-License-Identifier: GPL-2.0-only
+
+config LWS
+ bool "libwebsockets"
+ depends on HAVE_LIBWEBSOCKETS
+ help
+ Enable a webserver built on libwebsockets.
+
+ Endpoints:
+ POST /upload - upload a SWU image
+ (multipart/form-data or application/octet-stream)
+ POST /restart - trigger the post-update action
+ GET * - static file serving from a configurable web root
+ (set via -r or the web-root config option)
+ GET (ws) - WebSocket status stream
+ (same JSON protocol as the mongoose webserver)
diff --git a/lws/Makefile b/lws/Makefile
new file mode 100644
index 00000000..1789ffdb
--- /dev/null
+++ b/lws/Makefile
@@ -0,0 +1,5 @@
+# SPDX-FileCopyrightText: 2026 Bastian Germann
+#
+# SPDX-License-Identifier: GPL-2.0-only
+
+lib-$(CONFIG_LWS) += lws_interface.o
diff --git a/lws/lws_interface.c b/lws/lws_interface.c
new file mode 100644
index 00000000..9e1a5072
--- /dev/null
+++ b/lws/lws_interface.c
@@ -0,0 +1,886 @@
+ char json[]; /* flexible array -- allocated inline */
+ * WebSocket session registry (lws service thread only -- no lock needed)
+ if (pss->ipc_fd >= 0) {
+ ipc_end(pss->ipc_fd);
+ pss->ipc_fd = -1;
+ pss->upload_error = true;
+ }
+ break;
+ /* Not yet expired -- reschedule */
diff --git a/scripts/acceptance-tests/CheckImage.mk b/scripts/acceptance-tests/CheckImage.mk
index 1f04f2c8..23f6aebd 100644
--- a/scripts/acceptance-tests/CheckImage.mk
+++ b/scripts/acceptance-tests/CheckImage.mk
@@ -41,7 +41,7 @@ ifeq ($(CONFIG_SIGNED_IMAGES),$(CONFIG_SIGALG_CMS))
tests-$(CONFIG_LIBCONFIG) += $(if $(CONFIG_RAW), ValidImageTest)
endif
tests-y += InvOptsNoImg
-tests-$(CONFIG_MONGOOSE) += InvOptsCheckWithWeb
+tests-$(CONFIG_WEBSERVER) += InvOptsCheckWithWeb
tests-$(CONFIG_SURICATTA) += InvOptsCheckWithSur
tests-$(CONFIG_SIGALG_CMS) += InvSigNameCheck
tests-$(CONFIG_SIGALG_CMS) += ValidSigNameCheck
--
2.47.3

Bastian Germann

unread,
Jul 27, 2026, 6:03:53 PM (9 days ago) Jul 27
to swup...@googlegroups.com, Bastian Germann
Signed-off-by: Bastian Germann <ba...@debian.org>
---
--
2.47.3

Stefano Babic

unread,
Jul 28, 2026, 9:01:54 AM (8 days ago) Jul 28
to Bastian Germann, swup...@googlegroups.com
On 7/28/26 00:03, Bastian Germann wrote:
> The roadmap lists an alternative webserver implementation.
> Add a new web server backend based on libwebsockets (lws) as an
> alternative to mongoose.
>
> v2:
> * Remove the crypto/Makefile changes which were leftovers from an unsent
> iteration
> * Address the multipart upload LWS_UFS_CLOSE comment, freeing resources
>

Thanks, applied (whole series) to -master.

Best regards,
Stefano
Reply all
Reply to author
Forward
0 new messages