#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/timeb.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <time.h>


#define MAXMSG 256
#define PORT 5521

#define TIMETOLIVE 600 // seconds

typedef unsigned char uchar;
char logging = 0;

unsigned char mesg[MAXMSG];
int lastsize;
int network;
char masterhostname[256];

#define PKT_REGISTER 16
#define PKT_ACK 24
#define PKT_INFO 40
#define PKT_QUERY 32

#define MAXMATCHES 16


struct registration {
	uchar id;
	uchar unique[4];
	uchar password[4];
	uchar version[4];
	uchar name[16];
	uchar status;
};
struct query {
	uchar id;
	uchar password[4];
	uchar version[4];
};

struct gamehost {
	struct gamehost *next, *prev;
	uchar machine[4];
	uchar port[2];
	struct registration reg;
	long timeout;
};


struct gamehost *freehosts = 0, *activehosts = 0;


int udpsocket, myport;
struct sockaddr_in myname = {0}, mastername = {0};
socklen_t senderlength;
struct sockaddr_in sender = {0};

long longtime(void) {
	struct timeb tb;
	ftime(&tb);
	return tb.time;
}

char* timestr() {
	static char timestring[80];

	time_t t;
	int l;
	time(&t);
	strcpy(timestring, ctime(&t));
	l = strlen(timestring);
	if (l && timestring[l - 1] == '\n') timestring[l - 1] = 0;
	return timestring;
}


int putmsg(struct sockaddr_in* toname, unsigned char* msg, int len) {
	int status;

	status = sendto(udpsocket, msg, len, 0, (struct sockaddr*) toname, sizeof(struct sockaddr_in));
	return status;
}
void ack() {
	uchar copy[256];

	*copy = PKT_ACK;
	memmove(copy + 1, mesg, lastsize);
	putmsg(&sender, copy, lastsize + 1);
}

int getmsg(int seconds) {
	int size;

	lastsize = -1;
	memset(&sender, 0, sizeof(sender));
	senderlength = sizeof(sender);
	if (seconds) {
		struct timeval timeout;
		fd_set readfds;
		int res;

		timeout.tv_sec = seconds;
		timeout.tv_usec = 0;
		FD_ZERO(&readfds);
		FD_SET(udpsocket, &readfds);
		res = select(udpsocket + 1, &readfds, 0, 0, &timeout);
		if (res <= 0) return -1;
	}
	lastsize = size = recvfrom(udpsocket, mesg, MAXMSG, 0, (struct sockaddr*) &sender, &senderlength);
	return size;
}

long longind(unsigned char* p) {
	return (p[0] << 24L) | (p[1] << 16L) | (p[2] << 8) | p[3];
}
short shortind(unsigned char* p) {
	return (p[0] << 8L) | p[1];
}


void openport(int portwant) {
	int status;

	myport = portwant;
	udpsocket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (udpsocket == -1) {
		perror("socket()");
		exit(1);
	}
	memset(&myname, 0, sizeof(myname));
	myname.sin_family = AF_INET;
	myname.sin_addr.s_addr = htonl(INADDR_ANY);
	myname.sin_port = htons(myport);

	status = bind(udpsocket, (struct sockaddr*) &myname, sizeof(myname));
	if (status == -1) {
		perror("bind()");
		exit(1);
	}
}

#define PERBLOCK 512

struct gamehost* newhost() {
	struct gamehost *h, *block;
	int i;

	if (!freehosts) {
		block = malloc(sizeof(struct gamehost) * PERBLOCK);
		if (!block) return 0;
		freehosts = block;
		i = PERBLOCK - 1;
		while (i--) {
			block->next = block + 1;
			++block;
		}
		block->next = 0;
	}
	h = freehosts;
	freehosts = freehosts->next;
	memset(h, 0, sizeof(struct gamehost));
	return h;
}

void freehost(struct gamehost* h) {
	h->next = freehosts;
	freehosts = h;
}

struct gamehost* findmatch(struct registration* key) {
	struct gamehost* h;
	h = activehosts;
	while (h) {
		if (!memcmp(&h->reg, key, sizeof(struct registration) - 1)) return h;
		h = h->next;
	}
	return 0;
}


