/*******************************************************************************
 * Copyright (c) 2018, 2024 Wind River Systems, Inc., Ian Craggs and others
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * and Eclipse Distribution License v1.0 which accompany this distribution.
 *
 * The Eclipse Public License is available at
 *    https://www.eclipse.org/legal/epl-2.0/
 * and the Eclipse Distribution License is available at
 *   http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *    Keith Holman - initial implementation and documentation
 *    Ian Craggs - use memory tracking
 *    Ian Craggs - fix for one MQTT packet spread over >1 ws frame
 *    Sven Gambel - move WebSocket proxy support to generic proxy support
 *******************************************************************************/

#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include "WebSocket.h"

#include "Base64.h"
#include "Log.h"
#if defined(OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x030000000
#include "openssl/evp.h"
#else
#include "SHA1.h"
#endif
#include "LinkedList.h"
#include "MQTTProtocolOut.h"
#include "SocketBuffer.h"
#include "StackTrace.h"

#if defined(__linux__)
#  include <endian.h>
#elif defined(__APPLE__)
#  include <libkern/OSByteOrder.h>
#  define htobe16(x) OSSwapHostToBigInt16(x)
#  define htobe32(x) OSSwapHostToBigInt32(x)
#  define htobe64(x) OSSwapHostToBigInt64(x)
#  define be16toh(x) OSSwapBigToHostInt16(x)
#  define be32toh(x) OSSwapBigToHostInt32(x)
#  define be64toh(x) OSSwapBigToHostInt64(x)
#elif defined(__FreeBSD__) || defined(__NetBSD__)
#  include <sys/endian.h>
#elif defined(_WIN32)
#  pragma comment(lib, "rpcrt4.lib")
#  include <rpc.h>
#  if !(defined(__MINGW32__))
#    define strncasecmp(s1,s2,c) _strnicmp(s1,s2,c)
#  endif
#  if defined(__MINGW32__)
#    define htonll __builtin_bswap64
#    define ntohll __builtin_bswap64
#  else
#    define htonll(x) _byteswap_uint64(x)
#    define ntohll(x) _byteswap_uint64(x)
#  endif

#  if BYTE_ORDER == LITTLE_ENDIAN
#    define htobe16(x)   htons(x)
#    define htobe32(x)   htonl(x)
#    define htobe64(x)   htonll(x)
#    define be16toh(x)   ntohs(x)
#    define be32toh(x)   ntohl(x)
#    define be64toh(x)   ntohll(x)
#  elif BYTE_ORDER == BIG_ENDIAN
#    define htobe16(x)   (x)
#    define htobe32(x)   (x)
#    define htobe64(x)   (x)
#    define be16toh(x)   (x)
#    define be32toh(x)   (x)
#    define be64toh(x)   (x)
#  else
#    error "unknown endian"
#  endif
   /* For Microsoft Visual Studio < 2015 */
#  if defined(_MSC_VER) && _MSC_VER < 1900
#    define snprintf _snprintf
#  endif
#endif

#if defined(OPENSSL)
#include "SSLSocket.h"
#include <openssl/rand.h>
#endif /* defined(OPENSSL) */
#include "Socket.h"

#define HTTP_PROTOCOL(x) x ? "https" : "http"

#if !(defined(_WIN32))
#if defined(USE_LIBUUID)
#include <uuid/uuid.h>
#else /* if defined(USE_LIBUUID) */
#include <limits.h>
#include <stdlib.h>
#include <time.h>

/** @brief raw uuid type */
typedef unsigned char uuid_t[16];

/**
 * @brief generates a uuid, compatible with RFC 4122, version 4 (random)
 * @note Uses a very insecure algorithm but no external dependencies
 */
static void uuid_generate( uuid_t out )
{
#if defined(OPENSSL)
	int rc = RAND_bytes( out, sizeof(uuid_t));
	if ( !rc )
#endif /* defined (OPENSSL) */
	{
		/* very insecure, but generates a random uuid */
		int i;
		srand(time(NULL));
		for ( i = 0; i < 16; ++i )
			out[i] = (unsigned char)(rand() % UCHAR_MAX);
		out[6] = (out[6] & 0x0f) | 0x40;
		out[8] = (out[8] & 0x3F) | 0x80;
	}
}

/** @brief converts a uuid to a string */
#if 0
static void uuid_unparse( uuid_t uu, char *out )
{
	int i;
	for ( i = 0; i < 16; ++i )
	{
		if ( i == 4 || i == 6 || i == 8 || i == 10 )
		{
			*out = '-';
			++out;
		}
		out += sprintf( out, "%02x", uu[i] );
	}
	*out = '\0';
}
#endif
#endif /* else if defined(USE_LIBUUID) */
#endif /* if !(defined(_WIN32)) */

#include "Heap.h"

/** raw websocket frame data */
struct ws_frame
{
	size_t len; /**< length of frame */
	size_t pos; /**< current position within the buffer */
};

/** Current frame being processed */
struct ws_frame *last_frame = NULL;

/** Holds any received websocket frames, to be process */
static List* in_frames = NULL;

static char * frame_buffer = NULL;
static size_t frame_buffer_len = 0;
static size_t frame_buffer_index = 0;
static size_t frame_buffer_data_len = 0;

/* static function declarations */
static const char *WebSocket_strcasefind(
	const char *buf, const char *str, size_t len);

static char *WebSocket_getRawSocketData(
	networkHandles *net, size_t bytes, size_t* actual_len, int* rc);

static void WebSocket_rewindData( void );

static void WebSocket_pong(
	networkHandles *net, char *app_data, size_t app_data_len);

static int WebSocket_receiveFrame(networkHandles *net, size_t *actual_len);


