/*
 *
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/param.h>
#include <errno.h>
#include <unistd.h>
#include <netinet/in.h>
#include <linux/tipc.h>
#include <fcntl.h>
#include <math.h>
#include <time.h>

#include "iovtest.h"

void wait_for_server(__u32 name_type, __u32 name_instance, int wait)
{
	struct sockaddr_tipc topsrv;
	struct tipc_subscr subscr;
	struct tipc_event event;

	int sd = socket(AF_TIPC, SOCK_SEQPACKET, 0);

	memset(&topsrv, 0, sizeof(topsrv));
	topsrv.family = AF_TIPC;
	topsrv.addrtype = TIPC_ADDR_NAME;
	topsrv.addr.name.name.type = TIPC_TOP_SRV;
	topsrv.addr.name.name.instance = TIPC_TOP_SRV;

	/* Connect to topology server */

	if (0 > connect(sd, (struct sockaddr *)&topsrv, sizeof(topsrv))) {
		perror("Client: failed to connect to topology server");
		exit(1);
	}

	subscr.seq.type = htonl(name_type);
	subscr.seq.lower = htonl(name_instance);
	subscr.seq.upper = htonl(name_instance);
	subscr.timeout = htonl(wait);
	subscr.filter = htonl(TIPC_SUB_SERVICE);

	if (send(sd, &subscr, sizeof(subscr), 0) != sizeof(subscr)) {
		perror("Client: failed to send subscription");
		exit(1);
	}
	/* Now wait for the subscription to fire */

	if (recv(sd, &event, sizeof(event), 0) != sizeof(event)) {
		perror("Client: failed to receive event");
		exit(1);
	}
	if (event.event != htonl(TIPC_PUBLISHED)) {
		printf("Client: server {%u,%u} not published within %u [s]\n",
		       name_type, name_instance, wait/1000);
		exit(1);
	}

	close(sd);
}

