/* SCTP kernel reference Implementation
 * (C) Copyright Fujitsu Ltd. 2008, 2009
 *
 * The SCTP reference implementation is free software;
 * you can redistribute it and/or modify it under the terms of
 * the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * The SCTP reference implementation is distributed in the hope that it
 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
 *                 ************************
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with GNU CC; see the file COPYING.  If not, write to
 * the Free Software Foundation, 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * Please send any bug reports or fixes you make to the
 * email address(es):
 *    lksctp developers <linux-sctp@vger.kernel.org>
 *
 * Or submit a bug report through the following website:
 *    https://github.com/sctp/lksctp-tools/
 *
 * Any bugs reported to us we will try to fix... any fixes shared will
 * be incorporated into the next SCTP release.
 *
 * Written or modified by:
 *    Wei Yongjun <yjwei@cn.fujitsu.com>
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netinet/sctp.h>
#include <signal.h>
#include <sys/time.h>
#include <unistd.h>
#include <errno.h>
#include <netdb.h>
#include <string.h>

#define DEFAULT_SEC	0
#define DEFAULT_USEC	5000

#define REALLY_BIG 65536

#define SERVER 		0
#define CLIENT 		1
#define NOT_DEFINED	666

#define DEBUG_NONE	0
#define DEBUG_MIN	1
#define DEBUG_MAX	2

#define ORDER_PATTERN_UNORDERED   0
#define ORDER_PATTERN_ORDERED     1
#define ORDER_PATTERN_ALTERNATE   2
#define ORDER_PATTERN_RANDOM      3

#define STREAM_PATTERN_SEQUENTIAL 0
#define STREAM_PATTERN_RANDOM     1

#define MAX_BIND_RETRYS 10
#define BIG_REPEAT	1000000
#define REPEAT 		10

#define DEFAULT_MAX_WINDOW 32768
#define DEFAULT_MIN_WINDOW 1500

#define MSG_CNT		10

#define DEBUG_PRINT(level, print_this...)	\
{						\
	if (debug_level >= level) { 		\
		fprintf(stdout, print_this); 	\
		fflush(stdout); 		\
	}					\
} /* DEBUG_PRINT */

char *local_host = NULL;
int local_port = 0;
char *remote_host = NULL;
int remote_port = 0;
struct sockaddr_storage s_rem, s_loc;
int r_len, l_len;
int size_arg = 0;
int debug_level = DEBUG_NONE;
int order_pattern = ORDER_PATTERN_UNORDERED;
int order_state = 0;
int stream_pattern = STREAM_PATTERN_SEQUENTIAL;
int stream_state = 0;
int repeat = REPEAT;
int repeat_count = 0;
int max_msgsize = DEFAULT_MAX_WINDOW;
int msg_cnt = MSG_CNT;
int drain = 0;
int max_stream = 0;
int gsk = -1;
int period = 1;
char *statusfile = NULL;

void printstatus(int sk);
void sighandler(int signo);
void settimerhandle(void);
void usage(char *argv0);
void start_test(int role);

unsigned char msg[] = "012345678901234567890123456789012345678901234567890";

/* Convenience structure to determine space needed for cmsg. */
typedef union {
	struct sctp_initmsg init;
	struct sctp_sndrcvinfo sndrcvinfo;
} _sctp_cmsg_data_t;

