/*
 * qstat 2.14
 * by Steve Jankowski
 *
 * DirtyBomb query protocol
 * Copyright 2012 Steven Hartland
 *
 * Licensed under the Artistic License, see LICENSE.txt for license terms
 *
 */

#include <sys/types.h>
#ifndef _WIN32
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#else
#include <winsock.h>
#endif

#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>

#include "debug.h"
#include "utils.h"
#include "qstat.h"
#include "md5.h"
#include "packet_manip.h"


int unpack_msgpack(struct qserver *server, unsigned char *, unsigned char *);

query_status_t
send_dirtybomb_request_packet(struct qserver *server)
{
	char buf[1024], *password, chunks, len;

	password = get_param_value(server, "password", "");
	len = strlen(password);
	chunks = 0x01;
	if (server->flags & TF_RULES_QUERY) {
		chunks |= 0x02;
	}
	if (server->flags & TF_PLAYER_QUERY) {
		chunks |= 0x04; // Player
		chunks |= 0x08; // Team - Currently not supported
	}
	sprintf(buf, "%c%c%s%c", 0x01, len, password, chunks);

	server->saved_data.pkt_max = -1;

	return send_packet(server, buf, len + 3);
}

query_status_t
deal_with_dirtybomb_packet(struct qserver *server, char *rawpkt, int pktlen)
{
	unsigned char *s, *l, pkt_id, pkt_max;

	debug(2, "processing...");
	
	if (8 > pktlen) {
		// not valid response
		malformed_packet(server, "packet too small");
		return PKT_ERROR;
	}

	if (rawpkt[4] != 0x01) {
		// not a known response
		malformed_packet(server, "unknown packet type 0x%02hhx", rawpkt[4]);
		return PKT_ERROR;
	}

	// Response ID - long
	// Response Type - byte
	// Current Packet Number - byte
	pkt_id = rawpkt[5];

	// Last Packet Number - byte
	pkt_max = rawpkt[6];

	// Payload Blob - msgpack map
	s = (unsigned char*)rawpkt + 7;
	
	if (!server->combined) {
		int pkt_cnt = packet_count(server);

		server->retry1 = n_retries;
		if (0 == server->n_requests) {
			server->ping_total = time_delta( &packet_recv_time, &server->packet_time1 );
			server->n_requests++;
		}

		if (pkt_cnt < pkt_max) {
			// We're expecting more to come
			debug( 5, "fragment recieved..." );

			if (!add_packet(server, 0, pkt_id, pkt_max, pktlen, rawpkt, 1)) {
				// fatal error e.g. out of memory
				return MEM_ERROR;
			}

			// combine_packets will call us recursively
			return combine_packets(server);
		}
	}

	// Correct ping
	// Not quite right but gives a good estimate
	server->ping_total = (server->ping_total * server->n_requests) / 2;

	debug(3, "processing response...");

	gettimeofday(&server->packet_time1, NULL);

	l = (unsigned char *)rawpkt + pktlen - 1;
	if (!unpack_msgpack(server, s, l)) {
		return PKT_ERROR;
	}

	return DONE_FORCE;
}

int
unpack_msgpack_pos_fixnum(struct qserver *server, uint8_t *val, unsigned char **datap, unsigned char *last, int raw)
{


	if (!raw) {
		if (*datap > last) {
			malformed_packet(server, "packet too small");
			return 0;
		}

		if ((**datap & 0x80) != 0) {
			malformed_packet(server, "invalid positive fixnum type 0x%02hhx", **datap);
			return 0;
		}

	}

	*val = **datap;
	(*datap)++;

	return 1;
}

int
unpack_msgpack_uint16(struct qserver *server, uint16_t *val, unsigned char **datap, unsigned char *last, int raw)
{


	if (!raw) {
		if (*datap > last) {
			malformed_packet(server, "packet too small");
			return 0;
		}

		if (**datap != 0xcd) {
			malformed_packet(server, "invalid uint16 type 0x%02hhx", **datap);
			return 0;
		}

		(*datap)++;
	}

	if (*datap + 2 > last) {
		malformed_packet(server, "packet too small");
		return 0;
	}
	*val = (uint16_t)(*datap)[1] | ((uint16_t)(*datap)[0] << 8);
	*datap += 2;

	return 1;
}

int
unpack_msgpack_uint32(struct qserver *server, uint32_t *val, unsigned char **datap, unsigned char *last, int raw)
{


	if (!raw) {
		if (*datap > last) {
			malformed_packet(server, "packet too small");
			return 0;
		}

		if (**datap != 0xce) {
			malformed_packet(server, "invalid uint32 type 0x%02hhx", **datap);
			return 0;
		}

		(*datap)++;
	}

	if (*datap + 4 > last) {
		malformed_packet(server, "packet too small");
		return 0;
	}

	*val = (uint32_t)(*datap)[3] |
		((uint32_t)(*datap)[2] << 8) |
		((uint32_t)(*datap)[1] << 16) |
		((uint32_t)(*datap)[0] << 24);
	*datap += 2;

	return 1;
}