/**
 * calculates the amount of data required for the websocket header
 *
 * this function is used to calculate how much offset is required before calling
 * @p WebSocket_putdatas, as that function will write data before the passed in
 * buffer
 *
 * @param[in,out]  net                 network connection
 * @param[in]      mask_data           whether to mask the data
 * @param[in]      data_len            amount of data in the payload
 *
 * @return the size in bytes of the websocket header required
 *
 * @see WebSocket_putdatas
 */
size_t WebSocket_calculateFrameHeaderSize(networkHandles *net, int mask_data, size_t data_len)
{
	int ret = 0;
	if ( net && net->websocket )
	{
		if ( data_len < 126u)
			ret = 2; /* header 2 bytes */
		else if ( data_len < 65536u )
			ret = 4; /* for extra 2-bytes for payload length */
		else if ( data_len < 0xFFFFFFFFFFFFFFFF )
			ret = 10; /* for extra 8-bytes for payload length */
		if ( mask_data & 0x1 )
			ret += sizeof(uint32_t); /* for mask */
	}
	return ret;
}


/**
 * @brief builds a websocket frame for data transmission
 *
 * write a websocket header and will mask the payload in all the passed in
 * buffers
 *
 * @param[in,out]  net                 network connection
 * @param[in]      opcode              websocket opcode for the packet
 * @param[in]      mask_data           whether to mask the data
 * @param[in,out]  buf0                first buffer, will write before this
 * @param[in]      buf0len             size of first buffer
 * @param[in]      count               number of payload buffers
 * @param[in,out]  buffers             array of payload buffers
 * @param[in]      buflens             array of payload buffer sizes
 * @param[in]      freeData            array indicating to free payload buffers
 *
 * @return amount of data to write to socket
 */
struct frameData {
	char* wsbuf0;
	size_t wsbuf0len;
};

static struct frameData WebSocket_buildFrame(networkHandles* net, int opcode, int mask_data,
	char** pbuf0, size_t* pbuf0len, PacketBuffers* bufs)
{
	int buf_len = 0u;
	struct frameData rc;
	int new_mask = 0;

	FUNC_ENTRY;
	memset(&rc, '\0', sizeof(rc));
	if ( net->websocket )
	{
		size_t ws_header_size = 0u;
		size_t data_len = 0L;
		int i;

		/* Calculate total length of MQTT buffers */
		data_len = *pbuf0len;
		for (i = 0; i < bufs->count; ++i)
			data_len += bufs->buflens[i];

		/* add space for websocket frame header */
		ws_header_size = WebSocket_calculateFrameHeaderSize(net, mask_data, data_len);
		if (*pbuf0)
		{
			rc.wsbuf0len = *pbuf0len + ws_header_size;
			rc.wsbuf0 = malloc(rc.wsbuf0len);
			if (rc.wsbuf0 == NULL)
				goto exit;
			memcpy(&rc.wsbuf0[ws_header_size], *pbuf0, *pbuf0len);
		}
		else
		{
			rc.wsbuf0 = malloc(ws_header_size);
			if (rc.wsbuf0 == NULL)
				goto exit;
			rc.wsbuf0len = ws_header_size;
		}

		if (mask_data && (bufs->mask[0] == 0))
		{
			/* generate mask, since we are a client */
#if defined(OPENSSL)
			RAND_bytes(&bufs->mask[0], sizeof(bufs->mask));
#else /* if defined(OPENSSL) */
			bufs->mask[0] = (rand() % UINT8_MAX);
			bufs->mask[1] = (rand() % UINT8_MAX);
			bufs->mask[2] = (rand() % UINT8_MAX);
			bufs->mask[3] = (rand() % UINT8_MAX);
#endif /* else if defined(OPENSSL) */
			new_mask = 1;
		}

		/* 1st byte */
		rc.wsbuf0[buf_len] = (char)(1 << 7); /* final flag */
		/* 3 bits reserved for negotiation of protocol */
		rc.wsbuf0[buf_len] |= (char)(opcode & 0x0F); /* op code */
		++buf_len;

		/* 2nd byte */
		rc.wsbuf0[buf_len] = (char)((mask_data & 0x1) << 7); /* masking bit */

		/* payload length */
		if ( data_len < 126u )
			rc.wsbuf0[buf_len++] |= data_len & 0x7F;

		/* 3rd byte & 4th bytes - extended payload length */
		else if ( data_len < 65536u )
		{
			uint16_t len = htobe16((uint16_t)data_len);
			rc.wsbuf0[buf_len++] |= (126u & 0x7F);
			memcpy( &rc.wsbuf0[buf_len], &len, 2u );
			buf_len += 2;
		}
		else if ( data_len < 0xFFFFFFFFFFFFFFFF )
		{
			uint64_t len = htobe64((uint64_t)data_len);
			rc.wsbuf0[buf_len++] |= (127u & 0x7F);
			memcpy( &rc.wsbuf0[buf_len], &len, 8 );
			buf_len += 8;
		}
		else
		{
			Log(TRACE_PROTOCOL, 1, "Data too large for websocket frame" );
			buf_len = -1;
		}

		if (mask_data)
		{
			size_t idx = 0u;

			/* copy masking key into ws header */
			memcpy( &rc.wsbuf0[buf_len], &bufs->mask, sizeof(uint32_t));
			buf_len += sizeof(uint32_t);

			/* mask packet fixed header */
			for (i = (int)ws_header_size; i < (int)rc.wsbuf0len; ++i, ++idx)
				rc.wsbuf0[i] ^= bufs->mask[idx % 4];

			/* variable data buffers */
			for (i = 0; i < bufs->count; ++i)
			{
				size_t j;

				if (new_mask == 0 && (i == 2 || i == bufs->count-1))
					/* topic (2) and payload (last) buffers are already masked */
					break;
				for ( j = 0u; j < bufs->buflens[i]; ++j, ++idx )
				{
					bufs->buffers[i][j] ^= bufs->mask[idx % 4];
				}
			}
		}
	}
exit:
	FUNC_EXIT_RC(buf_len);
	return rc;
}