int main(int argc, char *argv[]) {
	int c, role = NOT_DEFINED;
	char *interface = NULL;
	struct sockaddr_in *t_addr;
	struct sockaddr_in6 *t_addr6;

	/* Parse the arguments.  */
	while ((c = getopt(argc, argv, ":H:L:P:h:p:c:d:lm:sx:X:o:M:Di:I:f:")) >= 0 ) {
		switch (c) {
		case 'H':
			local_host = optarg;
			break;
		case 'P':
			local_port = atoi(optarg);
			break;
		case 'h':
			remote_host = optarg;
			break;
		case 'p':
			remote_port = atoi(optarg);
			break;
		case 'l':
			if (role != NOT_DEFINED) {
				printf("%s: only -s or -l\n", argv[0]);
				usage(argv[0]);
				exit(1);
			}
			role = SERVER;
			break;
		case 's':
			if (role != NOT_DEFINED) {
				printf("%s: only -s or -l\n", argv[0]);
				usage(argv[0]);
				exit(1);
			}
			role = CLIENT;
			break;
		case 'D':
			drain = 1;
			break;
		case 'd':
			debug_level = atoi(optarg);
			if (debug_level < DEBUG_NONE
			    || debug_level > DEBUG_MAX) {
				usage(argv[0]);
				exit(1);
			}
			break;
		case 'I':
			period = atoi(optarg);
			if (period < 0) {
				usage(argv[0]);
				exit(1);
			}
			break;
		case 'x':
			repeat = atoi(optarg);
			if (!repeat) {
				repeat = BIG_REPEAT;
			}
			break;
		case 'X':
			msg_cnt = atoi(optarg);
			if ((msg_cnt <= 0) || (msg_cnt > MSG_CNT)) {
				usage(argv[0]);
				exit(1);
			}
			break;
		case 'c':
			size_arg = atoi(optarg);
			if (size_arg < 0) {
				usage(argv[0]);
				exit(1);
			}

			break;
		case 'o':
			order_pattern = atoi(optarg);
			if (order_pattern <  ORDER_PATTERN_UNORDERED
			    || order_pattern  > ORDER_PATTERN_RANDOM ) {
				usage(argv[0]);
				exit(1);
			}
			break;
		case 'M':
			max_stream = atoi(optarg);
			if (max_stream <  0
			    || max_stream >= (1<<16)) {
				usage(argv[0]);
				exit(1);
			}
			break;
		case 'm':
			max_msgsize = atoi(optarg);
			break;
		case 'i':
			interface = optarg;
			break;
		case 'f':
			statusfile = optarg;
			break;
		case '?':
		default:
			usage(argv[0]);
			exit(0);
		}
	} /* while() */

	if (NOT_DEFINED == role) {
		usage(argv[0]);
		exit(1);
	}

	if (SERVER == role && NULL == local_host && remote_host != NULL) {
		fprintf(stderr, "%s: Server needs local address, "
			 "not remote address\n", argv[0]);
		usage(argv[0]);
		exit(1);
	}
	if (CLIENT == role && NULL == remote_host) {
		fprintf(stderr, "%s: Client needs at least remote address "
			 "& port\n", argv[0]);
		usage(argv[0]);
		exit(1);
	}

	if (optind < argc) {
		fprintf(stderr, "%s: non-option arguments are illegal: ", argv[0]);
		while (optind < argc)
			fprintf(stderr, "%s ", argv[optind++]);
		fprintf (stderr, "\n");
		usage(argv[0]);
		exit(1);
	}

	if (remote_host != NULL && remote_port != 0) {
		struct addrinfo *res;
		int error;
		char *host_s, *serv_s;

		if ((host_s = malloc(NI_MAXHOST)) == NULL) {
			fprintf(stderr, "\n*** host_s malloc failed!!! ***\n");
			exit(1);
		}
		if ((serv_s = malloc(NI_MAXSERV)) == NULL) {
			fprintf(stderr, "\n*** serv_s malloc failed!!! ***\n");
			exit(1);
		}

		error = getaddrinfo(remote_host, 0, NULL, &res);
		if (error) {
			printf("%s.\n", gai_strerror(error));
			usage(argv[0]);
			exit(1);
		}

		switch (res->ai_family) {
			case AF_INET:
				t_addr = (struct sockaddr_in *)&s_rem;

				memcpy(t_addr, res->ai_addr,
				       res->ai_addrlen);
				t_addr->sin_family = res->ai_family;
				t_addr->sin_port = htons(remote_port);

				r_len = res->ai_addrlen;
#ifdef __FreeBSD__
				t_addr->sin_len = r_len;
#endif
				break;
			case AF_INET6:
				t_addr6 = (struct sockaddr_in6 *)&s_rem;

				memcpy(t_addr6, res->ai_addr,
				       res->ai_addrlen);
				t_addr6->sin6_family = res->ai_family;
				t_addr6->sin6_port = htons(remote_port);
				if (interface)
					t_addr6->sin6_scope_id = if_nametoindex(interface);

				r_len = res->ai_addrlen;

#ifdef __FreeBSD__
				t_addr6->sin6_len = r_len;
#endif
				break;
		}

		getnameinfo((struct sockaddr *)&s_rem, r_len, host_s,
			    NI_MAXHOST, serv_s, NI_MAXSERV, NI_NUMERICHOST);

		DEBUG_PRINT(DEBUG_MAX, "remote:addr=%s, port=%s, family=%d\n",
			    host_s, serv_s, res->ai_family);

		freeaddrinfo(res);
        }

	if (local_host != NULL) {
		struct addrinfo *res;
		int error;
		char *host_s, *serv_s;
		struct sockaddr_in *t_addr;
		struct sockaddr_in6 *t_addr6;

		if ((host_s = malloc(NI_MAXHOST)) == NULL) {
			fprintf(stderr, "\n*** host_s malloc failed!!! ***\n");
			exit(1);
		}
		if ((serv_s = malloc(NI_MAXSERV)) == NULL) {
			fprintf(stderr, "\n*** serv_s malloc failed!!! ***\n");
			exit(1);
		}

		if (strcmp(local_host, "0") == 0)
			local_host = "0.0.0.0";

		error = getaddrinfo(local_host, 0, NULL, &res);
		if (error) {
			printf("%s.\n", gai_strerror(error));
			usage(argv[0]);
			exit(1);
		}

		switch (res->ai_family) {
			case AF_INET:
				t_addr = (struct sockaddr_in *)&s_loc;

				memcpy(t_addr, res->ai_addr,
				       res->ai_addrlen);
				t_addr->sin_family = res->ai_family;
				t_addr->sin_port = htons(local_port);

				l_len = res->ai_addrlen;
#ifdef __FreeBSD__
				t_addr->sin_len = l_len;
#endif
				break;
			case AF_INET6:
				t_addr6 = (struct sockaddr_in6 *)&s_loc;

				memcpy(t_addr6, res->ai_addr,
				       res->ai_addrlen);
				t_addr6->sin6_family = res->ai_family;
				t_addr6->sin6_port = htons(local_port);
				if (interface)
					t_addr6->sin6_scope_id = if_nametoindex(interface);

				l_len = res->ai_addrlen;

#ifdef __FreeBSD__
				t_addr6->sin6_len = l_len;
#endif
				break;
		}

		error = getnameinfo((struct sockaddr *)&s_loc, l_len, host_s,
			    NI_MAXHOST, serv_s, NI_MAXSERV, NI_NUMERICHOST);

		if (error)
			printf("%s..\n", gai_strerror(error));

		DEBUG_PRINT(DEBUG_MAX, "local:addr=%s, port=%s, family=%d\n",
			    host_s, serv_s, res->ai_family);

		freeaddrinfo(res);
        }

	/* Let the testing begin. */
	start_test(role);

	return 0;
}

