/*
 * Differential harness: DragonFlyBSD sys/libkern/inet_ntop.c (compiled as
 * userland object, symbol renamed dk_inet_ntop) vs DragonFly libc inet_ntop.
 *
 * Phases:
 *  P1  IPv4 string-equivalence (corner cross-product + sequential + random)
 *      + inet_pton round-trip
 *  P2  IPv4 size-boundary sweep (size=1..16): accept/reject decision must
 *      equal libc at same size; on accept, strlen+1 <= size
 *  P3  IPv6 string-equivalence: structured zero-run space + specials +
 *      20M random
 *  P4  IPv6 size-boundary sweep (size=1..46): decision == libc decision
 *  P5  af fuzz: must return NULL for af != AF_INET/AF_INET6
 *  P6  errno observation (informational)
 *  P7  GUARD-PAGE knife-edge canary: dst placed so dst[size] is the first
 *      byte of a PROT_NONE page -> ANY overflow write = SIGSEGV = abort.
 *      Sizes {strlen-1 .. strlen+2} per address (the exact boundary).
 *
 * Exit 0 iff zero mismatches (and no SIGSEGV, which cannot be missed).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <arpa/inet.h>

char *dk_inet_ntop(int af, const void *src, char *dst, socklen_t size);

size_t strlcpy(char *dst, const char *src, size_t dsize);
size_t strlcpy(char *dst, const char *src, size_t dsize)
{
	size_t slen = strlen(src);
	if (dsize) {
		size_t n = slen < dsize ? slen : dsize - 1;
		memcpy(dst, src, n);
		dst[n] = '\0';
	}
	return slen;
}

static uint64_t rs = 0x9E379B97F4A7C15ULL;
static inline uint64_t rnd(void)
{
	rs ^= rs << 13; rs ^= rs >> 7; rs ^= rs << 17;
	return rs;
}
#define RNDB() ((unsigned char)(rnd() >> 32))

static long long mism = 0, total = 0;
#define MAXPRINT 5
static void report(const char *phase, int af, const void *src, socklen_t sz,
		   const char *kout, const char *cout)
{
	if (mism < MAXPRINT) {
		unsigned char *b = (unsigned char *)src;
		int n = af == AF_INET ? 4 : 16, i;
		fprintf(stderr, "MISMATCH[%s] af=%d src=", phase, af);
		for (i = 0; i < n; i++)
			fprintf(stderr, "%02x", b[i]);
		fprintf(stderr, " size=%u kernel=%s libc=%s\n", (unsigned)sz,
			kout ? kout : "(NULL)", cout ? cout : "(NULL)");
	}
	mism++;
}

/* guard-page canary: page pair [RW][NONE]; dst placed so dst[size] is the
 * first byte of the PROT_NONE page. Any overflow write faults instantly. */
static char *gp_base(void)
{
	char *base = mmap(NULL, 8192, PROT_READ | PROT_WRITE,
			  MAP_ANON | MAP_PRIVATE, -1, 0);
	if (base == MAP_FAILED) { perror("mmap"); exit(2); }
	if (mprotect(base + 4096, 4096, PROT_NONE)) { perror("mprotect"); exit(2); }
	return base;
}