static void WebSocket_unmaskData(size_t idx, PacketBuffers* bufs)
{
	int i;

	FUNC_ENTRY;
	for (i = 0; i < bufs->count; ++i)
	{
		size_t j;
		for (j = 0u; j < bufs->buflens[i]; ++j, ++idx)
			bufs->buffers[i][j] ^= bufs->mask[idx % 4];
	}
	/* show that the mask has been removed */
	bufs->mask[0] = bufs->mask[1] = bufs->mask[2] = bufs->mask[3] = 0;
	FUNC_EXIT;
}


/**
 * sends out a websocket request on the given uri
 *
 * @param[in]      net                 network connection
 * @param[in]      ssl                 ssl flag
 * @param[in]      uri                 uri to connect to
 *
 * @retval SOCKET_ERROR                on failure
 * @retval 1                           on success
 *
 * @see WebSocket_upgrade
 */
int WebSocket_connect( networkHandles *net, int ssl, const char *uri)
{
	int rc;
	char *buf = NULL;
	char *headers_buf = NULL;
	const MQTTClient_nameValue *headers = net->httpHeaders;
	int i, buf_len = 0;
	int headers_buf_len = 0;
	size_t hostname_len;
	int port = 80;
	const char *topic = NULL;
#if defined(_WIN32)
	UUID uuid;
#else /* if defined(_WIN32) */
	uuid_t uuid;
#endif /* else if defined(_WIN32) */

	FUNC_ENTRY;
	/* Generate UUID */
	if (net->websocket_key == NULL)
		net->websocket_key = malloc(25u);
	else
	{
		void* newPtr = realloc(net->websocket_key, 25u);
		if (newPtr == NULL)
		{
			free(net->websocket_key);
			net->websocket_key = NULL;
		}
		else
		{
			net->websocket_key = newPtr;
		}
	}
	if (net->websocket_key == NULL)
	{
		rc = PAHO_MEMORY_ERROR;
		goto exit;
	}
#if defined(_WIN32)
	ZeroMemory( &uuid, sizeof(UUID) );
	UuidCreate( &uuid );
	Base64_encode( net->websocket_key, 25u, (const b64_data_t*)&uuid, sizeof(UUID) );
#else /* if defined(_WIN32) */
	uuid_generate( uuid );
	Base64_encode( net->websocket_key, 25u, uuid, sizeof(uuid_t) );
#endif /* else if defined(_WIN32) */

	hostname_len = MQTTProtocol_addressPort(uri, &port, &topic, ssl ? WSS_DEFAULT_PORT : WS_DEFAULT_PORT);

	/* if no topic, use default */
	if ( !topic )
		topic = "/mqtt";

	if ( headers )
	{
		char *headers_buf_cur = NULL;
		while ( headers->name != NULL && headers->value != NULL )
		{
			headers_buf_len += (int)(strlen(headers->name) + strlen(headers->value) + 4);
			headers++;
		}
		headers_buf_len++;

		if ((headers_buf = malloc(headers_buf_len)) == NULL)
		{
			rc = PAHO_MEMORY_ERROR;
			goto exit;
		}
		headers = net->httpHeaders;
		headers_buf_cur = headers_buf;

		while ( headers->name != NULL && headers->value != NULL )
		{
			headers_buf_cur += snprintf(headers_buf_cur, headers_buf_len - (headers_buf_cur - headers_buf),
					"%s: %s\r\n", headers->name, headers->value);
			headers++;
		}
		*headers_buf_cur = '\0';
	}

	for ( i = 0; i < 2; ++i )
	{
		buf_len = snprintf( buf, (size_t)buf_len,
			"GET %s HTTP/1.1\r\n"
			"Host: %.*s:%d\r\n"
			"Upgrade: websocket\r\n"
			"Connection: Upgrade\r\n"
			"Origin: %s://%.*s:%d\r\n"
			"Sec-WebSocket-Key: %s\r\n"
			"Sec-WebSocket-Version: 13\r\n"
			"Sec-WebSocket-Protocol: mqtt\r\n"
			"%s"
			"\r\n", topic,
			(int)hostname_len, uri, port,
#if defined(OPENSSL)
			HTTP_PROTOCOL(net->ssl),
#else
			HTTP_PROTOCOL(0),
#endif
			
			(int)hostname_len, uri, port,
			net->websocket_key,
			headers_buf ? headers_buf : "");

		if ( i == 0 && buf_len > 0 )
		{
			++buf_len; /* need 1 extra byte for ending '\0' */
			if ((buf = malloc( buf_len )) == NULL)
			{
				rc = PAHO_MEMORY_ERROR;
				goto exit;
			}
		}
	}

	if (headers_buf)
		free( headers_buf );

	if ( buf )
	{
		PacketBuffers nulbufs = {0, NULL, NULL, NULL, {0, 0, 0, 0}};

#if defined(OPENSSL)
		if (net->ssl)
			SSLSocket_putdatas(net->ssl, net->socket, buf, buf_len, nulbufs);
		else
#endif
			Socket_putdatas(net->socket, buf, buf_len, nulbufs);
		free( buf );
		rc = 1;
	}
	else
	{
		free(net->websocket_key);
		net->websocket_key = NULL;
		rc = SOCKET_ERROR;
	}
exit:
	FUNC_EXIT_RC(rc);
	return rc;
}

