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

#ifndef __NDWATCH_IPV6_H__
#define __NDWATCH_IPV6_H__

extern "C" {
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
}
#include "common.h"




class IPv6Address_prefix
{
private:
	int _len;
	enum {MAX_LEN = 16};
	Octet _addr[MAX_LEN];
public:
	IPv6Address_prefix() { clear(); }
	IPv6Address_prefix(const IPv6Address_prefix &right){
		this->clear();
		_len = right._len;
		for (int i = 0; i < _len; i++) {
			_addr[i] = right._addr[i];
		}
	}
	IPv6Address_prefix& operator= (const IPv6Address_prefix &right){
		if (this == &right)
			return *this;

		this->clear();
		_len = right._len;
		for (int i = 0; i < _len; i++) {
			_addr[i] = right._addr[i];
		}
		return *this;
	}
	int length() const { return _len; }
	void length(int len) {_len = len;}
	const Octet *octet_addr() const { return _addr; }
	inline string addr() const { return to_string(); }
	string to_string(int level = 0) const;
	inline void clear() { memset(_addr, 0, sizeof(_addr)); }
	inline bool operator==(const IPv6Address_prefix &adr) const {
		if(_len != adr._len) return false;
		for (int i = 0; i < _len; i++) {
			if (_addr[i] != adr._addr[i]) return false;
		}
		return true;
	}
	inline bool operator!=(const IPv6Address_prefix &adr) const { return !(*this ==(adr)); }
	inline bool decode(Buffer &pkt, int len=MAX_LEN) {
		for (int i = 0; i < len; i++) {
			if (!pkt.get_octet(_addr[i])) return false;
		}
		return true;
	}
	~IPv6Address_prefix() { }
};


inline string IPv6Address_prefix::to_string(int level) const
{
	char buffer[20];
	string len;
	sprintf (buffer, "%u",_len);
	len = string(buffer);

	char str[INET6_ADDRSTRLEN];
	if (inet_ntop(AF_INET6, _addr, str, INET6_ADDRSTRLEN)) return string(str) +"/"+len;
	return "::/" +len;
}






//
// IPv6 Address
// RFC4291
class IPv6Address
{
private:
	Octet _addr[16];
public:
	IPv6Address() { clear(); }
	explicit IPv6Address(const string &str) { (void)addr(str); }
	IPv6Address(const IPv6Address &right){
		this->clear();
		for (int i = 0; i < 16; i++) {
			_addr[i] = right._addr[i];
		}
	}
	IPv6Address& operator= (const IPv6Address &right){
		if (this == &right)
			return *this;

		this->clear();
		for (int i = 0; i < 16; i++) {
			_addr[i] = right._addr[i];
		}
		return *this;
	}
	int length() const { return 16; }
	bool addr(const string &str);
	bool addr(const sockaddr *sa);
	const Octet *octet_addr() const { return _addr; }
	bool solicited_multicast(const IPv6Address &ip);
	string multicast_mac(bool solnode = false) const;
	inline string addr() const { return to_string(); }
	string to_string(int level = 0) const;
	inline void clear() { memset(_addr, 0, sizeof(_addr)); }
	// rfc4291 
	inline bool is_unspecified() const {	// ::
		for (int i = 0; i < 16; i++) {
			if (_addr[i]) return false;
		}
		return true;
	}
	inline bool is_loopback() const {	// ::1
		for (int i = 0; i < 15; i++) {
			if (_addr[i]) return false;
		}
		if (_addr[15] != 1) return false;
		return true;
	}
	inline bool is_v4compatible() const {	// ::1234:5678
		for (int i = 0; i < 12; i++) {
			if (_addr[i]) return false;
		}
		if (_addr[12] || _addr[13] || _addr[14]) return true;
		if (_addr[15] == 0 || _addr[15] == 1) return false;
		return true;
	}
	inline bool is_v4mapped() const {	// :ffff:1234:5678
		for (int i = 0; i < 10; i++) {
			if (_addr[i]) return false;
		}
		if (_addr[10] != 0xff || _addr[11] != 0xff) return false;
		return true;
	}
	inline bool is_linklocal() const {	// fe80::-feb0::
		if (_addr[0] != 0xfe || (_addr[1] & 0xc0) != 0x80) return false;
		return true;
	}
	inline bool is_sitelocal() const {	// fec0::-feff::
		if (_addr[0] != 0xfe || (_addr[1] & 0xc0) != 0xc0) return false;
		return true;
	}
	// rfc 3587
	inline bool is_globalunicast() const {	// 2000::-feff::
		if (_addr[0] < 2 || _addr[0] > 0xfe) return false;
		return true;
	}
	inline bool is_multicast() const { // ff00::-
		if (_addr[0] != 0xff) return false;
		return true;
	}
	inline bool is_prefix(const IPv6Address &pfx, int len) const {
		int i=0;
		if (len > 128) return false;
		while (len >= 8) {
			if (_addr[i] != pfx._addr[i]) return false;
			len -= 8; i++;
		}
		if (len == 0) return true;
		Octet mask = 0xffu << (8-len);
		if ((_addr[i] & mask) != (pfx._addr[i] & mask)) return false;
		return true;
	}

