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

/*
 GRE version 0 is specified in rfc1701.
 GRE version 0 has been respecified in rfc2784 as a subset of rfc1701.
 GRE version 1, as used by pptp, has been specified in rfc2637.
*/


/* GRE flag bits */
enum {
	GRE_chksum	= (1<<15),
	GRE_routing	= (1<<14),
	GRE_key		= (1<<13),
	GRE_seq		= (1<<12),
	GRE_srcrt		= (1<<11),
	GRE_recur	= (7<<8),
	GRE_ack		= (1<<7),
	GRE_version	= 0x7,
};


typedef struct Hdr	Hdr;
struct Hdr
{
	ushort flags;
	ushort proto;
	uchar version;
	ushort chksum;
	ushort offset;
	ulong key;
	ulong seq;
	ulong route;
	ulong ack;
};

enum
{
	Oproto,
};

static Field p_fields[] = 
{
	{"proto",		Fnum,	Oproto,	"encapsulated protocol",	} ,
	{0}
};

static Mux p_mux[] =
{
	{"pup",	0x0200, },
	{"xns",	0x0600, },
	{"ip",		0x0800, },
	{"chaos",	0x0804, },
	{"arp",	0x0806, },
	{"frarp",	0x0808, },
	{"vines",	0x0bad, },
	{"vinesecho",	0x0bae, },
	{"vinesloop",	0x0baf, },
	{"ppp",	0x880b, },
	{"llc",	0x007a, },
	{"dot1q",	0x8100, },
	{"eapol",	0x888e, },
	{"ip6",		0x86dd, },
	{"ether", 0x6558, },
	{0},
};

int
parthdrlen(ushort flags)
{
	return 4 + 
		(flags&GRE_chksum || flags&GRE_routing) ? 4 : 0 +
		flags&GRE_key ? 4 : 0 +
		flags&GRE_seq ? 4 : 0 +
		flags&GRE_ack ? 4 : 0;
}

int
parsehdr(Hdr *h, uchar *s, uchar *e)
{
	uchar *p;
	uchar n;

	if(e - s < 4)
		return -1;

	p = s;

	h->flags = NetS(p);
	p += 2;
	h->proto = NetS(p);
	p += 2;
	h->version = h->flags&GRE_version;

	if(parthdrlen(h->flags) > e - s)
		return -1;

	if(h->flags&(GRE_chksum|GRE_routing)){
		h->chksum = NetS(p);
		p += 2;
		h->offset = NetS(p);
		p += 2;
	}
	if(h->flags&GRE_key){
		h->key = NetL(p);
		p += 4;
	}
	if(h->flags&GRE_seq){
		h->seq = NetL(p);
		p += 4;
	}
	if(h->flags&GRE_ack){
		h->ack = NetL(p);
		p += 4;
	}
	if(h->flags&GRE_routing){
		for(;;){
			if(e - p < 4)
				return -1;
			if((n = p[3]) == 0)
				break;
			p += n;
		}
	}

	return p - s;
}

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

	if(f->op == '='){
		compile_cmp(gre.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 = Oproto;
			return;
		}
	sysfatal("unknown gre field or protocol: %s", f->s);
}

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

	len = parsehdr(&h, m->ps, m->pe);
	if(len < 0)
		return -1;
	m->ps += len;

	switch(f->subop){
	case Oproto:
		return h.proto == f->ulv;
	}
	return 0;
}

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

	len = parsehdr(&h, m->ps, m->pe);
	if(len < 0)
		return -1;
	m->ps += len;

	demux(p_mux, h.proto, h.proto, m, &dump);

	m->p = seprint(m->p, m->e, "version=%d proto=%#ux flags=%#.4ux", h.version, h.proto, h.flags);
	if(h.flags&GRE_chksum)
		m->p = seprint(m->p, m->e, " checksum=%#.4ux", h.chksum);
	if(h.flags&GRE_key)
		m->p = seprint(m->p, m->e, " key=%#.8ulx", h.key);
	if(h.flags&GRE_seq)
		m->p = seprint(m->p, m->e, " seq=%#.8ulx", h.seq);
	if(h.flags&GRE_ack)
		m->p = seprint(m->p, m->e, " ack=%#.8ulx", h.ack);
	if(h.flags&GRE_routing)
		m->p = seprint(m->p, m->e, " offset=%#ux haverouting", h.offset);
	if(h.version == 0)
		m->p = seprint(m->p, m->e, " recursion=%ud", (h.flags&GRE_recur)>>8);
	
	return 0;
}

Proto gre =
{
	"gre",
	p_compile,
	p_filter,
	p_seprint,
	p_mux,
	"%#.4ux",
	p_fields,
	defaultframer,
};
