/*
 * websocket_common - Common WebSocket functions (RFC6455)
 * (C)Copyright 2016 Bram Matthys and the UnrealIRCd team
 * License: GPLv2 or later
 * The websocket module was sponsored by Aberrant Software Inc.
 */
   
#include "unrealircd.h"

ModuleHeader MOD_HEADER
  = {
	"websocket_common",
	"6.1.4",
	"WebSocket support (RFC6455)",
	"UnrealIRCd Team",
	"unrealircd-6",
    };

#if CHAR_MIN < 0
 #error "In UnrealIRCd char should always be unsigned. Check your compiler"
#endif

#ifndef WEBSOCKET_SEND_BUFFER_SIZE
 #define WEBSOCKET_SEND_BUFFER_SIZE 16384
#endif

#define WSU(client)	((WebSocketUser *)moddata_client(client, websocket_md).ptr)

/* Forward declarations - public functions */
int _websocket_handle_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2, int callback(Client *client, char *buf, int len));
int _websocket_create_packet(int opcode, char **buf, int *len);
int _websocket_create_packet_ex(int opcode, char **buf, int *len, char *sendbuf, size_t sendbufsize);
int _websocket_create_packet_simple(int opcode, const char **buf, int *len);
/* Forward declarations - other */
int websocket_handle_packet(Client *client, const char *readbuf, int length, int callback(Client *client, char *buf, int len));
int websocket_handle_packet_ping(Client *client, const char *buf, int len);
int websocket_handle_packet_pong(Client *client, const char *buf, int len);
int websocket_send_pong(Client *client, const char *buf, int len);
const char *websocket_mdata_serialize(ModData *m);
void websocket_mdata_unserialize(const char *str, ModData *m);
void websocket_mdata_free(ModData *m);

/* Global variables */
ModDataInfo *websocket_md;
static int ws_text_mode_available = 1;

MOD_TEST()
{
	MARK_AS_OFFICIAL_MODULE(modinfo);
	EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_HANDLE_WEBSOCKET, _websocket_handle_websocket);
	EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_CREATE_PACKET, _websocket_create_packet);
	EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_CREATE_PACKET_EX, _websocket_create_packet_ex);
	EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_CREATE_PACKET_SIMPLE, _websocket_create_packet_simple);

	/* Init first, since we manage sockets */
	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, WEBSOCKET_MODULE_PRIORITY_INIT);

	return MOD_SUCCESS;
}

MOD_INIT()
{
	ModDataInfo mreq;

	MARK_AS_OFFICIAL_MODULE(modinfo);

	memset(&mreq, 0, sizeof(mreq));
	mreq.name = "websocket";
	mreq.serialize = websocket_mdata_serialize;
	mreq.unserialize = websocket_mdata_unserialize;
	mreq.free = websocket_mdata_free;
	mreq.sync = MODDATA_SYNC_EARLY;
	mreq.type = MODDATATYPE_CLIENT;
	mreq.priority = -999996;
	websocket_md = ModDataAdd(modinfo->handle, mreq);

	/* Unload last, since we manage sockets */
	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, WEBSOCKET_MODULE_PRIORITY_UNLOAD);

	return MOD_SUCCESS;
}

MOD_LOAD()
{
	return MOD_SUCCESS;
}

MOD_UNLOAD()
{
	return MOD_SUCCESS;
}

int _websocket_handle_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2, int callback(Client *client, char *buf, int len))
{
	int n;
	char *ptr;
	int length;
	int length1 = WSU(client)->lefttoparselen;
	char readbuf[MAXLINELENGTH];

	length = length1 + length2;
	if (length > sizeof(readbuf)-1)
	{
		dead_socket(client, "Illegal buffer stacking/Excess flood");
		return 0;
	}

	if (length1 > 0)
		memcpy(readbuf, WSU(client)->lefttoparse, length1);
	memcpy(readbuf+length1, readbuf2, length2);

	safe_free(WSU(client)->lefttoparse);
	WSU(client)->lefttoparselen = 0;

	ptr = readbuf;
	do {
		n = websocket_handle_packet(client, ptr, length, callback);
		if (n < 0)
			return -1; /* killed -- STOP processing */
		if (n == 0)
		{
			/* Short read. Stop processing for now, but save data for next time */
			safe_free(WSU(client)->lefttoparse);
			WSU(client)->lefttoparse = safe_alloc(length);
			WSU(client)->lefttoparselen = length;
			memcpy(WSU(client)->lefttoparse, ptr, length);
			return 0;
		}
		length -= n;
		ptr += n;
		if (length < 0)
			abort(); /* less than 0 is impossible */
	} while(length > 0);

	return 0;
}