	enum MulticastScope { res0, interface_local, link_local, res3, admin_local, site_local, unas6, unas7, organization_local, unas9, unasa, unasb, unasc, unasd, global, resf };
	inline Octet multicast_flags() const { return (_addr[1]>>4) & 0xf; }
	inline Octet multicast_scope() const { return (_addr[1] & 0xf); }
	inline bool multicast_transient() const { return (_addr[1] & 0x10); }
	// RFC 3306 Unicast-Prefix-based IPv6 Multicast Addresses
	inline bool multicast_prefix_based() const { return (_addr[1] & 0x20); }
	// RFC 3956 Embedding the RP Address in an IPv6 Multicast Address
	inline bool multicast_embedded_rp() const { return (_addr[1] & 0x40); }
	inline bool operator==(const IPv6Address &adr) const {
		for (int i = 0; i < 16; i++) {
			if (_addr[i] != adr._addr[i]) return false;
		}
		return true;
	}
	inline bool operator!=(const IPv6Address &adr) const { return !(*this ==(adr)); }
	inline bool operator<(const IPv6Address &adr) const {
		for (int i = 0; i < 16; i++) {
			if (_addr[i] != adr._addr[i]) return _addr[i] < adr._addr[i];
		}
		return false;
	}
	inline unsigned checksum(int len=16) const { 
		unsigned ck = 0;
		for (int i = 0; i < len; i+=2) { ck += (_addr[i]<<8) + _addr[i+1]; }
		return ck;
	}
	inline bool build(Buffer &pkt, int phase, int len=16) const {
		for (int i = 0; i < len; i++) pkt.add_octet(_addr[i]);
		return true;
	}
	inline bool decode(Buffer &pkt, int len=16) {
		for (int i = 0; i < len; i++) {
			if (!pkt.get_octet(_addr[i])) return false;
		}
		return true;
	}
	~IPv6Address() { }
};

inline ostream &operator<<(ostream &out, const IPv6Address &ip6)
{
	return out << ip6.to_string();
}

#ifdef _LIB
#include <sys/socket.h>
#include <arpa/inet.h>

bool IPv6Address::addr(const string &str)
{
	if (inet_pton(AF_INET6, str.c_str(), _addr) == 1) return true;
	return false;
}

bool IPv6Address::addr(const sockaddr *sa)
{
	if (sa->sa_family != AF_INET6) return false;
	memcpy(_addr, &reinterpret_cast<const sockaddr_in6 *>(sa)->sin6_addr, 16);
	// clear embeded scope id
	if (is_linklocal()) _addr[2] = _addr[3] = 0;
	return true;
}

bool IPv6Address::solicited_multicast(const IPv6Address &ip)
{
	inet_pton(AF_INET6, "ff02::1:ff00:0", _addr);
	_addr[15] = ip._addr[15];
	_addr[14] = ip._addr[14];
	_addr[13] = ip._addr[13];
	return true;
}

string IPv6Address::multicast_mac(bool solnode) const
{
	string str("33:33:");
	if (solnode) str += "ff";
	else str += cvt_hex(_addr[12], true);
	str += ":";
	str += cvt_hex(_addr[13], true);
	str += ":";
	str += cvt_hex(_addr[14], true);
	str += ":";
	str += cvt_hex(_addr[15], true);
	return str;
}

string IPv6Address::to_string(int level) const
{
	char str[INET6_ADDRSTRLEN];
	if (inet_ntop(AF_INET6, _addr, str, INET6_ADDRSTRLEN)) return string(str);
	return "::";
}
#endif

// 
// Extension Header Options
//
// Option Type:
// +---+---+-------+
// |act|chg|  type |
// +---+---+-------+
// act - 2 bits, packet action if type is unrecognized:
// chg - 1 bit, 0 - option data doesn't change en-route, 1 - may change
// type - 5 bits
// 00 - skip over, 01 - discard , 10 - discard and ICMP, 11 - discard and ICMP if unicast
// 0 - Pad1 (done)
// 1 - PadN (done)
// C2 - Jumbo Payload (RFC2675)
// 4 - Tunnel Encapsulation Limit (RFC2473)
// 5 - Router Alert (done)
// 26 - Quick Start (RFC4782)
// 7 - CALIPSO (RFC5570)
// C9 - Home Address (RFC3775)
// 8A - Endpoint Identification
// 


//
// Common Extension Header Options Format
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- - - - - - - - -
// |  Option Type  |  Opt Data Len |  Option Data
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- - - - - - - - -
// RFC2460 4.2
class IPv6Option
{
private:
	IPv6Option(const IPv6Option&);
	IPv6Option& operator=(const IPv6Option&);
protected:
	Octet _type;
	Octet _length;
	Octet *_data;	// data for unknown option
	inline int delta(int x, int y, int pos) const { return x*((pos-y+x-1)/x)+y-pos; }
public:
	enum IPv6OptionsType { Pad1, PadN, TunnelEncLimit=4, RouterAlert=5, QuickStart=6, Jumbo=0xc2, HomeAddress=0xc9 };
	IPv6Option(): _type(0), _length(0), _data(0) { }
	IPv6Option(int type): _type(type), _length(0), _data(0) { }
	int type() const { return _type; }
	int length() const { return _length; }
	virtual string name() const;
	virtual string to_string(int level);
	virtual bool decode(Buffer &pkt, int &left);
	virtual bool build(Buffer &pkt, int phase, Word &pos);
// As specified in section 4.2 of RFC 2460, every options has
// an alignment requirement ususally expressed xn+y, meaning
// the Option Type must appear at an integer multiple of x octest
// from the start of the header, plus y octet.
	virtual int alignment_delta(int pos);
	virtual ~IPv6Option();
};

#ifdef _LIB
IPv6Option::~IPv6Option()
{
	if (_data) delete[] _data;
}

string IPv6Option::name() const
{
	return "Unknown";
}

string IPv6Option::to_string(int level)
{
	string ret("type=");
	ret += cvt_int(_type);
	ret += " len=";
	ret += cvt_int(_length);
	return ret;
}

int IPv6Option::alignment_delta(int pos)
{
	return 0;
}

// decode unknown option
bool IPv6Option::decode(Buffer &pkt, int &left)
{
	if (left <= 0 || !pkt.get_octet(_length)) {
		if (debug > 0) cerr << "Missing option(" << (int)_type << ") length octet" << endl;
		return false;
	}
	--left;
	if (_length && _data == NULL) _data = new Octet[256];	// max. data
	for (int i = 0; i < _length; i++) {
		if (left <= 0 || !pkt.get_octet(_data[i])) {
			if (debug > 0) cerr << "Missing option(" << (int)_type << ") data octet at " << i << "/" << (int)_length << endl;
			return false;
		}
		--left;
	}
	return true;
}