int bind_r(int sk, struct sockaddr_storage *saddr) {
	int error = 0, i = 0;
	char *host_s, *serv_s;

	if ((host_s = malloc(NI_MAXHOST)) == NULL) {
		fprintf(stderr, "\n\t\t*** host_s malloc failed!!! ***\n");
		exit(1);
	}
	if ((serv_s = malloc(NI_MAXSERV)) == NULL) {
		fprintf(stderr, "\n\t\t*** serv_s malloc failed!!! ***\n");
		exit(1);
	}

	do {
		if (i > 0) sleep(1); /* sleep a while before new try... */

		error = getnameinfo((struct sockaddr *)saddr, l_len, host_s,
				    NI_MAXHOST, serv_s, NI_MAXSERV,
				    NI_NUMERICHOST);

		if (error)
			printf("%s\n", gai_strerror(error));

		DEBUG_PRINT(DEBUG_MIN,
			"\tbind(sk=%d, [a:%s,p:%s])  --  attempt %d/%d\n",
			sk, host_s, serv_s, i+1, MAX_BIND_RETRYS);

		error = bind(sk, (struct sockaddr *)saddr, l_len);

		if (error != 0) {
			if( errno != EADDRINUSE ) {
				fprintf(stderr, "\n\n\t\t***bind: can "
					"not bind to %s:%s: %s ****\n",
					host_s, serv_s, strerror(errno));
				exit(1);
			}
		}
		i++;
		if (i >= MAX_BIND_RETRYS) {
			fprintf(stderr, "Maximum bind() attempts. "
				"Die now...\n\n");
			exit(1);
		}
	} while (error < 0 && i < MAX_BIND_RETRYS);

	return 0;
} /* bind_r() */

