diff mbox series

mongoose: Allow WEB API v1 and v2 both be enabled

Message ID 1519652095-8588-1-git-send-email-sami.hartikainen@teleste.com
State Changes Requested
Headers show
Series mongoose: Allow WEB API v1 and v2 both be enabled | expand

Commit Message

Hartikainen, Sami Feb. 26, 2018, 1:34 p.m. UTC
To ease transition, or simply to enable the use of modern clients
while still supporting the use of the swuforwarder handler (which
only talks API v1), both the v1 and v2 API may be enabled.

When both API versions are enabled, the v2 message broadcast thread
consumes the status messages from the installer. A message queue is
set up to make those messages available for the v1 message polling
handler for URI /getstatus.json.

Signed-off-by: Sami Hartikainen <sami.hartikainen@teleste.com>
---
 mongoose/Config.in            |  15 +--
 mongoose/mongoose_interface.c | 246 +++++++++++++++++++++++++++++++-----------
 2 files changed, 194 insertions(+), 67 deletions(-)

Comments

Stefan Herbrechtsmeier Feb. 26, 2018, 7:29 p.m. UTC | #1
Hi Sami,

Am 26.02.2018 um 14:34 schrieb Sami Hartikainen:
> To ease transition, or simply to enable the use of modern clients
> while still supporting the use of the swuforwarder handler (which
> only talks API v1), both the v1 and v2 API may be enabled.
>
> When both API versions are enabled, the v2 message broadcast thread
> consumes the status messages from the installer. A message queue is
> set up to make those messages available for the v1 message polling
> handler for URI /getstatus.json.
>
> Signed-off-by: Sami Hartikainen <sami.hartikainen@teleste.com>
> ---
>   mongoose/Config.in            |  15 +--
>   mongoose/mongoose_interface.c | 246 +++++++++++++++++++++++++++++++-----------
>   2 files changed, 194 insertions(+), 67 deletions(-)
>
> diff --git a/mongoose/Config.in b/mongoose/Config.in
> index a001247..cd52f2c 100644
> --- a/mongoose/Config.in
> +++ b/mongoose/Config.in
> @@ -14,27 +14,28 @@ choice
>   config MONGOOSE
>   	bool "mongoose"
>   	help
> -	  Mongoose embeddded web server
> +	  Mongoose embedded web server
>   
>   endchoice
>   
> -choice
> -	prompt "Web Application Interface"
> -	default MONGOOSE_WEB_API_V1
> -	help
> -	  Choose the bootloader
> +menu "Web Application Interface"
>   
>   config MONGOOSE_WEB_API_V1
>   	bool "Version 1 (deprecated)"
> +	default y
>   	help
>   	  Support for version 1
>   
>   config MONGOOSE_WEB_API_V2
>   	bool "Version 2"
> +	default n

Maybe we should also enable V2 by default.