/** WebSocket packet handler.
 * For more information on the format, check out page 28 of RFC6455.
 * @returns The number of bytes processed (the size of the frame)
 *          OR 0 to indicate a possible short read (want more data)
 *          OR -1 in case of an error.
 */
int websocket_handle_packet(Client *client, const char *readbuf, int length, int callback(Client *client, char *buf, int len))
{
	char opcode; /**< Opcode */
	char masked; /**< Masked */
	int len; /**< Length of the packet */
	char maskkey[4]; /**< Key used for masking */
	const char *p;
	int total_packet_size;
	char *payload = NULL;
	static char payloadbuf[MAXLINELENGTH];
	int maskkeylen = 4;

	if (length < 4)
	{
		/* WebSocket packet too short */
		return 0;
	}

	/* fin    = readbuf[0] & 0x80; -- unused */
	opcode = readbuf[0] & 0x7F;
	masked = readbuf[1] & 0x80;
	len    = readbuf[1] & 0x7F;
	p = &readbuf[2]; /* point to next element */

	/* actually 'fin' is unused.. we don't care. */

	/* Masked. According to RFC6455 page 29:
	 * "All frames sent from client to server have this bit set to 1."
	 * But in practice i see that for PONG this may not always be
	 * true, so let's make an exception for that...
	 */
	if (!masked && (opcode != WSOP_PONG))
	{
		dead_socket(client, "WebSocket packet not masked");
		return -1; /* Having the masked bit set is required (RFC6455 p29) */
	}

	if (!masked)
		maskkeylen = 0;

	if (len == 127)
	{
		dead_socket(client, "WebSocket packet with insane size");
		return -1; /* Packets requiring 64bit lengths are not supported. Would be insane. */
	}

	total_packet_size = len + 2 + maskkeylen; /* 2 for header, 4 for mask key, rest for payload */

	/* Early (minimal) length check */
	if (length < total_packet_size)
	{
		/* WebSocket frame too short */
		return 0;
	}

	/* Len=126 is special. It indicates the data length is actually "126 or more" */
	if (len == 126)
	{
		/* Extended payload length (16 bit). For packets of >=126 bytes */
		len = (readbuf[2] << 8) + readbuf[3];
		if (len < 126)
		{
			dead_socket(client, "WebSocket protocol violation (extended payload length too short)");
			return -1; /* This is a violation (not a short read), see page 29 */
		}
		p += 2; /* advance pointer 2 bytes */

		/* Need to check the length again, now it has changed: */
		if (length < len + 4 + maskkeylen)
		{
			/* WebSocket frame too short */
			return 0;
		}
		/* And update the packet size */
		total_packet_size = len + 4 + maskkeylen; /* 4 for header, 4 for mask key, rest for payload */
	}

	if (masked)
	{
		memcpy(maskkey, p, maskkeylen);
		p+= maskkeylen;
	}

	if (len > 0)
	{
		memcpy(payloadbuf, p, len);
		payload = payloadbuf;
	} /* else payload is NULL */

	if (masked && (len > 0))
	{
		/* Unmask this thing (page 33, section 5.3) */
		int n;
		char v;
		char *p;
		for (p = payload, n = 0; n < len; n++)
		{
			v = *p;
			*p++ = v ^ maskkey[n % 4];
		}
	}

	switch(opcode)
	{
		case WSOP_CONTINUATION:
		case WSOP_TEXT:
		case WSOP_BINARY:
			if (len > 0)
			{
				if (!callback(client, payload, len))
					return -1; /* fatal error occured (such as flood kill) */
			}
			return total_packet_size;

		case WSOP_CLOSE:
			dead_socket(client, "Connection closed"); /* TODO: Improve I guess */
			return -1;

		case WSOP_PING:
			if (websocket_handle_packet_ping(client, payload, len) < 0)
				return -1;
			return total_packet_size;

		case WSOP_PONG:
			if (websocket_handle_packet_pong(client, payload, len) < 0)
				return -1;
			return total_packet_size;

		default:
			dead_socket(client, "WebSocket: Unknown opcode");
			return -1;
	}

	return -1; /* NOTREACHED */
}