int listen_r(int sk, int listen_count) {
	int error = 0;

	DEBUG_PRINT(DEBUG_MIN, "\tlisten(sk=%d,backlog=%d)\n",
		sk, listen_count);

	/* Mark sk as being able to accept new associations */
	error = listen(sk, 1);
	if (error != 0) {
		fprintf(stderr, "\n\n\t\t*** listen:  %s ***\n\n\n", strerror(errno));
		exit(1);
	}

	return 0;
} /* listen_r() */

int accept_r(int sk){
	socklen_t len = 0;

	DEBUG_PRINT(DEBUG_MIN, "\taccept(sk=%d)\n", sk);

	gsk = accept(sk, NULL, &len);
	if (gsk < 0) {
		fprintf(stderr, "\n\n\t\t*** accept:  %s ***\n\n\n", strerror(errno));
		exit(1);
	}

	return 0;
} /* accept_r() */

int connect_r(int sk, const struct sockaddr *serv_addr, socklen_t addrlen) {
	int error = 0;

	DEBUG_PRINT(DEBUG_MIN, "\tconnect(sk=%d)\n", sk);

	/* Mark sk as being able to accept new associations */
	error = connect(sk, serv_addr, addrlen);
	if (error != 0) {
		fprintf(stderr, "\n\n\t\t*** connect:  %s ***\n\n\n",
			strerror(errno));
		exit(1);
	}

	gsk = sk;

	return 0;
} /* connect_r() */

int close_r(int sk) {
	int error = 0;

	DEBUG_PRINT(DEBUG_MIN, "\tclose(sk=%d)\n",sk);

	error = close(sk);
	if (error != 0) {
		fprintf(stderr, "\n\n\t\t*** close: %s ***\n\n",
			strerror(errno));
		exit(1);
	}
	fflush(stdout);
	return 0;
} /* close_r() */

int receive_r(int sk)
{
	int error = 0;
	char incmsg[CMSG_SPACE(sizeof(_sctp_cmsg_data_t))];
	struct iovec iov;
	struct msghdr inmessage;

	/* Initialize inmessage with enough space for DATA... */
	memset(&inmessage, 0, sizeof(inmessage));
	if ((iov.iov_base = malloc(REALLY_BIG)) == NULL) {
		fprintf(stderr, "\n\t\t*** malloc not enough memory!!! ***\n");
		exit(1);
	}
	iov.iov_len = REALLY_BIG;
	inmessage.msg_iov = &iov;
	inmessage.msg_iovlen = 1;
	/* or a control message.  */
	inmessage.msg_control = incmsg;
	inmessage.msg_controllen = sizeof(incmsg);

	/* Get the messages sent */
	while (1) {
		DEBUG_PRINT(DEBUG_MIN, "\trecvmsg(sk=%d) ", sk);

		error = recvmsg(sk, &inmessage, MSG_WAITALL);
		if (error < 0 && errno != EAGAIN) {
			fprintf(stderr, "\n\t\t*** recvmsg: %s ***\n\n",
					strerror(errno));
			fflush(stdout);
			close(sk);
			free(iov.iov_base);
			exit(1);
		} else if (error == 0) {
			printf("\n\t\trecvmsg() returned 0 !!!!\n");
			fflush(stdout);
		}

		if(MSG_NOTIFICATION & inmessage.msg_flags)
			continue; /* got a notification... */

		inmessage.msg_control = incmsg;
		inmessage.msg_controllen = sizeof(incmsg);
		iov.iov_len = REALLY_BIG;
		break;
	}

	free(iov.iov_base);
	return 0;
} /* receive_r () */

void server(int sk) {
	int i;

	if (max_msgsize > DEFAULT_MAX_WINDOW) {
		if (setsockopt(sk, IPPROTO_SCTP, SO_RCVBUF, &max_msgsize,
			       sizeof(max_msgsize)) < 0) {
			perror("setsockopt(SO_RCVBUF)");
			exit(1);
		}
	}

	for (i = 0; i < msg_cnt; i++) {
		receive_r(sk);
		DEBUG_PRINT(DEBUG_MIN, "count %d\n", i+1);
	}
} /* server() */

