#include "u.h"
#include "lib.h"
#include "dat.h"
#include "fns.h"
#include "error.h"

#include "devip.h"

void		hnputl(void *p, unsigned long v);
void		hnputs(void *p, unsigned short v);
unsigned long	nhgetl(void *p);
unsigned short	nhgets(void *p);
unsigned long	parseip(char *to, char *from);
void	csclose(Chan*);
long	csread(Chan*, void*, long, vlong);
long	cswrite(Chan*, void*, long, vlong);

void osipinit(void);

enum
{
	Qtopdir		= 1,	/* top level directory */
	Qcs,
	Qprotodir,		/* directory for a protocol */
	Qclonus,
	Qconvdir,		/* directory for a conversation */
	Qdata,
	Qctl,
	Qstatus,
	Qremote,
	Qlocal,
	Qlisten,

	MAXPROTO	= 4
};
#define TYPE(x) 	((int)((x).path & 0xf))
#define CONV(x) 	((int)(((x).path >> 4)&0xfff))
#define PROTO(x) 	((int)(((x).path >> 16)&0xff))
#define QID(p, c, y) 	(((p)<<16) | ((c)<<4) | (y))

typedef struct Proto	Proto;
typedef struct Conv	Conv;
struct Conv
{
	int	x;
	Ref	r;
	int	sfd;
	int	perm;
	char	owner[KNAMELEN];
	char*	state;
	ulong	laddr;
	ushort	lport;
	ulong	raddr;
	ushort	rport;
	int	restricted;
	char	cerr[KNAMELEN];
	Proto*	p;
};

struct Proto
{
	Lock	l;
	int	x;
	int	stype;
	char	name[KNAMELEN];
	int	nc;
	int	maxconv;
	Conv**	conv;
	Qid	qid;
};

static	int	np;
static	Proto	proto[MAXPROTO];
int	eipfmt(Fmt*);

static	Conv*	protoclone(Proto*, char*, int);
static	void	setladdr(Conv*);

int
ipgen(Chan *c, char *nname, Dirtab *d, int nd, int s, Dir *dp)
{
	Qid q;
	Conv *cv;
	char *p;

	USED(nname);
	q.vers = 0;
	q.type = 0;
	switch(TYPE(c->qid)) {
	case Qtopdir:
		if(s >= 1+np)
			return -1;

		if(s == 0){
			q.path = QID(s, 0, Qcs);
			devdir(c, q, "cs", 0, "network", 0666, dp);
		}else{
			s--;
			q.path = QID(s, 0, Qprotodir);
			q.type = QTDIR;
			devdir(c, q, proto[s].name, 0, "network", DMDIR|0555, dp);
		}
		return 1;
	case Qprotodir:
		if(s < proto[PROTO(c->qid)].nc) {
			cv = proto[PROTO(c->qid)].conv[s];
			sprint(up->genbuf, "%d", s);
			q.path = QID(PROTO(c->qid), s, Qconvdir);
			q.type = QTDIR;
			devdir(c, q, up->genbuf, 0, cv->owner, DMDIR|0555, dp);
			return 1;
		}
		s -= proto[PROTO(c->qid)].nc;
		switch(s) {
		default:
			return -1;
		case 0:
			p = "clone";
			q.path = QID(PROTO(c->qid), 0, Qclonus);
			break;
		}
		devdir(c, q, p, 0, "network", 0555, dp);
		return 1;
	case Qconvdir:
		cv = proto[PROTO(c->qid)].conv[CONV(c->qid)];
		switch(s) {
		default:
			return -1;
		case 0:
			q.path = QID(PROTO(c->qid), CONV(c->qid), Qdata);
			devdir(c, q, "data", 0, cv->owner, cv->perm, dp);
			return 1;
		case 1:
			q.path = QID(PROTO(c->qid), CONV(c->qid), Qctl);
			devdir(c, q, "ctl", 0, cv->owner, cv->perm, dp);
			return 1;
		case 2:
			p = "status";
			q.path = QID(PROTO(c->qid), CONV(c->qid), Qstatus);
			break;
		case 3:
			p = "remote";
			q.path = QID(PROTO(c->qid), CONV(c->qid), Qremote);
			break;
		case 4:
			p = "local";
			q.path = QID(PROTO(c->qid), CONV(c->qid), Qlocal);
			break;
		case 5:
			p = "listen";
			q.path = QID(PROTO(c->qid), CONV(c->qid), Qlisten);
			break;
		}
		devdir(c, q, p, 0, cv->owner, 0444, dp);
		return 1;
	}
	return -1;
}

