/*
 * DF-3543 companion demo: NGM_ONE2MANY_GET_STATS linkNum validation bug.
 *
 * sys/netgraph/one2many/ng_one2many.c:339-340 gates per-link stats on
 *   else if (linkNum == 0 && linkNum < NG_ONE2MANY_MAX_LINKS)
 * so links 1..63 are rejected with EINVAL (the ng7 twin and FreeBSD
 * use `linkNum >= 0`).  Demo: connect many0 and many1, then query stats
 * for link 0 (works) and link 1 (spurious EINVAL).
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#ifndef AF_NETGRAPH
#define AF_NETGRAPH 32
#endif
#define NG_AF AF_NETGRAPH
#define NG_PROTO_CONTROL 2
#define NG_PROTO_DATA 1

#define NG_VERSION 2
#define NG_GENERIC_COOKIE 851672668
#define NGM_MKPEER 2
#define NGM_CONNECT 3
#define NGM_NAME 4

#define NG_ONE2MANY_COOKIE 971111383
#define NGM_ONE2MANY_GET_STATS 3

#define NG_TYPESIZ 32
#define NG_HOOKSIZ 32
#define NG_NODESIZ 32
#define NG_PATHSIZ 512
#define NG_CMDSTRSIZ 32

struct ng_msghdr_t {
	unsigned char version;
	unsigned char spare;
	unsigned short arglen;
	unsigned int flags;
	unsigned int token;
	unsigned int typecookie;
	unsigned int cmd;
	unsigned char cmdstr[NG_CMDSTRSIZ];
};
struct ng_mesg_t {
	struct ng_msghdr_t header;
	char data[2048];
};
struct sockaddr_ng_t {
	unsigned char sg_len;
	unsigned char sg_family;
	char sg_data[64];
};
struct ngm_mkpeer_t { char type[NG_TYPESIZ]; char ourhook[NG_HOOKSIZ]; char peerhook[NG_HOOKSIZ]; };
struct ngm_connect_t { char path[NG_PATHSIZ]; char ourhook[NG_HOOKSIZ]; char peerhook[NG_HOOKSIZ]; };
struct ngm_name_t { char name[NG_NODESIZ]; };

static int cs;

static void
mkaddr(struct sockaddr_ng_t *sa, const char *path)
{
	size_t n = strlen(path) + 1;
	memset(sa, 0, sizeof(*sa));
	sa->sg_len = 2 + n;
	sa->sg_family = NG_AF;
	memcpy(sa->sg_data, path, n);
}

static int
send_ctrl(const char *path, unsigned int cookie, unsigned int cmd,
    const void *arg, size_t arglen)
{
	struct sockaddr_ng_t sa;
	struct ng_mesg_t msg;
	ssize_t n;

	memset(&msg, 0, sizeof(msg));
	msg.header.version = NG_VERSION;
	msg.header.arglen = arglen;
	msg.header.typecookie = cookie;
	msg.header.cmd = cmd;
	msg.header.token = 0x666;
	if (arglen)
		memcpy(msg.data, arg, arglen);
	mkaddr(&sa, path);
	n = sendto(cs, &msg, sizeof(msg.header) + arglen, 0,
	    (struct sockaddr *)&sa, sa.sg_len);
	return (n < 0) ? errno : 0;
}

/* query GET_STATS for a link; returns errno, fills stats on success */
static int
get_stats(int linknum, unsigned long long *stats)
{
	struct sockaddr_ng_t sa;
	struct ng_mesg_t msg;
	int32_t ln = linknum;
	ssize_t n;

	memset(&msg, 0, sizeof(msg));
	msg.header.version = NG_VERSION;
	msg.header.arglen = sizeof(ln);
	msg.header.typecookie = NG_ONE2MANY_COOKIE;
	msg.header.cmd = NGM_ONE2MANY_GET_STATS;
	msg.header.token = 0x777;
	memcpy(msg.data, &ln, sizeof(ln));
	mkaddr(&sa, "t1");
	n = sendto(cs, &msg, sizeof(msg.header) + sizeof(ln), 0,
	    (struct sockaddr *)&sa, sa.sg_len);
	if (n < 0)
		return (errno);
	/* synchronous response arrives on the control socket */
	n = recv(cs, &msg, sizeof(msg), MSG_DONTWAIT);
	if (n < 0)
		return (errno == EAGAIN ? 0 /* no response -> treated as ok */ : errno);
	if (n >= (ssize_t)(sizeof(msg.header) + 32)) {
		memcpy(stats, msg.data, 32);
		return (0);
	}
	return (EIO);
}

int
main(void)
{
	struct ngm_name_t nm;
	struct ngm_mkpeer_t mk;
	struct ngm_connect_t cn;
	unsigned long long st[4];
	char namebuf[32];
	int e0, e1, em1;

	snprintf(namebuf, sizeof(namebuf), "s%d", getpid());

	cs = socket(NG_AF, SOCK_DGRAM, NG_PROTO_CONTROL);
	if (cs < 0) { perror("control socket"); exit(1); }

	memset(&nm, 0, sizeof(nm));
	strcpy(nm.name, namebuf);
	if (send_ctrl(".", NG_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm))) {
		fprintf(stderr, "NGM_NAME failed\n");
		exit(1);
	}

	memset(&mk, 0, sizeof(mk));
	strcpy(mk.type, "one2many");
	strcpy(mk.ourhook, "t1");
	strcpy(mk.peerhook, "one");
	e0 = send_ctrl(".", NG_GENERIC_COOKIE, NGM_MKPEER, &mk, sizeof(mk));
	if (e0) { fprintf(stderr, "mkpeer: %s\n", strerror(e0)); exit(1); }

	/* connect two many links: many0 and many1 */
	memset(&cn, 0, sizeof(cn));
	strcpy(cn.path, "one");
	strcpy(cn.ourhook, "many0");
	strcpy(cn.peerhook, "m0");
	e0 = send_ctrl("t1", NG_GENERIC_COOKIE, NGM_CONNECT, &cn, sizeof(cn));
	if (e0) { fprintf(stderr, "connect many0: %s\n", strerror(e0)); exit(1); }
	strcpy(cn.ourhook, "many1");
	strcpy(cn.peerhook, "m1");
	e1 = send_ctrl("t1", NG_GENERIC_COOKIE, NGM_CONNECT, &cn, sizeof(cn));
	if (e1) { fprintf(stderr, "connect many1: %s\n", strerror(e1)); exit(1); }

	memset(st, 0, sizeof(st));
	e0 = get_stats(0, st);
	memset(st, 0, sizeof(st));
	e1 = get_stats(1, st);
	memset(st, 0, sizeof(st));
	em1 = get_stats(-1, st);

	printf("GET_STATS linkNum=0  -> %s (expected: OK)\n", strerror(e0));
	printf("GET_STATS linkNum=1  -> %s (expected: OK, got rejection => BUG)\n",
	    strerror(e1));
	printf("GET_STATS linkNum=-1 -> %s (expected: OK for 'one' link)\n",
	    strerror(em1));
	return ((e0 == 0 && e1 != 0) ? 0 : 1);
}