int main(int argc, char **argv)
{
	long long iters = (argc > 1) ? atoll(argv[1]) : 20000000LL;
	if (argc > 2)
		rs ^= (uint64_t)atoll(argv[2]) * 0x2545F4914F6CDD1DULL;
	unsigned char a4[4], a6[16];
	char kb[64], cb[64];
	char *kr; const char *cr;
	int i, j;
	long long rt_bad = 0;

	/* ---------------- P1: IPv4 strings + roundtrip ---------------- */
	static const int oc[] = {0,1,2,9,10,99,100,127,128,169,170,172,192,198,
				 223,224,240,248,252,254,255};
	const int NOC = (int)(sizeof(oc)/sizeof(oc[0]));
	for (i = 0; i < NOC; i++)
	for (j = 0; j < NOC; j++)
	for (int k2 = 0; k2 < NOC; k2++)
	for (int l = 0; l < NOC; l++) {
		uint32_t be;
		a4[0]=oc[i]; a4[1]=oc[j]; a4[2]=oc[k2]; a4[3]=oc[l];
		kr = dk_inet_ntop(AF_INET, a4, kb, 16);
		cr = inet_ntop(AF_INET, a4, cb, 16);
		total++;
		if (!kr || !cr || strcmp(kr, cr))
			report("P1-corner", AF_INET, a4, 16, kr, cr);
		else {
			struct in_addr pa;
			inet_pton(AF_INET, kr, &pa);
			memcpy(&be, a4, 4);
			if (pa.s_addr != be) rt_bad++;
		}
	}
	for (i = 0; i < (1<<22); i++) {
		memcpy(a4, &i, 4);
		kr = dk_inet_ntop(AF_INET, a4, kb, 16);
		cr = inet_ntop(AF_INET, a4, cb, 16);
		total++;
		if (!kr || !cr || strcmp(kr, cr))
			report("P1-seq", AF_INET, a4, 16, kr, cr);
	}
	for (i = 0; i < iters; i++) {
		if ((rnd() & 3)) {
			a4[0]=oc[rnd()%NOC]; a4[1]=oc[rnd()%NOC];
			a4[2]=oc[rnd()%NOC]; a4[3]=oc[rnd()%NOC];
		} else {
			a4[0]=RNDB(); a4[1]=RNDB(); a4[2]=RNDB(); a4[3]=RNDB();
		}
		kr = dk_inet_ntop(AF_INET, a4, kb, 16);
		cr = inet_ntop(AF_INET, a4, cb, 16);
		total++;
		if (!kr || !cr || strcmp(kr, cr))
			report("P1-rand", AF_INET, a4, 16, kr, cr);
	}

	/* ---------------- P2: IPv4 size-boundary sweep ---------------- */
	for (i = 0; i < 200000; i++) {
		socklen_t sz;
		if (i < 50000) {
			a4[0]=oc[rnd()%NOC]; a4[1]=oc[rnd()%NOC];
			a4[2]=oc[rnd()%NOC]; a4[3]=oc[rnd()%NOC];
		} else {
			a4[0]=RNDB(); a4[1]=RNDB(); a4[2]=RNDB(); a4[3]=RNDB();
		}
		for (sz = 0; sz <= 16; sz++) {
			char *d = malloc(sz);
			char *r = dk_inet_ntop(AF_INET, a4, d, sz);
			char *c2 = malloc(sz);
			const char *cr2 = inet_ntop(AF_INET, a4, c2, sz);
			total++;
			if ((r != NULL) != (cr2 != NULL) ||
			    (r && (strcmp(r, c2) || strlen(r) + 1 > sz)))
				report("P2-bnd", AF_INET, a4, sz, r, cr2);
			free(d); free(c2);
		}
	}

	/* ---------------- P3: IPv6 strings ---------------- */
	{
		static const unsigned fill[] = {1, 0x0001, 0x0100, 0xffff,
						0x1234, 0xabcd, 0x00ff};
		const int NF = (int)(sizeof(fill)/sizeof(fill[0]));
		for (int b = 0; b <= 8; b++)
		for (int l = 0; l <= 8 - b; l++)
		for (int f = 0; f < NF; f++) {
			unsigned words[8];
			for (int w = 0; w < 8; w++)
				words[w] = (w >= b && w < b + l) ? 0 :
					   (w == 7 && fill[f] == 1) ? 2 : fill[f];
			for (int w = 0; w < 8; w++) {
				a6[w*2] = (unsigned char)(words[w] >> 8);
				a6[w*2+1] = (unsigned char)(words[w] & 0xff);
			}
			kr = dk_inet_ntop(AF_INET6, a6, kb, 64);
			cr = inet_ntop(AF_INET6, a6, cb, 64);
			total++;
			if (!kr || !cr || strcmp(kr, cr))
				report("P3-struct", AF_INET6, a6, 64, kr, cr);
		}
		static const unsigned char *sp[] = {
			(const unsigned char *)"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
			(const unsigned char *)"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1",
			(const unsigned char *)"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\2",
			(const unsigned char *)"\0\0\0\0\0\0\0\0\0\0\0\0\1\2\3\4",
			(const unsigned char *)"\0\0\0\0\0\0\0\0\0\0\xff\xff\1\2\3\4",
			(const unsigned char *)"\0\0\0\0\0\0\0\0\0\0\xff\xff\xff\xff\xff\xff",
			(const unsigned char *)"\0\0\0\0\0\0\0\0\0\1\0\1\0\1\0\1",
			(const unsigned char *)"\x20\x02\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07",
			(const unsigned char *)"\xfe\x80\x00\x00\x00\x00\x00\x00\x02\x11\x22\xff\xfe\x33\x44\x55",
			(const unsigned char *)"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff",
		};
		const int NSP = (int)(sizeof(sp)/sizeof(sp[0]));
		for (i = 0; i < NSP; i++) {
			kr = dk_inet_ntop(AF_INET6, sp[i], kb, 64);
			cr = inet_ntop(AF_INET6, sp[i], cb, 64);
			total++;
			if (!kr || !cr || strcmp(kr, cr))
				report("P3-special", AF_INET6, sp[i], 64, kr, cr);
		}
	}
	for (i = 0; i < iters; i++) {
		static const unsigned char zk[] = {0,0,0,1,0xff,0xfe};
		for (int k2 = 0; k2 < 16; k2++)
			a6[k2] = (rnd() & 3) ? RNDB() : zk[rnd()%6];
		kr = dk_inet_ntop(AF_INET6, a6, kb, 64);
		cr = inet_ntop(AF_INET6, a6, cb, 64);
		total++;
		if (!kr || !cr || strcmp(kr, cr))
			report("P3-rand", AF_INET6, a6, 64, kr, cr);
	}

	/* ---------------- P4: IPv6 size-boundary sweep ---------------- */
	for (i = 0; i < 300000; i++) {
		socklen_t sz;
		if (i < 100000) {
			int b = (int)(rnd()%9), l = (int)(rnd()%(9-b));
			for (int w = 0; w < 8; w++) {
				unsigned v = (w >= b && w < b+l) ? 0 :
					     (unsigned)(rnd() % 0xffff + 1);
				a6[w*2]=v>>8; a6[w*2+1]=v&0xff;
			}
		} else if (i < 150000) {
			memcpy(a6, "\0\0\0\0\0\0\0\0\0\0\xff\xff", 12);
			a6[12]=RNDB(); a6[13]=RNDB(); a6[14]=RNDB(); a6[15]=RNDB();
		} else {
			for (int k2 = 0; k2 < 16; k2++)
				a6[k2] = (rnd() & 3) ? RNDB() : 0;
		}
		for (sz = 0; sz <= 46; sz++) {
			char *d = malloc(sz);
			char *r = dk_inet_ntop(AF_INET6, a6, d, sz);
			char *c2 = malloc(sz);
			const char *cr2 = inet_ntop(AF_INET6, a6, c2, sz);
			total++;
			if ((r != NULL) != (cr2 != NULL) ||
			    (r && (strcmp(r, c2) || strlen(r) + 1 > sz)))
				report("P4-bnd", AF_INET6, a6, sz, r, cr2);
			free(d); free(c2);
		}
	}

	/* ---------------- P5: af fuzz ---------------- */
	/* kernel decision must equal libc decision for EVERY af (valid or not);
	   on DragonFly AF_INET=2, AF_INET6=28, so we test the full set and
	   additionally assert bogus afs (both refs) yield NULL. */
	{
		static const int afs[] = {-2,-1,0,1,2,3,4,5,10,17,26,28,100,255,
					  1000,0x7fffffff};
		const int NAF = (int)(sizeof(afs)/sizeof(afs[0]));
		for (i = 0; i < NAF; i++) {
			kr = dk_inet_ntop(afs[i], a6, kb, 64);
			cr = inet_ntop(afs[i], a6, cb, 64);
			total++;
			if ((kr != NULL) != (cr != NULL))
				report("P5-af", afs[i], a6, 64, kr, cr);
			else if (kr && cr && strcmp(kr, cr))
				report("P5-af-str", afs[i], a6, 64, kr, cr);
		}
	}

	/* ---------------- P6: errno observation ---------------- */
	{
		errno = 0;
		kr = dk_inet_ntop(AF_INET, "\x01\x02\x03\x04", kb, 2);
		printf("P6: size-too-small kernel-returns=%p errno-after=%d "
		       "(ENOSPC=%d EAFNOSUPPORT=%d)\n", (void *)kr, errno,
		       ENOSPC, EAFNOSUPPORT);
		errno = 0;
		kr = dk_inet_ntop(AF_UNSPEC, "\x01\x02\x03\x04", kb, 64);
		printf("P6: bad-af       kernel-returns=%p errno-after=%d\n",
		       (void *)kr, errno);
	}

	/* -------- P7: guard-page knife-edge overflow canary -------- */
	{
		long long gp = 0, gpacc = 0;
		for (i = 0; i < 100000; i++) {
			int af = (i & 1) ? AF_INET : AF_INET6;
			int canlen;
			if (af == AF_INET) {
				a4[0]=oc[rnd()%NOC]; a4[1]=oc[rnd()%NOC];
				a4[2]=RNDB();        a4[3]=RNDB();
				if (!dk_inet_ntop(AF_INET, a4, kb, 16)) abort();
			} else {
				if (i % 4 == 1) {	/* v4-mapped */
					memcpy(a6, "\0\0\0\0\0\0\0\0\0\0\xff\xff", 12);
					a6[12]=oc[rnd()%NOC]; a6[13]=oc[rnd()%NOC];
					a6[14]=RNDB();        a6[15]=RNDB();
				} else if (i % 4 == 2) {	/* trailing zero run */
					for (int w = 0; w < 8; w++) {
						unsigned v = (w >= (int)(rnd()%8)) ? 0 :
							(unsigned)(rnd()%0xffff+1);
						a6[w*2]=v>>8; a6[w*2+1]=v&0xff;
					}
				} else {
					for (int w = 0; w < 8; w++) {
						unsigned v = (unsigned)(rnd()%0x10000);
						a6[w*2]=v>>8; a6[w*2+1]=v&0xff;
					}
				}
				if (!dk_inet_ntop(AF_INET6, a6, kb, 64)) abort();
			}
			canlen = (int)strlen(kb);
			for (int d = -2; d <= 2; d++) {
				socklen_t sz = canlen + d;
				char *g, *gb;
				if (sz > 46) continue;
				gb = gp_base();
				if (sz == 0) {
					/* zero writable bytes: any write MUST fault */
					g = gb + 4096;	/* == PROT_NONE page start */
				} else {
					g = gb + 4096 - sz;
				}
				if (dk_inet_ntop(af, af == AF_INET ? (void*)a4 : (void*)a6,
						 g, sz) != NULL) {
					gpacc++;
					if ((int)strlen(g) != canlen) {
						fprintf(stderr,
						"P7 CORRUPTION len %zu != %d\n",
						strlen(g), canlen);
						mism++;
					}
				}
				gp++;
				munmap(gb, 8192);
			}
		}
		printf("P7: guardpage calls=%lld accepted=%lld (no SIGSEGV)\n",
		       gp, gpacc);
	}

	printf("TOTAL=%lld MISMATCH=%lld ROUNDTRIP_BAD=%lld\n", total, mism, rt_bad);
	return (mism || rt_bad) ? 1 : 0;
}
