/*
 * nfssrv3.c - functional-enough loopback NFSv3 server for DF-3053 / DF-3043 /
 * DF-3052 verification (extends the DF-3009 fakesrv skeleton).
 *
 *   rpcbind UDP 111 -> mountd TCP 779, nfs TCP 2049 (same as fakesrv)
 *   export "/" with files:
 *      seq  (fileid 2): 1 MiB, byte pattern (off ^ 0x33)
 *      seq2 (fileid 3): 1 MiB, byte pattern (off ^ 0x99)
 *      big  (fileid 4): claimed size $NFS_BIG_SIZE (default 2^40), reads of
 *                       any legal offset return zeros, EOF=false
 *   knobs (env):
 *      NFS_DELAY_MS  delay READ replies by N ms (default 0)
 *      NFS_BIG_SIZE  claimed size of 'big'      (default 1099511627776)
 *      NFS_MAXFILE   FSINFO maxfilesize claim   (default 2147483647)
 *      NFS_STATS     stats dump path            (default /tmp/nfssrv3.stats)
 *   stats: per-file READ RPC counts dumped every 2s and at exit.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT_PMAP 111
#define PORT_MNTD 779
#define PORT_NFS  2049
#define PROG_PMAP 100000
#define PROG_MNTD 100005
#define PROG_NFS  100003

static long  delay_ms;
static unsigned long long big_size = 1099511627776ULL;
static unsigned long long max_file = 2147483647ULL;
static const char *stats_path = "/tmp/nfssrv3.stats";

static unsigned char req[65536];
static unsigned char rep[65536];
static int rlen;
static int verbose;

static void put32(u_int32_t v){ rep[rlen++]=v>>24; rep[rlen++]=v>>16; rep[rlen++]=v>>8; rep[rlen++]=v; }
static void put64(u_int64_t v){ put32(v>>32); put32((u_int32_t)v); }
static void putbytes(const void*p,int n){ memcpy(rep+rlen,p,n); rlen+=n; }
static void pad4(int n){ while(n&3){rep[rlen++]=0;n++;} }
static void putstr(const char*s){ int n=strlen(s); put32(n); putbytes(s,n); pad4(n); }
static void putopaque(const void*p,int n){ put32(n); putbytes(p,n); pad4(n); }
static u_int32_t get32(const unsigned char*p){ return ((u_int32_t)p[0]<<24)|(p[1]<<16)|(p[2]<<8)|p[3]; }
static u_int64_t get64(const unsigned char*p){ return ((u_int64_t)get32(p)<<32)|get32(p+4); }

struct call { u_int32_t xid,prog,vers,proc; const unsigned char*args; int alen; };
static int parse_call(int len, struct call *c)
{
	const unsigned char *p=req; u_int32_t mtype,credlen,verflen; int off;
	if(len<28) return -1;
	c->xid=get32(p); mtype=get32(p+4); if(mtype!=0) return -1;
	c->prog=get32(p+12); c->vers=get32(p+16); c->proc=get32(p+20);
	off=24; if(off+8>len) return -1;
	credlen=get32(p+off+4); off+=8+((credlen+3)&~3);
	if(off+8>len) return -1;
	verflen=get32(p+off+4); off+=8+((verflen+3)&~3);
	if(off>len) return -1;
	c->args=p+off; c->alen=len-off; return 0;
}
static void reply_hdr(u_int32_t xid)
{ rlen=0; put32(xid); put32(1); put32(0); put32(0); put32(0); put32(0); }

/* ---------------- file table ---------------- */
struct nfile { const char*name; unsigned long long fileid; unsigned long long size; int pat; };
static struct nfile files[]= {
	{ "",   1, 512, 0 },
	{ "seq",  2, 1048576ULL, 0x33 },
	{ "seq2", 3, 1048576ULL, 0x99 },
	{ "big",  4, 0, 0 },     /* size filled from big_size */
};
static long read_rpcs[8];
static long total_rpcs;
static struct nfile* file_by_id(unsigned long long id)
{
	for(unsigned i=0;i<sizeof(files)/sizeof(files[0]);i++)
		if(files[i].fileid==id) return &files[i];
	return NULL;
}
static unsigned long long claimed_size(struct nfile*f)
{ return f->fileid==4 ? big_size : f->size; }

