/* DF-3531 isolation: which part of the cycle leaks the node?
 * modes: nodd (ldisc on/off only) | nodata (connect, no data)
 *        full (connect+data)      | socksock (socket<->socket connect+data)
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>

#define AF_NG			32
#define NG_DATA			1
#define NG_CONTROL		2
#define NG_VERSION		8
#define NGM_GENERIC_COOKIE	1137070366
#define NGM_MKPEER		2
#define NGM_CONNECT		3
#define NGM_NAME		4
#define NGM_NODEINFO		(6 | 0x10000000 | 0x20000000)
#define NG_TYPESIZ		32
#define NG_HOOKSIZ		32
#define NG_NODESIZ		32
#define NG_PATHSIZ		512
#define NETGRAPHDISC_		6

struct ngm_hdr_ { u_char version, spare; u_short spare2; u_int arglen, cmd, flags, token, typecookie; char cmdstr[32]; };
struct ng_mesg_ { struct ngm_hdr_ h; char data[1024]; };
struct ngm_connect_ { char path[NG_PATHSIZ]; char ourhook[NG_HOOKSIZ]; char peerhook[NG_HOOKSIZ]; };
struct nodeinfo_ { char name[NG_NODESIZ]; char type[NG_TYPESIZ]; u_int32_t id, hooks; };
struct sockaddr_ng_ { u_char sg_len; sa_family_t sg_family; char sg_data[64]; };
#define NGIOCGINFO_ _IOR('N', 40, struct nodeinfo_)

static u_int32_t tok = 0x7000;

static int
ngm(int cs, const char *path, u_int cmd, const void *arg, size_t al, struct ng_mesg_ *reply)
{
	struct ng_mesg_ m; struct sockaddr_ng_ sa; ssize_t n;
	memset(&m, 0, sizeof(m));
	m.h.version = NG_VERSION; m.h.arglen = al; m.h.cmd = cmd;
	m.h.token = ++tok; m.h.typecookie = NGM_GENERIC_COOKIE;
	if (al) memcpy(m.data, arg, al);
	memset(&sa, 0, sizeof(sa)); sa.sg_family = AF_NG;
	snprintf(sa.sg_data, sizeof(sa.sg_data), "%s", path);
	sa.sg_len = 2 + strlen(sa.sg_data) + 1;
	n = sendto(cs, &m, sizeof(m.h) + al, 0, (struct sockaddr *)&sa, sa.sg_len);
	if (n < 0) return (-1);
	if (reply) {
		u_int32_t mt = m.h.token; int t;
		for (t = 0; t < 32; t++) {
			n = recv(cs, reply, sizeof(*reply), 0);
			if (n < 0) return (-1);
			if (reply->h.token != mt) continue;
			if (reply->h.flags & 1) {
				if (reply->h.arglen == 4)
					return (-((int)*(u_int32_t *)reply->data ? (int)*(u_int32_t *)reply->data : EIO));
				return (0);
			}
		}
		return (-1);
	}
	return (0);
}

static int
myid(int cs, u_int32_t *id)
{
	static struct ng_mesg_ r;
	if (ngm(cs, ".", NGM_NODEINFO, NULL, 0, &r) < 0) return (-1);
	*id = ((struct nodeinfo_ *)r.data)->id;
	return (0);
}

static int
cycle_tty(int doconnect, int senddata)
{
	int master, slave, cs, ds, disc = NETGRAPHDISC_, zero = 0;
	struct nodeinfo_ ni; struct ngm_connect_ con; struct sockaddr_ng_ sa;
	static char buf[1500];

	if ((master = posix_openpt(O_RDWR | O_NOCTTY)) < 0) return -1;
	fcntl(master, F_SETFL, O_NONBLOCK);
	if (grantpt(master) || unlockpt(master)) return -1;
	if ((slave = open(ptsname(master), O_RDWR | O_NOCTTY)) < 0) return -1;
	if (ioctl(slave, TIOCSETD, &disc) < 0) { perror("TIOCSETD"); return -1; }
	memset(&ni, 0, sizeof(ni));
	if (ioctl(slave, NGIOCGINFO_, &ni) < 0) { perror("NGIOCGINFO"); return -1; }

	if (doconnect) {
		cs = socket(AF_NG, SOCK_DGRAM, NG_CONTROL);
		ds = socket(AF_NG, SOCK_DGRAM, NG_DATA);
		if (cs < 0 || ds < 0) return -1;
		memset(&con, 0, sizeof(con));
		snprintf(con.path, sizeof(con.path), "%s:", ni.name);
		strcpy(con.ourhook, "d"); strcpy(con.peerhook, "hook");
		if (ngm(cs, ".", NGM_CONNECT, &con, sizeof(con), NULL) < 0) {
			fprintf(stderr, "connect %s: %s\n", con.path, strerror(errno)); return -1;
		}
		u_int32_t id;
		if (myid(cs, &id) < 0) return -1;
		memset(&sa, 0, sizeof(sa)); sa.sg_family = AF_NG;
		snprintf(sa.sg_data, sizeof(sa.sg_data), "[%x]:", id);
		sa.sg_len = 2 + strlen(sa.sg_data) + 1;
		if (connect(ds, (struct sockaddr *)&sa, sa.sg_len) < 0) return -1;
		if (senddata) {
			memset(&sa, 0, sizeof(sa)); sa.sg_family = AF_NG;
			strcpy(sa.sg_data, "d"); sa.sg_len = 2 + strlen(sa.sg_data) + 1;
			if (sendto(ds, buf, sizeof(buf), 0, (struct sockaddr *)&sa, sa.sg_len)
			    != (ssize_t)sizeof(buf)) return -1;
		}
		usleep(20000);
		close(ds); close(cs);
	}
	close(master);
	if (ioctl(slave, TIOCSETD, &zero) < 0) { perror("TIOCSETD0"); return -1; }
	close(slave);
	return 0;
}

static int
cycle_socksock(void)
{
	int csA, csB, ds; struct ngm_connect_ con; struct sockaddr_ng_ sa;
	static char buf[1500]; u_int32_t id;

	csA = socket(AF_NG, SOCK_DGRAM, NG_CONTROL);
	csB = socket(AF_NG, SOCK_DGRAM, NG_CONTROL);
	ds = socket(AF_NG, SOCK_DGRAM, NG_DATA);
	if (csA < 0 || csB < 0 || ds < 0) return -1;
	if (myid(csA, &id) < 0) return -1;
	memset(&con, 0, sizeof(con));
	snprintf(con.path, sizeof(con.path), "[%x]:", id);
	strcpy(con.ourhook, "x"); strcpy(con.peerhook, "y");
	if (ngm(csB, ".", NGM_CONNECT, &con, sizeof(con), NULL) < 0) {
		fprintf(stderr, "socksock connect: %s\n", strerror(errno)); return -1;
	}
	if (myid(csB, &id) < 0) return -1;
	memset(&sa, 0, sizeof(sa)); sa.sg_family = AF_NG;
	snprintf(sa.sg_data, sizeof(sa.sg_data), "[%x]:", id);
	sa.sg_len = 2 + strlen(sa.sg_data) + 1;
	if (connect(ds, (struct sockaddr *)&sa, sa.sg_len) < 0) return -1;
	memset(&sa, 0, sizeof(sa)); sa.sg_family = AF_NG;
	strcpy(sa.sg_data, "x"); sa.sg_len = 2 + strlen(sa.sg_data) + 1;
	if (sendto(ds, buf, sizeof(buf), 0, (struct sockaddr *)&sa, sa.sg_len)
	    != (ssize_t)sizeof(buf)) return -1;
	usleep(20000);
	close(ds); close(csA); close(csB);
	return 0;
}

int main(int argc, char **argv)
{
	const char *mode = argc > 1 ? argv[1] : "full";
	int rounds = argc > 2 ? atoi(argv[2]) : 500, i;
	for (i = 0; i < rounds; i++) {
		if (!strcmp(mode, "nodd")) { if (cycle_tty(0, 0) < 0) exit(1); }
		else if (!strcmp(mode, "nodata")) { if (cycle_tty(1, 0) < 0) exit(1); }
		else if (!strcmp(mode, "full")) { if (cycle_tty(1, 1) < 0) exit(1); }
		else if (!strcmp(mode, "socksock")) { if (cycle_socksock() < 0) exit(1); }
		if (i % 200 == 199) { printf("... %d\n", i + 1); fflush(stdout); }
	}
	printf("done %s %d\n", mode, rounds);
	return 0;
}
