/*Copyright (c) 2012, Sierra Wireless, Inc
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
•Redistributions of source code must retain the above copyright notice, this
 list of conditions and the following disclaimer.
•Redistributions in binary form must reproduce the above copyright notice, this
 list of conditions and the following disclaimer in the documentation and/or
 other materials provided with the distribution.
•Neither the name of Sierra Wireless Inc, Richmond, Canada nor the names of its
 contributors may be used to endorse or promote products derived from this
 software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABIL ITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/* ************************************************************************* */
/*
   File:		webs.c
   Version:		v1.0 (20091211)
   Author:		SPT
*/
/* ************************************************************************* */

/* ************************************************************************* */
#include "adl_global.h"
#include "wip.h"
#include "wireless_internet_gateway.h"
#include "webs.h"
/* ************************************************************************* */
extern char gl_gprs_apn [32];
extern char gl_gprs_un  [32];
extern char gl_gprs_pw  [32];
extern u8 gl_gprs_state;
extern bool gl_gprs_params_new;
extern bool gl_restart_gprs;

wip_channel_t gl_web_server_ch = {0};
u8 gl_nb_of_spawned_sock = 0;
u8 gl_web_serv_rd_buf [RD_BUF_SIZE] = {0};
u8 gl_web_serv_wr_buf [WR_BUF_SIZE] = {0};
u8 pad3 = 0;

void fn_web_page_root_cb ( web_client_ctx_st* lo_web_client_ctx, u8 lo_http_method );
void fn_web_page_logo_cb ( web_client_ctx_st* lo_web_client_ctx, u8 lo_http_method );
void fn_web_page_not_found_cb ( web_client_ctx_st* lo_web_client_ctx, u8 lo_http_method );
void fn_web_page_404_cb ( web_client_ctx_st* lo_web_client_ctx, u8 lo_http_method );
void fn_web_page_no_signal_cb ( web_client_ctx_st* lo_web_client_ctx, u8 lo_http_method );
void fn_web_page_gsm_cb ( web_client_ctx_st* lo_web_client_ctx, u8 lo_http_method );
void fn_web_page_gprs_cb ( web_client_ctx_st* lo_web_client_ctx, u8 lo_http_method );
void fn_web_page_edge_cb ( web_client_ctx_st* lo_web_client_ctx, u8 lo_http_method );

web_pages_st web_pages [NB_OF_WEB_PAGES] =
{
	// URL                                 Callback
	{
		(char*) "/",    	               (web_page_cb) fn_web_page_root_cb
	},
	{
		(char*) "/logo",  	               (web_page_cb) fn_web_page_logo_cb
	},
	{
		(char*) "/404",  	               (web_page_cb) fn_web_page_404_cb
	},
	{
		(char*) "/no-signal",              (web_page_cb) fn_web_page_no_signal_cb
	},
	{
		(char*) "/gsm",                    (web_page_cb) fn_web_page_gsm_cb
	},
	{
		(char*) "/gprs",                   (web_page_cb) fn_web_page_gprs_cb
	},
	{
		(char*) "/edge",                   (web_page_cb) fn_web_page_edge_cb
	},	
	{
		(char*) "/not-found",              (web_page_cb) fn_web_page_not_found_cb
	}
};