void * build_msg(int len) {
	int i = len - 1;
	int n;
	char *msg_buf, *p;

	msg_buf = malloc(len);
	if (NULL == msg_buf) {
		fprintf(stderr, "\n\t\t*** malloc not enough memory!!! ***\n");
		exit(1);
	}
	p = msg_buf;

	do {
		n = ((i > 50)?50:i);
		memcpy(p, msg, ((i > 50)?50:i));
		p += n;
		i -= n;
	} while (i > 0);

	msg_buf[len-1] = '\0';

	return(msg_buf);

} /* build_msg() */

int send_r(int sk, int stream, int order, int send_size, int assoc_i) {
	int error = 0;
	struct msghdr outmsg;
	struct iovec iov;
	char *message = NULL;
	int msglen = 0;
	char outcmsg[CMSG_SPACE(sizeof(struct sctp_sndrcvinfo))];
	struct cmsghdr *cmsg;
	struct sctp_sndrcvinfo *sinfo;

	if (send_size > 0) {
		message = build_msg(send_size);
		msglen = strlen(message) + 1;
		iov.iov_base = message;
		iov.iov_len = msglen;
	} else {
			exit(1);
	}

	outmsg.msg_name = &s_rem;
	outmsg.msg_namelen = sizeof(struct sockaddr_storage);
	outmsg.msg_iov = &iov;
	outmsg.msg_iovlen = 1;
	outmsg.msg_control = outcmsg;
	outmsg.msg_controllen = sizeof(outcmsg);
	outmsg.msg_flags = 0;

	cmsg = CMSG_FIRSTHDR(&outmsg);
	cmsg->cmsg_level = IPPROTO_SCTP;
	cmsg->cmsg_type = SCTP_SNDRCV;
	cmsg->cmsg_len = CMSG_LEN(sizeof(struct sctp_sndrcvinfo));

	outmsg.msg_controllen = cmsg->cmsg_len;
	sinfo = (struct sctp_sndrcvinfo *)CMSG_DATA(cmsg);
	memset(sinfo, 0, sizeof(struct sctp_sndrcvinfo));
	sinfo->sinfo_ppid = rand();
	sinfo->sinfo_stream = stream;
	sinfo->sinfo_flags = 0;
	if (!order)
		sinfo->sinfo_flags = SCTP_UNORDERED;

	DEBUG_PRINT(DEBUG_MIN, "\tsendmsg(sk=%d, assoc=%d) %4d bytes.\n",
		    sk, assoc_i, send_size);
	DEBUG_PRINT(DEBUG_MAX, "\t  SNDRCV");
	if (DEBUG_MAX == debug_level) {
		printf("(stream=%u ", 	sinfo->sinfo_stream);
		printf("flags=0x%x ",	sinfo->sinfo_flags);
		printf("ppid=%u)\n",	sinfo->sinfo_ppid);
	}

	/* Send to our neighbor.  */
	error = sendmsg(sk, &outmsg, MSG_WAITALL);
	if (error != msglen) {
		fprintf(stderr, "\n\t\t*** sendmsg: %s ***\n\n",
			strerror(errno));
		fflush(stdout);
		exit(1);
	}

	if (send_size > 0) free(message);
	return 0;
} /* send_r() */

int next_order(int state, int pattern)
{
	switch (pattern){
	case ORDER_PATTERN_UNORDERED:
		state = 0;
		break;
	case ORDER_PATTERN_ORDERED:
		state = 1;
		break;
	case ORDER_PATTERN_ALTERNATE:
		state = state ? 0 : 1;
		break;
	case ORDER_PATTERN_RANDOM:
		state = rand() % 2;
		break;
	}

	return state;
}

int next_stream(int state, int pattern)
{
	switch (pattern){
	case STREAM_PATTERN_RANDOM:
		state = rand() % max_stream;
		break;
	case STREAM_PATTERN_SEQUENTIAL:
		state = state + 1;
		if (state >= max_stream)
			state = 0;
		break;
	}

	return state;
}

int next_msg_size(int msg_cnt)
{
	int msg_size;

	if (size_arg) {
		msg_size = size_arg;
	} else {
		msg_size = (rand() % max_msgsize) + 1;
	}

	return msg_size;

} /* next_msg_size() */