static void
newproto(char *name, int type, int maxconv)
{
	int l;
	Proto *p;

	if(np >= MAXPROTO) {
		print("no %s: increase MAXPROTO", name);
		return;
	}

	p = &proto[np];
	strcpy(p->name, name);
	p->stype = type;
	p->qid.path = QID(np, 0, Qprotodir);
	p->qid.type = QTDIR;
	p->x = np++;
	p->maxconv = maxconv;
	l = sizeof(Conv*)*(p->maxconv+1);
	p->conv = mallocz(l, 1);
	if(p->conv == 0)
		panic("no memory");
}

void
ipinit(void)
{
	osipinit();

	newproto("udp", S_UDP, 10);
	newproto("tcp", S_TCP, 30);

	fmtinstall('I', eipfmt);
	fmtinstall('E', eipfmt);
	
}

Chan *
ipattach(char *spec)
{
	Chan *c;

	c = devattach('I', spec);
	c->qid.path = QID(0, 0, Qtopdir);
	c->qid.type = QTDIR;
	c->qid.vers = 0;
	return c;
}

static Walkqid*
ipwalk(Chan *c, Chan *nc, char **name, int nname)
{
	return devwalk(c, nc, name, nname, 0, 0, ipgen);
}

int
ipstat(Chan *c, uchar *dp, int n)
{
	return devstat(c, dp, n, 0, 0, ipgen);
}

Chan *
ipopen(Chan *c, int omode)
{
	Proto *p;
	ulong raddr;
	ushort rport;
	int perm, sfd;
	Conv *cv, *lcv;

	omode &= 3;
	perm = 0;
	switch(omode) {
	case OREAD:
		perm = 4;
		break;
	case OWRITE:
		perm = 2;
		break;
	case ORDWR:
		perm = 6;
		break;
	}

	switch(TYPE(c->qid)) {
	default:
		break;
	case Qtopdir:
	case Qprotodir:
	case Qconvdir:
	case Qstatus:
	case Qremote:
	case Qlocal:
		if(omode != OREAD)
			error(Eperm);
		break;
	case Qclonus:
		p = &proto[PROTO(c->qid)];
		cv = protoclone(p, up->user, -1);
		if(cv == 0)
			error(Enodev);
		c->qid.path = QID(p->x, cv->x, Qctl);
		c->qid.vers = 0;
		break;
	case Qdata:
	case Qctl:
		p = &proto[PROTO(c->qid)];
		lock(&p->l);
		cv = p->conv[CONV(c->qid)];
		lock(&cv->r.lk);
		if((perm & (cv->perm>>6)) != perm) {
			if(strcmp(up->user, cv->owner) != 0 ||
		 	  (perm & cv->perm) != perm) {
				unlock(&cv->r.lk);
				unlock(&p->l);
				error(Eperm);
			}
		}
		cv->r.ref++;
		if(cv->r.ref == 1) {
			memmove(cv->owner, up->user, KNAMELEN);
			cv->perm = 0660;
		}
		unlock(&cv->r.lk);
		unlock(&p->l);
		break;
	case Qlisten:
		p = &proto[PROTO(c->qid)];
		lcv = p->conv[CONV(c->qid)];
		sfd = so_accept(lcv->sfd, &raddr, &rport);
		cv = protoclone(p, up->user, sfd);
		if(cv == 0) {
			close(sfd);
			error(Enodev);
		}
		cv->raddr = raddr;
		cv->rport = rport;
		setladdr(cv);
		cv->state = "Established";
		c->qid.path = QID(p->x, cv->x, Qctl);
		break;
	}
	c->mode = openmode(omode);
	c->flag |= COPEN;
	c->offset = 0;
	return c;
}