/**
 * closes a websocket connection
 *
 * @param[in,out]  net                 structure containing network connection
 * @param[in]      status_code         websocket close status code
 * @param[in]      reason              reason for closing connection (optional)
 */
void WebSocket_close(networkHandles *net, int status_code, const char *reason)
{
	struct frameData fd;
	PacketBuffers nulbufs = {0, NULL, NULL, NULL, {0, 0, 0, 0}};

	FUNC_ENTRY;
	if ( net->websocket )
	{
		char *buf0;
		size_t buf0len = sizeof(uint16_t);
		uint16_t status_code_be;
		const int mask_data = 1; /* all frames from client must be masked */

		if ( status_code < WebSocket_CLOSE_NORMAL ||
			status_code > WebSocket_CLOSE_TLS_FAIL )
			status_code = WebSocket_CLOSE_GOING_AWAY;

		if ( reason )
			buf0len += strlen(reason);

		buf0 = malloc(buf0len);
		if ( !buf0 )
			goto exit;

		/* encode status code */
		status_code_be = htobe16((uint16_t)status_code);
		memcpy(buf0, &status_code_be, sizeof(uint16_t));

		/* encode reason, if provided */
		if ( reason )
			strcpy( &buf0[sizeof(uint16_t)], reason );

		fd = WebSocket_buildFrame( net, WebSocket_OP_CLOSE, mask_data, &buf0, &buf0len, &nulbufs);

#if defined(OPENSSL)
		if (net->ssl)
			SSLSocket_putdatas(net->ssl, net->socket, fd.wsbuf0, fd.wsbuf0len, nulbufs);
		else
#endif
			Socket_putdatas(net->socket, fd.wsbuf0, fd.wsbuf0len, nulbufs);

		free(fd.wsbuf0); /* free temporary ws header */

		/* websocket connection is now closed */
		net->websocket = 0;
		free( buf0 );
	}
	if ( net->websocket_key )
	{
		free( net->websocket_key );
		net->websocket_key = NULL;
	}
exit:
	FUNC_EXIT;
}

/**
 * @brief receives 1 byte from a socket
 *
 * @param[in,out]  net                 network connection
 * @param[out]     c                   byte that was read
 *
 * @retval SOCKET_ERROR                on error
 * @retval TCPSOCKET_INTERRUPTED       no data available
 * @retval TCPSOCKET_COMPLETE          on success
 *
 * @see WebSocket_getdata
 */
int WebSocket_getch(networkHandles *net, char* c)
{
	int rc = SOCKET_ERROR;

	FUNC_ENTRY;
	if ( net->websocket )
	{
		struct ws_frame *frame = NULL;

		if ( in_frames && in_frames->first )
			frame = in_frames->first->content;

		if ( !frame  || frame->len == frame->pos )
		{
			size_t actual_len = 0u;
			rc = WebSocket_receiveFrame( net, &actual_len);
			if ( rc != TCPSOCKET_COMPLETE )
				goto exit;

			/* we got a frame, let take off the top of queue */
			if ( in_frames->first )
				frame = in_frames->first->content;
		}

		/* set current working frame */
		if (frame && frame->len > frame->pos)
		{
			unsigned char *buf =
				(unsigned char *)frame + sizeof(struct ws_frame);
			*c = buf[frame->pos++];
			rc = TCPSOCKET_COMPLETE;
		}
	}
#if defined(OPENSSL)
	else if ( net->ssl )
		rc = SSLSocket_getch(net->ssl, net->socket, c);
#endif
	else
		rc = Socket_getch(net->socket, c);

exit:
	FUNC_EXIT_RC(rc);
	return rc;
}

size_t WebSocket_framePos()
{
	if ( in_frames && in_frames->first )
	{
		struct ws_frame *frame = in_frames->first->content;
		return frame->pos;
	}
	else
	{
		return 0;
	}
}

void WebSocket_framePosSeekTo(size_t pos)
{
	if ( in_frames && in_frames->first )
	{
		struct ws_frame *frame = in_frames->first->content;
		frame->pos = pos;
	}
}

/**
 * @brief receives data from a socket.
 * It should receive all data from the socket that is immediately available.
 * Because it is encapsulated in websocket frames which cannot be
 *
 * @param[in,out]  net                 network connection
 * @param[in]      bytes               amount of data to get (0 if last packet)
 * @param[out]     actual_len          amount of data read
 *
 * @return a pointer to the read data
 *
 * @see WebSocket_getch
 */