void client(int sk) {
	int msg_size;
	int i;

	for (i = 0; i < msg_cnt; i++) {
		msg_size = next_msg_size(i);
		order_state = next_order(order_state, order_pattern);
		stream_state = next_stream(stream_state, stream_pattern);

		if (send_r(sk, stream_state, order_state, msg_size, 0) < 0) {
			close(sk);
			break;
		}

		/* The sender is echoing so do discard the echoed data. */
		if (drain && ((i + 1) % period == 0)) {
			receive_r(sk);
		}
	}
} /* client() */

void start_test(int role) {
	int sk, pid, ret;
	int i = 0;

	DEBUG_PRINT(DEBUG_NONE, "\nStarting tests...\n");

	repeat_count = repeat;

	DEBUG_PRINT(DEBUG_MIN, "\tsocket(SOCK_STREAM, IPPROTO_SCTP)");

	if ((sk = socket(s_loc.ss_family, SOCK_STREAM, IPPROTO_SCTP)) < 0 ) {
		fprintf(stderr, "\n\n\t\t*** socket: failed to create"
			" socket:  %s ***\n", strerror(errno));
		exit(1);
	}
	DEBUG_PRINT(DEBUG_MIN, "  ->  sk=%d\n", sk);

	bind_r(sk, &s_loc);

	if (role == SERVER) {
		listen_r(sk, 1);
		accept_r(sk);
	} else {
		if (max_stream > 0) {
			struct sctp_initmsg initmsg;

			memset(&initmsg, 0, sizeof(initmsg));
			initmsg.sinit_num_ostreams = max_stream;
			initmsg.sinit_max_instreams = max_stream;
			initmsg.sinit_max_attempts = 3;

			ret = setsockopt(sk, IPPROTO_SCTP, SCTP_INITMSG,
					 &initmsg, sizeof(initmsg));
			if (ret < 0) {
				perror("setsockopt(SCTP_INITMSG)");
				exit(0);
			}
		}

		connect_r(sk, (struct sockaddr *)&s_rem, r_len);
	}

	if ((pid = fork()) == 0) {
		settimerhandle();
		printstatus(gsk);
		while(1);
	} else {
		if (!debug_level) {
			printf("     ");
		}

		for(i = 0; i < repeat_count; i++) {

			if (role == SERVER) {
				DEBUG_PRINT(DEBUG_NONE, "Server: Receiving packets.(%d/%d)\n",
					i+1, repeat_count);
				server(gsk);
			} else {
				DEBUG_PRINT(DEBUG_NONE, "Client: Sending packets.(%d/%d)\n",
					i+1, repeat_count);
				client(sk);
			}

			fflush(stdout);
		}

		if (role == SERVER) close_r(gsk);
		close_r(sk);
	}
} /* start_test() */

void settimerhandle(void) {
	struct sigaction act;
	struct itimerval interval;

	act.sa_handler = sighandler;
	act.sa_flags = 0;
	sigemptyset(&act.sa_mask);
	sigaction(SIGPROF, &act, NULL);

	interval.it_value.tv_sec = DEFAULT_SEC;
	interval.it_value.tv_usec = DEFAULT_USEC;
	interval.it_interval = interval.it_value;

	setitimer(ITIMER_PROF, &interval, NULL);
}

void usage(char *argv0) {
	fprintf(stderr, "\nusage:\n");
	fprintf(stderr, "  server:\n");
	fprintf(stderr, "  %8s -H local-addr -P local-port -l [-d level] [-x]\n"
			"\t      [-L num-ports] [-S num-ports]\n"
			"\t      [-a assoc-pattern]\n"
			"\t      [-i interface]\n"
			"\t      [-f status-file]\n",
		argv0);
	fprintf(stderr, "\n");
	fprintf(stderr, "  client:\n");
	fprintf(stderr, "  %8s -H local-addr -P local-port -h remote-addr\n"
		"\t      -p remote-port -s [-c case ] [-d level]\n"
		"\t      [-x repeat] [-o order-pattern] ream-pattern]\n"
		"\t      [-M max-stream]\n"
		"\t      [-m max-msgsize]\n"
		"\t      [-L num-ports] [-S num-ports]\n"
		"\t      [-i interface]\n"
		"\t      [-f status-file]\n",
		argv0);
	fprintf(stderr, "\n");
	fprintf(stderr, "\t-c value = Packets of specifed size.\n");
	fprintf(stderr, "\t-m msgsize(1500-65515, default value 32768)\n");
	fprintf(stderr, "\t-x number of repeats\n");
	fprintf(stderr, "\t-X number of messages\n");
	fprintf(stderr, "\t-o order-pattern\n");
	fprintf(stderr, "\t   0 = all unordered(default) \n");
	fprintf(stderr, "\t   1 = all ordered \n");
	fprintf(stderr, "\t   2 = alternating \n");
	fprintf(stderr, "\t   3 = random\n");
	fprintf(stderr, "\t-M max-stream (default value 0)\n");
	fprintf(stderr, "\t-D drain. If in client mode do a read following send.\n");
	fprintf(stderr, "\t-I receive after <n> times of send, default value 1.\n");
	fprintf(stderr, "\n");
	fflush(stderr);

} /* usage() */