static inline void diep(const char *msg)
{
	perror(msg);
	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 struct msghdr *create_msg(int msgsize, int numiov) {

	struct msghdr *m;
	struct iovec *iov;
	int p, i, iovlen, total = 0;
	uint16_t cksum;
	char *data;
	char *iovp;

	iov = malloc(sizeof(struct iovec)*numiov);
	if (!iov)
		diep("malloc()");
	msgsize -= sizeof(uint16_t);
	data = malloc(msgsize);
	iovp = data;
	for (;iovp < data+msgsize; iovp++)
		*iovp = rand()%255;
	iov[0].iov_base = malloc(sizeof(uint16_t));	//Checksum in first iov
	iov[0].iov_len = sizeof(uint16_t);
	iovlen = msgsize/(numiov-1);
	for (i=1; i < numiov; i++) {
		iov[i].iov_base = data+total;
		iov[i].iov_len = iovlen;
		iovp += iovlen;
		total += iovlen;
	}
	printf("Allocated %d byte message using %d iovecs\n",
		total, numiov);
	cksum = cksum16(data,total);
	printf("Checksum is 0x%x\n", cksum);
	memcpy(iov[0].iov_base, &cksum, sizeof(uint16_t));
	m = malloc(sizeof(struct msghdr));
	if (!m)
		diep("malloc()");
	memset(m, 0, sizeof(*m));

	m->msg_iov = iov;
	m->msg_iovlen = numiov;

	return m;
}


void rdm_send(int msgsize, int numiov)
{
	int sd = socket(AF_TIPC, SOCK_RDM, 0);
	struct msghdr *m;
	struct sockaddr_tipc sa;

	sa.family = AF_TIPC;
	sa.addrtype = TIPC_ADDR_NAME;
	sa.addr.name.name.type = TEST_SERVER;
	sa.addr.name.name.instance = TEST_RDM;
	sa.addr.name.domain = 0;
	if (msgsize > TIPC_MAX_USER_MSG_SIZE) {
		msgsize = TIPC_MAX_USER_MSG_SIZE;
		printf("RDM: Message size clamped to %d\n", TIPC_MAX_USER_MSG_SIZE);
	}
	m = create_msg(msgsize, numiov);

	m->msg_name = &sa;
	m->msg_namelen = sizeof(sa);
	if (sendmsg(sd, m, 0) <= 0)
		diep("RDM: sendmsg()");
	close(sd);
}

void seqpacket_send(int msgsize, int numiov)
{
	int sd = socket(AF_TIPC, SOCK_SEQPACKET, 0);
	struct msghdr *m;
	struct sockaddr_tipc sa;

	sa.family = AF_TIPC;
	sa.addrtype = TIPC_ADDR_NAME;
	sa.addr.name.name.type = TEST_SERVER;
	sa.addr.name.name.instance = TEST_SEQPACKET;
	sa.addr.name.domain = 0;

	if (connect(sd, (struct sockaddr*)&sa, sizeof(sa)) != 0)
		diep("SEQPACKET: connect()");

	if (msgsize > TIPC_MAX_USER_MSG_SIZE) {
		msgsize = TIPC_MAX_USER_MSG_SIZE;
		printf("SEQPACKET: Message size clamped to %d\n", TIPC_MAX_USER_MSG_SIZE);
	}
	m = create_msg(msgsize, numiov);
	if (sendmsg(sd, m, 0) <= 0)
		diep("SEQPACKET: sendmsg()");
	close(sd);
}

void stream_send(int msgsize, int numiov, int nonblock)
{
	int sd = socket(AF_TIPC, SOCK_STREAM, 0);
	struct msghdr *m;
	struct sockaddr_tipc sa;
	int res;

	sa.family = AF_TIPC;
	sa.addrtype = TIPC_ADDR_NAME;
	sa.addr.name.name.type = TEST_SERVER;
	sa.addr.name.name.instance = TEST_STREAM;
	sa.addr.name.domain = 0;

	if (connect(sd, (struct sockaddr*)&sa, sizeof(sa)) != 0)
		diep("STREAM: connect() failed");

	m = create_msg(msgsize, numiov);
	if (nonblock)
		 fcntl(sd, F_SETFL, fcntl(sd, F_GETFL) | O_NONBLOCK);
	res = sendmsg(sd, m, 0);
	printf("sent %d bytes\n", res);
	if (res <= 0)
		diep("STREAM: sendmsg()");
	close(sd);
}

int main(int argc, char *argv[], char *envp[])
{
	int numiov = 1;
	int msgsize = 1024;
	int nonblock = 0; /* For stream only, default to nonblocking */
	int c;
	int tests = TEST_RDM | TEST_SEQPACKET | TEST_STREAM;

	printf("TIPC iovec test client started\n");
	srand(time(NULL));
	while ((c = getopt(argc, argv, "bi:m:rqs")) != -1)
	{
		switch(c) {
		case 'b':
			nonblock = 1;
		break;
		case 'i':
			numiov = atoi(optarg);
			if (numiov < UIO_MAXIOV && numiov > 1)
				break;
			printf("Requested number of IOV's must be > 1 and cannot exceed UIO_MAXIOV (%d)\n",
				UIO_MAXIOV);
			numiov = UIO_MAXIOV-1;
		break;
		case 'm':
			msgsize = 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("Message size= %d\n", msgsize);
	printf("%d iov's\n", numiov);

	if (tests & TEST_RDM) {
		wait_for_server(TEST_SERVER, TEST_RDM, 10000);
		rdm_send(msgsize, numiov);
	}
	if (tests & TEST_SEQPACKET) {
		wait_for_server(TEST_SERVER, TEST_SEQPACKET, 10000);
		seqpacket_send(msgsize, numiov);
	}
	if (tests & TEST_STREAM) {
		wait_for_server(TEST_SERVER, TEST_STREAM, 10000);
		if (nonblock)
			printf("Sending socket will be set nonblocking\n");
		stream_send(msgsize, numiov, nonblock);
	}
	printf("\nTIPC iovec test client finished\n");
	exit(0);
}