char *WebSocket_getdata(networkHandles *net, size_t bytes, size_t* actual_len)
{
	char *rv = NULL;
	int rc;

	FUNC_ENTRY;
	if ( net->websocket )
	{
		struct ws_frame *frame = NULL;

		if ( bytes == 0u )
		{
			/* done with current frame, move it to last frame */
			if ( in_frames && in_frames->first )
				frame = in_frames->first->content;

			/* return the data from the next frame, if we have one */
			if ( frame  && frame->pos == frame->len )
			{
				rv = (char *)frame +
					sizeof(struct ws_frame) + frame->pos;
				*actual_len = frame->len - frame->pos;

				if ( last_frame )
					free( last_frame );
				last_frame = ListDetachHead(in_frames);
			}
			goto exit;
		}

		/* look at the first websocket frame */
		if ( in_frames && in_frames->first )
			frame = in_frames->first->content;

		/* no current frame, so let's go receive one for the network */
		if ( !frame )
		{
			rc = WebSocket_receiveFrame( net, actual_len );

			if ( rc == TCPSOCKET_COMPLETE && in_frames && in_frames->first)
				frame = in_frames->first->content;
		}

		if ( frame )
		{
			rv = (char *)frame + sizeof(struct ws_frame) + frame->pos;
			*actual_len = frame->len - frame->pos; /* use the rest of the frame */


			while (*actual_len < bytes) {
				rc = WebSocket_receiveFrame(net, actual_len);

				if (rc != TCPSOCKET_COMPLETE) {
					goto exit;
				}

				/* refresh pointers */
				frame = in_frames->first->content;
				rv = (char *)frame + sizeof(struct ws_frame) + frame->pos;
				*actual_len = frame->len - frame->pos; /* use the rest of the frame */

			} /* end while */

			if (*actual_len > bytes)
			{
				frame->pos += bytes;
			}
			else if (*actual_len == bytes && in_frames)
			{
				if ( last_frame )
					free( last_frame );
				last_frame = ListDetachHead(in_frames);
			}
		}
	}
#if defined(OPENSSL)
	else if ( net->ssl )
		rv = SSLSocket_getdata(net->ssl, net->socket, bytes, actual_len, &rc);
#endif
	else
		rv = Socket_getdata(net->socket, bytes, actual_len, &rc);

exit:
	FUNC_EXIT_RC(rv);
	return rv;
}

void WebSocket_rewindData( void )
{
	frame_buffer_index = 0;
}

/**
 * reads raw socket data for underlying layers
 *
 * @param[in]      net                 network connection
 * @param[in]      bytes               number of bytes to read, 0 to complete packet
 * @param[in]      actual_len          amount of data read
 *
 * @return a buffer containing raw data
 */
char *WebSocket_getRawSocketData(networkHandles *net, size_t bytes, size_t* actual_len, int* rc)
{
	char *rv = NULL;

	size_t bytes_requested = bytes;

	FUNC_ENTRY;
	if (bytes > 0)
	{
		if (frame_buffer_data_len - frame_buffer_index >= bytes)
		{
			*actual_len = bytes;
			rv = frame_buffer + frame_buffer_index;
			frame_buffer_index += bytes;
			*rc = (int)bytes;
			goto exit;
		}
		else
		{
			bytes = bytes - (frame_buffer_data_len - frame_buffer_index);
		}
	}

	*actual_len = 0;
	
	// not enough data in the buffer, get data from socket
#if defined(OPENSSL)
	if ( net->ssl )
		rv = SSLSocket_getdata(net->ssl, net->socket, bytes, actual_len, rc);
	else
#endif
		rv = Socket_getdata(net->socket, bytes, actual_len, rc);

	if (*rc == 0)
	{
		*rc = SOCKET_ERROR;
		goto exit;
	}

	// clear buffer
	if (bytes == 0)
	{
		frame_buffer_index = 0;
		frame_buffer_data_len = 0;
		frame_buffer_len = 0;
		
		if (frame_buffer)
		{
			free (frame_buffer);
			frame_buffer = NULL;
		}
	}
	// append data to the buffer
	else if (rv != NULL && *actual_len != 0U)
	{
		// no buffer allocated
		if (!frame_buffer)
		{
			if ((frame_buffer = (char *)malloc(*actual_len)) == NULL)
			{
				rv = NULL;
				goto exit;
			}
			memcpy(frame_buffer, rv, *actual_len);

			frame_buffer_index = 0;
			frame_buffer_data_len = *actual_len;
			frame_buffer_len = *actual_len;
		}
		// buffer size is big enough
		else if (frame_buffer_data_len + *actual_len < frame_buffer_len)
		{
			memcpy(frame_buffer + frame_buffer_data_len, rv, *actual_len);
			frame_buffer_data_len += *actual_len;
		}
		// resize buffer
		else
		{
			void* newPtr = realloc(frame_buffer, frame_buffer_data_len + *actual_len);
			if (newPtr == NULL)
			{
				free(frame_buffer);
				frame_buffer = NULL;

				rv = NULL;
				goto exit;
			}
			else
			{
				frame_buffer = newPtr;
			}
			frame_buffer_len = frame_buffer_data_len + *actual_len;

			memcpy(frame_buffer + frame_buffer_data_len, rv, *actual_len);
			frame_buffer_data_len += *actual_len;
		}

		SocketBuffer_complete(net->socket);
	}
	else 
		goto exit;

	bytes = bytes_requested;

	// if possible, return data from the buffer
	if (bytes > 0)
	{
		if (frame_buffer_data_len - frame_buffer_index >= bytes)
		{
			*actual_len = bytes;
			rv = frame_buffer + frame_buffer_index;
			frame_buffer_index += bytes;
		}
		else
		{
			*actual_len = frame_buffer_data_len - frame_buffer_index;
			rv = frame_buffer + frame_buffer_index;
			frame_buffer_index += *actual_len;
		}
	}

exit:
	FUNC_EXIT;
	return rv;
}

/**
 * sends a "websocket pong" message
 *
 * @param[in]      net                 network connection
 * @param[in]      app_data            application data to put in payload
 * @param[in]      app_data_len        application data length
 */
void WebSocket_pong(networkHandles *net, char *app_data, size_t app_data_len)
{
	FUNC_ENTRY;
	if ( net->websocket )
	{
		char *buf0 = NULL;
		size_t buf0len = 0;
		int freeData = 0;
		struct frameData fd;
		const int mask_data = 1; /* all frames from client must be masked */
		PacketBuffers appbuf = {1, &app_data, &app_data_len, &freeData, {0, 0, 0, 0}};

		fd = WebSocket_buildFrame( net, WebSocket_OP_PONG, mask_data, &buf0, &buf0len, &appbuf);

		Log(TRACE_PROTOCOL, 1, "Sending WebSocket PONG" );

#if defined(OPENSSL)
		if (net->ssl)
			SSLSocket_putdatas(net->ssl, net->socket, fd.wsbuf0, fd.wsbuf0len /*header_len + app_data_len*/, appbuf);
		else
#endif
			Socket_putdatas(net->socket, fd.wsbuf0, fd.wsbuf0len /*header_len + app_data_len*/, appbuf);

		free(fd.wsbuf0);
		free(buf0);
	}
	FUNC_EXIT;
}