// build unknown option
bool IPv6Option::build(Buffer &pkt, int phase, Word &pos)
{
	if (!phase) {
		pos += 2+_length;
		return true;
	}
	if (!pkt.add_octet(_type)) return false;
	++pos;
	if (!pkt.add_octet(_length)) return false;
	++pos;
	for (int i = 0; i < _length; i++) {
		if (!pkt.add_octet(_data[i])) return false;
		++pos;
	}
	return true;
}
#endif

//
// Extension Header Option Pad1
//
// +-+-+-+-+-+-+-+-+
// |       0       |
// +-+-+-+-+-+-+-+-+
// RFC2460 4.2 
// special case, no Opt Data Len
class IPv6OptionPad1: public IPv6Option
{
public:
	IPv6OptionPad1(): IPv6Option(Pad1) { }
	string name() const { return "Pad1"; };
	string to_string(int level);
	bool build(Buffer &pkt, int phase, Word &pos);
	bool decode(Buffer &pkt, int &left) { return true; }
	// no alignment
	int alignment_delta(int cur) const { return 0; }
	~IPv6OptionPad1() { }
};

#ifdef _LIB
string IPv6OptionPad1::to_string(int level)
{
	return "Pad1";
}

bool IPv6OptionPad1::build(Buffer &pkt, int phase, Word &pos)
{ 
	if (phase && !pkt.add_octet(Pad1)) return false;
	++pos;
	return true;
}
#endif 

//
// Extension Header Option PadN
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- - - - - - - - -
// |       1       |  Opt Data Len |  Option Data
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- - - - - - - - -
// RFC2460 4.2 
class IPv6OptionPadN: public IPv6Option
{
public:
	IPv6OptionPadN(): IPv6Option(PadN) { }
	IPv6OptionPadN(int length): IPv6Option(PadN) { _length = length; }
	string name() const { return "PadN"; };
	string to_string(int level);
	bool build(Buffer &pkt, int phase, Word &pos);
	bool decode(Buffer &pkt, int &left);
	// no alignment
	int alignment_delta(int cur) const { return 0; }
	~IPv6OptionPadN() { }
};

#ifdef _LIB
string IPv6OptionPadN::to_string(int level)
{
	string ret("PadN len=");
	ret += cvt_int(_length);
	return ret;
}

bool IPv6OptionPadN::build(Buffer &pkt, int phase, Word &pos) 
{ 
	if (!phase) {
		pos += 2+_length;
		return true;
	}
	if (!pkt.add_octet(PadN)) return false;
	++pos;
	if (!pkt.add_octet(_length)) return false;
	++pos;
	for (int i = 0; i < _length; i++) {
		if (!pkt.add_octet(0)) return false;
		++pos;
	}
	return true;
}

bool IPv6OptionPadN::decode(Buffer &pkt, int &left) { 
	if (left <= 0 || !pkt.get_octet(_length)) {
		if (debug > 0) cerr << "Missing option(PadN) length octet" << endl;
		return false;
	}
	--left;
	for (int i = 0; i < _length; i++) {
		Octet dummy;
		if (left <= 0 || !pkt.get_octet(dummy)) {
			if (debug > 0) cerr << "Missing option(PadN) data octet at " << i << "/" << (int)_length << endl;
			return false;
		}
		--left;
	}
	return true;
}
#endif

//
// Extension Header Option Router Alert
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |       5       |       2       |        Value (2 octets)       |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// HBH Router Alert Option, fixed length 2
// RFC 2711 2.1
class IPv6OptionRouterAlert: public IPv6Option
{
private:
	Short _value;
public:
	enum ValueType { MLDMessage, RSVPMessage, ActiveNetworksMessage} ;
	IPv6OptionRouterAlert(): IPv6Option(RouterAlert),_value(0) { _length = 2; }
	IPv6OptionRouterAlert(unsigned value): IPv6Option(RouterAlert),_value(value) { _length = 2; }
	string name() const { return "RouterAlert"; };
	string to_string(int level);
	bool build(Buffer &pkt, int phase, Word &pos);
	bool decode(Buffer &pkt, int &left);
	// alignment requirements 2n+0
	int alignment_delta(int cur) const { return delta(2, 0, cur); }
	~IPv6OptionRouterAlert() { }
};

#ifdef _LIB
string IPv6OptionRouterAlert::to_string(int level)
{
	string ret("RouterAlert value=");
	if (_value == MLDMessage) ret += "MLD";
	else
	if (_value == RSVPMessage) ret += "RSVP";
	else
	if (_value == ActiveNetworksMessage) ret += "ActiveNetworks";
	else ret += cvt_int(_value);
	return ret;
}

bool IPv6OptionRouterAlert::build(Buffer &pkt, int phase, Word &pos) 
{ 
	if (!phase) {
		pos += 4;
		return true;
	}
	if (!pkt.add_octet(RouterAlert)) return false;
	++pos;
	if (!pkt.add_octet(_length)) return false;
	++pos;
	if (!pkt.add_hshort(_value)) return false;
	pos += 2;
	return true;
}

bool IPv6OptionRouterAlert::decode(Buffer &pkt, int &left) 
{ 
	if (left <= 0 || !pkt.get_octet(_length)) {
		if (debug > 0) cerr << "Missing option(RouterAlert) length octet" << endl;
		return false;
	}
	--left;
	if (_length != 2) {
		if (debug > 0) cerr << "Invalid option(RouterAlert) length " << (int)_length << endl;
		return false;
	}
	if (left <= 1 || !pkt.get_hshort(_value)) {
		if (debug > 0) cerr << "Missing option(RouterAlert) value data" << endl;
		return false;
	}
	left -= 2;
	return true;
}
#endif

