#include <u.h>
#include <libc.h>
#include <ip.h>
#include "dat.h"
#include "protos.h"

typedef struct Hdr	Hdr;
struct Hdr
{
	uchar	vcf[4];			/* Version and header length */
	uchar	length[2];		/* packet length */
	uchar	proto;			/* Protocol */
	uchar	ttl;			/* Time to live */
	uchar	src[IPaddrlen];		/* IP source */
	uchar	dst[IPaddrlen];		/* IP destination */
};

enum
{
	IP6HDR		= 40,		/* sizeof(Iphdr) */
	IP_VER		= 0x60,		/* Using IP version 4 */
	HBH_HDR		= 0,
	ROUT_HDR	= 43,
	FRAG_HDR	= 44,
	FRAG_HSZ	= 8, 		/* in bytes */
	DEST_HDR	= 60,
};

static Mux p_mux[] =
{
	{ "igmp", 2, },
	{ "ggp", 3, },
	{ "ip", 4, },
	{ "st", 5, },
	{ "tcp", 6, },
	{ "ucl", 7, },
	{ "egp", 8, },
	{ "igp", 9, },
	{ "bbn-rcc-mon", 10, },
	{ "nvp-ii", 11, },
	{ "pup", 12, },
	{ "argus", 13, },
	{ "emcon", 14, },
	{ "xnet", 15, },
	{ "chaos", 16, },
	{ "udp", 17, },
	{ "mux", 18, },
	{ "dcn-meas", 19, },
	{ "hmp", 20, },
	{ "prm", 21, },
	{ "xns-idp", 22, },
	{ "trunk-1", 23, },
	{ "trunk-2", 24, },
	{ "leaf-1", 25, },
	{ "leaf-2", 26, },
	{ "rdp", 27, },
	{ "irtp", 28, },
	{ "iso-tp4", 29, },
	{ "netblt", 30, },
	{ "mfe-nsp", 31, },
	{ "merit-inp", 32, },
	{ "sep", 33, },
	{ "3pc", 34, },
	{ "idpr", 35, },
	{ "xtp", 36, },
	{ "ddp", 37, },
	{ "idpr-cmtp", 38, },
	{ "tp++", 39, },
	{ "il", 40, },
	{ "sip", 41, },
	{ "sdrp", 42, },
	{ "idrp", 45, },
	{ "rsvp", 46, },
	{ "gre", 47, },
	{ "mhrp", 48, },
	{ "bna", 49, },
	{ "sipp-esp", 50, },
	{ "sipp-ah", 51, },
	{ "i-nlsp", 52, },
	{ "swipe", 53, },
	{ "nhrp", 54, },
	{ "icmp6", 58, },
	{ "any", 61, },
	{ "cftp", 62, },
	{ "any", 63, },
	{ "sat-expak", 64, },
	{ "kryptolan", 65, },
	{ "rvd", 66, },
	{ "ippc", 67, },
	{ "any", 68, },
	{ "sat-mon", 69, },
	{ "visa", 70, },
	{ "ipcv", 71, },
	{ "cpnx", 72, },
	{ "cphb", 73, },
	{ "wsn", 74, },
	{ "pvp", 75, },
	{ "br-sat-mon", 76, },
	{ "sun-nd", 77, },
	{ "wb-mon", 78, },
	{ "wb-expak", 79, },
	{ "iso-ip", 80, },
	{ "vmtp", 81, },
	{ "secure-vmtp", 82, },
	{ "vines", 83, },
	{ "ttp", 84, },
	{ "nsfnet-igp", 85, },
	{ "dgp", 86, },
	{ "tcf", 87, },
	{ "igrp", 88, },
	{ "ospf", 89, },
	{ "sprite-rpc", 90, },
	{ "larp", 91, },
	{ "mtp", 92, },
	{ "ax.25", 93, },
	{ "ipip", 94, },
	{ "micp", 95, },
	{ "scc-sp", 96, },
	{ "etherip", 97, },
	{ "encap", 98, },
	{ "any", 99, },
	{ "gmtp", 100, },
	{ "rudp", 254, },
	{ 0 }
};

enum
{
	Os,	/* source */
	Od,	/* destination */
	Osd,	/* source or destination */
	Ot,	/* type */
};

static Field p_fields[] =
{
	{"s",	Fv6ip,	Os,	"source address",	} ,
	{"d",	Fv6ip,	Od,	"destination address",	} ,
	{"a",	Fv6ip,	Osd,	"source|destination address",} ,
	{"t",	Fnum,	Ot,	"sub protocol number",	} ,
	{0}
};