/**
 * writes data to a socket (websocket header will be prepended if required)
 *
 * @warning buf0 will be expanded (backwords before @p buf0 buffer, to add a
 * websocket frame header to the data if required).  So use
 * @p WebSocket_calculateFrameHeader, to determine if extra space is needed
 * before the @p buf0 pointer.
 *
 * @param[in,out]  net                 network connection
 * @param[in,out]  buf0                first buffer
 * @param[in]      buf0len             size of first buffer
 * @param[in]      count               number of payload buffers
 * @param[in,out]  buffers             array of paylaod buffers
 * @param[in]      buflens             array of payload buffer sizes
 * @param[in]      freeData            array indicating to free payload buffers
 *
 * @return amount of data wrote to socket
 *
 * @see WebSocket_calculateFrameHeaderSize
 */
int WebSocket_putdatas(networkHandles* net, char** buf0, size_t* buf0len, PacketBuffers* bufs)
{
	const int mask_data = 1; /* must mask websocket data from client */
	int rc;

	FUNC_ENTRY;
	if (net->websocket)
	{
		struct frameData wsdata;

		wsdata = WebSocket_buildFrame(net, WebSocket_OP_BINARY, mask_data, buf0, buf0len, bufs);

#if defined(OPENSSL)
		if (net->ssl)
			rc = SSLSocket_putdatas(net->ssl, net->socket, wsdata.wsbuf0, wsdata.wsbuf0len, *bufs);
		else
#endif
			rc = Socket_putdatas(net->socket, wsdata.wsbuf0, wsdata.wsbuf0len, *bufs);

		if (rc != TCPSOCKET_INTERRUPTED)
		{
			if (mask_data)
				WebSocket_unmaskData(*buf0len, bufs);
			free(wsdata.wsbuf0); /* free temporary ws header */
		}
	}
	else
	{
#if defined(OPENSSL)
		if (net->ssl)
			rc = SSLSocket_putdatas(net->ssl, net->socket, *buf0, *buf0len, *bufs);
		else
#endif
			rc = Socket_putdatas(net->socket, *buf0, *buf0len, *bufs);
	}

	FUNC_EXIT_RC(rc);
	return rc;
}

/**
 * receives incoming socket data and parses websocket frames
 * Copes with socket reads returning partial websocket frames by using the
 * SocketBuffer mechanism.
 *
 * @param[in]      net                 network connection
 * @param[out]     actual_len          amount of data actually read
 *
 * @retval TCPSOCKET_COMPLETE          packet received
 * @retval TCPSOCKET_INTERRUPTED       incomplete packet received
 * @retval SOCKET_ERROR                an error was encountered
 */