void
ipclose(Chan *c)
{
	Conv *cc;

	switch(TYPE(c->qid)) {
	case Qcs:
		csclose(c);
		break;
	case Qdata:
	case Qctl:
		if((c->flag & COPEN) == 0)
			break;
		cc = proto[PROTO(c->qid)].conv[CONV(c->qid)];
		if(decref(&cc->r) != 0)
			break;
		strcpy(cc->owner, "network");
		cc->perm = 0666;
		cc->state = "Closed";
		cc->laddr = 0;
		cc->raddr = 0;
		cc->lport = 0;
		cc->rport = 0;
		close(cc->sfd);
		break;
	}
}

long
ipread(Chan *ch, void *a, long n, vlong offset)
{
	int r;
	Conv *c;
	Proto *x;
	uchar ip[4];
	char buf[128], *p;

/*print("ipread %s %lux\n", c2name(ch), (long)ch->qid.path);*/
	p = a;
	switch(TYPE(ch->qid)) {
	default:
		error(Eperm);
	case Qcs:
		return csread(ch, a, n, offset);
	case Qprotodir:
	case Qtopdir:
	case Qconvdir:
		return devdirread(ch, a, n, 0, 0, ipgen);
	case Qctl:
		sprint(buf, "%d", CONV(ch->qid));
		return readstr(offset, p, n, buf);
	case Qremote:
		c = proto[PROTO(ch->qid)].conv[CONV(ch->qid)];
		hnputl(ip, c->raddr);
		sprint(buf, "%I!%d\n", ip, c->rport);
		return readstr(offset, p, n, buf);
	case Qlocal:
		c = proto[PROTO(ch->qid)].conv[CONV(ch->qid)];
		hnputl(ip, c->laddr);
		sprint(buf, "%I!%d\n", ip, c->lport);
		return readstr(offset, p, n, buf);
	case Qstatus:
		x = &proto[PROTO(ch->qid)];
		c = x->conv[CONV(ch->qid)];
		sprint(buf, "%s/%d %d %s \n",
			c->p->name, c->x, c->r.ref, c->state);
		return readstr(offset, p, n, buf);
	case Qdata:
		c = proto[PROTO(ch->qid)].conv[CONV(ch->qid)];
		r = so_recv(c->sfd, a, n, 0);
		if(r < 0){
			oserrstr();
			nexterror();
		}
		return r;
	}
}

static void
setladdr(Conv *c)
{
	so_getsockname(c->sfd, &c->laddr, &c->lport);
}

static void
setlport(Conv *c)
{
	if(c->restricted == 0 && c->lport == 0)
		return;

	so_bind(c->sfd, c->restricted, c->lport);
}

static void
setladdrport(Conv *c, char *str)
{
	char *p, addr[4];

	p = strchr(str, '!');
	if(p == 0) {
		p = str;
		c->laddr = 0;
	}
	else {
		*p++ = 0;
		parseip(addr, str);
		c->laddr = nhgetl((uchar*)addr);
	}
	if(*p == '*')
		c->lport = 0;
	else
		c->lport = atoi(p);

	setlport(c);
}

static char*
setraddrport(Conv *c, char *str)
{
	char *p, addr[4];

	p = strchr(str, '!');
	if(p == 0)
		return "malformed address";
	*p++ = 0;
	parseip(addr, str);
	c->raddr = nhgetl((uchar*)addr);
	c->rport = atoi(p);
	p = strchr(p, '!');
	if(p) {
		if(strcmp(p, "!r") == 0)
			c->restricted = 1;
	}
	return 0;
}