static void mkfh(unsigned char*fh, unsigned long long id)
{ memset(fh,0,32); memcpy(fh,"NF3D",4); *(unsigned long long*)(fh+4)=id; }
static unsigned long long fh_id(const unsigned char*fh)
{ if(fh==NULL||memcmp(fh,"NF3D",4)) return 0; return *(const unsigned long long*)(fh+4); }

/* fattr3 (84 bytes) */
static void put_fattr(struct nfile*f)
{
	int isdir = (f->fileid==1);
	unsigned long long sz = claimed_size(f);
	put32(isdir?2:1);           /* type */
	put32(isdir?0755:0644);     /* mode */
	put32(1);                   /* nlink */
	put32(1001); put32(1001);   /* uid gid */
	put64(sz); put64(sz);       /* size, used */
	put32(0); put32(0);         /* rdev specdata1,2 */
	put64(0x1234);              /* fsid */
	put64(f->fileid);           /* fileid */
	put32(0x5DE00000);put32(0); /* atime */
	put32(0x5DE00000);put32(0); /* mtime */
	put32(0x5DE00000);put32(0); /* ctime */
}

/* ---------------- pending delayed sends ---------------- */
struct pend { int fd; unsigned char*buf; int len; long long due_ms; int active; };
#define MAXPEND 64
static struct pend pends[MAXPEND];
static long long now_ms(void){ struct timespec ts; clock_gettime(CLOCK_MONOTONIC,&ts);
	return (long long)ts.tv_sec*1000+ts.tv_nsec/1000000; }
static void queue_send(int fd, int delay)
{
	struct pend*P=NULL; for(int i=0;i<MAXPEND;i++) if(!pends[i].active){P=&pends[i];break;}
	if(!P){ write(fd,rep,rlen); return; }
	P->buf=malloc(4+rlen);
	u_int32_t mark=0x80000000u|rlen; unsigned char hdr[4]={mark>>24,mark>>16,mark>>8,mark};
	memcpy(P->buf,hdr,4); memcpy(P->buf+4,rep,rlen);
	P->len=4+rlen; P->fd=fd; P->due_ms=now_ms()+delay; P->active=1;
}
static int send_record(int s)
{
	u_int32_t mark=0x80000000u|rlen;
	unsigned char hdr[4]={mark>>24,mark>>16,mark>>8,mark};
	unsigned char*buf=malloc(4+rlen); memcpy(buf,hdr,4); memcpy(buf+4,rep,rlen);
	int n=write(s,buf,4+rlen); free(buf); return n;
}

/* ---------------- pmap ---------------- */
static void handle_pmap_udp(int s)
{
	struct sockaddr_in from; socklen_t flen=sizeof(from); struct call c;
	int n=recvfrom(s,req,sizeof(req),0,(struct sockaddr*)&from,&flen);
	u_int32_t prog,port=0;
	if(n<=0||parse_call(n,&c)<0) return;
	if(c.vers==2&&c.proc==3){ prog=get32(c.args); port=(prog==PROG_MNTD)?PORT_MNTD:(prog==PROG_NFS)?PORT_NFS:0; reply_hdr(c.xid); put32(port); if(verbose) fprintf(stderr,"pmap v2 getport prog %u -> %u\n",prog,port); }
	else if((c.vers==3||c.vers==4)&&c.proc==3){ prog=get32(c.args); reply_hdr(c.xid);
		if(prog==PROG_MNTD) putstr("127.0.0.1.3.11");
		else if(prog==PROG_NFS) putstr("127.0.0.1.8.1");
		else putstr(""); }
	else reply_hdr(c.xid);
	sendto(s,rep,rlen,0,(struct sockaddr*)&from,sizeof(from));
}

/* ---------------- nfs procs ---------------- */
static void nfs_reply_common(struct conn*, struct call*, int delay);

struct conn { int fd; int is_nfs; int is_mntd; int need; int len; unsigned char buf[65536]; };
#define MAXCONN 16
static struct conn conns[MAXCONN];