int WebSocket_receiveFrame(networkHandles *net, size_t *actual_len)
{
	struct ws_frame *res = NULL;
	int rc = TCPSOCKET_COMPLETE;
	int opcode = 0;

	FUNC_ENTRY;
	if ( !in_frames )
		in_frames = ListInitialize();

	/* see if there is frame currently on queue */
	if ( in_frames->first )
		res = in_frames->first->content;

	//while( !res )
	//{
		opcode = WebSocket_OP_BINARY;
		do
		{
			/* obtain all frames in the sequence */
			int is_final = 0;
			while ( is_final == 0 )
			{
				char *b;
				size_t len = 0u;
				int tmp_opcode;
				int has_mask;
				size_t cur_len = 0u;
				uint8_t mask[4] = { 0u, 0u, 0u, 0u };
				size_t payload_len;
				int rcs; /* socket return code */

				b = WebSocket_getRawSocketData(net, 2u, &len, &rcs);
				if (rcs == SOCKET_ERROR)
				{
					rc = rcs;
					goto exit;
				}
				if ( !b )
				{
					rc = TCPSOCKET_INTERRUPTED;
					goto exit;
				} 
				else if (len < 2u )
				{
					rc = TCPSOCKET_INTERRUPTED;
					goto exit;
				}

				/* 1st byte */
				is_final = (b[0] & 0xFF) >> 7;
				tmp_opcode = (b[0] & 0x0F);

				if ( tmp_opcode ) /* not a continuation frame */
					opcode = tmp_opcode;

				/* invalid websocket packet must return error */
				if ( opcode < WebSocket_OP_CONTINUE ||
					 opcode > WebSocket_OP_PONG ||
					 ( opcode > WebSocket_OP_BINARY &&
					   opcode < WebSocket_OP_CLOSE ) )
				{
					rc = SOCKET_ERROR;
					goto exit;
				}

				/* 2nd byte */
				has_mask = (b[1] & 0xFF) >> 7;
				payload_len = (b[1] & 0x7F);

				/* determine payload length */
				if ( payload_len == 126 )
				{
					/* If 126, the following 2 bytes interpreted as a
						  16-bit unsigned integer are the payload length. */
					b = WebSocket_getRawSocketData(net, 2u, &len, &rcs);
					if (rcs == SOCKET_ERROR)
					{
						rc = rcs;
						goto exit;
					}
					if ( !b )
					{
						rc = SOCKET_ERROR;
						goto exit;
					} 
					else if (len < 2u )
					{
						rc = TCPSOCKET_INTERRUPTED;
						goto exit;
					}
					/* convert from big endian 16 to host */
					payload_len = be16toh(*(uint16_t*)b);
				}
				else if ( payload_len == 127 )
				{
					 /* If 127, the following 8 bytes interpreted as a 64-bit unsigned integer (the
						  most significant bit MUST be 0) are the payload length */
					b = WebSocket_getRawSocketData(net, 8u, &len, &rcs);
					if (rcs == SOCKET_ERROR)
					{
						rc = rcs;
						goto exit;
					}
					if ( !b )
					{
						rc = SOCKET_ERROR;
						goto exit;
					} 
					else if (len < 8u )
					{
						rc = TCPSOCKET_INTERRUPTED;
						goto exit;
					}
					/* convert from big-endian 64 to host */
					payload_len = (size_t)be64toh(*(uint64_t*)b);
				}

				if ( has_mask )
				{
					uint8_t mask[4];
					b = WebSocket_getRawSocketData(net, 4u, &len, &rcs);
					if (rcs == SOCKET_ERROR)
					{
						rc = rcs;
						goto exit;
					}
					if ( !b )
					{
						rc = SOCKET_ERROR;
						goto exit;
					}
					if (len < 4u )
					{
						rc = TCPSOCKET_INTERRUPTED;
						goto exit;
					}
					memcpy( &mask[0], b, sizeof(uint32_t));
				}

				/* use the socket buffer to read in the whole websocket frame */
				b = WebSocket_getRawSocketData(net, payload_len, &len, &rcs);
				if (rcs == SOCKET_ERROR)
				{
					rc = rcs;
					goto exit;
				}
				if (!b)
				{
					rc = SOCKET_ERROR;
					goto exit;
				} 
				if (len < payload_len )
				{
					rc = TCPSOCKET_INTERRUPTED;
					goto exit;
				}

				/* unmask data */
				if ( has_mask )
				{
					size_t i;
					for ( i = 0u; i < payload_len; ++i )
						b[i] ^= mask[i % 4];
				}

				if ( res )
					cur_len = res->len;

				if (res == NULL)
				{
					if ((res = malloc( sizeof(struct ws_frame) + cur_len + len)) == NULL)
					{
						rc = PAHO_MEMORY_ERROR;
						goto exit;
					}
					res->pos = 0u;
				} else
				{
					void* newPtr = realloc( res, sizeof(struct ws_frame) + cur_len + len );
					if (newPtr == NULL)
					{
						free(res);
						res = NULL;

						rc = PAHO_MEMORY_ERROR;
						goto exit;
					}
					else
					{
						res = newPtr;
					}
				}
				if (in_frames && in_frames->first)
					in_frames->first->content = res; /* realloc moves the data */
				memcpy( (unsigned char *)res + sizeof(struct ws_frame) + cur_len, b, len );
				res->len = cur_len + len;

				WebSocket_getRawSocketData(net, 0u, &len, &rcs);
				if (rcs == SOCKET_ERROR)
				{
					rc = rcs;
					goto exit;
				}
			}

			if ( opcode == WebSocket_OP_PING || opcode == WebSocket_OP_PONG )
			{
				/* respond to a "ping" with a "pong" */
				if ( opcode == WebSocket_OP_PING )
					WebSocket_pong( net,
						(char *)res + sizeof(struct ws_frame),
						res->len );

				/* discard message */
				free( res );
				res = NULL;
			}
			else if ( opcode == WebSocket_OP_CLOSE )
			{
				/* server end closed websocket connection */
				free( res );
				WebSocket_close( net, WebSocket_CLOSE_GOING_AWAY, NULL );
				rc = SOCKET_ERROR; /* closes socket */
				goto exit;
			}
		} while ( opcode == WebSocket_OP_PING || opcode == WebSocket_OP_PONG );
	//}

	if (in_frames->count == 0)
		ListAppend( in_frames, res, sizeof(struct ws_frame) + res->len);
	*actual_len = res->len - res->pos;

exit:
	if (rc == TCPSOCKET_INTERRUPTED)
	{
		WebSocket_rewindData();
	}

	FUNC_EXIT_RC(rc);
	return rc;
}

/**
 * case-insensitive string search
 *
 * similar to @p strcase, but takes a maximum length
 *
 * @param[in]      buf                 buffer to search
 * @param[in]      str                 string to find
 * @param[in]       len                length of the buffer
 *
 * @retval !NULL                       location of string found
 * @retval NULL                        string not found
 */
const char *WebSocket_strcasefind(const char *buf, const char *str, size_t len)
{
	const char *res = NULL;
	if ( buf && len > 0u && str )
	{
		const size_t str_len = strlen( str );
		while ( len >= str_len && !res )
		{
			if ( strncasecmp( buf, str, str_len ) == 0 )
				res = buf;
			++buf;
			--len;
		}
	}
	return res;
}

/**
 * releases resources used by the websocket sub-system
 */
void WebSocket_terminate( void )
{
	FUNC_ENTRY;
	/* clean up and un-processed websocket frames */
	if ( in_frames )
	{
		struct ws_frame *f = ListDetachHead( in_frames );
		while ( f )
		{
			free( f );
			f = ListDetachHead( in_frames );
		}
		ListFree( in_frames );
		in_frames = NULL;
	}
	if ( last_frame )
	{
		free( last_frame );
		last_frame = NULL;
	}
	
	if ( frame_buffer )
	{
		free( frame_buffer );
		frame_buffer = NULL;
	}
	
	frame_buffer_len = 0;
	frame_buffer_index = 0;
	frame_buffer_data_len = 0;

	Socket_outTerminate();
#if defined(OPENSSL)
	SSLSocket_terminate();
#endif
	FUNC_EXIT;
}