int websocket_handle_packet_ping(Client *client, const char *buf, int len)
{
	if (len > 500)
	{
		dead_socket(client, "WebSocket: oversized PING request");
		return -1;
	}
	websocket_send_pong(client, buf, len);
	add_fake_lag(client, 1000); /* lag penalty of 1 second */
	return 0;
}

int websocket_handle_packet_pong(Client *client, const char *buf, int len)
{
	/* We only care about pongs for RPC websocket connections.
	 * Also, we don't verify the content, actually,
	 * so don't use this for security like a pingpong cookie.
	 */
	if (IsRPC(client))
	{
		client->local->last_msg_received = TStime();
		ClearPingSent(client);
	}
	return 0;
}

/** Create a simple websocket packet that is ready to be sent.
 * This is the simple version that is used ONLY for WSOP_PONG,
 * as it does not take \r\n into account.
 */
int _websocket_create_packet_simple(int opcode, const char **buf, int *len)
{
	static char sendbuf[8192];

	sendbuf[0] = opcode | 0x80; /* opcode & final */

	if (*len > sizeof(sendbuf) - 8)
		return -1; /* should never happen (safety) */

	if (*len < 126)
	{
		/* Short payload */
		sendbuf[1] = (char)*len;
		memcpy(&sendbuf[2], *buf, *len);
		*buf = sendbuf;
		*len += 2;
	} else {
		/* Long payload */
		sendbuf[1] = 126;
		sendbuf[2] = (char)((*len >> 8) & 0xFF);
		sendbuf[3] = (char)(*len & 0xFF);
		memcpy(&sendbuf[4], *buf, *len);
		*buf = sendbuf;
		*len += 4;
	}
	return 0;
}

/** Create a websocket packet that is ready to be send.
 * This version takes into account stripping off \r and \n,
 * and possibly multi line due to labeled-response.
 * It is used for WSOP_TEXT and WSOP_BINARY.
 * The end result is one or more websocket frames,
 * all in a single packet *buf with size *len.
 *
 * This is the version that uses the specified buffer,
 * it is used from the JSON-RPC code,
 * and indirectly from websocket_create_packet().
 */