int
unpack_msgpack_raw_len(struct qserver *server, char **valp, unsigned char **datap, unsigned char *last, uint32_t len)
{
	char *data;

	debug(4, "raw_len: 0x%lu\n", (unsigned long)len);

    if (*datap + len > last) {
		malformed_packet(server, "packet too small");
        return 0;
	}

	if (NULL == (data = malloc(len + 1))) {
		malformed_packet(server, "out of memory");
		return 0;
	}

	memcpy(data, *datap, len);
	data[len] = '\0';

	*valp = data;
	(*datap) += len;

	return 1;
}

int
unpack_msgpack_raw(struct qserver *server, char **valp, unsigned char **datap, unsigned char *last)
{
	unsigned char type, len;

    if (*datap + 1 > last) {
		malformed_packet(server, "packet too small");
        return 0;
	}

	type = (**datap & 0xa0);
	len = (**datap & 0x1f);
	if (0xa0 != type) {
		malformed_packet(server, "invalid raw type 0x%02hhx", **datap);
		return 0;
	}
	(*datap)++;

	return unpack_msgpack_raw_len(server, valp, datap, last, (uint32_t)len);
}

int
unpack_msgpack_raw16(struct qserver *server, char **valp, unsigned char **datap, unsigned char *last)
{
	uint16_t len;
    if (*datap + 3 > last) {
		malformed_packet(server, "packet too small");
        return 0;
	}
	(*datap)++;

	if (!unpack_msgpack_uint16(server, &len, datap, last, 1))
		return 0;

	return unpack_msgpack_raw_len(server, valp, datap, last, (uint32_t)len);
}

int
unpack_msgpack_raw32(struct qserver *server, char **valp, unsigned char **datap, unsigned char *last)
{
	uint32_t len;

    if (*datap + 5 > last) {
		malformed_packet(server, "packet too small");
        return 0;
	}
	(*datap)++;

	if (!unpack_msgpack_uint32(server, &len, datap, last, 1))
		return 0;

	return unpack_msgpack_raw_len(server, valp, datap, last, len);
}

int
unpack_msgpack_string(struct qserver *server, char **valp, unsigned char **datap, unsigned char *last)
{
	unsigned char type = **datap;

	debug(4, "string: 0x%02hhx\n", type);

	if (0xa0 == (type & 0xa0))
		return unpack_msgpack_raw(server, valp, datap, last);
	else if (type == 0xda)
		return unpack_msgpack_raw16(server, valp, datap, last);
	else if (type == 0xdb)
		return unpack_msgpack_raw32(server, valp, datap, last);
	else
		malformed_packet(server, "invalid string type 0x%02hhx", type);

	return 0;
}

int
unpack_msgpack(struct qserver *server, unsigned char *s, unsigned char *l)
{
	uint16_t elements;
	unsigned char type, type_len;
	char *var, *str_val;
	uint8_t uint8_val;

	type_len = *s;
	s++;
	debug(3, "type/len: 0x%02hhx\n", type_len);
	if (0x80 == (type_len & 0x80)) {
		type = (type_len & 0x80);
		elements = (type_len & 0x0f);
		debug(3, "map type: 0x%02hhx, elements: %d\n", type, elements);

	} else if (type_len == 0xde) {
		// map 16
		if (!unpack_msgpack_uint16(server, &elements, &s, l, 1))
			return 0;
	} else {
		// There is a map 32 but we don't support it
		malformed_packet(server, "invalid map type 0x%02hhx", type_len);
		return 0;
	}

	while(elements) {
		type = *s;
		if (!unpack_msgpack_string(server, &var, &s, l))
			return 0;

		debug(4, "Map[%s]\n", var);

		if (0 == strcmp(var, "SN")) {
			// Server Name
			if (!unpack_msgpack_string(server, &str_val, &s, l))
				return 0;
			server->server_name = str_val;

		} else if (0 == strcmp(var, "MAP")) {
			// Map
			if (!unpack_msgpack_string(server, &str_val, &s, l))
				return 0;
			server->map_name = str_val;

		} else if (0 == strcmp(var, "MP")) {
			// Max Players
			if (!unpack_msgpack_pos_fixnum(server, &uint8_val, &s, l, 0))
				return 0;
			server->max_players = uint8_val;

		} else if (0 == strcmp(var, "NP")) {
			// Number of Players
			if (!unpack_msgpack_pos_fixnum(server, &uint8_val, &s, l, 0))
				return 0;
			server->num_players = uint8_val;

		} else if (0 == strcmp(var, "RL")) {
			// Gametype
			if (!unpack_msgpack_string(server, &str_val, &s, l))
				return 0;

			server->game = str_val;
			add_rule(server, "gametype", str_val, NO_FLAGS);
		}

		free(var);
		elements--;
	}

	return 1;
}