//
// IPv6 Packet Extension Headers (in recommended order)
// 0 - HopByHop Header (done)
// 60 - Destination Options Header (with Routing Options) (done)
// 43 - Routing Header (done)
// 44 - Fragment Header (done)
// 51 - Authentication Header
// 50 - Encapsulation Security Payload Header
// 60 - Destination Options Header (done)
// 135 - Mobility Header
// 59 - No Next Header (done)
//
// Common Extension Header Format
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |  Next Header  |  Hdr Ext Len  |         Options               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               +
// .                                                               .
// length is multiply of 8 octets
// RFC2460 4.
class IPv6ExtensionHeader
{
private:
	IPv6ExtensionHeader(const IPv6ExtensionHeader&);
	IPv6ExtensionHeader& operator=(const IPv6ExtensionHeader&);
protected:
	Octet _type;	// extension header type
	Octet _nh;	// next header type
	Octet _hlen;	// header len in 8-octet units, not including the first 8
	IPv6Option *_opts[20];	// vector?
	int _num_opts;
	Octet *_data;	// for unknown extension
public:
	enum ExtensionHeaders { HopByHopHeader=0, RoutingHeader=43, FragmentHeader=44, ESPHeader=50, AHHeader=51, NoHeader=59, DestinationOptions=60, MobilityHeader=135 };
	IPv6ExtensionHeader(): _type(NoHeader),_nh(NoHeader),_hlen(0),_num_opts(0), _data(0) {}
	IPv6ExtensionHeader(int type): _type(type),_nh(NoHeader),_hlen(0),_num_opts(0), _data(0) {}
	int type() const { return _type; }
	int next_header() const { return _nh; }
	void next_header(int nh) { _nh = nh; }
	virtual bool build(Buffer &pkt, int phase, Word &pos);
	virtual bool decode(Buffer &pkt);
	virtual string name() const { return "Unknown"; }
	virtual string to_string(int level = 0) const;
	bool add_option(IPv6Option *opt) { 
		if (_num_opts >= 20) return false;
		_opts[_num_opts++] = opt; return true; 
	}
	void delete_options() { 
		for (int i = 0; i < _num_opts; i++) delete _opts[i]; 
		_num_opts = 0;
	}
	virtual ~IPv6ExtensionHeader();
protected:
	bool decode_options(Buffer &pkt, int &left, IPv6Option *&ret);
	bool build_options(Buffer &pkt, int phase, Word &pos);
};

#ifdef _LIB
IPv6ExtensionHeader::~IPv6ExtensionHeader()
{
	delete_options();
	if (_data) delete _data;
}

string IPv6ExtensionHeader::to_string(int level) const
{
	string ret = "<";
	ret += name();
	ret += " nh=";
	ret += cvt_int(_nh);
	ret += " hlen=";
	ret += cvt_int(_hlen);
	ret += " options=[";
	for (int i = 0; i < _num_opts; i++) {
		if (i) ret += " | ";
		ret += _opts[i]->to_string(level);
	}
	ret += "]>";
	return ret;
}

// output options, insert required padding
bool IPv6ExtensionHeader::build_options(Buffer &pkt, int phase, Word &pos)
{
	int j,d;
	for (int i = 0; i < _num_opts; i++) {
		d = _opts[i]->alignment_delta(pos);
		if (d == 1) {	// insert Pad1
			for (j = i+1; j < _num_opts; j++) _opts[j] = _opts[j-1];
			_opts[i] = new IPv6OptionPad1();
			++_num_opts;
		} else
		if (d != 0) {	
			for (j = i+1; j < _num_opts; j++) _opts[j] = _opts[j-1];
			_opts[i] = new IPv6OptionPadN(d-2);
			++_num_opts;
		}
		if (!_opts[i]->build(pkt, phase, pos)) return false;
	}
	// fill width pad to 8 octet boundary
	d = 8 - (pos % 8);
//	cerr << "fill " << pos << " dif " << d << endl;
	if (d == 1) {
		_opts[_num_opts++] = new IPv6OptionPad1();
		if (!_opts[_num_opts-1]->build(pkt, phase, pos)) return false;
	} else 
	if (d != 8) {
		_opts[_num_opts++] = new IPv6OptionPadN(d-2);
		if (!_opts[_num_opts-1]->build(pkt, phase, pos)) return false;
	}
	return true;
}

// 
bool IPv6ExtensionHeader::decode_options(Buffer &pkt, int &left, IPv6Option *&ret)
{
	Octet type;
	if (left <= 0) return true;	// no options left
	if (!pkt.get_octet(type)) {
		if (debug > 0) cerr << "Missing Extension Header option type" << left << endl;
		return false;
	}
	--left;
	if (type == IPv6Option::Pad1) {
		ret = new IPv6OptionPad1();
		// dummy decode, no data
		return true;
	} else
	if (type == IPv6Option::PadN) {
		ret = new IPv6OptionPadN();
		return ret->decode(pkt, left);
	} else
	if (type == IPv6Option::RouterAlert) {
		ret = new IPv6OptionRouterAlert();
		return ret->decode(pkt, left);
	} 
	// unknown
	ret = new IPv6Option(type);
	return ret->decode(pkt, left);
};

bool IPv6ExtensionHeader::build(Buffer &pkt, int phase, Word &pos)
{
	return true;
}

bool IPv6ExtensionHeader::decode(Buffer &pkt)
{
	delete_options();
	if (_data) delete _data;
	int left = 8;	// must be at least 8 octets long
	if (left <= 0 || !pkt.get_octet(_nh)) {	// next header type
		if (debug > 0) cerr << "Missing Next Header octet in Extension Header" << endl;
		return false;
	}
	--left;
	if (left <= 0 || !pkt.get_octet(_hlen)) {
		if (debug > 0) cerr << "Missing Header Extension Length octet in Extension HopByHop Header" << endl;
		return false;
	}
	--left;
	left += _hlen*8; // length is in 8-octet units, not counting first 8
	_data = new Octet[left];
	int i = 0;
	while (left > 0) {
		if (!pkt.get_octet(_data[i++])) return false;
	}
	return true;
}
#endif