int _websocket_create_packet_ex(int opcode, char **buf, int *len, char *sendbuf, size_t sendbufsize)
{
	char *s = *buf; /* points to start of current line */
	char *s2; /* used for searching of end of current line */
	char *lastbyte = *buf + *len - 1; /* points to last byte in *buf that can be safely read */
	int bytes_to_copy;
	char newline;
	char *o = sendbuf; /* points to current byte within 'sendbuf' of output buffer */
	int bytes_in_sendbuf = 0;
	int bytes_single_frame;

	/* Sending 0 bytes makes no sense, and the code below may assume >0, so reject this. */
	if (*len == 0)
		return -1;

	do {
		/* Find next \r or \n */
		for (s2 = s; *s2 && (s2 <= lastbyte); s2++)
		{
			if ((*s2 == '\n') || (*s2 == '\r'))
				break;
		}

		/* Now 's' points to start of line and 's2' points to beyond end of the line
		 * (either at \r, \n or beyond the buffer).
		 */
		bytes_to_copy = s2 - s;

		if (bytes_to_copy < 126)
			bytes_single_frame = 2 + bytes_to_copy;
		else if (bytes_to_copy < 65536)
			bytes_single_frame = 4 + bytes_to_copy;
		else
			bytes_single_frame = 10 + bytes_to_copy;

		if (bytes_in_sendbuf + bytes_single_frame > sendbufsize)
		{
			/* Overflow. This should never happen. */
			unreal_log(ULOG_WARNING, "websocket", "BUG_WEBSOCKET_OVERFLOW", NULL,
			           "[BUG] [websocket] Overflow prevented in _websocket_create_packet(): "
			           "$bytes_in_sendbuf + $bytes_single_frame > $sendbuf_size",
			           log_data_integer("bytes_in_sendbuf", bytes_in_sendbuf),
			           log_data_integer("bytes_single_frame", bytes_single_frame),
			           log_data_integer("sendbuf_size", sendbufsize));
			return -1;
		}

		/* Create the new frame */
		o[0] = opcode | 0x80; /* opcode & final */

		if (bytes_to_copy < 126)
		{
			/* Short payload */
			o[1] = (char)bytes_to_copy;
			memcpy(&o[2], s, bytes_to_copy);
		} else
		if (bytes_to_copy < 65536)
		{
			/* Long payload */
			o[1] = 126;
			o[2] = (char)((bytes_to_copy >> 8) & 0xFF);
			o[3] = (char)(bytes_to_copy & 0xFF);
			memcpy(&o[4], s, bytes_to_copy);
		} else {
			/* Longest payload */
			// XXX: yeah we don't support sending more than 4GB.
			o[1] = 127;
			o[2] = 0;
			o[3] = 0;
			o[4] = 0;
			o[5] = 0;
			o[6] = (char)((bytes_to_copy >> 24) & 0xFF);
			o[7] = (char)((bytes_to_copy >> 16) & 0xFF);
			o[8] = (char)((bytes_to_copy >> 8) & 0xFF);
			o[9] = (char)(bytes_to_copy & 0xFF);
			memcpy(&o[10], s, bytes_to_copy);
		}

		/* Advance destination pointer and counter */
		o += bytes_single_frame;
		bytes_in_sendbuf += bytes_single_frame;

		/* Advance source pointer and skip all trailing \n and \r */
		for (s = s2; *s && (s <= lastbyte) && ((*s == '\n') || (*s == '\r')); s++);
	} while(s <= lastbyte);

	*buf = sendbuf;
	*len = bytes_in_sendbuf;
	return 0;
}

/** Create a websocket packet that is ready to be send.
 * This version takes into account stripping off \r and \n,
 * and possibly multi line due to labeled-response.
 * It is used for WSOP_TEXT and WSOP_BINARY.
 * The end result is one or more websocket frames,
 * all in a single packet *buf with size *len.
 *
 * This is the version that uses a static sendbuf buffer,
 * it is used from IRC websockets.
 */
int _websocket_create_packet(int opcode, char **buf, int *len)
{
	static char sendbuf[WEBSOCKET_SEND_BUFFER_SIZE];
	return _websocket_create_packet_ex(opcode, buf, len, sendbuf, sizeof(sendbuf));
}

/** Create and send a WSOP_PONG frame */
int websocket_send_pong(Client *client, const char *buf, int len)
{
	const char *b = buf;
	int l = len;

	if (_websocket_create_packet_simple(WSOP_PONG, &b, &l) < 0)
		return -1;

	if (DBufLength(&client->local->sendQ) > get_sendq(client))
	{
		dead_socket(client, "Max SendQ exceeded");
		return -1;
	}

	dbuf_put(&client->local->sendQ, b, l);
	send_queued(client);
	return 0;
}

/** UnrealIRCd internals: free WebSocketUser object. */
void websocket_mdata_free(ModData *m)
{
	WebSocketUser *wsu = (WebSocketUser *)m->ptr;
	if (wsu)
	{
		safe_free(wsu->handshake_key);
		safe_free(wsu->lefttoparse);
		safe_free(wsu->sec_websocket_protocol);
		safe_free(m->ptr);
	}
}

/** This only serializes wsu->type atm */
const char *websocket_mdata_serialize(ModData *m)
{
	static char buf[32];
	WebSocketUser *wsu = m->ptr;

	if (!wsu)
		return NULL; /* not set */

	snprintf(buf, sizeof(buf), "%d", wsu->type);
	return buf;
}

/** This only sets wsu->type atm */
void websocket_mdata_unserialize(const char *str, ModData *m)
{
	WebSocketUser *wsu;
	if (m->ptr)
		websocket_mdata_free(m);
	if (BadPtr(str))
		return; /* empty/freed */
	m->ptr = wsu = safe_alloc(sizeof(WebSocketUser));
	wsu->type = atoi(str);
}
