#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/param.h>
#include <sys/poll.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <linux/tipc.h>
#include <stdint.h>
#include "iovtest.h"

pid_t server[3] = {0};
int bufsz = 500000;
void sigcatcher(int sig);

static inline void diep(const char *msg)
{
	perror(msg);
	kill(getppid(), SIGKILL);
	exit(-1);
}

uint16_t cksum16(void *addr, uint32_t length)
{
	uint32_t sum = 0;

	/*Sum all 16 bit words*/
	while (length > 1)
	{
		sum = sum + *((uint16_t*) addr++);
		length = length - 2;
	}
	/*Add left over byte if any*/
	if (length > 0)
		sum = sum + *((uint8_t*) addr);
	/*Fold 32bit sum to 16bits*/	
	while (sum >> 16)
		sum = (sum & 0xFFFF) + (sum >> 16);

	return (uint16_t)(~sum);
}
static void serve_rdm()
{
	struct sockaddr_tipc sa;
	int sd = socket(AF_TIPC, SOCK_RDM, 0);
	char buf[TIPC_MAX_USER_MSG_SIZE];
	int n;
	uint16_t cksum;
	uint16_t *rsum;

	sa.family = AF_TIPC;
	sa.addrtype = TIPC_ADDR_NAMESEQ;
	sa.addr.nameseq.type = TEST_SERVER;
	sa.addr.nameseq.lower = TEST_RDM;
	sa.addr.nameseq.upper = TEST_RDM;
	sa.scope = TIPC_ZONE_SCOPE;
	if (0 != bind(sd, (struct sockaddr*)&sa, sizeof(sa)))
		diep("bind()");
	while (1) {
		if((n = recv(sd, buf, TIPC_MAX_USER_MSG_SIZE, 0)) <=0)
			diep("recv()");
		printf("RDM: got message of %d bytes\n", n);
		rsum = (uint16_t*)buf;
		cksum = cksum16((buf+2), n-2);
		printf("Checksum = 0x%x\n",cksum);
		if (cksum != *rsum) {
			printf("Checksum error: is  0x%x should be 0x%x\n",
			     cksum, *rsum);
			diep("Checksum");
		}

	}
}

static void serve_seqpacket()
{
	struct sockaddr_tipc sa;
	int sd = socket(AF_TIPC, SOCK_SEQPACKET, 0);
	int peer;
	char buf[TIPC_MAX_USER_MSG_SIZE];
	int n;
	uint16_t cksum;
	uint16_t *rsum;

	sa.family = AF_TIPC;
	sa.addrtype = TIPC_ADDR_NAMESEQ;
	sa.addr.nameseq.type = TEST_SERVER;
	sa.addr.nameseq.lower = TEST_SEQPACKET;
	sa.addr.nameseq.upper = TEST_SEQPACKET;
	sa.scope = TIPC_ZONE_SCOPE;

	if (0 != bind(sd, (struct sockaddr*)&sa, sizeof(sa)))
		diep("bind()");
	if (0 != listen(sd, 0))
		diep("listen()");
	while (1) {
		if ((peer = accept(sd, 0, 0)) < 0)
			diep("accept()");
		if((n = recv(peer, buf, TIPC_MAX_USER_MSG_SIZE, 0)) <=0)
			diep("recv()");
		printf("SEQPACKET: got message of %d bytes\n", n);
		rsum = (uint16_t*)buf;
		cksum = cksum16((buf+2), n-2);
		printf("Checksum = 0x%x\n",cksum);
		if (cksum != *rsum) {
			printf("Checksum error: is  0x%x should be 0x%x\n",
			     cksum, *rsum);
			diep("Checksum");
		}

		close(peer);
	}
}

static void serve_stream()
{
	struct sockaddr_tipc sa;
	int sd = socket(AF_TIPC, SOCK_STREAM, 0);
	int peer;
	char *buf;
	int n, total=0;
	uint16_t *rsum, cksum;
	struct timeval tmo = {0};

	sa.family = AF_TIPC;
	sa.addrtype = TIPC_ADDR_NAMESEQ;
	sa.addr.nameseq.type = TEST_SERVER;
	sa.addr.nameseq.lower = TEST_STREAM;
	sa.addr.nameseq.upper = TEST_STREAM;
	sa.scope = TIPC_ZONE_SCOPE;
	buf = malloc(bufsz);
	if (!buf)
		diep("malloc()");
	if (0 != bind(sd, (struct sockaddr*)&sa, sizeof(sa)))
		diep("bind()");
	if (0 != listen(sd, 0))
		diep("listen()");
again:
	if ((peer = accept(sd, 0, 0)) < 0)
		diep("accept()");
	tmo.tv_sec=5;
	printf("STREAM: reading data for %d seconds\n", (int)tmo.tv_sec);
	if (0 != setsockopt(peer, SOL_SOCKET, SO_RCVTIMEO, (char*)&tmo, sizeof(tmo)))
		diep("setsockopt()");
	memset(buf,0,bufsz);
	total = 0;
	while (1) {
		if ((n = recv(peer, buf+total, bufsz-total, 0)) <= 0) {
			if (errno == EAGAIN) {
				printf("STREAM: failed to read all test data within timeout\n");
				goto done;
			} else if (errno == ECONNRESET) {
				printf("STREAM: client closed the connection\n");
				goto done;
			}
			diep("recv()");
		}
		total += n;
	}
		//diep("STREAM: input data buffer is too big.\n");
done:
	printf("STREAM: total %d bytes\n", total);
	rsum = (uint16_t*)buf;
	cksum = cksum16((buf+2), total-2);
	printf("Checksum = 0x%x\n",cksum);
	if (cksum != *rsum) {
		printf("Checksum error: is  0x%x should be 0x%x\n",
		     cksum, *rsum);
		diep("Checksum");
	}
	close(peer);
	goto again;
	free(buf);
}



int main(int argc, char* argv[], char* envp[])
{
	int c;
	int tests = TEST_RDM | TEST_SEQPACKET | TEST_STREAM;
	while ((c = getopt(argc, argv, "rqsz:")) != -1)
	{
		switch(c) {
		case 'z':
			bufsz=atoi(optarg);
		break;
		case 'r':
			tests = TEST_RDM;
		break;
		case 'q':
			tests = TEST_SEQPACKET;
		break;
		case 's':
			tests = TEST_STREAM;
		break;
		default:
			exit(0);
		break;
		}

	}
	printf("TIPC iovec test serer started (%d b buffer)\n", bufsz);
	if (tests & TEST_RDM) {
		if (!(server[0] = fork())) {
			printf("RDM test..\n");
			serve_rdm();
		}
	}
	if (tests & TEST_SEQPACKET) {
		if (!(server[1] = fork())) {
			printf("SEQPACKET test..\n");
			serve_seqpacket();
		}
	}
	if (tests & TEST_STREAM) {
		if (!(server[2] = fork())) {
			printf("STREAM test..\n");
			serve_stream();
		}
	}
	signal(SIGINT, sigcatcher);
	signal(SIGHUP, sigcatcher);
	signal(SIGTERM, sigcatcher);
	signal(SIGKILL, sigcatcher);
	while (1) {
		sleep(1);
	}
	exit(0);
}

void sigcatcher(int sig)
{
	int i;
	switch (sig)
	{
	case SIGINT:
	case SIGTERM:
	case SIGKILL:
	case SIGHUP:
		for (i=0; i < 3; i++) {
			if (server[i] > 0)
				printf("Killing test server %d\n", i);
				kill(server[i], SIGTERM);
		}
		printf("Iovec server killed\n");
		fflush(stdout);
		exit(0);
	break;
	}
}