static void handle_nfs(struct conn*c, struct call*cl)
{
	unsigned char fh[32]; struct nfile*f; const unsigned char*a=cl->args;
	int off=0;
	total_rpcs++;
	fprintf(stderr,"HN enter prog=%u vers=%u proc=%u alen=%d\n",cl->prog,cl->vers,cl->proc,cl->alen);
	switch(cl->proc){
	case 0: /* NULL */
		reply_hdr(cl->xid); break;
	case 1: { /* GETATTR */
		{ u_int32_t fhl=get32(a); memcpy(fh,a+4,fhl<=32?fhl:32); } f=file_by_id(fh_id(fh));
		reply_hdr(cl->xid);
		if(!f){ put32(2); break; } /* NOENT */
		put32(0); put_fattr(f);
		if(verbose){ fprintf(stderr,"GETATTR reply %d:",rlen);
			for(int q=20;q<rlen;q++) fprintf(stderr," %02x",rep[q]);
			fprintf(stderr,"\n"); }
		break; }
	case 3: { /* FSSTAT */
		{ u_int32_t fhl=get32(a); memcpy(fh,a+4,fhl<=32?fhl:32); } f=file_by_id(fh_id(fh));
		reply_hdr(cl->xid); if(!f){put32(2);break;} put32(0); put32(1); put_fattr(f);
		put64(1<<30); put64(1<<29); put64(1<<29);
		put64(1<<20); put64(1<<19); put64(1<<19); put32(0);
		break; }
	case 19: { /* FSINFO */
		{ u_int32_t fhl=get32(a); memcpy(fh,a+4,fhl<=32?fhl:32); } f=file_by_id(fh_id(fh));
		fprintf(stderr,"FSINFO fh=%02x%02x%02x%02x id=%llu f=%p\n",fh[0],fh[1],fh[2],fh[3],(unsigned long long)fh_id(fh),(void*)f);
		reply_hdr(cl->xid); if(!f){put32(2);break;} put32(0); put32(1); put_fattr(f);
		put32(32768); put32(32768); put32(32768);  /* rtmax rtpref rtmult */
		put32(32768); put32(32768); put32(32768);  /* wtmax wtpref wtmult */
		put32(8192);                                /* dtpref */
		put64(max_file);                            /* maxfilesize */
		if(verbose){ fprintf(stderr,"FSINFO reply len=%d:",rlen);
			for(int q=0;q<rlen;q++){ if(!(q%16)) fprintf(stderr,"\n %04x:",q); fprintf(stderr," %02x",rep[q]); }
			fprintf(stderr,"\n"); }
		put32(0); put32(1);                         /* time delta */
		put32(0x1f); break; }                       /* props */
	case 20: { /* PATHCONF */
		{ u_int32_t fhl=get32(a); memcpy(fh,a+4,fhl<=32?fhl:32); } f=file_by_id(fh_id(fh));
		reply_hdr(cl->xid); if(!f){put32(2);break;} put32(0); put32(1); put_fattr(f);
		put32(1); put32(255); put32(1); put32(1); put32(0); put32(1); break; }
	case 4: { /* LOOKUP */
		{ u_int32_t fhl=get32(a); memcpy(fh,a+4,fhl<=32?fhl:32); }
		const char*name=(const char*)a+4+32+4;
		int nl=get32(a+4+32);
		reply_hdr(cl->xid);
		struct nfile*hit=NULL;
		for(unsigned i=0;i<sizeof(files)/sizeof(files[0]);i++)
			if(files[i].fileid!=1&&(int)strlen(files[i].name)==nl&&!memcmp(files[i].name,name,nl)){hit=&files[i];break;}
		if(!hit){ put32(2); break; }
		mkfh(fh,hit->fileid);
		put32(0); putopaque(fh,32); put_fattr(hit);
		if(verbose){ fprintf(stderr,"LOOKUP %s reply %d:",hit->name,rlen);
			for(int q=20;q<rlen;q++) fprintf(stderr," %02x",rep[q]);
			fprintf(stderr,"\n"); }
		break; }
	case 7: { /* ACCESS */
		{ u_int32_t fhl=get32(a); memcpy(fh,a+4,fhl<=32?fhl:32); } f=file_by_id(fh_id(fh));
		reply_hdr(cl->xid); if(!f){put32(2);break;} put32(0); put32(1); put_fattr(f); put32(0x3f); break; }
	case 6: { /* READ */
		{ u_int32_t fhl=get32(a); memcpy(fh,a+4,fhl<=32?fhl:32); }
		off=4+32;
		unsigned long long roff=get64(a+off); off+=8;
		u_int32_t rcount=get32(a+off);
		f=file_by_id(fh_id(fh));
		reply_hdr(cl->xid);
		if(!f){ put32(2); break; }
		read_rpcs[f->fileid<8?f->fileid:0]++;
		if(verbose>1) fprintf(stderr,"READ %s off=%llu cnt=%u\n",f->name?f->name:"?",roff,rcount);
		put32(0); put32(1); put_fattr(f);
		unsigned long long csz=claimed_size(f);
		u_int32_t n=rcount;
		if(roff>=csz){ put32(0); put32(0); break; } /* count=0, EOF */
		if(roff+n>csz) n=(u_int32_t)(csz-roff);
		put32(n); put32(0);          /* count, EOF=false */
		unsigned char*db=malloc(n?n:1);
		for(u_int32_t i=0;i<n;i++) db[i]=(unsigned char)((roff+i)^f->pat);
		putbytes(db,n); pad4(n); free(db);
		queue_send(c->fd, (int)delay_ms); return; }   /* delayed path */
	case 16: { /* READDIR */
		{ u_int32_t fhl=get32(a); memcpy(fh,a+4,fhl<=32?fhl:32); } f=file_by_id(fh_id(fh));
		reply_hdr(cl->xid);
		if(!f||f->fileid!=1){ put32(2); break; }
		(void)0;
		put32(0); put32(1); put_fattr(f);
		/* cookieverf + entries (bool value_follows, fileid, name, cookie) + eof */
		putbytes("\0\0\0\0\0\0\0\0",8);
		put32(1); put64(2); putstr("seq");  put64(0x33);
		put32(1); put64(3); putstr("seq2"); put64(0x66);
		put32(1); put64(4); putstr("big");  put64(0x99);
		put32(0); put32(1); break; }
	default:
		reply_hdr(cl->xid); put32(0); put32(1); put_fattr(&files[0]);
		break; /* fattr for root; no fh deref */
	}
	send_record(c->fd);
}