long
ipwrite(Chan *ch, void *a, long n, vlong offset)
{
	Conv *c;
	Proto *x;
	int r, nf;
	char *p, *fields[3], buf[128];

	switch(TYPE(ch->qid)) {
	default:
		error(Eperm);
	case Qcs:
		return cswrite(ch, a, n, offset);
	case Qctl:
		x = &proto[PROTO(ch->qid)];
		c = x->conv[CONV(ch->qid)];
		if(n > sizeof(buf)-1)
			n = sizeof(buf)-1;
		memmove(buf, a, n);
		buf[n] = '\0';

		nf = tokenize(buf, fields, 3);
		if(strcmp(fields[0], "connect") == 0){
			switch(nf) {
			default:
				error("bad args to connect");
			case 2:
				p = setraddrport(c, fields[1]);
				if(p != 0)
					error(p);
				break;
			case 3:
				p = setraddrport(c, fields[1]);
				if(p != 0)
					error(p);
				c->lport = atoi(fields[2]);
				setlport(c);
				break;
			}
			so_connect(c->sfd, c->raddr, c->rport);
			setladdr(c);
			c->state = "Established";
			return n;
		}
		if(strcmp(fields[0], "announce") == 0) {
			switch(nf){
			default:
				error("bad args to announce");
			case 2:
				setladdrport(c, fields[1]);
				break;
			}
			so_listen(c->sfd);
			c->state = "Announced";
			return n;
		}
		if(strcmp(fields[0], "bind") == 0){
			switch(nf){
			default:
				error("bad args to bind");
			case 2:
				c->lport = atoi(fields[1]);
				break;
			}
			setlport(c);
			return n;
		}
		error("bad control message");
	case Qdata:
		x = &proto[PROTO(ch->qid)];
		c = x->conv[CONV(ch->qid)];
		r = so_send(c->sfd, a, n, 0);
		if(r < 0){
			oserrstr();
			nexterror();
		}
		return r;
	}
	return n;
}

static Conv*
protoclone(Proto *p, char *user, int nfd)
{
	Conv *c, **pp, **ep;

	c = 0;
	lock(&p->l);
	if(waserror()) {
		unlock(&p->l);
		nexterror();
	}
	ep = &p->conv[p->maxconv];
	for(pp = p->conv; pp < ep; pp++) {
		c = *pp;
		if(c == 0) {
			c = mallocz(sizeof(Conv), 1);
			if(c == 0)
				error(Enomem);
			lock(&c->r.lk);
			c->r.ref = 1;
			c->p = p;
			c->x = pp - p->conv;
			p->nc++;
			*pp = c;
			break;
		}
		lock(&c->r.lk);
		if(c->r.ref == 0) {
			c->r.ref++;
			break;
		}
		unlock(&c->r.lk);
	}
	if(pp >= ep) {
		unlock(&p->l);
		poperror();
		return 0;
	}

	strcpy(c->owner, user);
	c->perm = 0660;
	c->state = "Closed";
	c->restricted = 0;
	c->laddr = 0;
	c->raddr = 0;
	c->lport = 0;
	c->rport = 0;
	c->sfd = nfd;
	if(nfd == -1)
		c->sfd = so_socket(p->stype);

	unlock(&c->r.lk);
	unlock(&p->l);
	poperror();
	return c;
}

enum
{
	Isprefix= 16,
};

uchar prefixvals[256] =
{
/*0x00*/ 0 | Isprefix,
		   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0x10*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0x20*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0x30*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0x40*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0x50*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0x60*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0x70*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0x80*/ 1 | Isprefix,
		   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0x90*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0xA0*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0xB0*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0xC0*/ 2 | Isprefix,
		   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0xD0*/	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0xE0*/ 3 | Isprefix,
		   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*0xF0*/ 4 | Isprefix,
		   0, 0, 0, 0, 0, 0, 0, 
/*0xF8*/ 5 | Isprefix,
		   0, 0, 0, 
/*0xFC*/ 6 | Isprefix,
		   0,
/*0xFE*/ 7 | Isprefix,
/*0xFF*/ 8 | Isprefix,
};