void insert(struct gamehost* h) {
	if (activehosts) {
		h->next = activehosts;
		h->prev = activehosts->prev;
		activehosts->prev = h;
		activehosts = h;
	} else {
		h->next = h->prev = 0;
		activehosts = h;
	}
}
void delete(struct gamehost* h) {
	if (h->prev)
		h->prev->next = h->next;
	else
		activehosts = h->next;
	if (h->next) h->next->prev = h->prev;
	freehost(h);
}
void doreg() {
	struct registration* new;
	struct gamehost* match;
	long now;

	new = (struct registration*) mesg;
	match = findmatch(new);
	if (logging) {
		unsigned addr = ntohl(sender.sin_addr.s_addr);
		unsigned short port = ntohs(sender.sin_port);
		printf(
			"reg  :%s:%d.%d.%d.%d:%d  %c%lx '%s'\n",
			timestr(),
			(addr >> 24) & 255,
			(addr >> 16) & 255,
			(addr >> 8) & 255,
			addr & 255,
			port,
			new->status ? '+' : '-',
			(long) match,
			new->name);
		fflush(stdout);
	}
	if (!match && !new->status) {
		ack();
		return;
	}
	if (match && new->status) {
		ack();
		return;
	}
	if (!match && new->status) {
		match = newhost();
		if (!match) return; // No memory, what can we do?
		memmove(match->machine, &sender.sin_addr.s_addr, 4);
		memmove(match->port, &sender.sin_port, 2);
		match->reg = *new;
		now = longtime();
		match->timeout = now + TIMETOLIVE;
		ack();
		insert(match);
		return;
	} else // match && !new->status
	{
		delete (match);
		ack();
		return;
	}
}

void doquery() {
	uchar* password;
	uchar* version;
	struct gamehost* h;
	uchar response[2048], *rput, *countersave;
	int counter;

	if (logging) {
		unsigned addr = ntohl(sender.sin_addr.s_addr);
		unsigned short port = ntohs(sender.sin_port);
		printf("query:%s:%d.%d.%d.%d:%d\n", timestr(), (addr >> 24) & 255, (addr >> 16) & 255, (addr >> 8) & 255, addr & 255, port);
		fflush(stdout);
	}
	password = mesg + 1;
	version = mesg + 5;
	h = activehosts;
	rput = response;
	*rput++ = PKT_INFO;
	memmove(rput, password, 4);
	rput += 4;
	memmove(rput, version, 4);
	rput += 4;
	countersave = rput;
	*rput++ = 0;
	*rput++ = 0;
	counter = 0;

	while (h) {
		if (!memcmp(password, h->reg.password, 4) && !memcmp(version, h->reg.version, 4) && counter < MAXMATCHES) {
			++counter;
			memmove(rput, h->reg.unique, 4);
			rput += 4;
			memmove(rput, h->machine, 4);
			rput += 4;
			memmove(rput, h->port, 2);
			rput += 2;
			memmove(rput, h->reg.name, sizeof(h->reg.name));
			rput += sizeof(h->reg.name);
		}
		h = h->next;
	}
	*countersave++ = counter >> 8;
	*countersave++ = counter;
	putmsg(&sender, response, rput - response);
}

void purge(long cutoff) {
	struct gamehost *h, *h2;
	h = activehosts;
	while (h) {
		h2 = h;
		h = h->next;
		if (cutoff - h2->timeout > 0) { delete (h2); }
	}
}


int main(int argc, char** argv) {
	int i;
	int want;
	int size;
	long purgetime;
	long now;

	want = PORT;
	if (argc > 1) {
		for (i = 1; i < argc; ++i)
			if (!strncmp(argv[i], "-p", 2)) {
				if (strlen(argv[i]) > 2)
					want = atoi(argv[i] + 2);
				else if (i + 1 < argc)
					want = atoi(argv[i + 1]);
			}
	}
	freehosts = 0;
	openport(want);
	purgetime = longtime() + TIMETOLIVE;
	for (;;) {
		size = getmsg(10);
		if (size >= 1) switch (*mesg) {
			case PKT_REGISTER:
				if (size < sizeof(struct registration)) continue;
				doreg();
				break;
			case PKT_QUERY:
				if (size < sizeof(struct query)) continue;
				doquery();
				break;
			}
		now = longtime();
		if (now - purgetime > 0) // avoid year 203x bug...
		{
			purge(purgetime);
			purgetime += TIMETOLIVE;
		}
	}
}