/**
 * handles the websocket upgrade response
 *
 * @param[in,out]  net                 network connection to upgrade
 *
 * @retval SOCKET_ERROR                failed to upgrade network connection
 * @retval TCPSOCKET_INTERRUPTED       upgrade not complete, but not failed.  Try again
 * @retval 1                           socket upgraded to use websockets
 *
 * @see WebSocket_connect
 */
int WebSocket_upgrade( networkHandles *net )
{
	static const char *const ws_guid =
		"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
	int rc = SOCKET_ERROR;

	FUNC_ENTRY;
	if ( net->websocket_key )
	{
		char ws_key[62u] = {0};
		size_t rcv = 0u;
		char *read_buf;
#if defined(OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x030000000
		EVP_MD_CTX *sha1_ctx = NULL;
		unsigned char sha_hash[EVP_MAX_MD_SIZE];
		unsigned int sha_len = 0;
#else
		SHA_CTX ctx;
		unsigned char sha_hash[SHA1_DIGEST_LENGTH];
#endif

		/* calculate the expected websocket key, expected from server */
		snprintf(ws_key, sizeof(ws_key), "%s%s", net->websocket_key, ws_guid);
#if defined(OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x030000000
		sha1_ctx = EVP_MD_CTX_new();
		if (sha1_ctx) {
			rc = EVP_DigestInit(sha1_ctx, EVP_sha1());
			if (rc == 0)
				Log(LOG_ERROR, 1, "EVP_DigestInit failed");
			else
				rc = EVP_DigestUpdate(sha1_ctx, ws_key, strlen(ws_key));
			if (rc == 0)
				Log(LOG_ERROR, 1, "EVP_DigestUpdate failed");
			else
				rc = EVP_DigestFinal(sha1_ctx, sha_hash, &sha_len);
			if (rc == 0)
				Log(LOG_ERROR, 1, "EVP_DigestFinal failed");
			EVP_MD_CTX_free(sha1_ctx);
			if (rc == 0)
			{
				rc = SOCKET_ERROR;
				goto exit;
			}
		} else
		{
			Log(LOG_ERROR, 1, "EVP_MD_CTX_new failed");
			rc = SOCKET_ERROR;
			goto exit;
		}
		Base64_encode( ws_key, sizeof(ws_key), sha_hash, sha_len);
#else
		SHA1_Init( &ctx );
		SHA1_Update( &ctx, ws_key, strlen(ws_key));
		SHA1_Final( sha_hash, &ctx );
		Base64_encode( ws_key, sizeof(ws_key), sha_hash, SHA1_DIGEST_LENGTH );
#endif

		read_buf = WebSocket_getRawSocketData( net, 12u, &rcv, &rc);
		if (rc == SOCKET_ERROR)
			goto exit;

		if ((read_buf == NULL) || rcv < 12u)
		{
			Log(TRACE_PROTOCOL, 1, "WebSocket upgrade read not complete %lu", rcv );
			rc = TCPSOCKET_INTERRUPTED;
			goto exit;
		}

		if (strncmp( read_buf, "HTTP/1.1", 8u ) == 0)
		{
			if (strncmp( &read_buf[9], "101", 3u ) != 0)
			{
				Log(TRACE_PROTOCOL, 1, "WebSocket HTTP rc %.3s", &read_buf[9]);
				rc = SOCKET_ERROR;
				goto exit;
			}
		}

		if (strncmp( read_buf, "HTTP/1.1 101", 12u ) == 0)
		{
			const char *p;

			read_buf = WebSocket_getRawSocketData(net, 1024u, &rcv, &rc);
			if (rc == SOCKET_ERROR)
				goto exit;

			/* Did we read the whole response? */
			if (read_buf && rcv > 4 && memcmp(&read_buf[rcv-4], "\r\n\r\n", 4) != 0)
			{
				Log(TRACE_PROTOCOL, -1, "WebSocket HTTP upgrade response read not complete %lu", rcv);
				rc = SOCKET_ERROR;
				goto exit;
			}

			/* check for upgrade */
			p = WebSocket_strcasefind(
				read_buf, "Connection", rcv );
			if ( p )
			{
				const char *eol;
				eol = memchr( p, '\n', rcv-(read_buf-p) );
				if ( eol )
					p = WebSocket_strcasefind(
						p, "Upgrade", eol - p);
				else
					p = NULL;
			}

			/* check key hash */
			if ( p )
				p = WebSocket_strcasefind( read_buf,
					"sec-websocket-accept", rcv );
			if ( p )
			{
				const char *eol;
				eol = memchr( p, '\n', rcv-(read_buf-p) );
				if ( eol )
				{
					p = memchr( p, ':', eol-p );
					if ( p )
					{
						size_t hash_len = eol-p-1;
						while ( *p == ':' || *p == ' ' )
						{
							++p;
							--hash_len;
						}

						if ( strncmp( p, ws_key, hash_len ) != 0 )
							p = NULL;
					}
				}
				else
					p = NULL;
			}

			if ( p )
			{
				net->websocket = 1;
				Log(TRACE_PROTOCOL, 1, "WebSocket connection upgraded" );
				rc = 1;
			}
			else
			{
				Log(TRACE_PROTOCOL, 1, "WebSocket failed to upgrade connection" );
				rc = SOCKET_ERROR;
			}

			if ( net->websocket_key )
			{
				free(net->websocket_key);
				net->websocket_key = NULL;
			}

			/* indicate that we done with the packet */
			WebSocket_getRawSocketData( net, 0u, &rcv, &rc);
		}
	}

exit:
	FUNC_EXIT_RC(rc);
	return rc;
}