void sighandler(int signo) {
	DEBUG_PRINT(DEBUG_MAX, "timeout sig\n");
	printstatus(gsk);
}

char* get_sstat_state(int state) {
	switch(state) {
	case SCTP_EMPTY:
		return "EMPTY";
	case SCTP_CLOSED:
		return "CLOSED";
	case SCTP_COOKIE_WAIT:
		return "COOKIE_WAIT";
	case SCTP_COOKIE_ECHOED:
		return "COOKIE_ECHOED";
	case SCTP_ESTABLISHED:
		return "ESTABLISHED";
	case SCTP_SHUTDOWN_PENDING:
		return "SHUTDOWN_PENDING";
	case SCTP_SHUTDOWN_SENT:
		return "SHUTDOWN_SENT";
	case SCTP_SHUTDOWN_RECEIVED:
		return "SHUTDOWN_RECEIVED";
	case SCTP_SHUTDOWN_ACK_SENT:
		return "SHUTDOWN_ACK_SENT";
	default:
		return "UNKNOW";
	}
}

void printstatus(int sk) {
	static int cwnd = 0;
	static int count = 0;
	struct sctp_status status;
	socklen_t optlen;
	FILE * fp;
	const char *state_to_str[] = {
		[SCTP_INACTIVE]		=	"INACTIVE",
		[SCTP_PF]		=	"PF",
		[SCTP_ACTIVE]		=	"ACTIVE",
		[SCTP_UNCONFIRMED]	=	"UNCONFIRMED",
	};

	optlen = sizeof(struct sctp_status);
	if(getsockopt(sk, IPPROTO_SCTP, SCTP_STATUS, &status, &optlen) < 0) {
		fprintf(stderr, "Error getting status: %s.\n", strerror(errno));
		exit(1);
	}

	if (statusfile != NULL) {
		if (count == 0)
			unlink(statusfile);

		if((fp = fopen(statusfile, "a+")) == NULL) {
			perror("fopen");
			exit(1);
		}
	} else
		fp = stdout;

	if (count == 0)
		fprintf(fp, "NO. ASSOC-ID STATE             RWND     UNACKDATA PENDDATA INSTRMS OUTSTRMS "
				"FRAG-POINT SPINFO-STATE SPINFO-CWDN SPINFO-SRTT SPINFO-RTO SPINFO-MTU\n");

	if (cwnd != status.sstat_primary.spinfo_cwnd) {
		count++;

		fprintf(fp, "%-3d %-8d %-17s %-8d %-9d %-8d %-7d %-8d %-10d %-12s %-11d %-11d %-10d %d\n", count,
				status.sstat_assoc_id, get_sstat_state(status.sstat_state),
				status.sstat_rwnd, status.sstat_unackdata, status.sstat_penddata,
				status.sstat_instrms, status.sstat_outstrms, status.sstat_fragmentation_point,
				state_to_str[status.sstat_primary.spinfo_state],
				status.sstat_primary.spinfo_cwnd, status.sstat_primary.spinfo_srtt,
				status.sstat_primary.spinfo_rto, status.sstat_primary.spinfo_mtu);
	}

	cwnd = status.sstat_primary.spinfo_cwnd;

	fflush(fp);

	if (fp != stdout)
		fclose(fp);

	if (status.sstat_primary.spinfo_state != SCTP_ACTIVE) {
		close_r(sk);
		exit(1);
	}
}
