#define _GNU_SOURCE // for POLLRDHUP
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>

#ifdef __linux__
#include <sys/epoll.h>
#endif

#include <netinet/in.h>
#include <netinet/tcp.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

/* for OSes which don't have it */
#ifndef POLLRDHUP
#define POLLRDHUP 0
#endif

#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
#endif
#ifndef MSG_MORE
#define MSG_MORE 0
#endif

int verbose = 0;
int cmd = 0;
int cmdstep = 0;
int zero = 0;
int one  = 1;
int lfd = -1;
int cfd = -1;
int sfd = -1;
int connected = 0;
int use_epoll = 0;
struct sockaddr_in saddr, caddr;
socklen_t salen, calen;

static inline const char *side(int fd)
{
	if (fd == lfd)
		return "l";
	if (fd == sfd)
		return "s";
	if (fd == cfd)
		return "c";
	return "?";
}

void usage(const char *arg0)
{
	printf("Usage: %s [ arg [<action>[,...]] ] ...\n"
	       "args:\n"
	       "    -h            display this help\n"
	       "    -v            verbose mode (shows ret values)\n"
	       "    -e            use epoll instead of poll\n"
	       "    -c <actions>  perform <action> on client side socket\n"
	       "    -s <actions>  perform <action> on server side socket\n"
	       "    -l <actions>  perform <action> on listening socket\n"
	       "\n"
	       "actions for -c/-s/-l (multiple may be delimited by commas) :\n"
	       "    con           connect to listener, implicit before first -c/-s\n"
	       "    acc           accept on listener, implicit before first -s\n"
	       "    snd           send a few bytes of data\n"
	       "    mor           send a few bytes of data with MSG_MORE\n"
	       "    rcv           receive a few bytes of data\n"
	       "    drn           drain: receive till zero\n"
	       "    shr           SHUT_RD : shutdown read side\n"
	       "    shw           SHUT_WR : shutdown write side\n"
	       "    shb           SHUT_RDWR : shutdown both sides\n"
	       "    lin           disable lingering on the socket\n"
	       "    clo           close the file descriptor\n"
	       "    pol           poll() for any event\n"
	       "\n", arg0);
}

void die(const char *msg)
{
	if (msg)
		fprintf(stderr, "%s\n", msg);
	exit(1);
}

const char *get_errno(int ret)
{
	static char errmsg[100];

	if (ret >= 0)
		return "";

	snprintf(errmsg, sizeof(errmsg), " (%s)", strerror(errno));
	return errmsg;
}

void do_acc(int fd)
{
	int ret;

	calen = sizeof(caddr);
	ret = accept(lfd, (struct sockaddr*)&caddr, &calen);
	if (sfd < 0)
		sfd = ret;
	if (verbose)
		printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret));
}

void do_con(int fd)
{
	int ret;

        ret = connect(cfd, (const struct sockaddr*)&saddr, salen);
	if (verbose)
		printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret));
	connected = 1;
}

void do_snd(int fd)
{
	int ret;

	ret = send(fd, "foo", 3, MSG_NOSIGNAL|MSG_DONTWAIT);
	if (verbose)
		printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret));
}

void do_mor(int fd)
{
	int ret;

	ret = send(fd, "foo", 3, MSG_NOSIGNAL|MSG_DONTWAIT|MSG_MORE);
	if (verbose)
		printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret));
}

void do_rcv(int fd)
{
	char buf[10];
	int ret;

	ret = recv(fd, buf, sizeof(buf), MSG_DONTWAIT);
	if (verbose)
		printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret));
}

void do_drn(int fd)
{
	char buf[16384];
	int total = -1;
	int ret;

	while (1) {
		ret = recv(fd, buf, sizeof(buf), 0);
		if (ret <= 0)
			break;
		if (total < 0)
			total = 0;
		total += ret;
	}

	if (verbose)
		printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, total, get_errno(ret));
}

void do_shr(int fd)
{
	int ret;

	ret = shutdown(fd, SHUT_RD);
	if (verbose)
		printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret));
}

void do_shw(int fd)
{
	int ret;

	ret = shutdown(fd, SHUT_WR);
	if (verbose)
		printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret));
}

void do_shb(int fd)
{
	int ret;

	ret = shutdown(fd, SHUT_RDWR);
	if (verbose)
		printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret));
}

void do_lin(int fd)
{
	struct linger nolinger = { .l_onoff = 1, .l_linger = 0 };
	int ret;

	ret = setsockopt(fd, SOL_SOCKET, SO_LINGER, &nolinger, sizeof(nolinger));
	if (verbose)
		printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret));
}

void do_clo(int fd)
{
	int ret;

	ret = close(fd);
	if (verbose)
		printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret));
}