static void dump_stats(void)
{
	FILE*s=fopen(stats_path,"w");
	if(!s) return;
	fprintf(s,"total_rpcs=%ld\n",total_rpcs);
	fprintf(s,"read_seq=%ld read_seq2=%ld read_big=%ld read_other=%ld\n",
		read_rpcs[2],read_rpcs[3],read_rpcs[4],read_rpcs[0]);
	fclose(s);
}

int main(void)
{
	const char*e;
	setvbuf(stderr,NULL,_IOLBF,0);
	if((e=getenv("NFS_DELAY_MS"))) delay_ms=atol(e);
	if((e=getenv("NFS_BIG_SIZE"))) big_size=strtoull(e,NULL,0);
	if((e=getenv("NFS_MAXFILE"))) max_file=strtoull(e,NULL,0);
	if((e=getenv("NFS_STATS"))) stats_path=e;
	if((e=getenv("VERBOSE"))) verbose=atoi(e);
	for(int i=0;i<MAXCONN;i++) conns[i].fd=-1;
	for(int i=0;i<MAXPEND;i++) pends[i].active=0;

	int us=socket(AF_INET,SOCK_DGRAM,0);
	int ms=socket(AF_INET,SOCK_STREAM,0);
	int ns=socket(AF_INET,SOCK_STREAM,0);
	int one=1;
	struct sockaddr_in a; memset(&a,0,sizeof(a)); a.sin_family=AF_INET; a.sin_addr.s_addr=htonl(INADDR_LOOPBACK);
	setsockopt(us,SOL_SOCKET,SO_REUSEADDR,&one,sizeof(one));
	a.sin_port=htons(PORT_PMAP); if(bind(us,(struct sockaddr*)&a,sizeof(a))<0){fprintf(stderr,"bind pmap: %s\n",strerror(errno));}
	fprintf(stderr,"DBG past pmap bind\n");
	setsockopt(ms,SOL_SOCKET,SO_REUSEADDR,&one,sizeof(one));
	a.sin_port=htons(PORT_MNTD); if(bind(ms,(struct sockaddr*)&a,sizeof(a))<0)fprintf(stderr,"bind mntd: %s\n",strerror(errno));
	if(listen(ms,4)<0)fprintf(stderr,"listen mntd: %s\n",strerror(errno));
	fprintf(stderr,"DBG past mntd\n");
	setsockopt(ns,SOL_SOCKET,SO_REUSEADDR,&one,sizeof(one));
	a.sin_port=htons(PORT_NFS); if(bind(ns,(struct sockaddr*)&a,sizeof(a))<0)fprintf(stderr,"bind nfs: %s\n",strerror(errno));
	if(listen(ns,16)<0)fprintf(stderr,"listen nfs: %s\n",strerror(errno));
	fprintf(stderr,"DBG past nfs\n");
	fprintf(stderr,"nfssrv3: pmap/udp %d mntd/tcp %d nfs/tcp %d delay=%ld big=%llu maxfile=%llu\n",
		PORT_PMAP,PORT_MNTD,PORT_NFS,delay_ms,big_size,max_file);
	for(;;){
		fd_set r; FD_ZERO(&r); FD_SET(us,&r); FD_SET(ms,&r); FD_SET(ns,&r);
		int mx=ns>ms?ns:ms; if(us>mx)mx=us; if(us<ms&&us>ns)mx=us;
		for(int i=0;i<MAXCONN;i++) if(conns[i].fd>=0){FD_SET(conns[i].fd,&r); if(conns[i].fd>mx)mx=conns[i].fd;}
		struct timeval tv={0,20000};
		/* next pending due */
		long long nxt=-1;
		for(int i=0;i<MAXPEND;i++) if(pends[i].active&&(nxt<0||pends[i].due_ms<nxt)) nxt=pends[i].due_ms;
		long long now=now_ms();
		if(nxt>=0&&nxt>now){ long long d=nxt-now; tv.tv_sec=d/1000; tv.tv_usec=(d%1000)*1000; }
		int src=select(mx+1,&r,NULL,NULL,&tv);
		if(src<0){ fprintf(stderr,"select err %s\n",strerror(errno)); continue; }
		now=now_ms();
		for(int i=0;i<MAXPEND;i++) if(pends[i].active&&pends[i].due_ms<=now){
			write(pends[i].fd,pends[i].buf,pends[i].len);
			free(pends[i].buf); pends[i].active=0;
		}
		if(FD_ISSET(us,&r)) handle_pmap_udp(us);
		if(FD_ISSET(ms,&r)||FD_ISSET(ns,&r)){
			int l=FD_ISSET(ms,&r)?ms:ns;
			int fd=accept(l,NULL,NULL);
			if(fd>=0){ struct conn*c=NULL;
				for(int i=0;i<MAXCONN;i++) if(conns[i].fd<0){c=&conns[i];break;}
				if(c){ memset(c,0,sizeof(*c)); c->fd=fd; c->is_mntd=(l==ms); c->is_nfs=(l==ns); c->need=-1; }
				else close(fd);
			}
		}
		for(int k=0;k<MAXCONN;k++){
			struct conn*c=&conns[k];
			if(c->fd<0||!FD_ISSET(c->fd,&r)) continue;
			unsigned char tmp[65536];
			ssize_t n=read(c->fd,tmp,sizeof(tmp));
			if(n<=0){ close(c->fd); c->fd=-1; continue; }
			if(c->len+n>(int)sizeof(c->buf)) { c->len=0; c->need=-1; continue; }
			memcpy(c->buf+c->len,tmp,n); c->len+=n;
			for(;;){
				if(c->need<0){
					if(c->len<4) break;
					u_int32_t mk=get32(c->buf);
					if(!(mk&0x80000000u)){ /* non-last fragment: unsupported */
						u_int32_t tot=4+(mk&0x7fffffffu);
						memmove(c->buf,c->buf+tot,c->len-tot); c->len-=tot; continue; }
					c->need=4+(mk&0x7fffffffu);
				}
				if(c->len<c->need) break;
				memcpy(req,c->buf+4,c->need-4);
				struct call cl;
				if(parse_call(c->need-4,&cl)==0){
					if(c->is_mntd){
						if(verbose) fprintf(stderr,"mntd proc %u\n",cl.proc);
						if(cl.proc==0){ reply_hdr(cl.xid); } else if(cl.proc==1){ /* MNT / */
							reply_hdr(cl.xid); put32(0);
							unsigned char fh[32]; mkfh(fh,1);
							putopaque(fh,32); put32(1); put32(1);
							if(verbose){ fprintf(stderr,"MNT reply %d bytes:",rlen);
								for(int q=0;q<rlen&&q<40;q++) fprintf(stderr," %02x",rep[q]);
								fprintf(stderr,"\n"); } }
						else if(cl.proc==3){ reply_hdr(cl.xid); put32(0); }
						else { reply_hdr(cl.xid); put32(0); }
						send_record(c->fd);
					} else {
						if(verbose) fprintf(stderr,"nfs proc %u len %d\n",cl.proc,cl.alen);
	handle_nfs(c,&cl);
					}
				}
				memmove(c->buf,c->buf+c->need,c->len-c->need);
				c->len-=c->need; c->need=-1;
				dump_stats();
			}
		}
	}
}