extern u8 gl_nw_cap;
bool gl_reply_post_pending = FALSE;
/* ************************************************************************* */
void
fn_tcp_serv_finalizer_cb ( void* lo_ctx )
{
	web_client_ctx_st* lo_web_client_ctx = (web_client_ctx_st*) lo_ctx;
	
	// Done, reset context
	memset ( gl_web_serv_wr_buf, 0x00, WR_BUF_SIZE );
	lo_web_client_ctx->wr_buf_size = 0;
	lo_web_client_ctx->bytes_written = 0;
	gl_nb_of_spawned_sock--;
	adl_memRelease ( lo_web_client_ctx );
}
/* ************************************************************************* */
void
fn_write_data (
			   web_client_ctx_st* lo_web_client_ctx			 
			  )
{
	int lo_ret = 0;

	while ( ( lo_ret = wip_write ( lo_web_client_ctx->channel,
				                  (gl_web_serv_wr_buf + lo_web_client_ctx->bytes_written),
								  (lo_web_client_ctx->wr_buf_size - lo_web_client_ctx->bytes_written) )
			  > 0 ) )
	// [WORKAROUND_WIPWRITE_RETURNS_1] wip_write () always returns 1, assume it has written everything.
#define WORKAROUND_WIPWRITE_RETURNS_1
#ifdef WORKAROUND_WIPWRITE_RETURNS_1
	{		
		lo_web_client_ctx->bytes_written += lo_web_client_ctx->wr_buf_size;	

		wip_setCtx ( lo_web_client_ctx->channel, (void*) lo_web_client_ctx );		
		wip_close ( lo_web_client_ctx->channel );		
	}	
#else
	{		
		lo_web_client_ctx->bytes_written += lo_ret;		
	}

	if ( lo_web_client_ctx->bytes_written != lo_web_client_ctx->wr_buf_size )
	{
		// Not everyhting is written
		// Set context & wait for WIP_CEV_WRITE
		wip_setCtx ( lo_web_client_ctx->channel, (void*) lo_web_client_ctx );
	}
	else
	{
		wip_setCtx ( lo_web_client_ctx->channel, (void*) lo_web_client_ctx );		
		wip_close ( lo_web_client_ctx->channel );				
	}
#endif
}
/* ************************************************************************* */
void
fn_web_page_gsm_cb ( web_client_ctx_st* lo_web_client_ctx, u8 lo_http_method )
{
	memcpy ( gl_web_serv_wr_buf, gl_gsm_jpeg, sizeof(gl_gsm_jpeg) );
	lo_web_client_ctx->wr_buf_size = sizeof ( gl_gsm_jpeg );
	
	fn_write_data ( lo_web_client_ctx );
}
/* ************************************************************************* */
void
fn_web_page_gprs_cb ( web_client_ctx_st* lo_web_client_ctx, u8 lo_http_method )
{
	memcpy ( gl_web_serv_wr_buf, gl_gprs_jpeg, sizeof(gl_gprs_jpeg) );
	lo_web_client_ctx->wr_buf_size = sizeof ( gl_gprs_jpeg );
	
	fn_write_data ( lo_web_client_ctx );
}
/* ************************************************************************* */
void
fn_web_page_edge_cb ( web_client_ctx_st* lo_web_client_ctx, u8 lo_http_method )
{
	memcpy ( gl_web_serv_wr_buf, gl_edge_jpeg, sizeof(gl_edge_jpeg) );
	lo_web_client_ctx->wr_buf_size = sizeof ( gl_edge_jpeg );
	
	fn_write_data ( lo_web_client_ctx );
}
/* ************************************************************************* */
void
fn_web_page_no_signal_cb ( web_client_ctx_st* lo_web_client_ctx, u8 lo_http_method )
{
	memcpy ( gl_web_serv_wr_buf, gl_no_signal_jpeg, sizeof(gl_no_signal_jpeg) );
	lo_web_client_ctx->wr_buf_size = sizeof ( gl_no_signal_jpeg );
	
	fn_write_data ( lo_web_client_ctx );
}
/* ************************************************************************* */
void
fn_web_page_404_cb ( web_client_ctx_st* lo_web_client_ctx, u8 lo_http_method )
{
	memcpy ( gl_web_serv_wr_buf, gl_404_logo_jpeg, sizeof(gl_404_logo_jpeg) );
	lo_web_client_ctx->wr_buf_size = sizeof ( gl_404_logo_jpeg );
	
	fn_write_data ( lo_web_client_ctx );
}
/* ************************************************************************* */
void
fn_web_page_logo_cb ( web_client_ctx_st* lo_web_client_ctx, u8 lo_http_method )
{
	memcpy ( (u8*) &gl_web_serv_wr_buf, (u8*) &gl_swi_logo_jpeg, sizeof(gl_swi_logo_jpeg) );
	lo_web_client_ctx->wr_buf_size = sizeof ( gl_swi_logo_jpeg );
	
	fn_write_data ( lo_web_client_ctx );
}
/* ************************************************************************* */
void
fn_web_page_not_found_cb ( web_client_ctx_st* lo_web_client_ctx, u8 lo_http_method )
{
	TRACE (( 1, "[WEB] Page \"not-found\"" ));
	
	strcpy ( (char*)(gl_web_serv_wr_buf ),			 
		    "<html>"
			"<head>"
			"<title>Wireless Internet Gateway"
			"</title>"
			"<body>"
			"<p align=\"center\">&nbsp;<img alt=\"404\" src=\"404\" height=\"128\" width=\"384\" border=\"0\">"			
			"</body>"
			"</head>"
			"</html>"
			);
	
	lo_web_client_ctx->wr_buf_size = strlen ( (char*)&gl_web_serv_wr_buf );
	
	fn_write_data ( lo_web_client_ctx );
}
/* ************************************************************************* */
void
fn_wr_root_page ( web_client_ctx_st* lo_web_client_ctx )
{
	strcpy ( (char*)gl_web_serv_wr_buf,		    
		"<html>"
		"<head>"
		"<title>Wireless Internet Gateway"
		"</title>"
		"<body>"
		"<p align=\"center\">&nbsp;<img alt=\"SWI\" src=\"logo\" height=\"64\" width=\"192\" border=\"0\">"
		"<font face=\"arial,arial\" size=-1>"
		"<center><table cellpadding=10 cellspacing=0 border=0>"
		"<tr><td bgcolor=\"white\"><table cellpadding=1 cellspacing=0 border=0 width=100%><tr><td bgcolor=\"white\" align=center style=\"padding:2;padding-bottom:4\"><b><font size=-1 color=\"black\">Internet Access</font></th></tr>"
		);

	switch ( gl_nw_cap )
	{
	case NW_CAP_UNKNOWN:
		strcpy ( (char*)( gl_web_serv_wr_buf + strlen ( (char*)&gl_web_serv_wr_buf ) ),	
				 "<br><br><p align=\"left\">&nbsp;<img alt=\"No Signal\" src=\"no-signal\" height=\"24\" width=\"240\" border=\"0\">"
				);
		break;
	case NW_CAP_GSM:
		strcpy ( (char*)( gl_web_serv_wr_buf + strlen ( (char*)&gl_web_serv_wr_buf ) ),
				 "<br><br><p align=\"left\">&nbsp;<img alt=\"GSM Available\" src=\"gsm\" height=\"24\" width=\"240\" border=\"0\">"
				);
		break;
	case NW_CAP_GPRS:
		strcpy ( (char*)( gl_web_serv_wr_buf + strlen ( (char*)&gl_web_serv_wr_buf ) ),
				 "<br><br><p align=\"left\">&nbsp;<img alt=\"GPRS Available\" src=\"gprs\" height=\"24\" width=\"240\" border=\"0\">"
				);
		break;
	case NW_CAP_EDGE:
		strcpy ( (char*)( gl_web_serv_wr_buf + strlen ( (char*)&gl_web_serv_wr_buf ) ),
				 "<br><br><p align=\"left\">&nbsp;<img alt=\"EDGE Available\" src=\"edge\" height=\"24\" width=\"240\" border=\"0\">"
				);
		break;
	default:
		break;
	}			

	sprintf ( (char*)( gl_web_serv_wr_buf + strlen ( (char*)&gl_web_serv_wr_buf ) ),		    				
		    "<br><br><br><br><tr><td bgcolor=\"white\" style=\"padding:10\"><br>"
			"<form method=\"post\" action=\"http://192.168.1.1/\" name=pform>"
			"<input type=\"hidden\" name=\"action\" value=\"internet-access-settings\">"
			"<center><table>"			
			"<tr><td><font face=\"arial,arial\" size=-1>APN      : </td><td><input type=\"text\" name=\"apn\" maxlength=32 value=\"%s\"></td></tr>"
			"<tr><td><font face=\"arial,arial\" size=-1>Username : </td><td><input type=\"text\" name=\"un\" maxlength=32 value=\"%s\"></td></tr>"
			"<tr><td><font face=\"arial,arial\" size=-1>Password : </td><td><input type=\"password\" name=\"pw\" maxlength=32 value=\"%s\"></td></tr>"
			"<tr><td><center><font face=\"arial,arial\" size=-1><input type=\"submit\" value=\"Go\"></center></td></tr>"
			"<tr><td colspan=2><font face=\"arial,arial\" size=-1>&nbsp;</td></tr>"
			"</table></center>"
			"</form>"
			"</td></tr></table></td></tr></table>"
			"</body>"
			"</head>"
			"</html>",
			
			gl_gprs_apn, gl_gprs_un, gl_gprs_pw
			);
	lo_web_client_ctx->wr_buf_size = strlen ( (char*)&gl_web_serv_wr_buf );

	fn_write_data ( lo_web_client_ctx );
}
/* ************************************************************************* */
void
fn_reply_post ( u8 lo_id, void* lo_ctx )
{
	static u8 sc_wait_count = 5;
	if ( (gl_gprs_state == WIP_BEV_IP_CONNECTED) || (gl_gprs_state == GPRS_ERROR) || (sc_wait_count == 15) )
	{
		fn_wr_root_page ( (web_client_ctx_st*) lo_ctx );
		gl_reply_post_pending = FALSE;
		sc_wait_count = 5;
	}
	else
	{
		sc_wait_count++;
		adl_tmrSubscribeExt ( FALSE, 10, ADL_TMR_TYPE_100MS, fn_reply_post, lo_ctx, FALSE );
	}
}
/* ************************************************************************* */
void
fn_web_page_root_cb ( web_client_ctx_st* lo_web_client_ctx, u8 lo_http_method )
{
	TRACE (( 1, "[WEB] Page \"/\"" ));

	if ( lo_http_method == HTTP_GET )
	{
		fn_wr_root_page ( lo_web_client_ctx );
	}	
	else if ( lo_http_method == HTTP_POST )
	{
		char* lo_ptr = NULL;
		u32 lo_f = 0;
		char lo_gprs_apn [32] = {0};
		char lo_gprs_un  [32] = {0};
		char lo_gprs_pw  [32] = {0};
		char lo_buf [256] = {0};

		if ( ( lo_ptr = strstr ( gl_web_serv_rd_buf, "&apn=" ) ) != NULL )
		{
			for ( lo_f = 0; ( *(lo_ptr + 5 + lo_f) ) != '&'; lo_f++ )
			{
				lo_gprs_apn [lo_f] = ( *(lo_ptr + 5 + lo_f) );
			}
		}
		if ( ( lo_ptr = strstr ( gl_web_serv_rd_buf, "&un=" ) ) != NULL )
		{
			for ( lo_f = 0; ( *(lo_ptr + 4 + lo_f) ) != '&'; lo_f++ )
			{
				lo_gprs_un [lo_f] = ( *(lo_ptr + 4 + lo_f) );
			}
		}
		if ( ( lo_ptr = strstr ( gl_web_serv_rd_buf, "&pw=" ) ) != NULL )
		{
			for ( lo_f = 0; ( *(lo_ptr + 4 + lo_f) ) != NULL; lo_f++ )
			{
				lo_gprs_pw [lo_f] = ( *(lo_ptr + 4 + lo_f) );
			}
		}

		if ( strcmp ( lo_gprs_apn, gl_gprs_apn ) != 0 )
		{
			gl_gprs_params_new = TRUE;
			strcpy ( gl_gprs_apn, lo_gprs_apn );
			fn_store_params ( "GPRS", APN, strlen(gl_gprs_apn), gl_gprs_apn );
		}
		if ( strcmp ( lo_gprs_un, gl_gprs_un ) != 0 )
		{
			gl_gprs_params_new = TRUE;
			strcpy ( gl_gprs_un, lo_gprs_un );
			fn_store_params ( "GPRS", UNAME, strlen(gl_gprs_un), gl_gprs_un );
		}
		if ( strcmp ( lo_gprs_pw, gl_gprs_pw ) != 0 )
		{
			gl_gprs_params_new = TRUE;
			strcpy ( gl_gprs_pw, lo_gprs_pw );
			fn_store_params ( "GPRS", PWD, strlen(gl_gprs_pw), gl_gprs_pw );
		}

		sprintf ( lo_buf, "[GPRS] APN %s USERNAME %s PASSWORD %s", gl_gprs_apn, gl_gprs_un, gl_gprs_pw );
		TRACE (( 1, lo_buf ));

		if ( gl_gprs_state == WIP_BEV_CLOSED )
			fn_start_gprs ();
		else if ( gl_gprs_params_new == TRUE )
		{
			TRACE (( 1, "[GPRS] Parameters new, restarting GPRS" ));
			gl_restart_gprs = TRUE;
			fn_close_gprs ();
		}
		else
		{
			fn_wr_root_page ( lo_web_client_ctx );
			return;
		}
		gl_reply_post_pending = TRUE;
		adl_tmrSubscribeExt ( FALSE, 50, ADL_TMR_TYPE_100MS, fn_reply_post, lo_web_client_ctx, FALSE );
	}
}
/* ************************************************************************* */
bool
fn_parse_http_req (
				   char* lo_data, u32 lo_len,
				   web_client_ctx_st* lo_web_client_ctx )
{
	u8 lo_f = 0;

	if ( strncmp ( lo_data, "GET", 3 ) == 0 )
	{
		if (
			!(
				 ( *(lo_data + ( lo_len - 4 )) == 0x0D ) &&
				 ( *(lo_data + ( lo_len - 3 )) == 0x0A ) &&
				 ( *(lo_data + ( lo_len - 2 )) == 0x0D ) &&
				 ( *(lo_data + ( lo_len - 1 )) == 0x0A )
			 )
		   )
		{
			// No \r\n\r\n at the end
			return HTTP_REQ_INCOMPLETE;
		}

		TRACE (( 1, "[WEB] GET" ));

		for ( ; lo_f < (NB_OF_WEB_PAGES - 1); lo_f++ )
		{
			// Decode URL
			if ( ( strncmp ( (lo_data + 4), web_pages[lo_f].page, strlen(web_pages[lo_f].page) ) == 0 ) &&
				 ( *( lo_data + 4 + strlen(web_pages[lo_f].page) ) == ' ' )
			   )
			   // URL found
			   break;
		}
		// If not found, at the end of the table the page says "not-found".
		web_pages[lo_f].page_cb ( (web_client_ctx_st*) lo_web_client_ctx, HTTP_GET ); // Page callback		
		
		return HTTP_REQ_COMPLETE;
	}
	else if ( strncmp ( lo_data, "POST", 4 ) == 0 )
	{
		if (
			!(
				 ( strstr ( lo_data, "internet-access-settings" ) )
			 )
		   )
		{
			return HTTP_REQ_INCOMPLETE;
		}

		TRACE (( 1, "[WEB] POST" ));

		for ( ; lo_f < (NB_OF_WEB_PAGES - 1); lo_f++ )
		{
			// Decode URL
			if ( ( strncmp ( (lo_data + 5), web_pages[lo_f].page, strlen(web_pages[lo_f].page) ) == 0 ) &&
				 ( *( lo_data + 5 + strlen(web_pages[lo_f].page) ) == ' ' )
			   )
			   // URL found
			   break;
		}
		// If not found, at the end of the table the page says "not-found".
		web_pages[lo_f].page_cb ( (web_client_ctx_st*) lo_web_client_ctx, HTTP_POST ); // Page callback		
		
		return HTTP_REQ_COMPLETE;
	}
}
/* ************************************************************************* */
void
fn_web_serv_cb ( wip_event_t* lo_ev,
				 void* lo_ctx )
{
	switch ( lo_ev->kind )
	{
	case WIP_CEV_OPEN:
		TRACE (( 1, "[WEB] WIP_CEV_OPEN" ));
		{
			// New client connected, allocate context.
			web_client_ctx_st* lo_web_client_ctx = adl_memGet ( sizeof(web_client_ctx_st) );
			memset ( lo_web_client_ctx, 0x00, sizeof(web_client_ctx_st) );

			lo_web_client_ctx->channel = lo_ev->channel;
			lo_web_client_ctx->nb_wr_events = 0;
			
			wip_setCtx ( lo_ev->channel, (void*) lo_web_client_ctx );

			gl_nb_of_spawned_sock++;
			TRACE (( 1, "[WEB] %d client(s) connected", gl_nb_of_spawned_sock ));

			wip_setOpts  (
							lo_ev->channel,
							WIP_COPT_SND_BUFSIZE, WR_BUF_SIZE,
							WIP_COPT_NODELAY,     TRUE,
							WIP_COPT_FINALIZER,   fn_tcp_serv_finalizer_cb,
							WIP_COPT_END
						  );			
		}
		break;
	case WIP_CEV_READ:
		TRACE (( 1, "[WEB] WIP_CEV_READ" ));
		{
			int lo_ret = 0;

			while ( ( lo_ret = wip_read ( lo_ev->channel,
				                          (gl_web_serv_rd_buf + ((web_client_ctx_st*)lo_ctx)->bytes_read),
										  (RD_BUF_SIZE - ((web_client_ctx_st*)lo_ctx)->bytes_read) ) )
					  > 0 )
			{
				((web_client_ctx_st*)lo_ctx)->bytes_read += lo_ret;
			}
			if ( fn_parse_http_req ( (char*)&gl_web_serv_rd_buf, ((web_client_ctx_st*)lo_ctx)->bytes_read, ((web_client_ctx_st*)lo_ctx) )
				 == HTTP_REQ_INCOMPLETE )
			{
				// Not end of HTTP REQ, just set the context & there will be another WIP_CEV_READ to have rest of the request.
				wip_setCtx ( lo_ev->channel, (void*) lo_ctx );
			}
			else
			{
				// Request understood, reset context.
				memset ( gl_web_serv_rd_buf, 0x00, RD_BUF_SIZE );
				((web_client_ctx_st*)lo_ctx)->bytes_read = 0;
				wip_setCtx ( lo_ev->channel, (void*) lo_ctx );				
			}
		}
		break;
	case WIP_CEV_WRITE:
		TRACE (( 1, "[WEB] WIP_CEV_WRITE" ));

		if ( ((web_client_ctx_st*)lo_ctx)->nb_wr_events++ != 0 ) // 1st WIP_CEV_WRITE is ignored as it comes with socket opening.
		{
			fn_write_data ( ((web_client_ctx_st*)lo_ctx) );
		}

		break;
	case WIP_CEV_ERROR:
		TRACE (( 1, "[WEB] WIP_CEV_ERROR %d", lo_ev->content.error.errnum ));
		wip_close ( lo_ev->channel );
		adl_memRelease ( lo_ctx );
		gl_nb_of_spawned_sock--;
		break;
	case WIP_CEV_PEER_CLOSE:
		TRACE (( 1, "[WEB] WIP_CEV_PEER_CLOSE %d", lo_ev->content.error.errnum ));
		wip_close ( lo_ev->channel );
		adl_memRelease ( lo_ctx );
		gl_nb_of_spawned_sock--;
		break;
	default:
		break;
	}
}

/* ************************************************************************* */
void
fn_start_web_server ( void )
{
	wip_in_addr_t lo_local_ip = {0};

	wip_inet_aton ( DHCP_SERVER_IP, &lo_local_ip );

	// Web server on Ethernet
	gl_web_server_ch =
		               wip_TCPServerCreateOpts ( 80,
					                             fn_web_serv_cb,
											     0,
												 WIP_COPT_ADDR,        lo_local_ip,	
												 WIP_COPT_END );

	if ( gl_web_server_ch != NULL )
		TRACE (( 1, "[WEB] Web Server running .." ));
	else
		TRACE (( 1, "[WEB] Web Server creation failed" ));
}
/* ************************************************************************* */
void
fn_stop_web_server ( void )
{
	wip_close ( gl_web_server_ch );
}
/* ************************************************************************* */