void do_pol(int fd)
{
	struct pollfd fds = { .fd = fd, .events = POLLIN|POLLOUT|POLLRDHUP, .revents=0 };
	int flags, flag;
	int ret;

#ifdef __linux__
	while (use_epoll) {
		struct epoll_event evt;
		static int epoll_fd = -1;

		if (epoll_fd == -1)
			epoll_fd = epoll_create(1024);
		if (epoll_fd == -1)
			break;
		evt.events = EPOLLIN | EPOLLOUT | EPOLLRDHUP;
		evt.data.fd = fd;
		epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &evt);
		ret = epoll_wait(epoll_fd, &evt, 1, 0);

		if (verbose) {
			printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s ev=%#x ", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret), ret > 0 ? evt.events : 0);
			if (ret > 0 && evt.events) {
				putchar('(');

				for (flags = evt.events; flags; flags ^= flag) {
					flag = flags ^ (flags & (flags - 1)); // keep lowest bit only
					switch (flag) {
					case EPOLLIN: printf("IN"); break;
					case EPOLLOUT: printf("OUT"); break;
					case EPOLLPRI: printf("PRI"); break;
					case EPOLLHUP: printf("HUP"); break;
					case EPOLLERR: printf("ERR"); break;
					case EPOLLRDHUP: printf("RDHUP"); break;
					default: printf("???[%#x]", flag); break;
					}
					if (flags ^ flag)
						putchar(' ');
				}
				putchar(')');
			}
			putchar('\n');
		}

		evt.data.fd = fd;
		epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &evt);
		return;
	}
#endif
	ret = poll(&fds, 1, 0);
	if (verbose) {
		printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s ev=%#x ", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret), ret > 0 ? fds.revents : 0);
		if (ret > 0 && fds.revents) {
			putchar('(');

			for (flags = fds.revents; flags; flags ^= flag) {
				flag = flags ^ (flags & (flags - 1)); // keep lowest bit only
				switch (flag) {
				case POLLIN: printf("IN"); break;
				case POLLOUT: printf("OUT"); break;
				case POLLPRI: printf("PRI"); break;
				case POLLHUP: printf("HUP"); break;
				case POLLERR: printf("ERR"); break;
				case POLLNVAL: printf("NVAL"); break;
#if POLLRDHUP
				case POLLRDHUP: printf("RDHUP"); break;
#endif
				default: printf("???[%#x]", flag); break;
				}
				if (flags ^ flag)
					putchar(' ');
			}
			putchar(')');
		}
		putchar('\n');
	}
}

int main(int argc, char **argv)
{
	const char *arg0;
	char *word, *next;
	int fd;

	/* listener */
	lfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (lfd < 0)
		die("socket(l)");

	setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));

	memset(&saddr, 0, sizeof(saddr));
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(0);
	salen = sizeof(saddr);

	if (bind(lfd, (struct sockaddr *)&saddr, salen) < 0)
		die("bind()");

	if (listen(lfd, 1000) < 0)
		die("listen()");

	if (getsockname(lfd, (struct sockaddr *)&saddr, &salen) < 0)
		die("getsockname()");


	/* client */
	cfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (cfd < 0)
		die("socket(c)");

	arg0 = argv[0];
	if (argc < 2) {
		usage(arg0);
		exit(1);
	}

	write(1, "#### BEGIN ####\n", 16); // add a visible delimiter in the traces

	while (argc > 1) {
		argc--; argv++;
		if (**argv != '-') {
			usage(arg0);
			exit(1);
		}

		fd = -1;
		switch (argv[0][1]) {
		case 'h' :
			usage(arg0);
			exit(0);
			break;
		case 'v' :
			verbose++;
			break;
		case 'e' :
			use_epoll = 1;
			break;
		case 'c' :
			cmd++; cmdstep = 0;
			if (!connected) {
				do_con(cfd);
				/* connection is pending in accept queue, accept() will either be
				 * explicit with "-l acc" below, or implicit on "-s <cmd>"
				 */
			}
			fd = cfd;
			break;
		case 's' :
			cmd++; cmdstep = 0;
			if (!connected)
				do_con(cfd);
			if (sfd < 0)
				do_acc(lfd);
			if (sfd < 0)
				die("accept()");
			fd = sfd;
			break;
		case 'l' :
			cmd++; cmdstep = 0;
			fd = lfd;
			break;
		default  : usage(arg0); exit(1); break;
		}

		if (fd >= 0) { /* an action is required */
			if (argc < 2) {
				usage(arg0);
				exit(1);
			}

			for (word = argv[1]; word && *word; word = next) {
				next = strchr(word, ',');
				if (next)
					*(next++) = 0;
				cmdstep++;
				if (strcmp(word, "acc") == 0) {
					do_acc(fd);
				}
				else if (strcmp(word, "con") == 0) {
					do_con(fd);
				}
				else if (strcmp(word, "snd") == 0) {
					do_snd(fd);
				}
				else if (strcmp(word, "mor") == 0) {
					do_mor(fd);
				}
				else if (strcmp(word, "rcv") == 0) {
					do_rcv(fd);
				}
				else if (strcmp(word, "drn") == 0) {
					do_drn(fd);
				}
				else if (strcmp(word, "shb") == 0) {
					do_shb(fd);
				}
				else if (strcmp(word, "shr") == 0) {
					do_shr(fd);
				}
				else if (strcmp(word, "shw") == 0) {
					do_shw(fd);
				}
				else if (strcmp(word, "lin") == 0) {
					do_lin(fd);
				}
				else if (strcmp(word, "clo") == 0) {
					do_clo(fd);
				}
				else if (strcmp(word, "pol") == 0) {
					do_pol(fd);
				}
				else {
					printf("Ignoring unknown action '%s' in step #%d of cmd #%d\n", word, cmdstep, cmd);
				}
			}
			argc--; argv++;
		}
	}

	write(1, "#### END ####\n", 14); // add a visible delimiter in the traces

	if (!cmd) {
		printf("No command was requested!\n");
		usage(arg0);
		exit(1);
	}

	return 0;
}
