// Copyright (C) FIT VUT 
// Petr Lampa <lampa@fit.vutbr.cz>
// $Id$
// vi:set ts=8 sts=8 sw=8:
//

#ifndef __NDWATCH_VLAN_H__
#define __NDWATCH_VLAN_H__

// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |          TPID                 | PCP |C|        VID            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
class Vlan8021q: public Packet 
{
private:
	Short _tpid;	// tag protocol identifier
	Octet _pcp;	// priority
	bool _cfi;	// canonical format indicator
	Short _vid;	// vlan id

	Vlan8021q(const Vlan8021q&);
	Vlan8021q& operator=(const Vlan8021q&);
public:

	Vlan8021q(): _tpid(0x8100), _pcp(0), _cfi(false), _vid(0) { }
	explicit Vlan8021q(Short tpid): _tpid(tpid), _pcp(0), _cfi(false), _vid(0) { }

	inline bool pcp(unsigned val) { 
		if (val > 7) return false;
		_pcp = val; return true;
	}
	inline int pcp() const { return _pcp; }
	inline bool vlan_id(unsigned vid) { 
		if (vid > 4095) return false;
		_vid = vid; return true;
	}

	string to_string(int level = 0) const;
	string name() const { return "802.1Q"; }

	bool do_build(Buffer &pkt, int phase, Word &pos);
	bool decode(Buffer &pkt);
	void fixup() { }

	~Vlan8021q() { }
};

#ifdef _LIB
static Packet *Vlan8021q_factory()
{
	Packet *p = new Vlan8021q();
	// if (debug > 1) cerr << "new 802.1Q instance " << p << endl;
	return p;
}
static Packet *Vlan8021ad_factory()
{
	Packet *p = new Vlan8021q(0x9100);
	return p;
}
static Packet *PBBS_factory()
{
	Packet *p = new Vlan8021q(0x88e7);
	return p;
}
static Packet *PBBB_factory()
{
	Packet *p = new Vlan8021q(0x88a8);
	return p;
}

class autoinit
{
public:
	autoinit() { 
		Ethernet::add_proto(0x8100, Vlan8021q_factory); 
		Ethernet::add_proto(0x9100, Vlan8021ad_factory); 
		Ethernet::add_proto(0x88e7, PBBS_factory); 
		Ethernet::add_proto(0x88a8, PBBB_factory); 
		// cout << "INIT 802.1Q" << endl;  
	}
};
static autoinit init;

string Vlan8021q::to_string(int level) const
{
	string ret("<802.1Q");
	if (_tpid != 0x8100) {
		ret += " tpid=";
		ret += cvt_hex(_tpid);
		ret += " tag=";
		ret += cvt_int(_vid);
		ret += ">";
		return ret;
	}
	if (_pcp != 0) {
		ret += " pcp=";
		ret += cvt_int(_pcp);
	}
	if (_cfi != 0) {
		ret += " CFI";
	}
	ret += " vlan=";
	ret += cvt_int(_vid);
	ret += ">";
	return ret;
}

bool Vlan8021q::do_build(Buffer &pkt, int phase, Word &pos)
{
	// get the extension headers size, insert required paddings
	if (phase == 0) {
		pos += 4;
		if (payload()) {
			if (!payload()->do_build(pkt, phase, pos)) return false;
		}
		return true;
	}
	if (!pkt.add_hshort(_tpid)) return false;
	pos += 2;
	if (_tpid == 0x8100) {
		if (!pkt.add_hshort(((_pcp&7)<<13)+(_cfi?(1<<12):0)+(_vid&0xfff))) return false;
	} else {
		if (!pkt.add_hshort(_vid)) return false;
	}
	pos += 2;
	if (payload()) {	// build upper layer packet
		if (!payload()->do_build(pkt, phase, pos)) return false;
	}
	return true;
}

bool Vlan8021q::decode(Buffer &pkt)
{
	release();		// release previous content
	if (!pkt.get_hshort(_vid)) {
		pkt.reset();
		return false;
	}
	if (_tpid == 0x8100) {
		_pcp = (_vid >> 13) & 0x7;
		if ((_vid >> 12) & 1) _cfi = true;
		else _cfi = false;
		_vid &= 0xfff;
	} else {
		_pcp = 0;
		_cfi = 0;
	}
	// decode payload
	Packet *p = Ethernet::proto_factory(_tpid);
	if (p) {
		attach(p, true);
		if (!p->decode(pkt)) return false;
	}
	return true;
}
#endif
#endif