>   	help
>   	  Support for version 2
>   
> -endchoice
> +comment "Web API v1 or v2 or both required"
> +	depends on !MONGOOSE_WEB_API_V1 && !MONGOOSE_WEB_API_V2
> +
> +endmenu
>   
>   config MONGOOSEIPV6
>   	bool "IPv6 support"
> diff --git a/mongoose/mongoose_interface.c b/mongoose/mongoose_interface.c
> index 89a51f3..0401a9a 100644
> --- a/mongoose/mongoose_interface.c
> +++ b/mongoose/mongoose_interface.c
> @@ -25,6 +25,7 @@
>   #include <parselib.h>
>   #include <progress_ipc.h>
>   #include <swupdate_settings.h>
> +#include <bsdqueue.h>
>   
>   #include "mongoose.h"
>   
> @@ -32,6 +33,9 @@
>   #define MG_PORT "8080"
>   #define MG_ROOT "."
>   
> +#define WEB_API_v1 1
> +#define WEB_API_v2 2
> +
>   struct mongoose_options {
>   	char *root;
>   	char *listing;
> @@ -49,6 +53,25 @@ struct file_upload_state {
>   
>   static struct mg_serve_http_opts s_http_server_opts;
>   
> +#if !defined(CONFIG_MONGOOSE_WEB_API_V1) && !defined(CONFIG_MONGOOSE_WEB_API_V2)
> +#error "WEB API v1 or v2 or both must be defined"
> +#endif

Maybe we should fall back to V1 to support older .config files.

> +
> +#if defined(CONFIG_MONGOOSE_WEB_API_V1) && defined(CONFIG_MONGOOSE_WEB_API_V2)
> +#define NUM_CACHED_MESSAGES 100
> +
> +struct msg_elem {
> +	ipc_message msg;
> +	SIMPLEQ_ENTRY(msg_elem) next;
> +};
> +
> +SIMPLEQ_HEAD(msglist, msg_elem);
> +static struct msglist ipcmessages;
> +static unsigned long nrmsgs = 0;
> +static pthread_mutex_t msglock = PTHREAD_MUTEX_INITIALIZER;
> +static pthread_cond_t msgcond = PTHREAD_COND_INITIALIZER;
> +#endif
> +
>   #if defined(CONFIG_MONGOOSE_WEB_API_V2)
>   #define enum_string(x)	[x] = #x
>   static const char *get_status_string(unsigned int status)
> @@ -112,20 +135,163 @@ static size_t snescape(char *dst, size_t n, const char *src)
>   }
>   #endif
>   
> -static void upload_handler(struct mg_connection *nc, int ev, void *p)
> +static int get_inst_status(ipc_message *msg, int  __attribute__ ((__unused__)) api_vers)
> +{
> +#if defined(CONFIG_MONGOOSE_WEB_API_V1) && defined(CONFIG_MONGOOSE_WEB_API_V2)
> +	struct msg_elem *newmsg;
> +	struct msg_elem *oldmsg;
> +	int ret = -1;
> +
> +	switch (api_vers) {
> +	case WEB_API_v1:
> +		pthread_mutex_lock(&msglock);
> +		pthread_cond_wait(&msgcond, &msglock);
> +		oldmsg = SIMPLEQ_FIRST(&ipcmessages);
> +		if (oldmsg) {
> +			SIMPLEQ_REMOVE_HEAD(&ipcmessages, next);
> +			nrmsgs--;
> +			memcpy(msg, &oldmsg->msg, sizeof(*msg));
> +			free(oldmsg);
> +			ret = 0;
> +		}
> +		pthread_mutex_unlock(&msglock);
> +		break;
> +
> +	case WEB_API_v2:
> +		newmsg = (struct msg_elem *)calloc(1, sizeof(*newmsg));
> +		if (!newmsg)
> +			return -1;
> +
> +		ret = ipc_get_status(msg);
> +
> +		/* Cache message for APIv1, but consider
> +		 * changed messages only. */
> +		if (!ret) {
> +			pthread_mutex_lock(&msglock);
> +
> +			oldmsg = SIMPLEQ_LAST(&ipcmessages, msg_elem, next);
> +			if (!oldmsg || memcmp(&oldmsg->msg.data.status,
> +					      &msg->data.status,
> +					      sizeof(msg->data.status))) {
> +
> +				nrmsgs++;
> +				if (nrmsgs > NUM_CACHED_MESSAGES) {
> +					oldmsg = SIMPLEQ_FIRST(&ipcmessages);
> +					SIMPLEQ_REMOVE_HEAD(&ipcmessages, next);
> +					free(oldmsg);
> +					nrmsgs--;
> +				}
> +
> +				memcpy(&newmsg->msg, msg, sizeof(*msg));
> +				SIMPLEQ_INSERT_TAIL(&ipcmessages, newmsg, next);
> +				newmsg = NULL;
> +			}
> +
> +			pthread_cond_signal(&msgcond);
> +			pthread_mutex_unlock(&msglock);
> +		}
> +
> +		if (newmsg)
> +			free(newmsg);
> +		break;
> +	}
> +
> +	return ret;
> +#else
> +	return ipc_get_status(msg);
> +#endif
> +}

Why you bundle two functions into one function with a switch case?

> +
> +static void cleanup_msg_list(void)
> +{
> +#if defined(CONFIG_MONGOOSE_WEB_API_V1) && defined(CONFIG_MONGOOSE_WEB_API_V2)
> +	struct msg_elem *elem;
> +
> +	pthread_mutex_lock(&msglock);
> +
> +	while (!SIMPLEQ_EMPTY(&ipcmessages)) {
> +		elem = SIMPLEQ_FIRST(&ipcmessages);
> +		SIMPLEQ_REMOVE_HEAD(&ipcmessages, next);
> +		free(elem);
> +	}
> +
> +	nrmsgs = 0;
> +	pthread_mutex_unlock(&msglock);
> +#endif
> +}
> +
> +static void upload_handler_v2(struct mg_connection *nc, int ev, void *p)
>   {
>   	struct mg_http_multipart_part *mp;
>   	struct file_upload_state *fus;
> +
> +	switch (ev) {
> +	case MG_EV_HTTP_PART_BEGIN:
> +		mp = (struct mg_http_multipart_part *) p;
> +
> +		fus = (struct file_upload_state *) calloc(1, sizeof(*fus));
> +		if (fus == NULL) {
> +			mg_http_send_error(nc, 500, "Out of memory");
> +			break;
> +		}
> +
> +		cleanup_msg_list();
> +
> +		fus->fd = ipc_inst_start_ext(SOURCE_WEBSERVER, strlen(mp->file_name), mp->file_name);
> +		if (fus->fd < 0) {
> +			mg_http_send_error(nc, 500, "Failed to queue command");
> +			free(fus);
> +			break;
> +		}
> +
> +		mp->user_data = fus;
> +
> +		break;
> +
> +	case MG_EV_HTTP_PART_DATA:
> +		mp = (struct mg_http_multipart_part *) p;
> +		fus = (struct file_upload_state *) mp->user_data;
> +
> +		if (!fus)
> +			break;
> +
> +		ipc_send_data(fus->fd, (char *) mp->data.p, mp->data.len);
> +		fus->len += mp->data.len;
> +
> +		break;
> +
> +	case MG_EV_HTTP_PART_END:
> +		mp = (struct mg_http_multipart_part *) p;
> +		fus = (struct file_upload_state *) mp->user_data;
> +
> +		if (!fus)
> +			break;
> +
> +		ipc_end(fus->fd);
> +
> +		mg_send_response_line(nc, 200,
> +			"Content-Type: text/plain\r\n"
> +			"Connection: close");
> +		mg_send(nc, "\r\n", 2);
> +		mg_printf(nc, "Ok, %s - %d bytes.\r\n", mp->file_name, (int) fus->len);
> +		nc->flags |= MG_F_SEND_AND_CLOSE;
> +
> +		mp->user_data = NULL;
> +		free(fus);
> +		break;
> +	}
> +}
> +
>   #if defined(CONFIG_MONGOOSE_WEB_API_V1)
> +static void upload_handler_v1(struct mg_connection *nc, int ev, void *p)
> +{
>   	struct mg_str *filename, *data;
>   	struct http_message *hm;
>   	size_t length;
>   	char buf[16];
>   	int fd;
> -#endif
>   
>   	switch (ev) {
> -#if defined(CONFIG_MONGOOSE_WEB_API_V1)
>   	case MG_EV_HTTP_REQUEST:
>   		hm = (struct http_message *) p;
>   
> @@ -149,6 +315,8 @@ static void upload_handler(struct mg_connection *nc, int ev, void *p)
>   			return;
>   		}
>   
> +		cleanup_msg_list();
> +
>   		fd = ipc_inst_start_ext(SOURCE_WEBSERVER, filename->len, filename->p);
>   		ipc_send_data(fd, (char *) hm->body.p, hm->body.len);
>   		ipc_end(fd);
> @@ -161,62 +329,13 @@ static void upload_handler(struct mg_connection *nc, int ev, void *p)
>   		nc->flags |= MG_F_SEND_AND_CLOSE;
>   
>   		break;
> -#endif
> -	case MG_EV_HTTP_PART_BEGIN:
> -		mp = (struct mg_http_multipart_part *) p;
>   
> -		fus = (struct file_upload_state *) calloc(1, sizeof(*fus));
> -		if (fus == NULL) {
> -			mg_http_send_error(nc, 500, "Out of memory");
> -			break;
> -		}
> -
> -		fus->fd = ipc_inst_start_ext(SOURCE_WEBSERVER, strlen(mp->file_name), mp->file_name);
> -		if (fus->fd < 0) {
> -			mg_http_send_error(nc, 500, "Failed to queue command");
> -			free(fus);
> -			break;
> -		}
> -
> -		mp->user_data = fus;
> -
> -		break;
> -
> -	case MG_EV_HTTP_PART_DATA:
> -		mp = (struct mg_http_multipart_part *) p;
> -		fus = (struct file_upload_state *) mp->user_data;
> -
> -		if (!fus)
> -			break;
> -
> -		ipc_send_data(fus->fd, (char *) mp->data.p, mp->data.len);
> -		fus->len += mp->data.len;
> -
> -		break;
> -
> -	case MG_EV_HTTP_PART_END:
> -		mp = (struct mg_http_multipart_part *) p;
> -		fus = (struct file_upload_state *) mp->user_data;
> -
> -		if (!fus)
> -			break;
> -
> -		ipc_end(fus->fd);
> -
> -		mg_send_response_line(nc, 200,
> -			"Content-Type: text/plain\r\n"
> -			"Connection: close");
> -		mg_send(nc, "\r\n", 2);
> -		mg_printf(nc, "Ok, %s - %d bytes.\r\n", mp->file_name, (int) fus->len);
> -		nc->flags |= MG_F_SEND_AND_CLOSE;
> -
> -		mp->user_data = NULL;
> -		free(fus);
> +	default:
> +		upload_handler_v2(nc, ev, p);
>   		break;
>   	}
>   }
>   
> -#if defined(CONFIG_MONGOOSE_WEB_API_V1)
>   static void recovery_status(struct mg_connection *nc, int ev, void *ev_data)
>   {
>   	ipc_message ipc;
> @@ -226,7 +345,7 @@ static void recovery_status(struct mg_connection *nc, int ev, void *ev_data)
>   	(void)ev;
>   	(void)ev_data;
>   
> -	ret = ipc_get_status(&ipc);
> +	ret = get_inst_status(&ipc, WEB_API_v1);
>   
>   	if (ret) {
>   		mg_http_send_error(nc, 500, NULL);
> @@ -306,7 +425,9 @@ static void post_update_cmd(struct mg_connection *nc, int ev, void *ev_data)
>   
>   	nc->flags |= MG_F_SEND_AND_CLOSE;
>   }
> -#elif defined(CONFIG_MONGOOSE_WEB_API_V2)
> +#endif
> +
> +#if defined(CONFIG_MONGOOSE_WEB_API_V2)
>   static void restart_handler(struct mg_connection *nc, int ev, void *ev_data)
>   {
>   	struct http_message *hm = (struct http_message *) ev_data;
> @@ -350,7 +471,7 @@ static void *broadcast_message_thread(void *data)
>   {
>   	for (;;) {
>   		ipc_message msg;
> -		int ret = ipc_get_status(&msg);
> +		int ret = get_inst_status(&msg, WEB_API_v2);
>   
>   		if (!ret && strlen(msg.data.status.desc) != 0) {
>   			struct mg_mgr *mgr = (struct mg_mgr *) data;
> @@ -610,15 +731,20 @@ int start_mongoose(const char *cfgfname, int argc, char *argv[])
>   		exit(EXIT_FAILURE);
>   	}
>   
> +#if defined(CONFIG_MONGOOSE_WEB_API_V1) && defined(CONFIG_MONGOOSE_WEB_API_V2)
> +	SIMPLEQ_INIT(&ipcmessages);
> +#endif
> +
>   #if defined(CONFIG_MONGOOSE_WEB_API_V1)
> -	mg_register_http_endpoint(nc, "/handle_post_request", MG_CB(upload_handler, NULL));
> +	mg_register_http_endpoint(nc, "/handle_post_request", MG_CB(upload_handler_v1, NULL));
>   	mg_register_http_endpoint(nc, "/getstatus.json", MG_CB(recovery_status, NULL));
>   	mg_register_http_endpoint(nc, "/rebootTarget", MG_CB(reboot_target, NULL));
>   	mg_register_http_endpoint(nc, "/postUpdateCommand", MG_CB(post_update_cmd, NULL));
> -#elif defined(CONFIG_MONGOOSE_WEB_API_V2)
> +#endif
> +#if defined(CONFIG_MONGOOSE_WEB_API_V2)
>   	mg_register_http_endpoint(nc, "/restart", restart_handler);
>   #endif
> -	mg_register_http_endpoint(nc, "/upload", MG_CB(upload_handler, NULL));
> +	mg_register_http_endpoint(nc, "/upload", MG_CB(upload_handler_v2, NULL));

Could you please move the upload_handler_v2 into to V2 guards.

>   	mg_set_protocol_http_websocket(nc);
>   
>   #if defined(CONFIG_MONGOOSE_WEB_API_V2)

The code use many ifdefs, maybe it is wise to split the code into three 
files.

Best regards
   Stefan
Hartikainen, Sami Feb. 27, 2018, 10:08 a.m. UTC | #2
Hi Stefan,

> > To ease transition, or simply to enable the use of modern clients
> > while still supporting the use of the swuforwarder handler (which only
> > talks API v1), both the v1 and v2 API may be enabled.
> >
> > When both API versions are enabled, the v2 message broadcast thread
> > consumes the status messages from the installer. A message queue is
> > set up to make those messages available for the v1 message polling
> > handler for URI /getstatus.json.
> >
> > Signed-off-by: Sami Hartikainen <sami.hartikainen@teleste.com>
> > ---
> >   mongoose/Config.in            |  15 +--
> >   mongoose/mongoose_interface.c | 246
> +++++++++++++++++++++++++++++++-----------
> >   2 files changed, 194 insertions(+), 67 deletions(-)
> >
> > diff --git a/mongoose/Config.in b/mongoose/Config.in index
> > a001247..cd52f2c 100644
> > --- a/mongoose/Config.in
> > +++ b/mongoose/Config.in
> > @@ -14,27 +14,28 @@ choice
> >   config MONGOOSE
> >   	bool "mongoose"
> >   	help
> > -	  Mongoose embeddded web server
> > +	  Mongoose embedded web server
> >
> >   endchoice
> >
> > -choice
> > -	prompt "Web Application Interface"
> > -	default MONGOOSE_WEB_API_V1
> > -	help
> > -	  Choose the bootloader
> > +menu "Web Application Interface"
> >
> >   config MONGOOSE_WEB_API_V1
> >   	bool "Version 1 (deprecated)"
> > +	default y
> >   	help
> >   	  Support for version 1
> >
> >   config MONGOOSE_WEB_API_V2
> >   	bool "Version 2"
> > +	default n
> 
> Maybe we should also enable V2 by default.

Enabling both adds binary footprint size and code complexity, so
I chose to be cautious with this. On the other hand, describing V1
as "deprecated" implies it should not be the only one by default
enabled.

> >   	help
> >   	  Support for version 2
> >
> > -endchoice
> > +comment "Web API v1 or v2 or both required"
> > +	depends on !MONGOOSE_WEB_API_V1 &&
> !MONGOOSE_WEB_API_V2
> > +
> > +endmenu
> >
> >   config MONGOOSEIPV6
> >   	bool "IPv6 support"
> > diff --git a/mongoose/mongoose_interface.c
> > b/mongoose/mongoose_interface.c index 89a51f3..0401a9a 100644
> > --- a/mongoose/mongoose_interface.c
> > +++ b/mongoose/mongoose_interface.c
> > @@ -25,6 +25,7 @@
> >   #include <parselib.h>
> >   #include <progress_ipc.h>
> >   #include <swupdate_settings.h>
> > +#include <bsdqueue.h>
> >
> >   #include "mongoose.h"
> >
> > @@ -32,6 +33,9 @@
> >   #define MG_PORT "8080"
> >   #define MG_ROOT "."
> >
> > +#define WEB_API_v1 1
> > +#define WEB_API_v2 2
> > +
> >   struct mongoose_options {
> >   	char *root;
> >   	char *listing;
> > @@ -49,6 +53,25 @@ struct file_upload_state {
> >
> >   static struct mg_serve_http_opts s_http_server_opts;
> >
> > +#if !defined(CONFIG_MONGOOSE_WEB_API_V1) &&
> > +!defined(CONFIG_MONGOOSE_WEB_API_V2)
> > +#error "WEB API v1 or v2 or both must be defined"
> > +#endif
> 
> Maybe we should fall back to V1 to support older .config files.

The _defconfig files with no mention of either CONFIG_MONGOOSE_WEB_API_V1 or
CONFIG_MONGOOSE_WEB_API_V2 end up selecting the defaults, i.e. V1 currently.

Old .config files will trigger Kconfig to query the setting for these two new options
interactively.

I believe it is best to require that these settings do exist in the generated autoconf headers,
rather than falling back to defining them in a file-by-file basis; therefore it should be enough
that during build we simply error out in the event the user has (intentionally) misconfigured
these settings.

> > +
> > +#if defined(CONFIG_MONGOOSE_WEB_API_V1) &&
> > +defined(CONFIG_MONGOOSE_WEB_API_V2)
> > +#define NUM_CACHED_MESSAGES 100
> > +
> > +struct msg_elem {
> > +	ipc_message msg;
> > +	SIMPLEQ_ENTRY(msg_elem) next;
> > +};
> > +
> > +SIMPLEQ_HEAD(msglist, msg_elem);
> > +static struct msglist ipcmessages;
> > +static unsigned long nrmsgs = 0;
> > +static pthread_mutex_t msglock = PTHREAD_MUTEX_INITIALIZER; static
> > +pthread_cond_t msgcond = PTHREAD_COND_INITIALIZER; #endif
> > +
> >   #if defined(CONFIG_MONGOOSE_WEB_API_V2)
> >   #define enum_string(x)	[x] = #x
> >   static const char *get_status_string(unsigned int status) @@ -112,20
> > +135,163 @@ static size_t snescape(char *dst, size_t n, const char *src)
> >   }
> >   #endif
> >
> > -static void upload_handler(struct mg_connection *nc, int ev, void *p)
> > +static int get_inst_status(ipc_message *msg, int  __attribute__
> > +((__unused__)) api_vers) { #if
> defined(CONFIG_MONGOOSE_WEB_API_V1) &&
> > +defined(CONFIG_MONGOOSE_WEB_API_V2)
> > +	struct msg_elem *newmsg;
> > +	struct msg_elem *oldmsg;
> > +	int ret = -1;
> > +
> > +	switch (api_vers) {
> > +	case WEB_API_v1:
> > +		pthread_mutex_lock(&msglock);
> > +		pthread_cond_wait(&msgcond, &msglock);
> > +		oldmsg = SIMPLEQ_FIRST(&ipcmessages);
> > +		if (oldmsg) {
> > +			SIMPLEQ_REMOVE_HEAD(&ipcmessages, next);
> > +			nrmsgs--;
> > +			memcpy(msg, &oldmsg->msg, sizeof(*msg));
> > +			free(oldmsg);
> > +			ret = 0;
> > +		}
> > +		pthread_mutex_unlock(&msglock);
> > +		break;
> > +
> > +	case WEB_API_v2:
> > +		newmsg = (struct msg_elem *)calloc(1, sizeof(*newmsg));
> > +		if (!newmsg)
> > +			return -1;
> > +
> > +		ret = ipc_get_status(msg);
> > +
> > +		/* Cache message for APIv1, but consider
> > +		 * changed messages only. */
> > +		if (!ret) {
> > +			pthread_mutex_lock(&msglock);
> > +
> > +			oldmsg = SIMPLEQ_LAST(&ipcmessages, msg_elem,
> next);
> > +			if (!oldmsg || memcmp(&oldmsg->msg.data.status,
> > +					      &msg->data.status,
> > +					      sizeof(msg->data.status))) {
> > +
> > +				nrmsgs++;
> > +				if (nrmsgs > NUM_CACHED_MESSAGES) {
> > +					oldmsg =
> SIMPLEQ_FIRST(&ipcmessages);
> > +
> 	SIMPLEQ_REMOVE_HEAD(&ipcmessages, next);
> > +					free(oldmsg);
> > +					nrmsgs--;
> > +				}
> > +
> > +				memcpy(&newmsg->msg, msg,
> sizeof(*msg));
> > +				SIMPLEQ_INSERT_TAIL(&ipcmessages,
> newmsg, next);
> > +				newmsg = NULL;
> > +			}
> > +
> > +			pthread_cond_signal(&msgcond);
> > +			pthread_mutex_unlock(&msglock);
> > +		}
> > +
> > +		if (newmsg)
> > +			free(newmsg);
> > +		break;
> > +	}
> > +
> > +	return ret;
> > +#else
> > +	return ipc_get_status(msg);
> > +#endif
> > +}
> 
> Why you bundle two functions into one function with a switch case?

To emphasize the fact that the function, from the caller's viewpoint, provides the same
function(ality). Only if both the V1 and V2 APIs are enabled does the implementation
differ.

> > +
> > +static void cleanup_msg_list(void)
> > +{
> > +#if defined(CONFIG_MONGOOSE_WEB_API_V1) &&
> defined(CONFIG_MONGOOSE_WEB_API_V2)
> > +	struct msg_elem *elem;
> > +
> > +	pthread_mutex_lock(&msglock);
> > +
> > +	while (!SIMPLEQ_EMPTY(&ipcmessages)) {
> > +		elem = SIMPLEQ_FIRST(&ipcmessages);
> > +		SIMPLEQ_REMOVE_HEAD(&ipcmessages, next);
> > +		free(elem);
> > +	}
> > +
> > +	nrmsgs = 0;
> > +	pthread_mutex_unlock(&msglock);
> > +#endif
> > +}
> > +
> > +static void upload_handler_v2(struct mg_connection *nc, int ev, void
> > +*p)
> >   {
> >   	struct mg_http_multipart_part *mp;
> >   	struct file_upload_state *fus;
> > +
> > +	switch (ev) {
> > +	case MG_EV_HTTP_PART_BEGIN:
> > +		mp = (struct mg_http_multipart_part *) p;
> > +
> > +		fus = (struct file_upload_state *) calloc(1, sizeof(*fus));
> > +		if (fus == NULL) {
> > +			mg_http_send_error(nc, 500, "Out of memory");
> > +			break;
> > +		}
> > +
> > +		cleanup_msg_list();
> > +
> > +		fus->fd = ipc_inst_start_ext(SOURCE_WEBSERVER,
> strlen(mp->file_name), mp->file_name);
> > +		if (fus->fd < 0) {
> > +			mg_http_send_error(nc, 500, "Failed to queue
> command");
> > +			free(fus);
> > +			break;
> > +		}
> > +
> > +		mp->user_data = fus;
> > +
> > +		break;
> > +
> > +	case MG_EV_HTTP_PART_DATA:
> > +		mp = (struct mg_http_multipart_part *) p;
> > +		fus = (struct file_upload_state *) mp->user_data;
> > +
> > +		if (!fus)
> > +			break;
> > +
> > +		ipc_send_data(fus->fd, (char *) mp->data.p, mp->data.len);
> > +		fus->len += mp->data.len;
> > +
> > +		break;
> > +
> > +	case MG_EV_HTTP_PART_END:
> > +		mp = (struct mg_http_multipart_part *) p;
> > +		fus = (struct file_upload_state *) mp->user_data;
> > +
> > +		if (!fus)
> > +			break;
> > +
> > +		ipc_end(fus->fd);
> > +
> > +		mg_send_response_line(nc, 200,
> > +			"Content-Type: text/plain\r\n"
> > +			"Connection: close");
> > +		mg_send(nc, "\r\n", 2);
> > +		mg_printf(nc, "Ok, %s - %d bytes.\r\n", mp->file_name, (int)
> fus->len);
> > +		nc->flags |= MG_F_SEND_AND_CLOSE;
> > +
> > +		mp->user_data = NULL;
> > +		free(fus);
> > +		break;
> > +	}
> > +}
> > +
> >   #if defined(CONFIG_MONGOOSE_WEB_API_V1)
> > +static void upload_handler_v1(struct mg_connection *nc, int ev, void
> > +*p) {
> >   	struct mg_str *filename, *data;
> >   	struct http_message *hm;
> >   	size_t length;
> >   	char buf[16];
> >   	int fd;
> > -#endif
> >
> >   	switch (ev) {
> > -#if defined(CONFIG_MONGOOSE_WEB_API_V1)
> >   	case MG_EV_HTTP_REQUEST:
> >   		hm = (struct http_message *) p;
> >
> > @@ -149,6 +315,8 @@ static void upload_handler(struct mg_connection
> *nc, int ev, void *p)
> >   			return;
> >   		}
> >
> > +		cleanup_msg_list();
> > +
> >   		fd = ipc_inst_start_ext(SOURCE_WEBSERVER, filename->len,
> filename->p);
> >   		ipc_send_data(fd, (char *) hm->body.p, hm->body.len);
> >   		ipc_end(fd);
> > @@ -161,62 +329,13 @@ static void upload_handler(struct mg_connection
> *nc, int ev, void *p)
> >   		nc->flags |= MG_F_SEND_AND_CLOSE;
> >
> >   		break;
> > -#endif
> > -	case MG_EV_HTTP_PART_BEGIN:
> > -		mp = (struct mg_http_multipart_part *) p;
> >
> > -		fus = (struct file_upload_state *) calloc(1, sizeof(*fus));
> > -		if (fus == NULL) {
> > -			mg_http_send_error(nc, 500, "Out of memory");
> > -			break;
> > -		}
> > -
> > -		fus->fd = ipc_inst_start_ext(SOURCE_WEBSERVER,
> strlen(mp->file_name), mp->file_name);
> > -		if (fus->fd < 0) {
> > -			mg_http_send_error(nc, 500, "Failed to queue
> command");
> > -			free(fus);
> > -			break;
> > -		}
> > -
> > -		mp->user_data = fus;
> > -
> > -		break;
> > -
> > -	case MG_EV_HTTP_PART_DATA:
> > -		mp = (struct mg_http_multipart_part *) p;
> > -		fus = (struct file_upload_state *) mp->user_data;
> > -
> > -		if (!fus)
> > -			break;
> > -
> > -		ipc_send_data(fus->fd, (char *) mp->data.p, mp->data.len);
> > -		fus->len += mp->data.len;
> > -
> > -		break;
> > -
> > -	case MG_EV_HTTP_PART_END:
> > -		mp = (struct mg_http_multipart_part *) p;
> > -		fus = (struct file_upload_state *) mp->user_data;
> > -
> > -		if (!fus)
> > -			break;
> > -
> > -		ipc_end(fus->fd);
> > -
> > -		mg_send_response_line(nc, 200,
> > -			"Content-Type: text/plain\r\n"
> > -			"Connection: close");
> > -		mg_send(nc, "\r\n", 2);
> > -		mg_printf(nc, "Ok, %s - %d bytes.\r\n", mp->file_name, (int)
> fus->len);
> > -		nc->flags |= MG_F_SEND_AND_CLOSE;
> > -
> > -		mp->user_data = NULL;
> > -		free(fus);
> > +	default:
> > +		upload_handler_v2(nc, ev, p);
> >   		break;
> >   	}
> >   }
> >
> > -#if defined(CONFIG_MONGOOSE_WEB_API_V1)
> >   static void recovery_status(struct mg_connection *nc, int ev, void
> *ev_data)
> >   {
> >   	ipc_message ipc;
> > @@ -226,7 +345,7 @@ static void recovery_status(struct mg_connection
> *nc, int ev, void *ev_data)
> >   	(void)ev;
> >   	(void)ev_data;
> >
> > -	ret = ipc_get_status(&ipc);
> > +	ret = get_inst_status(&ipc, WEB_API_v1);
> >
> >   	if (ret) {
> >   		mg_http_send_error(nc, 500, NULL); @@ -306,7 +425,9 @@
> static void
> > post_update_cmd(struct mg_connection *nc, int ev, void *ev_data)
> >
> >   	nc->flags |= MG_F_SEND_AND_CLOSE;
> >   }
> > -#elif defined(CONFIG_MONGOOSE_WEB_API_V2)
> > +#endif
> > +
> > +#if defined(CONFIG_MONGOOSE_WEB_API_V2)
> >   static void restart_handler(struct mg_connection *nc, int ev, void
> *ev_data)
> >   {
> >   	struct http_message *hm = (struct http_message *) ev_data; @@
> > -350,7 +471,7 @@ static void *broadcast_message_thread(void *data)
> >   {
> >   	for (;;) {
> >   		ipc_message msg;
> > -		int ret = ipc_get_status(&msg);
> > +		int ret = get_inst_status(&msg, WEB_API_v2);
> >
> >   		if (!ret && strlen(msg.data.status.desc) != 0) {
> >   			struct mg_mgr *mgr = (struct mg_mgr *) data; @@ -
> 610,15 +731,20
> > @@ int start_mongoose(const char *cfgfname, int argc, char *argv[])
> >   		exit(EXIT_FAILURE);
> >   	}
> >
> > +#if defined(CONFIG_MONGOOSE_WEB_API_V1) &&
> defined(CONFIG_MONGOOSE_WEB_API_V2)
> > +	SIMPLEQ_INIT(&ipcmessages);
> > +#endif
> > +
> >   #if defined(CONFIG_MONGOOSE_WEB_API_V1)
> > -	mg_register_http_endpoint(nc, "/handle_post_request",
> MG_CB(upload_handler, NULL));
> > +	mg_register_http_endpoint(nc, "/handle_post_request",
> > +MG_CB(upload_handler_v1, NULL));
> >   	mg_register_http_endpoint(nc, "/getstatus.json",
> MG_CB(recovery_status, NULL));
> >   	mg_register_http_endpoint(nc, "/rebootTarget",
> MG_CB(reboot_target, NULL));
> >   	mg_register_http_endpoint(nc, "/postUpdateCommand",
> > MG_CB(post_update_cmd, NULL)); -#elif
> > defined(CONFIG_MONGOOSE_WEB_API_V2)
> > +#endif
> > +#if defined(CONFIG_MONGOOSE_WEB_API_V2)
> >   	mg_register_http_endpoint(nc, "/restart", restart_handler);
> >   #endif
> > -	mg_register_http_endpoint(nc, "/upload", MG_CB(upload_handler,
> NULL));
> > +	mg_register_http_endpoint(nc, "/upload",
> MG_CB(upload_handler_v2,
> > +NULL));
> 
> Could you please move the upload_handler_v2 into to V2 guards.

Please note how the upload_handler_v1 calls upload_handler_v2 in the default
switch case. This was done to provide the same code paths for V1 (also when only
V1 is enabled) as before the patch.

Or do you mean the mg_register_http_endpoint() call for "/upload"?

> >   	mg_set_protocol_http_websocket(nc);
> >
> >   #if defined(CONFIG_MONGOOSE_WEB_API_V2)
> 
> The code use many ifdefs, maybe it is wise to split the code into three files.

Do you mean three files as in
 - mongoose_interface_v1.c
 - mongoose_interface_v2.c
 - mongoose_interface_v1and2.c

Or just one additional file containing the bits that are enabled only if both V1 and V2
are enabled?

Br, Sami
Stefan Herbrechtsmeier Feb. 27, 2018, 7:54 p.m. UTC | #3
Hi Sami,

Am 27.02.2018 um 11:08 schrieb Sami.Hartikainen@teleste.com:
>>> To ease transition, or simply to enable the use of modern clients
>>> while still supporting the use of the swuforwarder handler (which only
>>> talks API v1), both the v1 and v2 API may be enabled.
>>>
>>> When both API versions are enabled, the v2 message broadcast thread
>>> consumes the status messages from the installer. A message queue is
>>> set up to make those messages available for the v1 message polling
>>> handler for URI /getstatus.json.
>>>
>>> Signed-off-by: Sami Hartikainen <sami.hartikainen@teleste.com>
>>> ---
>>>    mongoose/Config.in            |  15 +--
>>>    mongoose/mongoose_interface.c | 246
>> +++++++++++++++++++++++++++++++-----------
>>>    2 files changed, 194 insertions(+), 67 deletions(-)
>>>
>>> diff --git a/mongoose/Config.in b/mongoose/Config.in index
>>> a001247..cd52f2c 100644
>>> --- a/mongoose/Config.in
>>> +++ b/mongoose/Config.in
>>> @@ -14,27 +14,28 @@ choice
>>>    config MONGOOSE
>>>    	bool "mongoose"
>>>    	help
>>> -	  Mongoose embeddded web server
>>> +	  Mongoose embedded web server
>>>
>>>    endchoice
>>>
>>> -choice
>>> -	prompt "Web Application Interface"
>>> -	default MONGOOSE_WEB_API_V1
>>> -	help
>>> -	  Choose the bootloader
>>> +menu "Web Application Interface"
>>>
>>>    config MONGOOSE_WEB_API_V1
>>>    	bool "Version 1 (deprecated)"
>>> +	default y
>>>    	help
>>>    	  Support for version 1
>>>
>>>    config MONGOOSE_WEB_API_V2
>>>    	bool "Version 2"
>>> +	default n
>> Maybe we should also enable V2 by default.
> Enabling both adds binary footprint size and code complexity, so
> I chose to be cautious with this. On the other hand, describing V1
> as "deprecated" implies it should not be the only one by default
> enabled.

The v1 was only the default to be backward compatible.

@Stefano: What do you think?

>>>    	help
>>>    	  Support for version 2
>>>
>>> -endchoice
>>> +comment "Web API v1 or v2 or both required"
>>> +	depends on !MONGOOSE_WEB_API_V1 &&
>> !MONGOOSE_WEB_API_V2
>>> +
>>> +endmenu
>>>
>>>    config MONGOOSEIPV6
>>>    	bool "IPv6 support"
>>> diff --git a/mongoose/mongoose_interface.c
>>> b/mongoose/mongoose_interface.c index 89a51f3..0401a9a 100644
>>> --- a/mongoose/mongoose_interface.c
>>> +++ b/mongoose/mongoose_interface.c
>>> @@ -25,6 +25,7 @@
>>>    #include <parselib.h>
>>>    #include <progress_ipc.h>
>>>    #include <swupdate_settings.h>
>>> +#include <bsdqueue.h>
>>>
>>>    #include "mongoose.h"
>>>
>>> @@ -32,6 +33,9 @@
>>>    #define MG_PORT "8080"
>>>    #define MG_ROOT "."
>>>
>>> +#define WEB_API_v1 1
>>> +#define WEB_API_v2 2
>>> +
>>>    struct mongoose_options {
>>>    	char *root;
>>>    	char *listing;
>>> @@ -49,6 +53,25 @@ struct file_upload_state {
>>>
>>>    static struct mg_serve_http_opts s_http_server_opts;
>>>
>>> +#if !defined(CONFIG_MONGOOSE_WEB_API_V1) &&
>>> +!defined(CONFIG_MONGOOSE_WEB_API_V2)
>>> +#error "WEB API v1 or v2 or both must be defined"
>>> +#endif
>> Maybe we should fall back to V1 to support older .config files.
> The _defconfig files with no mention of either CONFIG_MONGOOSE_WEB_API_V1 or
> CONFIG_MONGOOSE_WEB_API_V2 end up selecting the defaults, i.e. V1 currently.
>
> Old .config files will trigger Kconfig to query the setting for these two new options
> interactively.
>
> I believe it is best to require that these settings do exist in the generated autoconf headers,
> rather than falling back to defining them in a file-by-file basis; therefore it should be enough
> that during build we simply error out in the event the user has (intentionally) misconfigured
> these settings.
>
>>> +
>>> +#if defined(CONFIG_MONGOOSE_WEB_API_V1) &&
>>> +defined(CONFIG_MONGOOSE_WEB_API_V2)
>>> +#define NUM_CACHED_MESSAGES 100
>>> +
>>> +struct msg_elem {
>>> +	ipc_message msg;
>>> +	SIMPLEQ_ENTRY(msg_elem) next;
>>> +};
>>> +
>>> +SIMPLEQ_HEAD(msglist, msg_elem);
>>> +static struct msglist ipcmessages;
>>> +static unsigned long nrmsgs = 0;
>>> +static pthread_mutex_t msglock = PTHREAD_MUTEX_INITIALIZER; static
>>> +pthread_cond_t msgcond = PTHREAD_COND_INITIALIZER; #endif
>>> +
>>>    #if defined(CONFIG_MONGOOSE_WEB_API_V2)
>>>    #define enum_string(x)	[x] = #x
>>>    static const char *get_status_string(unsigned int status) @@ -112,20
>>> +135,163 @@ static size_t snescape(char *dst, size_t n, const char *src)
>>>    }
>>>    #endif
>>>
>>> -static void upload_handler(struct mg_connection *nc, int ev, void *p)
>>> +static int get_inst_status(ipc_message *msg, int  __attribute__
>>> +((__unused__)) api_vers) { #if
>> defined(CONFIG_MONGOOSE_WEB_API_V1) &&
>>> +defined(CONFIG_MONGOOSE_WEB_API_V2)
>>> +	struct msg_elem *newmsg;
>>> +	struct msg_elem *oldmsg;
>>> +	int ret = -1;
>>> +
>>> +	switch (api_vers) {
>>> +	case WEB_API_v1:
>>> +		pthread_mutex_lock(&msglock);
>>> +		pthread_cond_wait(&msgcond, &msglock);
>>> +		oldmsg = SIMPLEQ_FIRST(&ipcmessages);
>>> +		if (oldmsg) {
>>> +			SIMPLEQ_REMOVE_HEAD(&ipcmessages, next);
>>> +			nrmsgs--;
>>> +			memcpy(msg, &oldmsg->msg, sizeof(*msg));
>>> +			free(oldmsg);
>>> +			ret = 0;
>>> +		}
>>> +		pthread_mutex_unlock(&msglock);
>>> +		break;
>>> +
>>> +	case WEB_API_v2:
>>> +		newmsg = (struct msg_elem *)calloc(1, sizeof(*newmsg));
>>> +		if (!newmsg)
>>> +			return -1;
>>> +
>>> +		ret = ipc_get_status(msg);
>>> +
>>> +		/* Cache message for APIv1, but consider
>>> +		 * changed messages only. */
>>> +		if (!ret) {
>>> +			pthread_mutex_lock(&msglock);
>>> +
>>> +			oldmsg = SIMPLEQ_LAST(&ipcmessages, msg_elem,
>> next);
>>> +			if (!oldmsg || memcmp(&oldmsg->msg.data.status,
>>> +					      &msg->data.status,
>>> +					      sizeof(msg->data.status))) {
>>> +
>>> +				nrmsgs++;
>>> +				if (nrmsgs > NUM_CACHED_MESSAGES) {
>>> +					oldmsg =
>> SIMPLEQ_FIRST(&ipcmessages);
>>> +
>> 	SIMPLEQ_REMOVE_HEAD(&ipcmessages, next);
>>> +					free(oldmsg);
>>> +					nrmsgs--;
>>> +				}
>>> +
>>> +				memcpy(&newmsg->msg, msg,
>> sizeof(*msg));
>>> +				SIMPLEQ_INSERT_TAIL(&ipcmessages,
>> newmsg, next);
>>> +				newmsg = NULL;
>>> +			}
>>> +
>>> +			pthread_cond_signal(&msgcond);
>>> +			pthread_mutex_unlock(&msglock);
>>> +		}
>>> +
>>> +		if (newmsg)
>>> +			free(newmsg);
>>> +		break;
>>> +	}
>>> +
>>> +	return ret;
>>> +#else
>>> +	return ipc_get_status(msg);
>>> +#endif
>>> +}
>> Why you bundle two functions into one function with a switch case?
> To emphasize the fact that the function, from the caller's viewpoint, provides the same
> function(ality). Only if both the V1 and V2 APIs are enabled does the implementation
> differ.

In this case I would move the if def and ipc_get_status(msg) function to 
its old place and create two new meaningful functions.

In the v1 and v2 API case you have one function with two totally 
different behaviors.

Do you need the v1 and v2 API at the same time? Otherwise we could start 
the threads after the first web socket connected and stop them after the 
last web socket was closed.

>>> +
>>> +static void cleanup_msg_list(void)
>>> +{
>>> +#if defined(CONFIG_MONGOOSE_WEB_API_V1) &&
>> defined(CONFIG_MONGOOSE_WEB_API_V2)
>>> +	struct msg_elem *elem;
>>> +
>>> +	pthread_mutex_lock(&msglock);
>>> +
>>> +	while (!SIMPLEQ_EMPTY(&ipcmessages)) {
>>> +		elem = SIMPLEQ_FIRST(&ipcmessages);
>>> +		SIMPLEQ_REMOVE_HEAD(&ipcmessages, next);
>>> +		free(elem);
>>> +	}
>>> +
>>> +	nrmsgs = 0;
>>> +	pthread_mutex_unlock(&msglock);
>>> +#endif
>>> +}
>>> +
>>> +static void upload_handler_v2(struct mg_connection *nc, int ev, void
>>> +*p)
>>>    {
>>>    	struct mg_http_multipart_part *mp;
>>>    	struct file_upload_state *fus;
>>> +
>>> +	switch (ev) {
>>> +	case MG_EV_HTTP_PART_BEGIN:
>>> +		mp = (struct mg_http_multipart_part *) p;
>>> +
>>> +		fus = (struct file_upload_state *) calloc(1, sizeof(*fus));
>>> +		if (fus == NULL) {
>>> +			mg_http_send_error(nc, 500, "Out of memory");
>>> +			break;
>>> +		}
>>> +
>>> +		cleanup_msg_list();
>>> +
>>> +		fus->fd = ipc_inst_start_ext(SOURCE_WEBSERVER,
>> strlen(mp->file_name), mp->file_name);
>>> +		if (fus->fd < 0) {
>>> +			mg_http_send_error(nc, 500, "Failed to queue
>> command");
>>> +			free(fus);
>>> +			break;
>>> +		}
>>> +
>>> +		mp->user_data = fus;
>>> +
>>> +		break;
>>> +
>>> +	case MG_EV_HTTP_PART_DATA:
>>> +		mp = (struct mg_http_multipart_part *) p;
>>> +		fus = (struct file_upload_state *) mp->user_data;
>>> +
>>> +		if (!fus)
>>> +			break;
>>> +
>>> +		ipc_send_data(fus->fd, (char *) mp->data.p, mp->data.len);
>>> +		fus->len += mp->data.len;
>>> +
>>> +		break;
>>> +
>>> +	case MG_EV_HTTP_PART_END:
>>> +		mp = (struct mg_http_multipart_part *) p;
>>> +		fus = (struct file_upload_state *) mp->user_data;
>>> +
>>> +		if (!fus)
>>> +			break;
>>> +
>>> +		ipc_end(fus->fd);
>>> +
>>> +		mg_send_response_line(nc, 200,
>>> +			"Content-Type: text/plain\r\n"
>>> +			"Connection: close");
>>> +		mg_send(nc, "\r\n", 2);
>>> +		mg_printf(nc, "Ok, %s - %d bytes.\r\n", mp->file_name, (int)
>> fus->len);
>>> +		nc->flags |= MG_F_SEND_AND_CLOSE;
>>> +
>>> +		mp->user_data = NULL;
>>> +		free(fus);
>>> +		break;
>>> +	}
>>> +}
>>> +
>>>    #if defined(CONFIG_MONGOOSE_WEB_API_V1)
>>> +static void upload_handler_v1(struct mg_connection *nc, int ev, void
>>> +*p) {
>>>    	struct mg_str *filename, *data;
>>>    	struct http_message *hm;
>>>    	size_t length;
>>>    	char buf[16];
>>>    	int fd;
>>> -#endif
>>>
>>>    	switch (ev) {
>>> -#if defined(CONFIG_MONGOOSE_WEB_API_V1)
>>>    	case MG_EV_HTTP_REQUEST:
>>>    		hm = (struct http_message *) p;
>>>
>>> @@ -149,6 +315,8 @@ static void upload_handler(struct mg_connection
>> *nc, int ev, void *p)
>>>    			return;
>>>    		}
>>>
>>> +		cleanup_msg_list();
>>> +
>>>    		fd = ipc_inst_start_ext(SOURCE_WEBSERVER, filename->len,
>> filename->p);
>>>    		ipc_send_data(fd, (char *) hm->body.p, hm->body.len);
>>>    		ipc_end(fd);
>>> @@ -161,62 +329,13 @@ static void upload_handler(struct mg_connection
>> *nc, int ev, void *p)
>>>    		nc->flags |= MG_F_SEND_AND_CLOSE;
>>>
>>>    		break;
>>> -#endif
>>> -	case MG_EV_HTTP_PART_BEGIN:
>>> -		mp = (struct mg_http_multipart_part *) p;
>>>
>>> -		fus = (struct file_upload_state *) calloc(1, sizeof(*fus));
>>> -		if (fus == NULL) {
>>> -			mg_http_send_error(nc, 500, "Out of memory");
>>> -			break;
>>> -		}
>>> -
>>> -		fus->fd = ipc_inst_start_ext(SOURCE_WEBSERVER,
>> strlen(mp->file_name), mp->file_name);
>>> -		if (fus->fd < 0) {
>>> -			mg_http_send_error(nc, 500, "Failed to queue
>> command");
>>> -			free(fus);
>>> -			break;
>>> -		}
>>> -
>>> -		mp->user_data = fus;
>>> -
>>> -		break;
>>> -
>>> -	case MG_EV_HTTP_PART_DATA:
>>> -		mp = (struct mg_http_multipart_part *) p;
>>> -		fus = (struct file_upload_state *) mp->user_data;
>>> -
>>> -		if (!fus)
>>> -			break;
>>> -
>>> -		ipc_send_data(fus->fd, (char *) mp->data.p, mp->data.len);
>>> -		fus->len += mp->data.len;
>>> -
>>> -		break;
>>> -
>>> -	case MG_EV_HTTP_PART_END:
>>> -		mp = (struct mg_http_multipart_part *) p;
>>> -		fus = (struct file_upload_state *) mp->user_data;
>>> -
>>> -		if (!fus)
>>> -			break;
>>> -
>>> -		ipc_end(fus->fd);
>>> -
>>> -		mg_send_response_line(nc, 200,
>>> -			"Content-Type: text/plain\r\n"
>>> -			"Connection: close");
>>> -		mg_send(nc, "\r\n", 2);
>>> -		mg_printf(nc, "Ok, %s - %d bytes.\r\n", mp->file_name, (int)
>> fus->len);
>>> -		nc->flags |= MG_F_SEND_AND_CLOSE;
>>> -
>>> -		mp->user_data = NULL;
>>> -		free(fus);
>>> +	default:
>>> +		upload_handler_v2(nc, ev, p);
>>>    		break;
>>>    	}
>>>    }
>>>
>>> -#if defined(CONFIG_MONGOOSE_WEB_API_V1)
>>>    static void recovery_status(struct mg_connection *nc, int ev, void
>> *ev_data)
>>>    {
>>>    	ipc_message ipc;
>>> @@ -226,7 +345,7 @@ static void recovery_status(struct mg_connection
>> *nc, int ev, void *ev_data)
>>>    	(void)ev;
>>>    	(void)ev_data;
>>>
>>> -	ret = ipc_get_status(&ipc);
>>> +	ret = get_inst_status(&ipc, WEB_API_v1);
>>>
>>>    	if (ret) {
>>>    		mg_http_send_error(nc, 500, NULL); @@ -306,7 +425,9 @@
>> static void
>>> post_update_cmd(struct mg_connection *nc, int ev, void *ev_data)
>>>
>>>    	nc->flags |= MG_F_SEND_AND_CLOSE;
>>>    }
>>> -#elif defined(CONFIG_MONGOOSE_WEB_API_V2)
>>> +#endif
>>> +
>>> +#if defined(CONFIG_MONGOOSE_WEB_API_V2)
>>>    static void restart_handler(struct mg_connection *nc, int ev, void
>> *ev_data)
>>>    {
>>>    	struct http_message *hm = (struct http_message *) ev_data; @@
>>> -350,7 +471,7 @@ static void *broadcast_message_thread(void *data)
>>>    {
>>>    	for (;;) {
>>>    		ipc_message msg;
>>> -		int ret = ipc_get_status(&msg);
>>> +		int ret = get_inst_status(&msg, WEB_API_v2);
>>>
>>>    		if (!ret && strlen(msg.data.status.desc) != 0) {
>>>    			struct mg_mgr *mgr = (struct mg_mgr *) data; @@ -
>> 610,15 +731,20
>>> @@ int start_mongoose(const char *cfgfname, int argc, char *argv[])
>>>    		exit(EXIT_FAILURE);
>>>    	}
>>>
>>> +#if defined(CONFIG_MONGOOSE_WEB_API_V1) &&
>> defined(CONFIG_MONGOOSE_WEB_API_V2)
>>> +	SIMPLEQ_INIT(&ipcmessages);
>>> +#endif
>>> +
>>>    #if defined(CONFIG_MONGOOSE_WEB_API_V1)
>>> -	mg_register_http_endpoint(nc, "/handle_post_request",
>> MG_CB(upload_handler, NULL));
>>> +	mg_register_http_endpoint(nc, "/handle_post_request",
>>> +MG_CB(upload_handler_v1, NULL));
>>>    	mg_register_http_endpoint(nc, "/getstatus.json",
>> MG_CB(recovery_status, NULL));
>>>    	mg_register_http_endpoint(nc, "/rebootTarget",
>> MG_CB(reboot_target, NULL));
>>>    	mg_register_http_endpoint(nc, "/postUpdateCommand",
>>> MG_CB(post_update_cmd, NULL)); -#elif
>>> defined(CONFIG_MONGOOSE_WEB_API_V2)
>>> +#endif
>>> +#if defined(CONFIG_MONGOOSE_WEB_API_V2)
>>>    	mg_register_http_endpoint(nc, "/restart", restart_handler);
>>>    #endif
>>> -	mg_register_http_endpoint(nc, "/upload", MG_CB(upload_handler,
>> NULL));
>>> +	mg_register_http_endpoint(nc, "/upload",
>> MG_CB(upload_handler_v2,
>>> +NULL));
>> Could you please move the upload_handler_v2 into to V2 guards.
> Please note how the upload_handler_v1 calls upload_handler_v2 in the default
> switch case. This was done to provide the same code paths for V1 (also when only
> V1 is enabled) as before the patch.
>
> Or do you mean the mg_register_http_endpoint() call for "/upload"?

Yes I mean the mg_register_http_endpoint() call. I missed to move this 
into the if def.

>>>    	mg_set_protocol_http_websocket(nc);
>>>
>>>    #if defined(CONFIG_MONGOOSE_WEB_API_V2)
>> The code use many ifdefs, maybe it is wise to split the code into three files.
> Do you mean three files as in
>   - mongoose_interface_v1.c
>   - mongoose_interface_v2.c
>   - mongoose_interface_v1and2.c
>
> Or just one additional file containing the bits that are enabled only if both V1 and V2
> are enabled?

I mean the three files as mentioned.

@Stefano: What do you think?

Best regards
   Stefan
Hartikainen, Sami March 1, 2018, 3:27 p.m. UTC | #4
Hi Stefan,

> >>> diff --git a/mongoose/Config.in b/mongoose/Config.in index
<snip>
> >>> +menu "Web Application Interface"
> >>>
> >>>    config MONGOOSE_WEB_API_V1
> >>>    	bool "Version 1 (deprecated)"
> >>> +	default y
> >>>    	help
> >>>    	  Support for version 1
> >>>
> >>>    config MONGOOSE_WEB_API_V2
> >>>    	bool "Version 2"
> >>> +	default n
> >> Maybe we should also enable V2 by default.
> > Enabling both adds binary footprint size and code complexity, so I
> > chose to be cautious with this. On the other hand, describing V1 as
> > "deprecated" implies it should not be the only one by default enabled.
> 
> The v1 was only the default to be backward compatible.
> 
> @Stefano: What do you think?

Ok. Enabling both by default unless someone objects.

> >>> diff --git a/mongoose/mongoose_interface.c
> >>> b/mongoose/mongoose_interface.c index 89a51f3..0401a9a 100644
> >>> --- a/mongoose/mongoose_interface.c
> >>> +++ b/mongoose/mongoose_interface.c
<snip>
> 
> Do you need the v1 and v2 API at the same time? Otherwise we could start
> the threads after the first web socket connected and stop them after the last
> web socket was closed.

No, I think they will not be needed at same time in any sane scenario. Starting
the v2 broadcast threads only when needed sounds like a good idea. I'll look into
that.

> >> 610,15 +731,20
> >>> @@ int start_mongoose(const char *cfgfname, int argc, char *argv[])
> >>>    		exit(EXIT_FAILURE);
> >>>    	}
> >>>
> >>> +#if defined(CONFIG_MONGOOSE_WEB_API_V1) &&
> >> defined(CONFIG_MONGOOSE_WEB_API_V2)
> >>> +	SIMPLEQ_INIT(&ipcmessages);
> >>> +#endif
> >>> +
> >>>    #if defined(CONFIG_MONGOOSE_WEB_API_V1)
> >>> -	mg_register_http_endpoint(nc, "/handle_post_request",
> >> MG_CB(upload_handler, NULL));
> >>> +	mg_register_http_endpoint(nc, "/handle_post_request",
> >>> +MG_CB(upload_handler_v1, NULL));
> >>>    	mg_register_http_endpoint(nc, "/getstatus.json",
> >> MG_CB(recovery_status, NULL));
> >>>    	mg_register_http_endpoint(nc, "/rebootTarget",
> >> MG_CB(reboot_target, NULL));
> >>>    	mg_register_http_endpoint(nc, "/postUpdateCommand",
> >>> MG_CB(post_update_cmd, NULL)); -#elif
> >>> defined(CONFIG_MONGOOSE_WEB_API_V2)
> >>> +#endif
> >>> +#if defined(CONFIG_MONGOOSE_WEB_API_V2)
> >>>    	mg_register_http_endpoint(nc, "/restart", restart_handler);
> >>>    #endif
> >>> -	mg_register_http_endpoint(nc, "/upload", MG_CB(upload_handler,
> >> NULL));
> >>> +	mg_register_http_endpoint(nc, "/upload",
> >> MG_CB(upload_handler_v2,
> >>> +NULL));
> >> Could you please move the upload_handler_v2 into to V2 guards.
> >
> > Or do you mean the mg_register_http_endpoint() call for "/upload"?
> 
> Yes I mean the mg_register_http_endpoint() call. I missed to move this into
> the if def.

Ok.

> >> The code use many ifdefs, maybe it is wise to split the code into three
> files.
> > Do you mean three files as in
> >   - mongoose_interface_v1.c
> >   - mongoose_interface_v2.c
> >   - mongoose_interface_v1and2.c
> >
> > Or just one additional file containing the bits that are enabled only
> > if both V1 and V2 are enabled?
> 
> I mean the three files as mentioned.
> 
> @Stefano: What do you think?

Let's revisit this after patch v2.

Br, Sami
diff mbox series

Patch

diff --git a/mongoose/Config.in b/mongoose/Config.in
index a001247..cd52f2c 100644
--- a/mongoose/Config.in
+++ b/mongoose/Config.in
@@ -14,27 +14,28 @@  choice
 config MONGOOSE
 	bool "mongoose"
 	help
-	  Mongoose embeddded web server
+	  Mongoose embedded web server
 
 endchoice
 
-choice
-	prompt "Web Application Interface"
-	default MONGOOSE_WEB_API_V1
-	help
-	  Choose the bootloader
+menu "Web Application Interface"
 
 config MONGOOSE_WEB_API_V1
 	bool "Version 1 (deprecated)"
+	default y
 	help
 	  Support for version 1
 
 config MONGOOSE_WEB_API_V2
 	bool "Version 2"
+	default n
 	help
 	  Support for version 2
 
-endchoice
+comment "Web API v1 or v2 or both required"
+	depends on !MONGOOSE_WEB_API_V1 && !MONGOOSE_WEB_API_V2
+
+endmenu
 
 config MONGOOSEIPV6
 	bool "IPv6 support"
diff --git a/mongoose/mongoose_interface.c b/mongoose/mongoose_interface.c
index 89a51f3..0401a9a 100644
--- a/mongoose/mongoose_interface.c
+++ b/mongoose/mongoose_interface.c
@@ -25,6 +25,7 @@ 
 #include <parselib.h>
 #include <progress_ipc.h>
 #include <swupdate_settings.h>
+#include <bsdqueue.h>
 
 #include "mongoose.h"
 
@@ -32,6 +33,9 @@ 
 #define MG_PORT "8080"
 #define MG_ROOT "."
 
+#define WEB_API_v1 1
+#define WEB_API_v2 2
+
 struct mongoose_options {
 	char *root;
 	char *listing;
@@ -49,6 +53,25 @@  struct file_upload_state {
 
 static struct mg_serve_http_opts s_http_server_opts;
 
+#if !defined(CONFIG_MONGOOSE_WEB_API_V1) && !defined(CONFIG_MONGOOSE_WEB_API_V2)
+#error "WEB API v1 or v2 or both must be defined"
+#endif
+
+#if defined(CONFIG_MONGOOSE_WEB_API_V1) && defined(CONFIG_MONGOOSE_WEB_API_V2)
+#define NUM_CACHED_MESSAGES 100
+
+struct msg_elem {
+	ipc_message msg;
+	SIMPLEQ_ENTRY(msg_elem) next;
+};
+
+SIMPLEQ_HEAD(msglist, msg_elem);
+static struct msglist ipcmessages;
+static unsigned long nrmsgs = 0;
+static pthread_mutex_t msglock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t msgcond = PTHREAD_COND_INITIALIZER;
+#endif
+
 #if defined(CONFIG_MONGOOSE_WEB_API_V2)
 #define enum_string(x)	[x] = #x
 static const char *get_status_string(unsigned int status)
@@ -112,20 +135,163 @@  static size_t snescape(char *dst, size_t n, const char *src)
 }
 #endif
 
-static void upload_handler(struct mg_connection *nc, int ev, void *p)
+static int get_inst_status(ipc_message *msg, int  __attribute__ ((__unused__)) api_vers)
+{
+#if defined(CONFIG_MONGOOSE_WEB_API_V1) && defined(CONFIG_MONGOOSE_WEB_API_V2)
+	struct msg_elem *newmsg;
+	struct msg_elem *oldmsg;
+	int ret = -1;
+
+	switch (api_vers) {
+	case WEB_API_v1:
+		pthread_mutex_lock(&msglock);
+		pthread_cond_wait(&msgcond, &msglock);
+		oldmsg = SIMPLEQ_FIRST(&ipcmessages);
+		if (oldmsg) {
+			SIMPLEQ_REMOVE_HEAD(&ipcmessages, next);
+			nrmsgs--;
+			memcpy(msg, &oldmsg->msg, sizeof(*msg));
+			free(oldmsg);
+			ret = 0;
+		}
+		pthread_mutex_unlock(&msglock);
+		break;
+
+	case WEB_API_v2:
+		newmsg = (struct msg_elem *)calloc(1, sizeof(*newmsg));
+		if (!newmsg)
+			return -1;
+
+		ret = ipc_get_status(msg);
+
+		/* Cache message for APIv1, but consider
+		 * changed messages only. */
+		if (!ret) {
+			pthread_mutex_lock(&msglock);
+
+			oldmsg = SIMPLEQ_LAST(&ipcmessages, msg_elem, next);
+			if (!oldmsg || memcmp(&oldmsg->msg.data.status,
+					      &msg->data.status,
+					      sizeof(msg->data.status))) {
+
+				nrmsgs++;
+				if (nrmsgs > NUM_CACHED_MESSAGES) {
+					oldmsg = SIMPLEQ_FIRST(&ipcmessages);
+					SIMPLEQ_REMOVE_HEAD(&ipcmessages, next);
+					free(oldmsg);
+					nrmsgs--;
+				}
+
+				memcpy(&newmsg->msg, msg, sizeof(*msg));
+				SIMPLEQ_INSERT_TAIL(&ipcmessages, newmsg, next);
+				newmsg = NULL;
+			}
+
+			pthread_cond_signal(&msgcond);
+			pthread_mutex_unlock(&msglock);
+		}
+
+		if (newmsg)
+			free(newmsg);
+		break;
+	}
+
+	return ret;
+#else
+	return ipc_get_status(msg);
+#endif
+}
+
+static void cleanup_msg_list(void)
+{
+#if defined(CONFIG_MONGOOSE_WEB_API_V1) && defined(CONFIG_MONGOOSE_WEB_API_V2)
+	struct msg_elem *elem;
+
+	pthread_mutex_lock(&msglock);
+
+	while (!SIMPLEQ_EMPTY(&ipcmessages)) {
+		elem = SIMPLEQ_FIRST(&ipcmessages);
+		SIMPLEQ_REMOVE_HEAD(&ipcmessages, next);
+		free(elem);
+	}
+
+	nrmsgs = 0;
+	pthread_mutex_unlock(&msglock);
+#endif
+}
+
+static void upload_handler_v2(struct mg_connection *nc, int ev, void *p)
 {
 	struct mg_http_multipart_part *mp;
 	struct file_upload_state *fus;
+
+	switch (ev) {
+	case MG_EV_HTTP_PART_BEGIN:
+		mp = (struct mg_http_multipart_part *) p;
+
+		fus = (struct file_upload_state *) calloc(1, sizeof(*fus));
+		if (fus == NULL) {
+			mg_http_send_error(nc, 500, "Out of memory");
+			break;
+		}
+
+		cleanup_msg_list();
+
+		fus->fd = ipc_inst_start_ext(SOURCE_WEBSERVER, strlen(mp->file_name), mp->file_name);
+		if (fus->fd < 0) {
+			mg_http_send_error(nc, 500, "Failed to queue command");
+			free(fus);
+			break;
+		}
+
+		mp->user_data = fus;
+
+		break;
+
+	case MG_EV_HTTP_PART_DATA:
+		mp = (struct mg_http_multipart_part *) p;
+		fus = (struct file_upload_state *) mp->user_data;
+
+		if (!fus)
+			break;
+
+		ipc_send_data(fus->fd, (char *) mp->data.p, mp->data.len);
+		fus->len += mp->data.len;
+
+		break;
+
+	case MG_EV_HTTP_PART_END:
+		mp = (struct mg_http_multipart_part *) p;
+		fus = (struct file_upload_state *) mp->user_data;
+
+		if (!fus)
+			break;
+
+		ipc_end(fus->fd);
+
+		mg_send_response_line(nc, 200,
+			"Content-Type: text/plain\r\n"
+			"Connection: close");
+		mg_send(nc, "\r\n", 2);
+		mg_printf(nc, "Ok, %s - %d bytes.\r\n", mp->file_name, (int) fus->len);
+		nc->flags |= MG_F_SEND_AND_CLOSE;
+
+		mp->user_data = NULL;
+		free(fus);
+		break;
+	}
+}
+
 #if defined(CONFIG_MONGOOSE_WEB_API_V1)
+static void upload_handler_v1(struct mg_connection *nc, int ev, void *p)
+{
 	struct mg_str *filename, *data;
 	struct http_message *hm;
 	size_t length;
 	char buf[16];
 	int fd;
-#endif
 
 	switch (ev) {
-#if defined(CONFIG_MONGOOSE_WEB_API_V1)
 	case MG_EV_HTTP_REQUEST:
 		hm = (struct http_message *) p;
 
@@ -149,6 +315,8 @@  static void upload_handler(struct mg_connection *nc, int ev, void *p)
 			return;
 		}
 
+		cleanup_msg_list();
+
 		fd = ipc_inst_start_ext(SOURCE_WEBSERVER, filename->len, filename->p);
 		ipc_send_data(fd, (char *) hm->body.p, hm->body.len);
 		ipc_end(fd);
@@ -161,62 +329,13 @@  static void upload_handler(struct mg_connection *nc, int ev, void *p)
 		nc->flags |= MG_F_SEND_AND_CLOSE;
 
 		break;
-#endif
-	case MG_EV_HTTP_PART_BEGIN:
-		mp = (struct mg_http_multipart_part *) p;
 
-		fus = (struct file_upload_state *) calloc(1, sizeof(*fus));
-		if (fus == NULL) {
-			mg_http_send_error(nc, 500, "Out of memory");
-			break;
-		}
-
-		fus->fd = ipc_inst_start_ext(SOURCE_WEBSERVER, strlen(mp->file_name), mp->file_name);
-		if (fus->fd < 0) {
-			mg_http_send_error(nc, 500, "Failed to queue command");
-			free(fus);
-			break;
-		}
-
-		mp->user_data = fus;
-
-		break;
-
-	case MG_EV_HTTP_PART_DATA:
-		mp = (struct mg_http_multipart_part *) p;
-		fus = (struct file_upload_state *) mp->user_data;
-
-		if (!fus)
-			break;
-
-		ipc_send_data(fus->fd, (char *) mp->data.p, mp->data.len);
-		fus->len += mp->data.len;
-
-		break;
-
-	case MG_EV_HTTP_PART_END:
-		mp = (struct mg_http_multipart_part *) p;
-		fus = (struct file_upload_state *) mp->user_data;
-
-		if (!fus)
-			break;
-
-		ipc_end(fus->fd);
-
-		mg_send_response_line(nc, 200,
-			"Content-Type: text/plain\r\n"
-			"Connection: close");
-		mg_send(nc, "\r\n", 2);
-		mg_printf(nc, "Ok, %s - %d bytes.\r\n", mp->file_name, (int) fus->len);
-		nc->flags |= MG_F_SEND_AND_CLOSE;
-
-		mp->user_data = NULL;
-		free(fus);
+	default:
+		upload_handler_v2(nc, ev, p);
 		break;
 	}
 }
 
-#if defined(CONFIG_MONGOOSE_WEB_API_V1)
 static void recovery_status(struct mg_connection *nc, int ev, void *ev_data)
 {
 	ipc_message ipc;
@@ -226,7 +345,7 @@  static void recovery_status(struct mg_connection *nc, int ev, void *ev_data)
 	(void)ev;
 	(void)ev_data;
 
-	ret = ipc_get_status(&ipc);
+	ret = get_inst_status(&ipc, WEB_API_v1);
 
 	if (ret) {
 		mg_http_send_error(nc, 500, NULL);
@@ -306,7 +425,9 @@  static void post_update_cmd(struct mg_connection *nc, int ev, void *ev_data)
 
 	nc->flags |= MG_F_SEND_AND_CLOSE;
 }
-#elif defined(CONFIG_MONGOOSE_WEB_API_V2)
+#endif
+
+#if defined(CONFIG_MONGOOSE_WEB_API_V2)
 static void restart_handler(struct mg_connection *nc, int ev, void *ev_data)
 {
 	struct http_message *hm = (struct http_message *) ev_data;
@@ -350,7 +471,7 @@  static void *broadcast_message_thread(void *data)
 {
 	for (;;) {
 		ipc_message msg;
-		int ret = ipc_get_status(&msg);
+		int ret = get_inst_status(&msg, WEB_API_v2);
 
 		if (!ret && strlen(msg.data.status.desc) != 0) {
 			struct mg_mgr *mgr = (struct mg_mgr *) data;
@@ -610,15 +731,20 @@  int start_mongoose(const char *cfgfname, int argc, char *argv[])
 		exit(EXIT_FAILURE);
 	}
 
+#if defined(CONFIG_MONGOOSE_WEB_API_V1) && defined(CONFIG_MONGOOSE_WEB_API_V2)
+	SIMPLEQ_INIT(&ipcmessages);
+#endif
+
 #if defined(CONFIG_MONGOOSE_WEB_API_V1)
-	mg_register_http_endpoint(nc, "/handle_post_request", MG_CB(upload_handler, NULL));
+	mg_register_http_endpoint(nc, "/handle_post_request", MG_CB(upload_handler_v1, NULL));
 	mg_register_http_endpoint(nc, "/getstatus.json", MG_CB(recovery_status, NULL));
 	mg_register_http_endpoint(nc, "/rebootTarget", MG_CB(reboot_target, NULL));
 	mg_register_http_endpoint(nc, "/postUpdateCommand", MG_CB(post_update_cmd, NULL));
-#elif defined(CONFIG_MONGOOSE_WEB_API_V2)
+#endif
+#if defined(CONFIG_MONGOOSE_WEB_API_V2)
 	mg_register_http_endpoint(nc, "/restart", restart_handler);
 #endif
-	mg_register_http_endpoint(nc, "/upload", MG_CB(upload_handler, NULL));
+	mg_register_http_endpoint(nc, "/upload", MG_CB(upload_handler_v2, NULL));
 	mg_set_protocol_http_websocket(nc);
 
 #if defined(CONFIG_MONGOOSE_WEB_API_V2)