int
eipfmt(Fmt *f)
{
	char buf[5*8];
	static char *efmt = "%.2lux%.2lux%.2lux%.2lux%.2lux%.2lux";
	static char *ifmt = "%d.%d.%d.%d";
	uchar *p, ip[16];
	ulong ul;

	switch(f->r) {
	case 'E':		/* Ethernet address */
		p = va_arg(f->args, uchar*);
		snprint(buf, sizeof buf, efmt, p[0], p[1], p[2], p[3], p[4], p[5]);
		return fmtstrcpy(f, buf);

	case 'I':
		ul = va_arg(f->args, ulong);
		hnputl(ip, ul);
		snprint(buf, sizeof buf, ifmt, ip[0], ip[1], ip[2], ip[3]);
		return fmtstrcpy(f, buf);
	}
	return fmtstrcpy(f, "(eipfmt)");
}

void
hnputl(void *p, unsigned long v)
{
	unsigned char *a;

	a = p;
	a[0] = v>>24;
	a[1] = v>>16;
	a[2] = v>>8;
	a[3] = v;
}

void
hnputs(void *p, unsigned short v)
{
	unsigned char *a;

	a = p;
	a[0] = v>>8;
	a[1] = v;
}

unsigned long
nhgetl(void *p)
{
	unsigned char *a;
	a = p;
	return (a[0]<<24)|(a[1]<<16)|(a[2]<<8)|(a[3]<<0);
}

unsigned short
nhgets(void *p)
{
	unsigned char *a;
	a = p;
	return (a[0]<<8)|(a[1]<<0);
}

#define CLASS(p) ((*(unsigned char*)(p))>>6)

unsigned long
parseip(char *to, char *from)
{
	int i;
	char *p;

	p = from;
	memset(to, 0, 4);
	for(i = 0; i < 4 && *p; i++){
		to[i] = strtoul(p, &p, 10);
		if(*p != '.' && *p != 0){
			memset(to, 0, 4);
			return 0;
		}
		if(*p == '.')
			p++;
	}
	switch(CLASS(to)){
	case 0:	/* class A - 1 byte net */
	case 1:
		if(i == 3){
			to[3] = to[2];
			to[2] = to[1];
			to[1] = 0;
		} else if (i == 2){
			to[3] = to[1];
			to[1] = 0;
		}
		break;
	case 2:	/* class B - 2 byte net */
		if(i == 3){
			to[3] = to[2];
			to[2] = 0;
		}
		break;
	}
	return nhgetl(to);
}

void
csclose(Chan *c)
{
	free(c->aux);
}

long
csread(Chan *c, void *a, long n, vlong offset)
{
	if(c->aux == nil)
		return 0;
	return readstr(offset, a, n, c->aux);
}