//
// Hop By Hop Header (Jumbograms, Router Alert/MLD,RSVP)
// 
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |  Next Header  |  Hdr Ext Len  |                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               +
// |                                                               |
// .                                                               .
// .                            Options                            .
// .                                                               .
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// RFC 2460 4.3 Hop-by-Hop Options Header
class IPv6HopByHopHeader: public IPv6ExtensionHeader
{
private:
public:
	IPv6HopByHopHeader(): IPv6ExtensionHeader(HopByHopHeader) { }
	string name() const { return "HopByHop"; }
	// base string to_string(int level = 0) const;
	bool build(Buffer &pkt, int phase, Word &pos);
	bool decode(Buffer &pkt);
	~IPv6HopByHopHeader() { }
};

#ifdef _LIB
bool IPv6HopByHopHeader::build(Buffer &pkt, int phase, Word &pos)
{
	// compute options length
	if (phase == 0) {
		pos += 2;
		if (!build_options(pkt, phase, pos)) return false;
		_hlen = (pos+7)/8 - 1;	// first 8 octets not counted
		return true;
	}
	if (!pkt.add_octet(_nh)) return false;
	++pos;
	if (!pkt.add_octet(_hlen)) return false;
	++pos;
	if (!build_options(pkt, phase, pos)) return false;
	return true;
}

bool IPv6HopByHopHeader::decode(Buffer &pkt)
{
	IPv6Option *ret;
	delete_options();
	int left = 8;	// must be at least 8 octets long
	if (left <= 0 || !pkt.get_octet(_nh)) {	// next header type
		if (debug > 0) cerr << "Missing Next Header octet in HopByHop Header" << endl;
		return false;
	}
	--left;
	if (left <= 0 || !pkt.get_octet(_hlen)) {
		if (debug > 0) cerr << "Missing Header Extension Length octet in HopByHop Header" << endl;
		return false;
	}
	--left;
	left += _hlen*8; // length is in 8-octet units, not counting first 8
	while (left > 0) {
		if (!decode_options(pkt, left, ret)) return false;
		_opts[_num_opts++] = ret;
	}
	return true;
}
#endif

//
// Routing Header (IPv6 Mobility, Source Routing)
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |  Next Header  |  Hdr Ext Len  |  Routing Type | Segments Left |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// .                                                               .
// .                       type-specific data                      .
// .                                                               .
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// RFC 2460 4.4 Routing Header
// RFC 5095, RFC 3775
class IPv6RoutingHeader: public IPv6ExtensionHeader
{
private:
	Octet _rtype;	// routing header variant
	Octet _sleft;	// number of route segments remaining
	IPv6Address *_addrs[32];	// route type 0 address list 
	int _num_addrs;

	enum { SourceRoute=0, Nimrod=1, Type2=2 };
	IPv6RoutingHeader(const IPv6RoutingHeader&);
	IPv6RoutingHeader& operator=(const IPv6RoutingHeader&);
public:
	IPv6RoutingHeader(): IPv6ExtensionHeader(RoutingHeader),_rtype(0),_sleft(0),_num_addrs(0) { }
	bool add_route(IPv6Address *addr) { 
		if (_num_addrs >= 32) return false;
		_addrs[_num_addrs++] = addr; 
		_sleft++; 
	}
	bool get_route(int i, IPv6Address &addr) {
		if (_num_addrs >= _num_addrs) return false;
		addr = *(_addrs[i]);
	}
	void delete_routes() { 
		for (int i = 0; i < _num_addrs; i++) delete _addrs[i];
		_num_addrs = _sleft = 0;
	}
	string name() const { return "RoutingHeader"; }
	// base string to_string(int level = 0) const;
	bool build(Buffer &pkt, int phase, Word &pos);
	bool decode(Buffer &pkt);
	~IPv6RoutingHeader() { delete_routes(); }
};

#ifdef _LIB
bool IPv6RoutingHeader::build(Buffer &pkt, int phase, Word &pos)
{	
	if (phase == 0) {
		pos += 6;	// pad to 8
		if (_rtype == 0) {
			pos += 16*_num_addrs;
		}
		return true;
	}
	if (!pkt.add_octet(_rtype)) return false;
	++pos;
	if (!pkt.add_octet(_sleft)) return false;
	++pos;
	if (!pkt.add_hlong(0)) return false;
	pos += 4;
	if (_rtype == 0) {	// Type 0 headers
		for (int i = 0; i < _num_addrs; i++) {
			if (!_addrs[i]->build(pkt, phase)) return false;
			pos += 16;
		}
	}
	return true;
}

bool IPv6RoutingHeader::decode(Buffer &pkt)
{
	delete_routes();
	int left = 8;	// must be at least 8 octets long
	if (left <= 0 || !pkt.get_octet(_nh)) {	// next header type
		if (debug > 0) cerr << "Missing Next Header octet in Routing Header" << endl;
		return false;
	}
	--left;
	if (left <= 0 || !pkt.get_octet(_hlen)) {
		if (debug > 0) cerr << "Missing Header Extension Length octet in Routing Header" << endl;
		return false;
	}
	--left;
	left += _hlen*8; // length is in 8-octet units, not counting first 8
	if (left <= 0 || !pkt.get_octet(_rtype)) {
		if (debug > 0) cerr << "Missing Routing Header Type octet " << endl;
		return false;
	}
	--left;
	if (left <= 0 || !pkt.get_octet(_sleft)) {
		if (debug > 0) cerr << "Missing Routing Header Segments Left octet " << endl;
		return false;
	}
	--left;
	Word res;
	if (left <= 3 || !pkt.get_hlong(res)) {
		if (debug > 0) cerr << "Missing Routing Header reserved octet " << endl;
		return false;
	}
	left -= 4;
	while (left > 0) {
		if (left <= 15) {
			if (debug > 0) cerr << "Missing Routing Header address octet " << endl;
			return false;
		}
		_addrs[_num_addrs++] = new IPv6Address();
		if (!_addrs[_num_addrs-1]->decode(pkt)) return false;
		left -= 16;
	}
	return true;
}
#endif