static void
p_compile(Filter *f)
{
	Mux *m;

	if(f->op == '='){
		compile_cmp(ip6.name, f, p_fields);
		return;
	}
	for(m = p_mux; m->name != nil; m++)
		if(strcmp(f->s, m->name) == 0){
			f->pr = m->pr;
			f->ulv = m->val;
			f->subop = Ot;
			return;
		}
	sysfatal("unknown ip6 field or protocol: %s", f->s);
}

static int
v6hdrlen(Hdr *h, int *nexthdr)
{
	int plen, len = IP6HDR;
	int pktlen = IP6HDR + NetS(h->length);
	uchar *pkt = (uchar*) h;

	pkt += len;
	plen = len;

	*nexthdr = h->proto;
	while (*nexthdr == HBH_HDR || *nexthdr == ROUT_HDR ||
	    *nexthdr == FRAG_HDR || *nexthdr == DEST_HDR) {
		if (*nexthdr == FRAG_HDR)
			len = FRAG_HSZ;
		else
			len = ((int)pkt[1] + 1) * 8;

		if (plen + len > pktlen)
			return -1;

		*nexthdr = *pkt;
		pkt += len;
		plen += len;
	}
	return plen;
}

static int
p_filter(Filter *f, Msg *m)
{
	Hdr *h;
	int hlen, proto;

	if(m->pe - m->ps < IP6HDR)
		return 0;

	h = (Hdr*)m->ps;

	if ((hlen = v6hdrlen(h, &proto)) < 0)
		return 0;
	else
		m->ps += hlen;
	switch(f->subop){
	case Os:
		return memcmp(h->src, f->a, IPaddrlen) == 0;
	case Od:
		return memcmp(h->dst, f->a, IPaddrlen) == 0;
	case Osd:
		return memcmp(h->src, f->a, IPaddrlen) == 0 ||
			memcmp(h->dst, f->a, IPaddrlen) == 0;
	case Ot:
		return proto == f->ulv;
	}
	return 0;
}

static int
v6hdr_seprint(Msg *m)
{
	int plen, len = IP6HDR;
	uchar *pkt = m->ps;
	Hdr *h = (Hdr *)pkt;
	int pktlen = IP6HDR + NetS(h->length);
	int nexthdr = h->proto;

	pkt += len;
	plen = len;

	while (nexthdr == HBH_HDR || nexthdr == ROUT_HDR ||
	    nexthdr == FRAG_HDR || nexthdr == DEST_HDR) {
		switch (nexthdr) {
		case FRAG_HDR:
			m->p = seprint(m->p, m->e, "\n	  xthdr=frag id=%d "
				"offset=%d pr=%d more=%d res1=%d res2=%d",
				NetL(pkt+4), NetS(pkt+2) & ~7, (int)*pkt,
				(int)(*(pkt+3) & 0x1), (int)*(pkt+1),
				(int)(*(pkt+3) & 0x6));
			len = FRAG_HSZ;
			break;

		case HBH_HDR:
		case ROUT_HDR:
		case DEST_HDR:
			len = ((int)*(pkt+1) + 1) * 8;
			break;
		}

		if (plen + len > pktlen) {
			m->p = seprint(m->p, m->e, "bad pkt");
			m->pr = &dump;
			return -1;
		}
		nexthdr = *pkt;
		plen += len;
		pkt += len;
	}

	demux(p_mux, nexthdr, nexthdr, m, &dump);

	m->ps = pkt;
	return 1;
}

static int
p_seprint(Msg *m)
{
	int len;
	Hdr *h;

	if(m->pe - m->ps < IP6HDR)
		return -1;
	h = (Hdr*)m->ps;

	/* truncate the message if there's extra */
	len = NetS(h->length) + IP6HDR;
	if(len < m->pe - m->ps)
		m->pe = m->ps + len;

	m->p = seprint(m->p, m->e, "s=%I d=%I ttl=%3d pr=%d ln=%d",
		h->src, h->dst, h->ttl, h->proto, NetS(h->length));
	v6hdr_seprint(m);
	return 0;
}

Proto ip6 =
{
	"ip6",
	p_compile,
	p_filter,
	p_seprint,
	p_mux,
	"%lud",
	p_fields,
	defaultframer,
};