static struct
{
	char *name;
	uint num;
} tab[] = {
	"cs", 1,
	"echo", 7,
	"discard", 9,
	"systat", 11,
	"daytime", 13,
	"netstat", 15,
	"chargen", 19,
	"ftp-data", 20,
	"ftp", 21,
	"ssh", 22,
	"telnet", 23,
	"smtp", 25,
	"time", 37,
	"whois", 43,
	"dns", 53,
	"domain", 53,
	"uucp", 64,
	"gopher", 70,
	"rje", 77,
	"finger", 79,
	"http", 80,
	"link", 87,
	"supdup", 95,
	"hostnames", 101,
	"iso-tsap", 102,
	"x400", 103,
	"x400-snd", 104,
	"csnet-ns", 105,
	"pop-2", 109,
	"pop3", 110,
	"portmap", 111,
	"uucp-path", 117,
	"nntp", 119,
	"netbios", 139,
	"imap4", 143,
	"NeWS", 144,
	"print-srv", 170,
	"z39.50", 210,
	"fsb", 400,
	"sysmon", 401,
	"proxy", 402,
	"proxyd", 404,
	"https", 443,
	"cifs", 445,
	"ssmtp", 465,
	"rexec", 512,
	"login", 513,
	"shell", 514,
	"printer", 515,
	"courier", 530,
	"cscan", 531,
	"uucp", 540,
	"snntp", 563,
	"9fs", 564,
	"whoami", 565,
	"guard", 566,
	"ticket", 567,
	"dlsftp", 666,
	"fmclient", 729,
	"imaps", 993,
	"pop3s", 995,
	"ingreslock", 1524,
	"pptp", 1723,
	"nfs", 2049,
	"webster", 2627,
	"weather", 3000,
	"secstore", 5356,
	"Xdisplay", 6000,
	"styx", 6666,
	"mpeg", 6667,
	"rstyx", 6668,
	"infdb", 6669,
	"infsigner", 6671,
	"infcsigner", 6672,
	"inflogin", 6673,
	"bandt", 7330,
	"face", 32000,
	"dhashgate", 11978,
	"exportfs", 17007,
	"rexexec", 17009,
	"ncpu", 17010,
	"cpu", 17013,
	"glenglenda1", 17020,
	"glenglenda2", 17021,
	"glenglenda3", 17022,
	"glenglenda4", 17023,
	"glenglenda5", 17024,
	"glenglenda6", 17025,
	"glenglenda7", 17026,
	"glenglenda8", 17027,
	"glenglenda9", 17028,
	"glenglenda10", 17029,
	"flyboy", 17032,
	"dlsftp", 17033,
	"venti", 17034,
	"wiki", 17035,
	"vica", 17036,
	0
};

static int
lookupport(char *s)
{
	int i;
	char buf[10], *p;

	i = strtol(s, &p, 0);
	if(*s && *p == 0)
		return i;

	i = so_getservbyname(s, "tcp", buf);
	if(i != -1)
		return atoi(buf);
	for(i=0; tab[i].name; i++)
		if(strcmp(s, tab[i].name) == 0)
			return tab[i].num;
	return 0;
}

static ulong
lookuphost(char *s)
{
	char to[4];
	ulong ip;

	memset(to, 0, sizeof to);
	parseip(to, s);
	ip = nhgetl(to);
	if(ip != 0)
		return ip;
	if((s = hostlookup(s)) == nil)
		return 0;
	parseip(to, s);
	ip = nhgetl(to);
	free(s);
	return ip;
}

long
cswrite(Chan *c, void *a, long n, vlong offset)
{
	char *f[4];
	char *s, *ns;
	ulong ip;
	int nf, port;

	s = malloc(n+1);
	if(s == nil)
		error(Enomem);
	if(waserror()){
		free(s);
		nexterror();
	}
	memmove(s, a, n);
	s[n] = 0;
	nf = getfields(s, f, nelem(f), 0, "!");
	if(nf != 3)
		error("can't translate");

	port = lookupport(f[2]);
	if(port <= 0)
		error("no translation for port found");

	ip = lookuphost(f[1]);
	if(ip == 0)
		error("no translation for host found");

	ns = smprint("/net/%s/clone %I!%d", f[0], ip, port);
	if(ns == nil)
		error(Enomem);
	free(c->aux);
	c->aux = ns;
	poperror();
	free(s);
	return n;
}

Dev ipdevtab = 
{
	'I',
	"ip",

	devreset,
	ipinit,
	devshutdown,
	ipattach,
	ipwalk,
	ipstat,
	ipopen,
	devcreate,
	ipclose,
	ipread,
	devbread,
	ipwrite,
	devbwrite,
	devremove,
	devwstat,
};