// 
// Fragmentation Header (fragmented packets)
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |  Next Header  |   Reserved    |      Fragment Offset    |Res|M|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                         Identification                        |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// RFC 2460 4.5
class IPv6FragmentHeader: public IPv6ExtensionHeader
{
private:
	Short _foff;	// fragment offset, lower 3 bits = 0
	Word _id;	// identification
public:
	IPv6FragmentHeader(): IPv6ExtensionHeader(FragmentHeader),_foff(1),_id(0) { }
	string name() const { return "FragmentHeader"; }
	// base string to_string(int level = 0) const;
	bool build(Buffer &pkt, int phase, Word &pos);
	bool decode(Buffer &pkt);

	bool is_last() const { return (_foff & 1) == 0; }
	void set_last(bool last) { if (last) _foff &= ~1; else _foff |= 1; }
	Short fragment_offset() const { return _foff & 0xfff8; }
	bool fragment_offset(Short off) { 
		if (off & 0x7) return false;
		_foff = (_foff&0x7) | off;
	}
	Word ident() const { return _id; }
	void ident(Word id) { _id = id; }
	~IPv6FragmentHeader() { }
};

#ifdef _LIB
bool IPv6FragmentHeader::build(Buffer &pkt, int phase, Word &pos)
{	
	if (phase == 0) {
		pos += 8;
		return true;
	}
	if (!pkt.add_octet(_type)) return false;
	++pos;
	if (!pkt.add_octet(0)) return false;
	++pos;
	if (!pkt.add_hshort(_foff)) return false;
	pos += 2;
	if (!pkt.add_hlong(_id)) return false;
	pos += 4;
	return true;
}

bool IPv6FragmentHeader::decode(Buffer &pkt)
{
	int left = 8;	// must be at least 8 octets long
	if (left <= 0 || !pkt.get_octet(_nh)) {	// next header type
		if (debug > 0) cerr << "Missing Next Header octet in Routing Header" << endl;
		return false;
	}
	--left;
	if (left <= 0 || !pkt.get_octet(_hlen)) {	// must by zero
		if (debug > 0) cerr << "Missing Header Extension Length octet in Routing Header" << endl;
		return false;
	}
	--left;
	if (left <= 1 || !pkt.get_hshort(_foff)) {
		if (debug > 0) cerr << "Missing Fragment Header Offset octet " << endl;
		return false;
	}
	left -= 2;	
	if (left <= 3 || !pkt.get_hlong(_id)) {
		if (debug > 0) cerr << "Missing Fragment Header id octet " << endl;
		return false;
	}
	left -= 4;
	return true;
}
#endif

//
// Destination Options Header (IPv6 Mobility)
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |  Next Header  |  Hdr Ext Len  |                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               +
// |                                                               |
// .                                                               .
// .                            Options                            .
// .                                                               .
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
class IPv6DestinationOptionsHeader: public IPv6ExtensionHeader
{
private:
public:
	IPv6DestinationOptionsHeader(): IPv6ExtensionHeader(DestinationOptions) { }
	string name() const { return "DestinationOptions"; }
	// base string to_string(int level = 0) const;
	bool build(Buffer &pkt, int phase, Word &pos);
	bool decode(Buffer &pkt);
	~IPv6DestinationOptionsHeader() { }
};

#ifdef _LIB
bool IPv6DestinationOptionsHeader::build(Buffer &pkt, int phase, Word &pos)
{	
	if (phase == 0) {
		return true;
	}
	return true;
}

bool IPv6DestinationOptionsHeader::decode(Buffer &pkt)
{
	IPv6Option *ret;
	int left = 8;	// must be at least 8 octets long
	delete_options();
	if (left <= 0 || !pkt.get_octet(_nh)) {	// next header type
		if (debug > 0) cerr << "Missing Next Header octet in Routing Header" << endl;
		return false;
	}
	--left;
	if (left <= 0 || !pkt.get_octet(_hlen)) {	// must by zero
		if (debug > 0) cerr << "Missing Header Extension Length octet in Routing Header" << endl;
		return false;
	}
	--left;
	left += _hlen*8; // length is in 8-octet units, not counting first 8
	while (left > 0) {
		if (!decode_options(pkt, left, ret)) return false;
		_opts[_num_opts++] = ret;
	}
	return true;
}
#endif

// 
// Authentication Header Format
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Next Header   |  Payload Len  |          RESERVED             |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                 Security Parameters Index (SPI)               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                    Sequence Number Field                      |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// +                Authentication Data (variable)                 |
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// SPI
// 1 - ISAKMP (RFC 2407)
// 2 - IPSEC_AH
// 3 - IPSEC_ESP
// 4 - IPCOMP
// 5 - GigaBeam Radio (RFC4705)

// 
// IPv6 Packet
//

// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |Version| Traffic Class |           Flow Label                  |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |         Payload Length        |  Next Header  |   Hop Limit   |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// +                                                               +
// |                                                               |
// +                         Source Address                        +
// |                                                               |
// +                                                               +
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// +                                                               +
// |                                                               |
// +                      Destination Address                      +
// |                                                               |
// +                                                               +
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// RFC2460 3.
class IPv6: public Packet 
{
private:
	enum { MAX_EXTS=20 };
	Octet _version;	// IP version number = 6
	Octet _tc;	// traffic class
	Word _flow;	// flow label
	Short _plen;	// payload length including all extension headers
	Octet _nh;	// type of header immediately following the IPv6 header
	Octet _hlim;	// hop limit
	IPv6Address _src;	// source address
	IPv6Address _dest;	// destination address
	IPv6ExtensionHeader *_exts[MAX_EXTS];	// extension headers
	int _num_exts;
	Octet *_frag;
	Short _frag_size;
	bool _own;
	IPv6(const IPv6&);
	IPv6& operator=(const IPv6&);
public:

	IPv6(): _version(6), _tc(0), _flow(0), _plen(0), _nh(IPv6ExtensionHeader::NoHeader), _hlim(64), _src(), _dest(), _num_exts(0), _frag(0), _frag_size(0), _own(false) { }
	explicit IPv6(const string &dest): _version(6), _tc(0), _flow(0), _plen(0), _nh(IPv6ExtensionHeader::NoHeader), _hlim(64), _src(), _dest(dest), _num_exts(0), _frag(0), _frag_size(0), _own(false) { }
	IPv6(const IPv6Address &src, const IPv6Address &dest): _version(6), _tc(0), _flow(0), _plen(0), _nh(IPv6ExtensionHeader::NoHeader), _hlim(64), _src(src), _dest(dest), _num_exts(0), _frag(0), _frag_size(0), _own(false) { }

	inline bool is_fragment() const { return _frag != 0; }
	inline Octet *fragment_data() { return _frag; }
	inline Short fragment_length() const { return _frag_size; }

	inline bool dest(string str) { return _dest.addr(str); }
	inline const IPv6Address& dest() const { return _dest; }
	inline bool src(string str) { return _src.addr(str); }
	inline const IPv6Address& src() const { return _src; }
	inline bool tc(unsigned val) { 
		if (val > 255) return false;
		_tc = val; return true;
	}
	inline int tc() const { return _tc; }
	inline bool flow(unsigned flow) {
		if (flow >= 1<<20) return false;
		_flow = flow; return true;
	}
	inline int flow() const { return _flow; }
	inline bool plen(unsigned len) { 
		if (len > 65535) return false;
		_plen = len; return true;
	}
	inline int plen() const { return _plen; }
	inline bool next_header(unsigned nh) { 
		if (nh > 255) return false;
		_nh = nh; return true;
	}
	inline int next_header() const { return _nh; }
	inline bool hlim(unsigned hlim) {
		if (hlim > 255) return false;
		_hlim = hlim; return true;
	}
	inline int hlim() const { return _hlim; }

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

	Word pseudo_checksum(Octet proto, Word plen) const;

	void fixup();
	bool do_fragmentation(IPv6 ** &frags, int &num);
	bool do_build(Buffer &pkt, int phase, Word &pos);
	bool build(Buffer &pkt);
	bool decode(Buffer &pkt);
	int get_last_nh() const { 
		if (_num_exts == 0) return _nh;
		return _exts[_num_exts-1]->next_header();
	}
	void set_last_nh(int nh) {
		if (_num_exts == 0) _nh = nh; 
		else _exts[_num_exts-1]->next_header(nh);
	}
	// taking ownership of h
	bool add_exthdr(IPv6ExtensionHeader *h) { 
		if (_num_exts >= MAX_EXTS) return false;
		if (_num_exts == 0) { h->next_header(_nh); _nh = h->type(); }
		else { h->next_header(_exts[_num_exts-1]->next_header()); _exts[_num_exts-1]->next_header(h->type()); }
		_exts[_num_exts++] = h; 
		return true; 
	}
	void delete_exthdrs() {
		for (int i = 0; i < _num_exts; i++) delete _exts[i];
		_num_exts = 0;
	}

	~IPv6();

        inline static void add_proto(Octet code, Packet *(p)(Buffer &pkt)) { _map_p[code] = p; }

private:
	inline static Packet *proto_factory(Octet code, Buffer &pkt) {
		if (_map_p[code]) return (*_map_p[code])(pkt);
		return NULL;
	}
	static Packet *(*_map_p[256])(Buffer &pkt);
};

#ifdef _LIB

// proto table
Packet *(*IPv6::_map_p[256])(Buffer &pkt);

IPv6::~IPv6()
{
	if (_frag && _own) delete _frag;
	delete_exthdrs();
}

string IPv6::to_string(int level) const
{
	string ret("<IPv6");
	if (_version != 6) {
		ret += " version=";
		ret += cvt_int(_version);
	}
	if (_tc) {
		ret += " tc=";
		ret += cvt_int(_tc);
	}
	if (_flow) {
		ret += " flow=";
		ret += cvt_int(_flow);
	}
	ret += " plen=";
	ret += cvt_int(_plen);
	ret += " nh=";
	ret += cvt_int(_nh);
	ret += " hlim=";
	ret += cvt_int(_hlim);
	ret += " src=";
	ret += _src.to_string(level);
	ret += " dst=";
	ret += _dest.to_string(level);
	for (int i = 0; i < _num_exts; i++) {
		ret += " | ";
		ret += _exts[i]->to_string(level);
	}
	ret += ">";
	return ret;
}

void IPv6::fixup()
{
	Ethernet *e;
	if (underlayer() &&	// if in ethernet packet, setup type
	    (e = dynamic_cast<Ethernet *>(underlayer())) != NULL) e->type(0x86dd);
}

//
// IPv6 Pseudo Header
//

// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// +                                                               +
// |                                                               |
// +                         Source Address                        +
// |                                                               |
// +                                                               +
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// +                                                               +
// |                                                               |
// +                      Destination Address                      +
// |                                                               |
// +                                                               +
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                   Upper-Layer Packet Length                   |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                      zero                     |  Next Header  |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// RFC 2460 8.1
// Upper-Layer packet length doesn't include Extension headers (in contrary
// to Payload length in IPv6 header)
Word IPv6::pseudo_checksum(Octet nh, Word plen) const
{
	// XXX TODO - if routing header, take the last destination address
	return _src.checksum() + _dest.checksum() + (plen>>16) + (plen&0xffff) + nh;
}

//  +------------------+--------+--------------+
//  |  Unfragmentable  |Fragment|    first     |
//  |       Part       | Header |   fragment   |
//  +------------------+--------+--------------+
//
//  +------------------+--------+--------------+
//  |  Unfragmentable  |Fragment|    second    |
//  |       Part       | Header |   fragment   |
//  +------------------+--------+--------------+
//  o
//  o
//  o
//  +------------------+--------+----------+
//  |  Unfragmentable  |Fragment|   last   |
//  |       Part       | Header | fragment |
//  +------------------+--------+----------+
//  The Unfragmentable Part of the original packet, with the
//  Payload Length of the original IPv6 header changed to contain
//  the length of this fragment packet only (excluding the length
//  of the IPv6 header itself), and the Next Header field of the
//  last header of the Unfragmentable Part changed to 44.
bool IPv6::do_fragmentation(IPv6 ** &frags, int &num)
{
	// break packet into list and return header
	num = 1;
	frags = new IPv6 *[num];
	frags[0] = this;
	return true;
}

bool IPv6::build(Buffer &pkt)
{
	Word pos = 0;
	if (!do_build(pkt, 0, pos)) return false;
	pos = 0;
	if (!do_build(pkt, 1, pos)) return false;
	return true;
}
	
bool IPv6::do_build(Buffer &pkt, int phase, Word &pos)
{
	Word plen = 0;
	// get the extension headers size, insert required paddings
	if (phase == 0) {
		pos += 10*4;
		for (int i = 0; i < _num_exts; i++) {
			if (!_exts[i]->build(pkt, phase, plen)) return false;
		}
		if (payload()) {
			if (!payload()->do_build(pkt, phase, plen)) return false;
		}
		_plen = plen;	// all extension headers + payload
		pos += plen;
		// TODO - if _plen > MTU, perform fragmentation
		return true;
	}
	pos += 10*4;
	if (!pkt.add_hlong(((_version&0xf)<<28)+(_tc<<20)+(_flow & 0xfffff))) return false;
	if (!pkt.add_hshort(_plen)) return false;
	if (!pkt.add_octet(_nh)) return false;
	if (!pkt.add_octet(_hlim)) return false;
	if (!_src.build(pkt, phase)) return false;
	if (!_dest.build(pkt, phase)) return false;
	for (int i = 0; i < _num_exts; i++) {
		if (!_exts[i]->build(pkt, phase, plen)) return false;
	}
	if (payload()) {	// build upper layer packet
		if (!payload()->do_build(pkt, phase, plen)) return false;
	}
	pos += plen;
	return true;
}

bool IPv6::decode(Buffer &pkt)
{
	Octet b1,b2,b3,b4;
	delete_exthdrs();
	if (_frag && _own) delete _frag;
	_own = false;
	_frag = 0;
	release();		// release previous content
	pkt.set();
	if (!pkt.get_octet(b1) || !pkt.get_octet(b2) || !pkt.get_octet(b3) || !pkt.get_octet(b4)) {
		pkt.reset();
		return false;
	}
	_version = b1 >> 4;				// 28..31
	if (_version != 6) {
		if (debug > 0) cerr << "IPV6::decode(): invalid IPv6 version " << (int)_version << endl;
		return false;
	}
	_tc = ((b1 & 0xf) << 4) + (b2 >> 4);		// 20..27
	_flow = ((b2 & 0xf) << 16) + (b3 << 8) + b4;	// 0..19
	if (!pkt.get_octet(b1) || !pkt.get_octet(b2) || !pkt.get_octet(b3) || !pkt.get_octet(b4)) {
		pkt.reset();
		return false;
	}
	_plen = (b1 << 8) + b2;
	_nh = b3;
	_hlim = b4;
	if (!_src.decode(pkt) || !_dest.decode(pkt)) {
		pkt.reset();
		return false;
	}
	int nh = _nh;
	bool is_frag = false;
	Short extlen = pkt.left();
	while (1) {	// process extension headers
		IPv6ExtensionHeader *ret;
		if (nh == IPv6ExtensionHeader::HopByHopHeader) {
			ret = new IPv6HopByHopHeader();
			if (!ret->decode(pkt)) return false;
			nh = ret->next_header();
		} else
		if (nh == IPv6ExtensionHeader::RoutingHeader) {
			ret = new IPv6RoutingHeader();
			if (!ret->decode(pkt)) return false;
			nh = ret->next_header();
		} else
		if (nh == IPv6ExtensionHeader::FragmentHeader) {
			IPv6FragmentHeader *fh = new IPv6FragmentHeader();
			ret = fh;
			if (!ret->decode(pkt)) return false;
			if (fh->fragment_offset()) is_frag = true;
			nh = ret->next_header();
		} else
		if (nh == IPv6ExtensionHeader::NoHeader) {
			break;
		} else
		if (nh == IPv6ExtensionHeader::DestinationOptions) {
			ret = new IPv6DestinationOptionsHeader();
			if (!ret->decode(pkt)) return false;
			nh = ret->next_header();
		} else 
		if (nh == IPv6ExtensionHeader::AHHeader || nh == IPv6ExtensionHeader::ESPHeader) {
			// uknown extension header
			ret = new IPv6ExtensionHeader(nh);
			if (!ret->decode(pkt)) return false;
			nh = ret->next_header();
		} else break;	// end of extension headers
		_exts[_num_exts++] = ret;
	} 
	extlen -= pkt.left();	// length of extension headers
	if (is_frag) {
		_frag_size = _plen-extlen;
		_frag = new Octet[_frag_size];
		_own = true;
		for (int i = 0; i < _frag_size; i++) {
			if (!pkt.get_octet(_frag[i])) return false;
		}
		return true;
	}
	// decode payload
	Packet *p = proto_factory(nh, pkt);
	if (p) {
		attach(p, true);
		if (!p->decode(pkt)) return false;
	}
	return true;
}

static Packet *IPv6_factory()
{
       Packet *p = new IPv6();
       // if (debug > 1) cerr << "new IPv6 instance " << p << endl;
       return p;
}

#endif
#endif
