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

#ifndef __NDWATCH_ICMPV6_H__
#define __NDWATCH_ICMPV6_H__

//
// ICMPv6 protocols
//
// RFC4443 ICMPv6 - done
// RFC2710 MLD - done
// RFC4861 ND - done 
// RFC4191 Router Preference - done
// RFC2894 Router Renum
// RFC4620 Node Information - TODO
// RFC3122 IND
// RFC3810 MLDv2 - done
// RFC3775 Mobile
// RFC3971 SEND
// RFC5380 Hierarchical Mobile IPv6 (HMIPv6) Mobility Manageme
// RFC5568 Mobile IPv6 Fast Handovers
// RFC4286 MRD
// RFC6106 RA DNS Options - done
// RFC5175 IPv6 Router Advertisement Flags Option
// RFC5269 Distributing a Symmetric Fast Mobile IPv6 (FMIPv6) Handover Key 
// 	   Using SEcure Neighbor Discovery (SEND)
// RFC5271 Mobile IPv6 Fast Handovers for 3G CDMA Networks
// 

//
// Common ICMPv6 Packet Format
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Type      |     Code      |          Checksum             |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// +                         Message Body                          +
// |                                                               |
// RFC2463 2.1
//
class ICMPv6: public Packet
{
protected:
	Octet _type;
	Octet _code;
	Short _checksum;

	ICMPv6(const ICMPv6&);
	ICMPv6& operator=(const ICMPv6&);
public:
	enum ICMPv6Types { Unknown=0,DestinationUnreachable=1,BufferTooBig=2,
		TimeExceeded=3,ParameterProblem=4,EchoRequest=128,
		EchoReply=129,MLD_Query=130,MLDv2_Query=130,MLD_Report=131,
		MLD_Done=132,ND_RS=133,ND_RA=134,
		ND_NS=135,ND_NA=136,ND_Redirect=137,
		RouterRenumbering=138,NI_Query=139,NI_Reply=140,
		IND_Solicitation=141,IND_Advertisment=142,MLDv2_Report=143,
		HAAD_Request=144,HAAD_Reply=145,MP_Sol=146,
		MP_Advertisement=147,SEND_CPSolicitation=148,SEND_CPAdvertisement=149,
		MRD_Advertisement=151,MRD_Solicitation=152,MRD_Termination=153,
	};

	ICMPv6(): _type(0), _code(0), _checksum(0) { }
	explicit ICMPv6(Octet type): _type(type), _code(0), _checksum(0) { }

	void fixup() { IPv6 *ip6 = dynamic_cast<IPv6 *>(underlayer()); ip6->set_last_nh(ProtoICMPv6); }
	int type() const { return _type; }
	int code() const { return _code; }

	virtual class NDOption *get_option(int i) const { return NULL; }
	virtual string name() const { return "ICMPv6Unkn"; }
	virtual string to_string(int level=0) const;
	virtual string body_to_string(int level) const;
	virtual bool build_body(Buffer &pkt, int phase, Word &pos, Word &checksum);
	virtual bool decode_body(Buffer &pkt);
	virtual bool decode(Buffer &pkt);
	virtual bool do_build(Buffer &pkt, int phase, Word &pos);
	virtual ~ICMPv6() { }
};

#ifdef _LIB
string ICMPv6::body_to_string(int level) const
{
	return "";
}

string ICMPv6::to_string(int level) const
{
	string str("<ICMPv6 type=");
	if (name() == "Unknown") str += cvt_int(_type);
	else str += name();
	if (_code) {
		str += " code=";
		str += cvt_int(_code);
	}
	str += body_to_string(level);
	str += ">";
	return str;
}

bool ICMPv6::build_body(Buffer &pkt, int phase, Word &pos, Word &checksum)
{
	return true;
}

bool ICMPv6::decode_body(Buffer &pkt)
{
	return true;
}

bool ICMPv6::do_build(Buffer &pkt, int phase, Word &pos)
{
	Word checksum;
	Word plen = 4;
	if (!underlayer()) {
		if (debug > 0) cerr << "No lower IPv6 header in ICMPv6" << endl;
		return false;
	}
	if (phase == 0) {
		checksum = 0;
		if (!build_body(pkt, phase, plen, checksum)) return false;
		// now, plen is valid
		pos += plen;
		// plen in pseudo header doesn't include ipv6 extension headers
		checksum += underlayer()->pseudo_checksum(ProtoICMPv6, plen);
		checksum += (_type<<8) + _code;
		checksum = (checksum&0xffff)+(checksum>>16);	// add carry for 1'complement arithmetic
		if (checksum > 65535) checksum -= 65535;
		_checksum = ~checksum;	// truncated to 16bits
		return true;
	}
	if (!pkt.add_octet(_type)) return false;
	if (!pkt.add_octet(_code)) return false;
	if (!pkt.add_octet(_checksum>>8)) return false;
	if (!pkt.add_octet(_checksum & 0xff)) return false;
	if (!build_body(pkt, phase, plen, checksum)) return false;
	pos += plen;
	return true;
}

bool ICMPv6::decode(Buffer &pkt)
{
	if (!pkt.get_octet(_code)) return false;
	if (!pkt.get_hshort(_checksum)) return false;
	if (!decode_body(pkt)) return false;
	return true;
}
#endif

//  Common base ICMPv6 packet format
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |     Type      |     Code      |          Checksum             |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |                             Word2                             |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |                             Data                              |
class ICMPv6Error: public ICMPv6
{
protected:
	enum { maxdata = IPV6_MIN_MTU - 5*4 - 2*4 };
	Word _length;
	Word _word2;
	Octet _data[maxdata];	// IPv6 + ICMPv6 length
public:
	ICMPv6Error(): _length(0), _word2(0) { }
	ICMPv6Error(int type, int code, Word word2, Buffer &pkt);
	bool build_body(Buffer &pkt, int phase, Word &pos, Word &checksum);
	bool decode_body(Buffer &pkt);
	~ICMPv6Error() { }
};

#ifdef _LIB
ICMPv6Error::ICMPv6Error(int type, int code, Word word2, Buffer &pkt): ICMPv6(type), _length(0), _word2(word2)
{
	_code = code;
	_length = (pkt.length()>maxdata?maxdata:pkt.length());
	_length &= 0xfffc;
	pkt.reset();
	for (unsigned i = 0; i < _length; i++) {
		if (!pkt.get_octet(_data[i])) break;
	}
	pkt.reset();
}

bool ICMPv6Error::build_body(Buffer &pkt, int phase, Word &pos, Word &checksum)
{
	unsigned i;
	if (phase == 0) {
		pos += _length + 4;
		checksum += (_word2>>16) + (_word2 & 0xffff);
		for (i = 0; i < _length; i+=2) {
			checksum += (_data[i]<<8) + _data[i+1];
		}
		return true;
	}
	if (!pkt.add_hlong(_word2)) return false;
	pos += 4;
	for (i = 0; i < _length; i++) {
		if (!pkt.add_octet(_data[i])) return false;
		pos++;
	}
	return true;
}

bool ICMPv6Error::decode_body(Buffer &pkt)
{
	if (!pkt.get_hlong(_word2)) return false;
	_length = 0;
	while (pkt.left() > 0) {
		if (_length >= maxdata) return false;
		if (!pkt.get_octet(_data[_length])) return false;
		_length++;
	}
	return true;
}
#endif

//
// ICMPv6 Destination Unreachable
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Type      |     Code      |          Checksum             |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                             Unused                            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                    As much of invoking packet                 |
// +                as possible without the ICMPv6 packet          +
// |                exceeding the minimum IPv6 MTU [IPv6]          |
// RFC 4443 3.1
class ICMPv6DestinationUnreachable: public ICMPv6Error
{
public:
	enum CodeType { NoRoute=0, AdmProhib=1, BeyondScope=2, AddrUnreach=3,
		PortUnreach=4, SAPolicy=5, DestRouteRej=6 };
	ICMPv6DestinationUnreachable() { _type = DestinationUnreachable; }
	ICMPv6DestinationUnreachable(int code, Buffer &pkt): ICMPv6Error(DestinationUnreachable, code, 0, pkt) { }
	string name() const { return "DestinationUnreachable"; }
	~ICMPv6DestinationUnreachable() { }
};

//
// ICMPv6 Buffer Too Big
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Type      |     Code      |          Checksum             |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                             MTU                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                    As much of invoking packet                 |
// +               as possible without the ICMPv6 packet           +
// |               exceeding the minimum IPv6 MTU [IPv6]           |
// RFC 4443 3.2
class ICMPv6BufferTooBig: public ICMPv6Error
{
public:
	ICMPv6BufferTooBig() { _type = BufferTooBig; }
	ICMPv6BufferTooBig(int code, Word mtu, Buffer &pkt): ICMPv6Error(BufferTooBig, code, mtu, pkt) { }
	string name() const { return "BufferTooBig"; }
	string body_to_string(int level) const;
	~ICMPv6BufferTooBig() { }
};

#ifdef _LIB
string ICMPv6BufferTooBig::body_to_string(int level) const
{
	string str(" mtu=");
	str += cvt_int(_word2);
	return str;
}
#endif

//
// ICMPv6 Time Exceeded
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Type      |     Code      |          Checksum             |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                             Unused                            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                    As much of invoking packet                 |
// +               as possible without the ICMPv6 packet           +
// |               exceeding the minimum IPv6 MTU [IPv6]           |
// RFC 4443 3.3
class ICMPv6TimeExceeded: public ICMPv6Error
{
public:
	enum CodeType { HopLimit=0, FragReass=1 };
	ICMPv6TimeExceeded() { _type = TimeExceeded; }
	ICMPv6TimeExceeded(int code, Buffer &pkt): ICMPv6Error(TimeExceeded, code, 0, pkt) { }
	string name() const { return "TimeExceeded"; }
	~ICMPv6TimeExceeded() { }
};

//
// ICMPv6 Parameters Problem
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Type      |     Code      |          Checksum             |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                            Pointer                            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                    As much of invoking packet                 |
// +               as possible without the ICMPv6 packet           +
// |               exceeding the minimum IPv6 MTU [IPv6]           |
// RFC 4443 3.4
class ICMPv6ParameterProblem: public ICMPv6Error
{
public:
	enum CodeType { ErrHdrField=0, UnNextHdr=1, UnOpt=2 };
	ICMPv6ParameterProblem() { _type = ParameterProblem; }
	ICMPv6ParameterProblem(int code, Word ptr, Buffer &pkt): ICMPv6Error(ParameterProblem, code, ptr, pkt) { }
	string name() const { return "ParameterProblem"; }
	string body_to_string(int level) const;
	~ICMPv6ParameterProblem() { }
};

#ifdef _LIB
string ICMPv6ParameterProblem::body_to_string(int level) const
{
	string str(" ptr=");
	str += cvt_int(_word2);
	return str;
}
#endif

//
// ICMPv6 Echo Request
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Type      |     Code      |          Checksum             |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |           Identifier          |        Sequence Number        |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Data ...
// +-+-+-+-+-
// RFC 4443 4.1
class ICMPv6EchoRequest: public ICMPv6Error
{
public:
	ICMPv6EchoRequest() { _type = EchoRequest; }
	ICMPv6EchoRequest(Word id, Word seq, Buffer &pkt): ICMPv6Error(EchoRequest, 0, (id << 16)+seq, pkt) {  }
	string name() const { return "EchoRequest"; }
	string body_to_string(int level) const;
	~ICMPv6EchoRequest() { }
};

#ifdef _LIB
string ICMPv6EchoRequest::body_to_string(int level) const
{
	string str(" id=");
	str += cvt_int(_word2 >> 16);
	str += " seq=";
	str += cvt_int(_word2 & 0xffff);
	return str;
}
#endif

// RFC 4443 4.2
class ICMPv6EchoReply: public ICMPv6Error
{
public:
	ICMPv6EchoReply() { _type = EchoReply; }
	ICMPv6EchoReply(Word id, Word seq, Buffer &pkt): ICMPv6Error(EchoReply, 0, (id<<16)+seq, pkt) { }
	string name() const { return "EchoReply"; }
	string body_to_string(int level) const;
	~ICMPv6EchoReply() { }
};

#ifdef _LIB
string ICMPv6EchoReply::body_to_string(int level) const
{
	string str(" id=");
	str += cvt_int(_word2 >> 16);
	str += " seq=";
	str += cvt_int(_word2 & 0xffff);
	return str;
}
#endif

//
// ICMPv6 MLD Query
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Type      |     Code      |          Checksum             |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Maximum Response Delay    |          Reserved             |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// +                                                               +
// |                                                               |
// +                       Multicast Address                       +
// |                                                               |
// +                                                               +
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// RFC2710 3.
class ICMPv6MLD_Query: public ICMPv6
{
private:
	Short _mrd;		// maximum response delay
	IPv6Address _group;	// multicast address
public:
	ICMPv6MLD_Query(): ICMPv6(MLD_Query), _mrd(10000), _group() { }
	explicit ICMPv6MLD_Query(const IPv6Address &grp, Short mrd=10000): ICMPv6(MLD_Query), _mrd(mrd), _group(grp) { }
	string name() const { return "MLD_Query"; }
	string body_to_string(int level) const;
	void group(const IPv6Address &grp) { _group = grp; }
	const IPv6Address& group() const { return _group; }
	void mrd(Short md) { _mrd = md; }
	Short mrd() const { return _mrd; }
	bool build_body(Buffer &pkt, int phase, Word &pos, Word &checksum);
	bool decode_body(Buffer &pkt);

	~ICMPv6MLD_Query() { }
};

#ifdef _LIB
string ICMPv6MLD_Query::body_to_string(int level) const
{
	string str(" mrd=");
	str += cvt_int(_mrd);
	str += " group=";
	str += _group.to_string(level);
	return str;
}

bool ICMPv6MLD_Query::build_body(Buffer &pkt, int phase, Word &pos, Word &checksum)
{
	if (phase == 0) {
		checksum += _mrd;
		checksum += _group.checksum();
		pos += 4+16;
		return true;
	}
	if (!pkt.add_hshort(_mrd)) return false;
	pos += 2;
	if (!pkt.add_hshort(0)) return false;
	pos += 2;
	if (!_group.build(pkt, phase)) return false;
	pos += 16;
	return true;
}

bool ICMPv6MLD_Query::decode_body(Buffer &pkt)
{
	if (!pkt.get_hshort(_mrd)) {
		if (debug > 0) cerr << "ICMPv6MLDQuery: Missing Maximum Response Delay octets" << endl;
		return false;
	}
	Short res;
	if (!pkt.get_hshort(res)) {
		if (debug > 0) cerr << "ICMPv6MLDQuery: Missing Maximum Response Delay octets" << endl;
		return false;
	}
	if (!_group.decode(pkt)) {
		if (debug > 0) cerr << "ICMPv6MLDQuery: Missing Multicast Address" << endl;
		return false;
	}
	return true;
}
#endif

//
// ICMPv6 MLD Report
//
class ICMPv6MLD_Report: public ICMPv6MLD_Query
{
private:
public:
	ICMPv6MLD_Report() { _type = MLD_Report; }
	string name() const { return "MLD_Report"; }
	~ICMPv6MLD_Report() { }
};

class ICMPv6MLD_Done: public ICMPv6MLD_Query
{
private:
public:
	ICMPv6MLD_Done() { _type = MLD_Done; }
	string name() const { return "MLD_Done"; }
	~ICMPv6MLD_Done() { }
};


//
//  Version 2 Multicast Listener Query 
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |  Type = 130   |      Code     |           Checksum            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |    Maximum Response Code      |           Reserved            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// *                                                               *
// |                                                               |
// *                       Multicast Address                       *
// |                                                               |
// *                                                               *
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Resv  |S| QRV |     QQIC      |     Number of Sources (N)     |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// *                                                               *
// |                                                               |
// *                       Source Address...                       *
// |                                                               |
// *                                                               *
// |                                                               |
// RFC 3810 5.1
class ICMPv6MLDv2_Query: public ICMPv6
{
private:
	enum { MAX_SOURCES = 89 };		// 1424/16
	Short _max_resp_code;			// maximum response time in exp
	IPv6Address _group;			// multicast address
	enum { SFlag=8, QRV = 7 };
	Octet _flags;
	Octet _qqic;
	Short _nsources;			// 
	IPv6Address _sources[MAX_SOURCES];	// source address

	ICMPv6MLDv2_Query(const ICMPv6MLDv2_Query&);
	ICMPv6MLDv2_Query &operator=(const ICMPv6MLDv2_Query&);
public:
	ICMPv6MLDv2_Query(): ICMPv6(MLDv2_Query), _max_resp_code(10), _group(), _flags(0), _qqic(0), _nsources(0) { }
	string name() const { return "MLDv2_Query"; }
	string body_to_string(int level) const;
	void group(const IPv6Address &grp) { _group = grp; }
	const IPv6Address& group() const { return _group; }
	Word max_response_time() const {
		if (_max_resp_code < 32768) return _max_resp_code;
		return ((_max_resp_code & 0xfff)|0x1000) << (((_max_resp_code & 0x7000)>>12)+3);
	}
	void max_response_time(Word rt) { 
		if (rt < 32768) _max_resp_code = rt; 
		else {
			// TODO - convert to exp format
		}
	}
	Word QQIC() const {
		if (_qqic < 128) return _qqic;
		return ((_qqic & 0xf) | 0x10) << (((_qqic & 0x70)>>4)+3);
	}
	void QQIC(Word qqic) {
		if (qqic < 128) _qqic = qqic;
		else {
			// TODO
		}
	}
	bool add_source(const IPv6Address &src) { 
		if (_nsources >= MAX_SOURCES) return false;
		_sources[_nsources++] = src;
		return true;
	}
	bool get_source(Short i, IPv6Address &src) const { 
		if (i >= _nsources) return false;
		src = _sources[i];
		return true;
	}
	bool build_body(Buffer &pkt, int phase, Word &pos, Word &checksum);
	bool decode_body(Buffer &pkt);

	~ICMPv6MLDv2_Query() { }
};

#ifdef _LIB
string ICMPv6MLDv2_Query::body_to_string(int level) const
{
	string str(" max_resp_delay=");
	str += cvt_int(max_response_time());
	str += " group=";
	str += _group.to_string(level);
	if (_flags & SFlag) {
		str += " SF";
	}
	if (_flags & QRV) {
		str += " QRV=";
		str += cvt_int(_flags & QRV);
	}
	if (_qqic) {
		str += " QQIC=";
		str += cvt_int(QQIC());
	}
	for (unsigned i = 0; i < _nsources; i++) {
		if (i) str += ", ";
		else str = " srcs=[";
		str += _sources[i].to_string(level);
	}
	if (_nsources) str += "]";
	return str;
}

bool ICMPv6MLDv2_Query::build_body(Buffer &pkt, int phase, Word &pos, Word &checksum)
{
	if (phase == 0) {
		checksum += _max_resp_code;
		pos += 4;
		checksum += _group.checksum();
		pos += 16;
		checksum += (_flags << 8) + _qqic;
		checksum += _nsources;
		pos += 4;
		for (unsigned i = 0; i < _nsources; i++) {
			checksum += _sources[i].checksum();
			pos += 16;
		}
		return true;
	}
	if (!pkt.add_hshort(_max_resp_code)) return false;
	// reserved
	if (!pkt.add_hshort(0)) return false;
	if (!pkt.add_hshort(_nsources)) return false;
	pos += 4;
	if (!_group.build(pkt, phase)) return false;
	pos += 16;
	if (!pkt.add_octet(_flags)) return false;
	++pos;
	if (!pkt.add_octet(_qqic)) return false;
	++pos;
	if (!pkt.add_hshort(_nsources)) return false;
	pos += 2;
	for (unsigned i = 0; i < _nsources; i++) {
		if (!_sources[i].build(pkt, phase)) return false;
		pos += 16;
	}
	return true;
}

bool ICMPv6MLDv2_Query::decode_body(Buffer &pkt)
{
	Short res, nsources;
	_nsources = 0;
	if (!pkt.get_hshort(_max_resp_code)) {
		if (debug > 0) cerr << "ICMPv6MLDv2_Query: Missing Max Response Code octets" << endl;
		return false;
	}
	if (!pkt.get_hshort(res)) {
		if (debug > 0) cerr << "ICMPv6MLDv2_Query: Missing Reserved octets" << endl;
		return false;
	}
	if (!_group.decode(pkt)) {
		if (debug > 0) cerr << "ICMPv6MLDv2_Query: Missing Multicast Address" << endl;
		return false;
	}
	if (!pkt.get_octet(_flags)) {
		if (debug > 0) cerr << "ICMPv6MLDv2_Query: Missing Flags octet" << endl;
		return false;
	}
	if (!pkt.get_octet(_qqic)) {
		if (debug > 0) cerr << "ICMPv6MLDv2_Query: Missing QQIC octet" << endl;
		return false;
	}
	if (!pkt.get_hshort(nsources)) {
		if (debug > 0) cerr << "ICMPv6MLDv2_Query: Missing Number of Sources octets" << endl;
		return false;
	}
	for (unsigned i = 0; i < nsources; i++) {
		IPv6Address source;
		if (!source.decode(pkt)) {
			if (debug > 0) cerr << "ICMPv6MLDv2_Query: Missing Multicast Source Address" << endl;
			return false;
		}
		if (i < MAX_SOURCES) {
			_sources[i] = source;
			_nsources = i+1;
		}
	}
	return true;
}
#endif

//
// Multicast Address Record has the following internal format:
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |  Record Type  |  Aux Data Len |     Number of Sources (N)     |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// *                       Multicast Address                       *
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// *                       Source Address [1..N]                   *
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// .                         Auxiliary Data                        .
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
class MLDv2_MAR
{
	enum { MAX_SOURCES = 10 };
	Octet _rtype;
	Octet _auxlen;
	Short _nsources;
	IPv6Address _group;
	IPv6Address _sources[MAX_SOURCES];
	// TODO
	// Octet *_auxdata;
public:
	enum { MODE_IS_INCLUDE=1, MODE_IS_EXCLUDE=2, CHANGE_TO_INCLUDE=3,
		CHANGE_TO_EXCLUDE=4, ALLOW_NEW_SOURCES=5, BLOCK_OLD_SOURCES=6 };
	MLDv2_MAR(Octet rtype, const IPv6Address &grp): _rtype(rtype), _auxlen(0), _nsources(0), _group(grp) { }
	Octet type() const { return _rtype; }
	Octet auxlen() const { return _auxlen; }
	Short nsources() const { return _nsources; }
	const IPv6Address &group() const { return _group; }
	inline bool add_source(const IPv6Address &src) { 
		if (_nsources >= MAX_SOURCES) return false;
		_sources[_nsources++] = src;
		return true;
	}
	inline bool get_source(Short i, IPv6Address &src) const {
		if (i >= _nsources) return false;
		src = _sources[i];
		return true;
	}
	~MLDv2_MAR() { }
};

//
//  Version 2 Multicast Listener Report Message
//
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |  Type = 143   |    Reserved   |           Checksum            |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |           Reserved            |Nr of Mcast Address Records (M)|
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |                                                               |
//  .                                                               .
//  .                  Multicast Address Record [1-n]               .
//  .                                                               .
//  |                                                               |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// RFC 3810 5.2
class ICMPv6MLDv2_Report: public ICMPv6
{
private:
	enum { MAX_RECS = 20 };
	Short _nrecs;		// 
	MLDv2_MAR *_recs[MAX_RECS];	// multicast address records

	ICMPv6MLDv2_Report(const ICMPv6MLDv2_Report&);
	ICMPv6MLDv2_Report &operator=(const ICMPv6MLDv2_Report&);
public:
	ICMPv6MLDv2_Report(): ICMPv6(MLDv2_Report), _nrecs(0) { }
	string name() const { return "MLDv2_Report"; }
	string body_to_string(int level) const;
	bool add_group(Octet rtype, const IPv6Address &grp) { 
		if (_nrecs >= MAX_RECS) return false;
		_recs[_nrecs++] = new MLDv2_MAR(rtype, grp);
		return true;
	}
	bool add_source(const IPv6Address &src) {
		if (_nrecs == 0) return false;
		_recs[_nrecs-1]->add_source(src);
	}
	bool get_group(Short i, IPv6Address &grp) const { 
		if (i >= _nrecs) return false;
		grp = _recs[i]->group();
		return true;
	}
	bool get_source(Short i, Short j, IPv6Address &src) const {
		if (i >= _nrecs) return false;
		return _recs[i]->get_source(j, src);
	}
	bool build_body(Buffer &pkt, int phase, Word &pos, Word &checksum);
	bool decode_body(Buffer &pkt);

	~ICMPv6MLDv2_Report() { 
		for (unsigned i = 0; i < _nrecs; i++) delete _recs[i];
	}
};

#ifdef _LIB
string ICMPv6MLDv2_Report::body_to_string(int level) const
{
	string str;
	for (unsigned i = 0; i < _nrecs; i++) {
		str += " grp=";
		str += _recs[i]->group().to_string(level);
		str += " mode=";
		switch (_recs[i]->type()) {
		case MLDv2_MAR::MODE_IS_INCLUDE: str += "is_inc"; break;
		case MLDv2_MAR::MODE_IS_EXCLUDE: str += "is_exc"; break;
		case MLDv2_MAR::CHANGE_TO_INCLUDE: str += "set_inc"; break;
		case MLDv2_MAR::CHANGE_TO_EXCLUDE: str += "set_exc"; break;
		case MLDv2_MAR::ALLOW_NEW_SOURCES: str += "allow_new"; break;
		case MLDv2_MAR::BLOCK_OLD_SOURCES: str += "block_old"; break;
		default: str += cvt_int(_recs[i]->type());
		}
		unsigned j = 0;
		IPv6Address src;
		while (_recs[i]->get_source(j, src)) {
			if (j) str += ", ";
			else str = " srcs=[";
			str += src.to_string(level);
			j++;
		}
		if (j) str += "]";
	}
	return str;
}

bool ICMPv6MLDv2_Report::build_body(Buffer &pkt, int phase, Word &pos, Word &checksum)
{
	if (phase == 0) {
		checksum += _nrecs;
		pos += 4;
		for (unsigned i = 0; i < _nrecs; i++) {
			checksum += (_recs[i]->type() << 8) + (_recs[i]->auxlen());
			checksum += _recs[i]->nsources();
			checksum += _recs[i]->group().checksum();
			pos += 16 + 4;
			unsigned j = 0;
			IPv6Address src;
			while (_recs[i]->get_source(j, src)) {
				checksum += src.checksum();
				pos += 16;
				j++;
			}
		}
		return true;
	}
	if (!pkt.add_hshort(0)) return false;
	if (!pkt.add_hshort(_nrecs)) return false;
	pos += 4;
	for (unsigned i = 0; i < _nrecs; i++) {
		if (!pkt.add_octet(_recs[i]->type())) return false;
		++pos;
		if (!pkt.add_octet(_recs[i]->auxlen())) return false;
		++pos;
		if (!pkt.add_hshort(_recs[i]->nsources())) return false;
		pos += 2;
		if (!_recs[i]->group().build(pkt, phase)) return false;
		pos += 16;
		unsigned j = 0;
		IPv6Address src;
		while (_recs[i]->get_source(j, src)) {
			if (src.build(pkt, phase)) return false;
			pos += 16;
			j++;
		}
	}
	return true;
}

bool ICMPv6MLDv2_Report::decode_body(Buffer &pkt)
{
	Short res, nrecs;
	for (unsigned i = 0; i < _nrecs; i++) delete _recs[i];
	_nrecs = 0;
	if (!pkt.get_hshort(res)) {
		if (debug > 0) cerr << "ICMPv6MLDv2_Report: Missing Reserved octets" << endl;
		return false;
	}
	if (!pkt.get_hshort(nrecs)) {
		if (debug > 0) cerr << "ICMPv6MLDv2_Report: Missing Number of Records octets" << endl;
		return false;
	}
	for (unsigned i = 0; i < nrecs; i++) {
		Octet type, auxlen;
		Short nsrc;
		IPv6Address group, source;
		if (!pkt.get_octet(type)) {
			if (debug > 0) cerr << "ICMPv6MLDv2_Report: Missing Record Type octet" << endl;
			return false;
		}
		if (!pkt.get_octet(auxlen)) {
			if (debug > 0) cerr << "ICMPv6MLDv2_Report: Missing Aux Data Len octet" << endl;
			return false;
		}
		if (!pkt.get_hshort(nsrc)) {
			if (debug > 0) cerr << "ICMPv6MLDv2_Report: Missing Number of Sources octets" << endl;
			return false;
		}
		if (!group.decode(pkt)) {
			if (debug > 0) cerr << "ICMPv6MLDv2_Report: Missing Multicast Address" << endl;
			return false;
		}
		if (i < MAX_RECS) {
			_recs[i] = new MLDv2_MAR(type, group);
			_nrecs = i+1;
		}
		for (unsigned j = 0; j < nsrc; j++) {
			if (!source.decode(pkt)) {
				if (debug > 0) cerr << "ICMPv6MLDv2_Report: Missing Multicast Address" << endl;
				return false;
			}
			if (i < MAX_RECS) _recs[i]->add_source(source);
		}
		// TODO auxdata decode
		for (unsigned k = 0; k < auxlen; k++) {
			Octet dummy;
			if (!pkt.get_octet(dummy)) {
				if (debug > 0) cerr << "ICMPv6MLDv2_Report: Missing Aux Data octet" << endl;
				return false;
			}
		}
	}
	return true;
}
#endif

//
// Common Neighbor Discovery Options
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Type      |    Length     |              ...              |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// ~                              ...                              ~
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// RFC4861 4.6
class NDOption
{
protected:
	Octet _type;	// NDOptionType
	Octet _length;	// in 8 octet units, min. 1
public:
	enum NDOptionType { Unknown=0, SourceLLA=1, TargetLLA=2, PrefixInfo=3, 
		RedirectedHeader=4, MTU=5, ShortcutLimit=6,
		AdvertisementInterval=7, HomeAgent=8,
		SourceAddressList=9, TargetAddressList=10, 
		CGAOption=11, RSASignature=12, TimeStamp=13, Nonce=14,
		TrustAnchor=15, Certificate=16, IPAddress=17,
		NewRouterPrefix=18, LLAddress=19, NAAck=20, MAP=23, 
		RouteInfo=24, RecursiveDNS=25, 
		RAFlags=26, HandoverKeyRequest=27, HandoverKeyReply=28,
		HandoverAssist=29, MobileNodeId=30, DNSSearchList=31,  };
	NDOption(Octet type, Octet len): _type(type), _length(len) { }
	virtual string to_string(int level) const = 0;
	virtual string name() const = 0;
	virtual bool build(Buffer &pkt, int phase, Word &pos, Word &checksum) = 0;
	virtual bool decode(Buffer &pkt) = 0;
	virtual ~NDOption() { }
};

//
// ND Option Source Link Level Address
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Type      |    Length     |    Link-Layer Address ...
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// RFC4861 4.6.1/RFC2464 Ethernet only
// option length = 1, no padding
class NDOptionSourceLLA: public NDOption
{
protected:
	MacAddress _lla;
public:
	explicit NDOptionSourceLLA(Octet len): NDOption(SourceLLA, len),_lla() { }
	explicit NDOptionSourceLLA(const MacAddress &lla): NDOption(SourceLLA, 1), _lla(lla) {}
	explicit NDOptionSourceLLA(string mac): NDOption(SourceLLA, 1), _lla(mac) {}
	string name() const { return "SourceLLA"; }
	string to_string(int level) const;
	const MacAddress& lla() const { return _lla; }
	bool build(Buffer &pkt, int phase, Word &pos, Word &checksum);
	bool decode(Buffer &pkt);
};

#ifdef _LIB
string NDOptionSourceLLA::to_string(int level) const
{
	string str(name());
	if (_length != 1) {
		str += " length=";
		str += cvt_int(_length);
	}
	str += " mac=";
	str += _lla.to_string(level);
	return str;
}

bool NDOptionSourceLLA::build(Buffer &pkt, int phase, Word &pos, Word &checksum)
{
	if (phase == 0) {
		checksum += (_type << 8) + _length;
		checksum += _lla.checksum();
		pos += 8;
		return true;
	}
	if (!pkt.add_octet(_type)) return false;
	if (!pkt.add_octet(_length)) return false;
	if (!_lla.build(pkt, phase)) return false;
	return true;
}

bool NDOptionSourceLLA::decode(Buffer &pkt)
{
	if (_length != 1) {
		if (debug > 0) cerr << "NDOptionSourceLLA: Invalid length " << _length << endl;
		return false;
	}
	if (!_lla.decode(pkt)) return false;
	return true;
}
#endif

//
// ND Option Target Link Level Address
//
class NDOptionTargetLLA: public NDOptionSourceLLA
{
public:
	explicit NDOptionTargetLLA(Octet len): NDOptionSourceLLA(len) { _type = TargetLLA; }
	explicit NDOptionTargetLLA(const MacAddress &lla): NDOptionSourceLLA(lla) { _type = TargetLLA; }
	explicit NDOptionTargetLLA(string str): NDOptionSourceLLA(str) { _type = TargetLLA; }
	string name() const { return "TargetLLA"; }
};

//
// ND Option Prefix Information
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Type      |    Length     | Prefix Length |L|A|R|Reserved1|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                         Valid Lifetime                        |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                       Preferred Lifetime                      |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                           Reserved2                           |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// +                                                               +
// |                                                               |
// +                            Prefix                             +
// |                                                               |
// +                                                               +
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// RFC4861 4.6.2
// RFC3775 7.2.
class NDOptionPrefixInfo: public NDOption
{
protected:
	Octet _plen;		// prefix length (network)
	Octet _pflags;		// prefix flags (L, A)
	Word _vltime;		// valid lifetime
	Word _pltime;		// preferred lifetime
	Word _res2;		// reserved
	IPv6Address _prefix;	// prefix
public:
	enum PrefixFlag { Onlink=0x80, Auto=0x40, RouterAddress=0x20, DisablePrivacy=0x10 };
	explicit NDOptionPrefixInfo(Octet len): NDOption(PrefixInfo, len), _plen(0), _pflags(Onlink|Auto), _vltime(2592000), _pltime(604800), _res2(0), _prefix()  { }
	NDOptionPrefixInfo(const IPv6Address &addr, Octet plen, Word pflags=Onlink|Auto, Word vltime=2592000, Word pltime=604800): NDOption(PrefixInfo, 4), _plen(plen), _pflags(pflags), _vltime(vltime), _pltime(pltime), _res2(0), _prefix(addr)  { }
	NDOptionPrefixInfo(const string &addr, Octet plen, Word pflags=Onlink|Auto, Word vltime=2592000, Word pltime=604800): NDOption(PrefixInfo, 4), _plen(plen), _pflags(pflags), _vltime(vltime), _pltime(pltime), _res2(0), _prefix(addr)  { }

	const IPv6Address &prefix() const { return _prefix; }
	Octet plen() const { return _plen; } 
	Word valid_lifetime() const { return _vltime; }
	Word preferred_lifetime() const { return _pltime; }

	// RFC4861
	void set_onlink() { _pflags |= Onlink; }
	bool is_onlink() const { return (_pflags & Onlink); }
	void set_auto() { _pflags |= Auto; }
	bool is_auto() const { return (_pflags & Auto); }
	// RFC3775
	void set_router_address() { _pflags |= RouterAddress; }
	bool is_router_address() const { return (_pflags & RouterAddress); }

	string name() const { return "PrefixInfo"; }
	string to_string(int level) const;
	bool build(Buffer &pkt, int phase, Word &pos, Word &checksum);
	bool decode(Buffer &pkt);
};

#ifdef _LIB
string NDOptionPrefixInfo::to_string(int level) const
{
	string str("PrefixInfo");
	if (_length != 4) {
		str += " length=";
		str += cvt_int(_length);
	}
	str += " flags=";
	if (_pflags & Onlink) str += "L";
	if (_pflags & Auto) str += "A";
	str += " vltime=";
	str += cvt_int(_vltime);
	str += " pltime=";
	str += cvt_int(_pltime);
	str += " prefix=";
	str += _prefix.to_string(level);
	str += "/";
	str += cvt_int(_plen);
	return str;
}

bool NDOptionPrefixInfo::build(Buffer &pkt, int phase, Word &pos, Word &checksum)
{
	if (phase == 0) {
		checksum += (_type << 8) + _length;
		checksum += (_plen << 8) + _pflags;
		checksum += (_vltime >> 15) + (_vltime & 0xffff);
		checksum += (_pltime >> 15) + (_pltime & 0xffff);
		checksum += _prefix.checksum();
		pos += 4*8;
		return true;
	}
	if (!pkt.add_octet(_type)) return false;
	if (!pkt.add_octet(_length)) return false;
	if (!pkt.add_octet(_plen)) return false;
	if (!pkt.add_octet(_pflags)) return false;
	if (!pkt.add_hlong(_vltime)) return false;
	if (!pkt.add_hlong(_pltime)) return false;
	if (!pkt.add_hlong(0)) return false;
	if (!_prefix.build(pkt, phase)) return false;
	return true;
}

bool NDOptionPrefixInfo::decode(Buffer &pkt)
{
	if (_length != 4) {
		if (debug > 0) cerr << "NDOptionSourceLLA: Invalid length " << _length << endl;
		return false;
	}
	if (!pkt.get_octet(_plen)) return false;
	if (!pkt.get_octet(_pflags)) return false;
	if (!pkt.get_hlong(_vltime)) return false;
	if (!pkt.get_hlong(_pltime)) return false;
	if (!pkt.get_hlong(_res2)) return false;
	if (!_prefix.decode(pkt)) return false;
	return true;
}
#endif

//
// ND Redirected
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Type      |    Length     |            Reserved           |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                           Reserved                            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// ~                       IP header + data                        ~
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// RFC 4861 4.6.3
class NDRedirectedHeader: public NDOption
{
protected:
	enum { maxdata = IPV6_MIN_MTU - 5*4 - 2*4 };
	Octet _data[maxdata];	// IPv6 + ICMPv6 length
public:
	explicit NDRedirectedHeader(Octet len): NDOption(RedirectedHeader, len) { }
	string name() const { return "RedirectedHeader"; }
	string to_string(int level) const;
	bool build(Buffer &pkt, int phase, Word &pos, Word &checksum);
	bool decode(Buffer &pkt);
};

#ifdef _LIB
string NDRedirectedHeader::to_string(int level) const
{
	string str("RedirectedHeader");
	str += " length=";
	str += cvt_int(_length);
	return str;
}

bool NDRedirectedHeader::build(Buffer &pkt, int phase, Word &pos, Word &checksum)
{
	int i;
	if (phase == 0) {
		checksum += (_type << 8) + _length;
		for (i = 0; i < (_length-1)*8; i+=2) {
			checksum += (_data[i]<<8) + _data[i+1];
		}
		pos += _length*8;
		return true;
	}
	if (!pkt.add_octet(_type)) return false;
	if (!pkt.add_octet(_length)) return false;
	if (!pkt.add_hshort(0)) return false;
	if (!pkt.add_hlong(0)) return false;
	for (i = 0; i < (_length-1)*8; i++) {
		if (!pkt.add_octet(_data[i])) return false;
	}
	return true;
}

bool NDRedirectedHeader::decode(Buffer &pkt)
{
	Short res;
	Word res2;
	if (_length < 1 || _length > maxdata/8) {
		if (debug > 0) cerr << "NDRedirectedHeader: Invalid length " << _length << endl;
		return false;
	}
	if (!pkt.get_hshort(res)) return false;
	if (!pkt.get_hlong(res2)) return false;
	for (int i = 0; i < (_length-1)*8; i++) {
		if (!pkt.get_octet(_data[i])) return false;
	}
	return true;
}
#endif

//
// ND Option MTU
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Type      |    Length     |           Reserved            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                              MTU                              |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// RFC4861 4.6.4
class NDOptionMTU: public NDOption
{
protected:
	Word _mtu;
public:
	explicit NDOptionMTU(Octet len): NDOption(MTU, len),_mtu(0) { }
	NDOptionMTU(): NDOption(MTU, 1), _mtu(0) {}
	inline void mtu(Word mtu) { _mtu = mtu; }
	inline Word mtu() const { return _mtu; }
	string name() const { return "MTU"; }
	string to_string(int level) const;
	bool build(Buffer &pkt, int phase, Word &pos, Word &checksum);
	bool decode(Buffer &pkt);
};

#ifdef _LIB
string NDOptionMTU::to_string(int level) const
{
	string str("MTU");
	if (_length != 1) {
		str += " length=";
		str += cvt_int(_length);
	}
	str += " mtu=";
	str += cvt_int(_mtu);
	return str;
}

bool NDOptionMTU::build(Buffer &pkt, int phase, Word &pos, Word &checksum)
{
	if (phase == 0) {
		checksum += (_type << 8) + _length;
		checksum += (_mtu >> 16) + (_mtu & 0xffff);
		pos += 8;
		return true;
	}
	if (!pkt.add_octet(_type)) return false;
	if (!pkt.add_octet(_length)) return false;
	if (!pkt.add_hshort(0)) return false;
	if (!pkt.add_hlong(_mtu)) return false;
	return true;
}

bool NDOptionMTU::decode(Buffer &pkt)
{
	Short res;
	if (_length != 1) {
		if (debug > 0) cerr << "NDOptionMTU: Invalid length " << _length << endl;
		return false;
	}
	if (!pkt.get_hshort(res)) return false;
	if (!pkt.get_hlong(_mtu)) return false;
	return true;
}
#endif

// 
// ND Option Shortcut Limit
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Type      |    Length     | Shortcut Limit|   Reserved1   |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                         Reserverd2                            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// RFC4861 4.6.4
class NDOptionShortcutLimit: public NDOption
{
protected:
	Octet _limit;
public:
	explicit NDOptionShortcutLimit(Octet len): NDOption(ShortcutLimit, len),_limit(0) { }
	NDOptionShortcutLimit(): NDOption(ShortcutLimit, 1), _limit(0) {}
	inline void set_limit(Octet limit) { _limit = limit; }
	inline Octet get_limit() const { return _limit; }
	string name() const { return "ShortcutLimit"; }
	string to_string(int level) const;
	bool build(Buffer &pkt, int phase, Word &pos, Word &checksum);
	bool decode(Buffer &pkt);
};

#ifdef _LIB
string NDOptionShortcutLimit::to_string(int level) const
{
	string str("ShortcutLimit");
	if (_length != 1) {
		str += " length=";
		str += cvt_int(_length);
	}
	str += " ";
	str += cvt_int(_limit);
	return str;
}

bool NDOptionShortcutLimit::build(Buffer &pkt, int phase, Word &pos, Word &checksum)
{
	if (phase == 0) {
		checksum += (_type << 8) + _length + (_limit << 8);
		pos += 8;
		return true;
	}
	if (!pkt.add_octet(_type)) return false;
	if (!pkt.add_octet(_length)) return false;
	if (!pkt.add_octet(_limit)) return false;
	if (!pkt.add_octet(0)) return false;
	if (!pkt.add_hlong(0)) return false;
	return true;
}

bool NDOptionShortcutLimit::decode(Buffer &pkt)
{
	Octet res1;
	Word res2;
	if (_length != 1) {
		if (debug > 0) cerr << "NDOptionShortcutLimit: Invalid length " << _length << endl;
		return false;
	}
	if (!pkt.get_octet(_limit)) return false;
	if (!pkt.get_octet(res1)) return false;
	if (!pkt.get_hlong(res2)) return false;
	return true;
}
#endif

//
// ND Option Advertisement Interval
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Type      |    Length     |           Reserved            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                     Advertisement Interval                    |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// RFC3775 7.1.
class NDOptionAdvertisementInterval: public NDOption
{
protected:
	Word _adv_int;
public:
	explicit NDOptionAdvertisementInterval(Octet len): NDOption(MTU, len), _adv_int(0) { }
	NDOptionAdvertisementInterval(): NDOption(MTU, 1), _adv_int(0) {}
	inline void set_advertisement_interval(Word advint) { _adv_int = advint; }
	inline Word get_advertisement_interval() { return _adv_int; }
	string name() const { return "AdvertisementInterval"; }
	string to_string(int level) const;
	bool build(Buffer &pkt, int phase, Word &pos, Word &checksum);
	bool decode(Buffer &pkt);
};

#ifdef _LIB
string NDOptionAdvertisementInterval::to_string(int level) const
{
	string str("AdvertisementInterval");
	if (_length != 1) {
		str += " length=";
		str += cvt_int(_length);
	}
	str += " ";
	str += cvt_int(_adv_int);
	return str;
}

bool NDOptionAdvertisementInterval::build(Buffer &pkt, int phase, Word &pos, Word &checksum)
{
	if (phase == 0) {
		checksum += (_type << 8) + _length;
		checksum += (_adv_int >> 16) + (_adv_int & 0xffff);
		pos += 8;
		return true;
	}
	if (!pkt.add_octet(_type)) return false;
	if (!pkt.add_octet(_length)) return false;
	if (!pkt.add_hshort(0)) return false;
	if (!pkt.add_hlong(_adv_int)) return false;
	return true;
}

bool NDOptionAdvertisementInterval::decode(Buffer &pkt)
{
	Short res;
	if (_length != 1) {
		if (debug > 0) cerr << "NDAdvertisementInterval: Invalid length " << _length << endl;
		return false;
	}
	if (!pkt.get_hshort(res)) return false;
	if (!pkt.get_hlong(_adv_int)) return false;
	return true;
}
#endif

//
// ND Option Home Agent
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Type      |    Length     |           Reserved            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Home Agent Preference     |       Home Agent Lifetime     |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// RFC3775 7.4.
class NDOptionHomeAgent: public NDOption
{
protected:
	Short _pref;
	Short _ltime;
public:
	explicit NDOptionHomeAgent(Octet len): NDOption(HomeAgent, len), _pref(0),_ltime(0) { }
	NDOptionHomeAgent(Short pref, Short ltime): NDOption(HomeAgent, 1), _pref(pref), _ltime(ltime) {}

	inline void set_preference(Short pref) { _pref = pref; }
	inline Short get_preference() const { return _pref; }
	inline void set_lifetime(Short ltime) { _ltime = ltime; }
	inline Short get_lifetime() const { return _ltime; }
	string name() const { return "HomeAgent"; }
	string to_string(int level) const;
	bool build(Buffer &pkt, int phase, Word &pos, Word &checksum);
	bool decode(Buffer &pkt);
};

#ifdef _LIB
string NDOptionHomeAgent::to_string(int level) const
{
	string str("HomeAgent");
	if (_length != 1) {
		str += " length=";
		str += cvt_int(_length);
	}
	str += " pref=";
	str += cvt_int(_pref);
	str += " ltime=";
	str += cvt_int(_ltime);
	return str;
}

bool NDOptionHomeAgent::build(Buffer &pkt, int phase, Word &pos, Word &checksum)
{
	if (phase == 0) {
		checksum += (_type << 8) + _length;
		checksum += _pref + _ltime;
		pos += 8;
		return true;
	}
	if (!pkt.add_octet(_type)) return false;
	if (!pkt.add_octet(_length)) return false;
	if (!pkt.add_hshort(0)) return false;
	if (!pkt.add_hshort(_pref)) return false;
	if (!pkt.add_hshort(_ltime)) return false;
	return true;
}

bool NDOptionHomeAgent::decode(Buffer &pkt)
{
	Short res;
	if (_length != 1) {
		if (debug > 0) cerr << "NDOptionHomeAgent: Invalid length " << _length << endl;
		return false;
	}
	if (!pkt.get_hshort(res)) return false;
	if (!pkt.get_hshort(_pref)) return false;
	if (!pkt.get_hshort(_ltime)) return false;
	return true;
}
#endif

// 
// ND Option Route Information
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Type      |    Length     | Prefix Length |Resvd|Prf|Resvd|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                        Route Lifetime                         |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                   Prefix (Variable Length)                    |
// .                                                               .
// .                                                               .
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// Route Information Option
// RFC4191 2.3.
class NDOptionRouteInfo: public NDOption
{
protected:
	Octet _plen;		// prefix length (network)
	Octet _pflags;		// prefix flags (L, A)
	Word _rltime;		// valid lifetime
	IPv6Address _prefix;	// prefix
public:
	enum RouteFlag { PrefHigh=0x8, PrefMed=0, PrefLow=0x18 };
	explicit NDOptionRouteInfo(Octet len=1): NDOption(RouteInfo, len), _plen(0), _pflags(0), _rltime(604800), _prefix()  { }

	bool set_prefix(const IPv6Address &addr, Octet plen) { 
		_prefix = addr; 
		if (_plen > 128) return false;
		_plen = plen; 
		if (plen > 64) _length = 3;
		else
		if (plen > 0) _length = 2;
		else _length = 1;
		return true;
	}
	bool set_prefix(string addr, Octet plen) {
		if (!_prefix.addr(addr)) return false;
		if (_plen > 128) return false;
		_plen = plen; 
		if (plen > 64) _length = 3;
		else
		if (plen > 0) _length = 2;
		else _length = 1;
		return true;
	}
	inline const IPv6Address& get_prefix() const { return _prefix; }
	inline void set_lifetime(Word ltime) { _rltime = ltime; }
	inline Word get_lifetime() const { return _rltime; }
	inline void set_pref_high() { _pflags = (_pflags & ~PrefLow) | PrefHigh; }
	inline void set_pref_low() { _pflags = (_pflags & ~PrefLow) | PrefLow; }
	inline void set_pref_normal() { _pflags = (_pflags & ~PrefLow); }
	inline bool is_pref_high() const { return ((_pflags&PrefLow) == PrefHigh); }
	inline bool is_pref_low() const { return ((_pflags&PrefLow) == PrefLow); }
	inline bool is_pref_normal() const { return ((_pflags&PrefLow) == 0); }

	string name() const { return "RouteInfo"; }
	string to_string(int level) const;
	bool build(Buffer &pkt, int phase, Word &pos, Word &checksum);
	bool decode(Buffer &pkt);
};

#ifdef _LIB
string NDOptionRouteInfo::to_string(int level) const
{
	string str("RouteInfo");
	if (_length != 3) {
		str += " length=";
		str += cvt_int(_length);
	}
	if (is_pref_high()) str += " prf=H";
	else
	if (is_pref_low()) str += " prf=L";
	str += " rltime=";
	str += cvt_int(_rltime);
	str += " prefix=";
	str += _prefix.to_string(level);
	str += "/";
	str += cvt_int(_plen);
	return str;
}

bool NDOptionRouteInfo::build(Buffer &pkt, int phase, Word &pos, Word &checksum)
{
	if (phase == 0) {
		checksum += (_type << 8) + _length;
		checksum += (_plen << 8) + _pflags;
		checksum += (_rltime >> 15) + (_rltime & 0xffff);
		pos += 2*8;
		// length is multiple of 8 octets
		if (_plen > 64) {
			pos += 16;
			checksum += _prefix.checksum(16);
		} else
		if (_plen > 0) {
			pos += 8;
			checksum += _prefix.checksum(8);
		}
		return true;
	}
	if (!pkt.add_octet(_type)) return false;
	if (!pkt.add_octet(_length)) return false;
	if (!pkt.add_octet(_plen)) return false;
	if (!pkt.add_octet(_pflags)) return false;
	if (!pkt.add_hlong(_rltime)) return false;
	if (_plen > 64) {
		if (!_prefix.build(pkt, phase, 16)) return false;
	} else
	if (_plen > 0) {
		if (!_prefix.build(pkt, phase, 8)) return false;
	}
	return true;
}

bool NDOptionRouteInfo::decode(Buffer &pkt)
{
	if (_length > 3) {
		if (debug > 0) cerr << "NDOptionRouteInfo: Invalid length " << _length << endl;
		return false;
	}
	if (!pkt.get_octet(_plen)) return false;
	if (!pkt.get_octet(_pflags)) return false;
	if (!pkt.get_hlong(_rltime)) return false;
	_prefix.clear();
	if (_plen > 64) {
		if (!_prefix.decode(pkt)) return false;
	} else
	if (_plen > 0) {
		if (!_prefix.decode(pkt, 8)) return false;
	} else
	if (_length == 3) {	// invalid _plen
		if (!_prefix.decode(pkt)) return false;
	} else
	if (_length == 2) {	// invalid _plen
		if (!_prefix.decode(pkt, 8)) return false;
	}
	return true;
}
#endif

//
// ND Option Recursive DNS
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Type      |     Length    |           Reserved            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                           Lifetime                            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// :            Addresses of IPv6 Recursive DNS Servers            :
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// RFC6106 5.1. Recursive DNS Server Option
class NDOptionRecursiveDNS: public NDOption
{
	enum { MAX_ADDR=10 };
protected:
	Word _ltime;		// valid lifetime
	int _num_addrs;
	IPv6Address _addr[MAX_ADDR];	// prefix
public:
	NDOptionRecursiveDNS(): NDOption(RecursiveDNS, 1), _ltime(604800), _num_addrs(0)  { }
	explicit NDOptionRecursiveDNS(Octet len): NDOption(RecursiveDNS, len), _ltime(604800), _num_addrs(0)  { }

	bool add_addr(const IPv6Address &addr) { 
		if (_num_addrs >= MAX_ADDR) return false;
		_addr[_num_addrs++] = addr;
		return true;
	}
	bool get_addr(int i, IPv6Address &addr) {
		if (i >= _num_addrs) return false;
		addr = _addr[i];
		return true;
	}
	inline void set_lifetime(Word ltime) { _ltime = ltime; }
	inline Word get_lifetime() const { return _ltime; }

	string name() const { return "RecursiveDNS"; }
	string to_string(int level) const;
	bool build(Buffer &pkt, int phase, Word &pos, Word &checksum);
	bool decode(Buffer &pkt);
};

#ifdef _LIB
string NDOptionRecursiveDNS::to_string(int level) const
{
	string str("RecursiveDNS");
	str += " length=";
	str += cvt_int(_length);
	str += " ltime=";
	str += cvt_int(_ltime);
	for (int i = 0; i < _num_addrs; i++) {
		str += " ";
		str += _addr[i].to_string();
	}
	return str;
}

bool NDOptionRecursiveDNS::build(Buffer &pkt, int phase, Word &pos, Word &checksum)
{
	if (phase == 0) {
		checksum += (_type << 8) + _length;
		checksum += (_ltime >> 15) + (_ltime & 0xffff);
		pos += 8 + 2*_num_addrs;
		for (int i = 0; i < _num_addrs; i++) {
			checksum += _addr[i].checksum();
		}
		return true;
	}
	if (!pkt.add_octet(_type)) return false;
	if (!pkt.add_octet(_length)) return false;
	if (!pkt.add_hshort(0)) return false;
	if (!pkt.add_hlong(_ltime)) return false;
	for (int i = 0; i < _num_addrs; i++) {
		if (!_addr[i].build(pkt, phase)) return false;
	}
	return true;
}

bool NDOptionRecursiveDNS::decode(Buffer &pkt)
{
	Short res;
	if (!pkt.get_hshort(res)) return false;
	if (!pkt.get_hlong(_ltime)) return false;
	_num_addrs = 0;
	int i = _length-1;
	while (i > 0) {
		if (_num_addrs < MAX_ADDR) {
			if (!_addr[_num_addrs].decode(pkt)) return false;
			_num_addrs++;
		}
		i--;
	}
	return true;
}
#endif

//
// ND Option DNS Search List
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Type      |     Length    |           Reserved            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                           Lifetime                            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// :                Domain Names of DNS Search List                :
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// RFC6106 5.2. DNS Search List Option
class NDOptionDNSSearchList: public NDOption
{
	enum { MAX_NAMES=10 };
protected:
	Word _ltime;		// valid lifetime
	int _num_names;
	// XXX fix DNS domain name
	string _names[MAX_NAMES];	// prefix
public:
	NDOptionDNSSearchList(): NDOption(DNSSearchList, 1), _ltime(604800), _num_names(0)  { }
	explicit NDOptionDNSSearchList(Octet len): NDOption(DNSSearchList, len), _ltime(604800), _num_names(0)  { }

	bool add_name(const string name) {
		if (_num_names >= MAX_NAMES) return false;
		_names[_num_names++] = name;
		return true;
	}
	bool get_name(string &name, int i) {
		if (i >= _num_names) return false;
		name = _names[i];
		return true;
	}
	inline void set_lifetime(Word ltime) { _ltime = ltime; }
	inline Word get_lifetime() const { return _ltime; }

	string name() const { return "DNSSearchList"; }
	string to_string(int level) const;
	bool build(Buffer &pkt, int phase, Word &pos, Word &checksum);
	bool decode(Buffer &pkt);
};

#ifdef _LIB
string NDOptionDNSSearchList::to_string(int level) const
{
	string str("DNSSearchList");
	str += " length=";
	str += cvt_int(_length);
	str += " ltime=";
	str += cvt_int(_ltime);
	for (int i = 0; i < _num_names; i++) {
		str += " ";
		str += _names[i];
	}
	return str;
}

bool NDOptionDNSSearchList::build(Buffer &pkt, int phase, Word &pos, Word &checksum)
{
	if (phase == 0) {
		checksum += (_type << 8) + _length;
		checksum += (_ltime >> 15) + (_ltime & 0xffff);
		pos += 8;
		// XXX debug this
		Octet b1=0;
		for (int i = 0; i < _num_names; i++) {
			unsigned p1 = 0,p2,l;
			while (p1 < _names[i].length()) {
				if ((p2 = _names[i].find(".", p1)) == _names[i].npos) p2 = _names[i].length();
				l = p2-p1;
				if ((pos & 1) == 0) b1 = l;
				else checksum += (b1<<8) + l;
				pos++;
				for (;p1 < p2; p1++) {
					if (pos & 1) checksum += (b1<<8) + _names[i][p1];
					else b1 = _names[i][p1];
					pos++;
				}
				p1 = p2 + 1;
			}
			if (pos & 1) checksum += (b1<<8);
			pos++;	// zero end
			// zero pad
		}
		return true;
	}
	pos = 0;
	if (!pkt.add_octet(_type)) return false;
	if (!pkt.add_octet(_length)) return false;
	if (!pkt.add_hshort(0)) return false;
	if (!pkt.add_hlong(_ltime)) return false;
	pos += 8;
	for (int i = 0; i < _num_names; i++) {
		for (int i = 0; i < _num_names; i++) {
			unsigned p1 = 0,p2,l;
			while (p1 < _names[i].length()) {
				if ((p2 = _names[i].find(".", p1)) == _names[i].npos) p2 = _names[i].length();
				l = p2-p1;
				if (!pkt.add_octet(l)) return false;
				pos++;
				for (;p1 < p2; p1++) {
					if (!pkt.add_octet(_names[i][p1])) return false;
					pos++;
				}
				p1 = p2 + 1;
			}
			if (!pkt.add_octet(0)) return false;
			pos++;	// zero end
		}
		int l = 8 - (pos % 8);
		if (l == 8) l = 0;
		while (l) {
			if (!pkt.add_octet(0)) return false;
			l--;
		}
	}
	return true;
}

bool NDOptionDNSSearchList::decode(Buffer &pkt)
{
	Short res;
	if (!pkt.get_hshort(res)) return false;
	if (!pkt.get_hlong(_ltime)) return false;
	_num_names = 0;
	int i = (_length-1)*8;
	while (i > 0) {
		Octet b,l;
		if (!pkt.get_octet(l)) return false;
		i--;
		_names[_num_names].erase();
		while (i > 0 && l > 0) {
			for (int j = 0; j < l; j++) {
				if (!pkt.get_octet(b)) return false;
				_names[_num_names] += b;
				i--;
			}
			_names[_num_names] += '.';
			if (!pkt.get_octet(l)) return false;
			i--;
		}
		if (_num_names < MAX_NAMES) _num_names++;
	}
	return true;
}
#endif

//
// Common ICMPv6 Neighbor Discovery
//
// RFC4861
class ICMPv6ND: public ICMPv6
{
private:
	ICMPv6ND(const ICMPv6ND&);
	ICMPv6ND& operator=(const ICMPv6ND&);
	enum { MAX_OPT=20 };
protected:
	NDOption *_opts[MAX_OPT];
	int _num_opts;
public:
	enum { MAX_INITIAL_RTR_ADVERT_INTERVAL = 16000,
		MAX_INITIALI_RTR_ADVERTISEMENTS = 3,
		MAX_FINAL_RTR_ADVERTISEMENTS = 3,
		MIN_DELAY_BETWEEN_RAS = 3000,
		MAX_RA_DELAY_TIME = 500,
		MAX_RTR_SOLICITATION_DELAY = 1000,
		RTR_SOLICITATION_INTERVAL = 4000,
		MAX_RTR_SOLICITATIONS = 3,
		MAX_MULTICAST_SOLICIT = 3,
		MAX_UNICAST_COLICIT = 3,
		MAX_ANYCST_DELAY_TIME = 1000,
		MAX_NEIGHBOR_ADVERTISEMENT = 3,
		REACHABLE_TIME = 30000,
		RETRANS_TIMER = 1000,
		DELAY_FIRST_PROBE_TIME = 5000,};

	ICMPv6ND(): _num_opts(0) { }
	ICMPv6ND(Octet type, Octet code): _num_opts(0) { _type = type; _code = code; }
	string body_to_string(int level) const;
	// taking ownership of NDOption
	bool add_option(NDOption *opt) { 
		if (_num_opts >= MAX_OPT) return false;
		_opts[_num_opts++] = opt; 
		return true;
	}
	NDOption *get_option(int i) const { 
		if (i >= _num_opts) return NULL;
		return _opts[i];
	}
	void clear_options() { 
		for (int i = 0; i < _num_opts; i++) delete _opts[i];
		_num_opts = 0;
	}
	bool decode(Buffer &pkt);
	~ICMPv6ND() { clear_options(); }
};

#ifdef _LIB
string ICMPv6ND::body_to_string(int level) const
{
	string str(" options=[");
	for (int i = 0; i < _num_opts; i++) {
		if (i) str += " | ";
		str += _opts[i]->to_string(level);
	}
	str += "]";
	return str;
}

bool ICMPv6ND::decode(Buffer &pkt)
{
	clear_options();
	if (!pkt.get_octet(_code)) return false;
	if (!pkt.get_hshort(_checksum)) return false;
	if (!decode_body(pkt)) return false;
	while (pkt.left() > 0) {
		Octet _type, _len;
		NDOption *ret = NULL;
		if (!pkt.get_octet(_type)) return false;
		if (!pkt.get_octet(_len)) return false;
		switch (_type) {
		case NDOption::SourceLLA:
			ret = new NDOptionSourceLLA(_len);
			break;
		case NDOption::TargetLLA:
			ret = new NDOptionTargetLLA(_len);
			break;
		case NDOption::PrefixInfo:
			ret = new NDOptionPrefixInfo(_len);
			break;
		case NDOption::RedirectedHeader:
			ret = new NDRedirectedHeader(_len);
			break;
		case NDOption::MTU:
			ret = new NDOptionMTU(_len);
			break;
		case NDOption::ShortcutLimit:
			ret = new NDOptionShortcutLimit(_len);
			break;
		case NDOption::AdvertisementInterval:
			ret = new NDOptionAdvertisementInterval(_len);
			break;
		case NDOption::HomeAgent:
			ret = new NDOptionHomeAgent(_len);
			break;
		case NDOption::RouteInfo:
			ret = new NDOptionRouteInfo(_len);
			break;
		default:
			if (debug > 0) cerr << "NDdecode_options: unknown option " << _type << endl;
			return false;
		}
		if (ret == NULL) return false;
		if (!ret->decode(pkt)) {
			if (debug > 0) cerr << "NDdecode_options: invalid option " << ret->name() << endl;
			delete ret;
			return false;
		}
		if (_num_opts >= MAX_OPT) {
			if (debug > 0) cerr << "NDdecode_options: too many options" << endl;
			delete ret;
			return false;
		}
		_opts[_num_opts++] = ret;
	}
	return true;
}
#endif

// 
// ICMPv6 Neighbor Discovery Router Solicitation
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Type      |     Code      |          Checksum             |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                            Reserved                           |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |   Options ...
// +-+-+-+-+-+-+-+-+-+-+-+-
// RFC 4861 4.1
class ICMPv6ND_RS: public ICMPv6ND
{
protected:
public:
	ICMPv6ND_RS(): ICMPv6ND(ND_RS, 0) { }
	explicit ICMPv6ND_RS(Octet code): ICMPv6ND(ND_RS, code) { }
	string body_to_string(int level) const;
	string name() const { return "ND_RS"; }
	bool decode_body(Buffer &pkt);
	bool build_body(Buffer &pkt, int phase, Word &pos, Word &checksum);
	~ICMPv6ND_RS() { }
};

#ifdef _LIB
bool ICMPv6ND_RS::decode_body(Buffer &pkt)
{
	Word res;
	if (!pkt.get_hlong(res)) return false;
	return true;
}

bool ICMPv6ND_RS::build_body(Buffer &pkt, int phase, Word &pos, Word &checksum)
{
	int i;
	if (phase == 0) {
		pos += 4;	// reserved
		for (i = 0; i < _num_opts; i++) {
			if (!_opts[i]->build(pkt, 0, pos, checksum)) return false;
		}
		return true;
	}
	if (!pkt.add_hlong(0)) return false;	// reserved
	pos += 4;
	for (i = 0; i < _num_opts; i++) {
		if (!_opts[i]->build(pkt, phase, pos, checksum)) return false;
	}
	return true;
}

string ICMPv6ND_RS::body_to_string(int level) const
{
	return ICMPv6ND::body_to_string(level);
}
#endif

// 
// ICMPv6 Neighbor Discovery Router Advertisement
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Type      |     Code      |          Checksum             |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Cur Hop Limit |M|O|  Reserved |       Router Lifetime         |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                         Reachable Time                        |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                          Retrans Timer                        |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |   Options ...
// +-+-+-+-+-+-+-+-+-+-+-+-
// RFC 4861 4.2
class ICMPv6ND_RA: public ICMPv6ND
{
protected:
	Octet _chlim;	// Cur Hop Limit 
	Octet _raflags;	// flags
	Short _rltime;	// Router lifetime (only for default router)
	Word _rtime;	// Reachable time (neighbor is assumed reachable)
	Word _retrans;	// Retrans Timer (between retransmitted NS messages)
public:
	enum Flags { Managed=0x80, OtherConf=0x40, HomeRouter=0x20, PrefHigh=0x8, PrefNormal=0, PrefLow=0x18};
	ICMPv6ND_RA(): ICMPv6ND(ND_RA, 0), _chlim(64),_raflags(0),_rltime(1800),_rtime(0),_retrans(0) { }
	explicit ICMPv6ND_RA(Octet code): ICMPv6ND(ND_RA, code),_chlim(64),_raflags(0),_rltime(1800),_rtime(0),_retrans(0) { }

	inline void chlim(Short ch) { _chlim = ch; }
	inline Short chlim() const { return _chlim; }

	inline Short raflags() const { return _raflags; }
	inline void raflags(Short raflags) { _raflags = raflags; }
	// RFC4861 4.2
	inline void set_managed() { _raflags |= Managed; }	
	inline void unset_managed() { _raflags &= ~Managed; }
	inline bool is_managed() const { return ((_raflags&Managed) != 0); }
	inline void set_other_conf() { _raflags |= OtherConf; }
	inline void unset_other_conf() { _raflags &= ~OtherConf; }
	inline bool is_other_conf() const { return ((_raflags&OtherConf) != 0); }
	// RFC3775 7.1 Mobile IPv6
	inline void set_home_router() { _raflags |= HomeRouter; }	
	inline void unset_home_router() { _raflags &= ~HomeRouter; }	
	inline bool is_home_router() const { return ((_raflags&HomeRouter) != 0); }
	// RFC 4191 2.2. Router Preferences
	inline void set_pref_high() { _raflags = (_raflags & ~PrefLow) | PrefHigh; }
	inline void set_pref_low() { _raflags = (_raflags & ~PrefLow) | PrefLow; }
	inline void set_pref_normal() { _raflags = (_raflags & ~PrefLow); }
	inline bool is_pref_high() const { return ((_raflags&PrefLow) == PrefHigh); }
	inline bool is_pref_low() const { return ((_raflags&PrefLow) == PrefLow); }
	inline bool is_pref_normal() const { return ((_raflags&PrefLow) == 0); }

	inline void rltime(Short rm) { _rltime = rm; }
	inline Short rltime() const { return _rltime; }
	inline void rtime(Word rm) { _rtime = rm; }
	inline Short rtime() const { return _rtime; }
	inline void retrans(Word rm) { _retrans = rm; }
	inline Short retrans() const { return _retrans; }

	string name() const { return "ND_RA"; }
	string body_to_string(int level) const;
	bool decode_body(Buffer &pkt);
	bool build_body(Buffer &pkt, int phase, Word &pos, Word &checksum);
	~ICMPv6ND_RA() { }
};

#ifdef _LIB
bool ICMPv6ND_RA::decode_body(Buffer &pkt)
{
	if (!pkt.get_octet(_chlim)) return false;
	if (!pkt.get_octet(_raflags)) return false;
	if (!pkt.get_hshort(_rltime)) return false;
	if (!pkt.get_hlong(_rtime)) return false;
	if (!pkt.get_hlong(_retrans)) return false;
	return true;
}

bool ICMPv6ND_RA::build_body(Buffer &pkt, int phase, Word &pos, Word &checksum)
{
	int i;
	if (phase == 0) {
		checksum += (_chlim<<8) + _raflags;
		checksum += _rltime;
		checksum += (_rtime>>16) + (_rtime & 0xffff);
		checksum += (_retrans>>16) + (_retrans & 0xffff);
		pos += 3*4;
		for (i = 0; i < _num_opts; i++) {
			if (!_opts[i]->build(pkt, 0, pos, checksum)) return false;
		}
		return true;
	}
	if (!pkt.add_octet(_chlim)) return false;
	++pos;
	if (!pkt.add_octet(_raflags)) return false;
	++pos;
	if (!pkt.add_hshort(_rltime)) return false;
	pos += 2;
	if (!pkt.add_hlong(_rtime)) return false;
	pos += 4;
	if (!pkt.add_hlong(_retrans)) return false;
	pos += 4;
	for (i = 0; i < _num_opts; i++) {
		if (!_opts[i]->build(pkt, phase, pos, checksum)) return false;
	}
	return true;
}

string ICMPv6ND_RA::body_to_string(int level) const
{
	string str(" chlim=");
	str += cvt_int(_chlim);
	str += " raflags=";
	if (is_managed()) str += "M";
	if (is_other_conf()) str += "O";
	if (is_home_router()) str += "H";
	if (is_pref_high()) str += "P";
	if (is_pref_low()) str += "L";
	if (_rltime) {
		str += " rltime=";
		str += cvt_int(_rltime);
	}
	if (_rtime) {
		str += " rtime=";
		str += cvt_int(_rtime);
	}
	if (_retrans) {
		str += " retrans=";
		str += cvt_int(_retrans);
	}
	// add options
	str += ICMPv6ND::body_to_string(level);
	return str;
}
#endif

// 
// ICMPv6 Neighbor Discovery Neighbor Solicitation
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Type      |     Code      |          Checksum             |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                           Reserved                            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// +                                                               +
// |                                                               |
// +                       Target Address                          +
// |                                                               |
// +                                                               +
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |   Options ...
// +-+-+-+-+-+-+-+-+-+-+-+-
// RFC 4861 4.3
class ICMPv6ND_NS: public ICMPv6ND
{
protected:
	IPv6Address _target;
public:
	ICMPv6ND_NS(): ICMPv6ND(ND_NS, 0),_target() { }
	explicit ICMPv6ND_NS(Octet code): ICMPv6ND(ND_NS, code),_target() { }
	explicit ICMPv6ND_NS(const IPv6Address &addr): ICMPv6ND(ND_NS, 0), _target(addr) { }
	explicit ICMPv6ND_NS(string str): ICMPv6ND(ND_NS, 0), _target(str) { }

	inline const IPv6Address& target() const { return _target; }
	inline void target(const IPv6Address& t) { _target = t; }

	string name() const { return "ND_NS"; }
	string body_to_string(int level) const;
	bool decode_body(Buffer &pkt);
	bool build_body(Buffer &pkt, int phase, Word &pos, Word &checksum);
	~ICMPv6ND_NS() { }
};

bool ICMPv6ND_NS::decode_body(Buffer &pkt)
{
	Word res;
	if (!pkt.get_hlong(res)) return false;
	if (!_target.decode(pkt)) return false;
	return true;
}

bool ICMPv6ND_NS::build_body(Buffer &pkt, int phase, Word &pos, Word &checksum)
{
	int i;
	if (phase == 0) {
		checksum += _target.checksum();
		pos += 1*4 + 16;
		for (i = 0; i < _num_opts; i++) {
			if (!_opts[i]->build(pkt, 0, pos, checksum)) return false;
		}
		return true;
	}
	if (!pkt.add_hlong(0)) return false;
	if (!_target.build(pkt, phase)) return false;
	for (i = 0; i < _num_opts; i++) {
		if (!_opts[i]->build(pkt, phase, pos, checksum)) return false;
	}
	return true;
}

string ICMPv6ND_NS::body_to_string(int level) const
{
	string str(" target=");
	str += _target.to_string(level);
	str += ICMPv6ND::body_to_string(level);
	return str;
}

// 
// ICMPv6 Neighbor Discovery Neighbor Advertisement
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Type      |     Code      |          Checksum             |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |R|S|O|D|                   Reserved                            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// +                                                               +
// |                                                               |
// +                       Target Address                          +
// |                                                               |
// +                                                               +
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |   Options ...
// +-+-+-+-+-+-+-+-+-+-+-+-
// RFC 4861 4.4
class ICMPv6ND_NA: public ICMPv6ND
{
protected:
	Octet _flags;
	IPv6Address _target;
public:
	enum Flags { Router=0x80, Solicited=0x40, Override=0x20 };
	ICMPv6ND_NA(): ICMPv6ND(ND_NA, 0),_flags(0),_target() { }
	explicit ICMPv6ND_NA(Octet code): ICMPv6ND(ND_NA, code),_flags(0),_target() { }
	explicit ICMPv6ND_NA(const IPv6Address &addr, int flags=0): ICMPv6ND(ND_NA, 0), _flags(flags), _target(addr) { }
	explicit ICMPv6ND_NA(const string &str, int flags=0): ICMPv6ND(ND_NA, 0), _flags(flags), _target(str) { }

	inline const IPv6Address& target() const { return _target; }
	inline void target(const IPv6Address& t) { _target = t; }

	inline Octet flags() const { return _flags; }
	inline void set_router() { _flags |= Router; }
	inline void set_solicited() { _flags |= Solicited; }
	inline void set_override() { _flags |= Override; }
	inline void clear_flags() { _flags = 0; }

	string name() const { return "ND_NA"; }
	string body_to_string(int level) const;
	bool decode_body(Buffer &pkt);
	bool build_body(Buffer &pkt, int phase, Word &pos, Word &checksum);
	~ICMPv6ND_NA() { }
};

bool ICMPv6ND_NA::decode_body(Buffer &pkt)
{
	Octet res;
	if (!pkt.get_octet(_flags)) return false;
	if (!pkt.get_octet(res)) return false;
	if (!pkt.get_octet(res)) return false;
	if (!pkt.get_octet(res)) return false;
	if (!_target.decode(pkt)) return false;
	return true;
}

bool ICMPv6ND_NA::build_body(Buffer &pkt, int phase, Word &pos, Word &checksum)
{
	int i;
	if (phase == 0) {
		checksum += (_flags<<8);
		checksum += _target.checksum();
		pos += 1*4 + 16;
		for (i = 0; i < _num_opts; i++) {
			if (!_opts[i]->build(pkt, 0, pos, checksum)) return false;
		}
		return true;
	}
	if (!pkt.add_octet(_flags)) return false;
	if (!pkt.add_octet(0)) return false;
	if (!pkt.add_octet(0)) return false;
	if (!pkt.add_octet(0)) return false;
	if (!_target.build(pkt, phase)) return false;
	for (i = 0; i < _num_opts; i++) {
		if (!_opts[i]->build(pkt, phase, pos, checksum)) return false;
	}
	return true;
}

string ICMPv6ND_NA::body_to_string(int level) const
{
	string str(" flags=");
	if (_flags & Router) str += "R";
	if (_flags & Solicited) str += "S";
	if (_flags & Override) str += "O";
	str += " target=";
	str += _target.to_string(level);
	str += ICMPv6ND::body_to_string(level);
	return str;
}

// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Type      |     Code      |          Checksum             |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                           Reserved                            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// +                                                               +
// |                                                               |
// +                       Target Address                          +
// |                                                               |
// +                                                               +
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// +                                                               +
// |                                                               |
// +                     Destination Address                       +
// |                                                               |
// +                                                               +
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |   Options ...
// +-+-+-+-+-+-+-+-+-+-+-+-
// RFC 4861 4.5
class ICMPv6ND_Redirect: public ICMPv6ND
{
protected:
	IPv6Address _target;
	IPv6Address _dest;
public:
	ICMPv6ND_Redirect(): ICMPv6ND(ND_Redirect, 0),_target(),_dest() { }
	explicit ICMPv6ND_Redirect(Octet code): ICMPv6ND(ND_Redirect, code),_target(),_dest() { }
	ICMPv6ND_Redirect(const IPv6Address &tgt, const IPv6Address &dest): ICMPv6ND(ND_Redirect, 0), _target(tgt), _dest(dest) { }
	ICMPv6ND_Redirect(string tgt, string dest): ICMPv6ND(ND_Redirect, 0), _target(tgt), _dest(dest) { }

	inline void target(const IPv6Address &tgt) { _target = tgt; }
	inline const IPv6Address& target() const { return _target; }
	inline void dest(const IPv6Address &dst) { _dest = dst; }
	inline const IPv6Address& dest() const { return _dest; }

	string name() const { return "ND_Redirect"; }
	string body_to_string(int level) const;
	bool decode_body(Buffer &pkt);
	bool build_body(Buffer &pkt, int phase, Word &pos, Word &checksum);
	~ICMPv6ND_Redirect() { }
};

bool ICMPv6ND_Redirect::decode_body(Buffer &pkt)
{
	Word res;
	if (!pkt.get_hlong(res)) return false;
	if (!_target.decode(pkt)) return false;
	if (!_dest.decode(pkt)) return false;
	return true;
}

bool ICMPv6ND_Redirect::build_body(Buffer &pkt, int phase, Word &pos, Word &checksum)
{
	int i;
	if (phase == 0) {
		checksum += _target.checksum();
		checksum += _dest.checksum();
		pos += 1*4 + 16 + 16;
		for (i = 0; i < _num_opts; i++) {
			if (!_opts[i]->build(pkt, 0, pos, checksum)) return false;
		}
		return true;
	}
	if (!pkt.add_hlong(0)) return false;
	if (!_target.build(pkt, phase)) return false;
	if (!_dest.build(pkt, phase)) return false;
	for (i = 0; i < _num_opts; i++) {
		if (!_opts[i]->build(pkt, phase, pos, checksum)) return false;
	}
	return true;
}

string ICMPv6ND_Redirect::body_to_string(int level) const
{
	string str(" target=");
	str += _target.to_string(level);
	str += " dst=";
	str += _dest.to_string(level);
	str += ICMPv6ND::body_to_string(level);
	return str;
}

// Node Information Query
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Type      |     Code      |           Checksum            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |             Qtype             |             Flags             |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// +                             Nonce                             +
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// /                             Data                              /
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// RFC 4620 4.
class ICMPv6NI_Query: public ICMPv6
{
private:
	enum { maxdata = IPV6_MIN_MTU - 5*4 - 4*4 };
	Short _qtype;		//
	Short _flags;
	Word _nonce[2];
	Octet _data[maxdata];
	Word _dlen;
public:
	enum { NOOP=0, NodeName=2, NodeAddresses=3, IPv4Addresses=4 };
	enum { CodeIPv6=0, CodeSubject=1, CodeIPv4=2 };
	ICMPv6NI_Query(): ICMPv6(NI_Query), _qtype(NOOP), _flags(0), _dlen(0) { }
	explicit ICMPv6NI_Query(Short qtype): ICMPv6(NI_Query), _qtype(qtype), _flags(0), _dlen(0) { }
	string name() const { return "NI_Query"; }
	string body_to_string(int level) const;
	void qtype(Short qtype) { _qtype = qtype; }
	Short qtype() const { return _qtype; }
	void flags(Short flags) { _flags = flags; }
	Short flags() const { return _flags; }
	void nonce(Word nonce0, Word nonce1) { _nonce[0] = nonce0; _nonce[1] = nonce1; }
	void set_data(const IPv6Address &addr);
	void set_data(const string &subject);
	void set_data(const IPv4Address &addr);
	bool build_body(Buffer &pkt, int phase, Word &pos, Word &checksum);
	bool decode_body(Buffer &pkt);

	~ICMPv6NI_Query() { }
};

void ICMPv6NI_Query::set_data(const IPv6Address &addr)
{
	memcpy(_data, addr.octet_addr(), addr.length());
	_code = CodeIPv6;
	_dlen = addr.length();
}

void ICMPv6NI_Query::set_data(const string &subject)
{
	memcpy(_data, subject.c_str(), subject.length());
	_code = CodeSubject;
	_dlen = subject.length();
}

void ICMPv6NI_Query::set_data(const IPv4Address &addr)
{
	memcpy(_data, addr.octet_addr(), addr.length());
	_code = CodeIPv4;
	_dlen = addr.length();
}

string ICMPv6NI_Query::body_to_string(int level) const
{
	string str(" qtype=");
	switch (_qtype) {
	case NOOP: str += "NOOP";
	case NodeName: str += "NodeName";
	case NodeAddresses: str += "NodeAddresses";
	case IPv4Addresses: str += "IPv4Addresses";
	default: str += cvt_hex(_qtype);
	}
	if (_flags) {
		str += " flags=";
		str += cvt_hex(_flags);
	}
	str += " nonce=";
	str += cvt_hex(_nonce[0]);
	str += cvt_hex(_nonce[1]);
	if (_dlen) {
		str += " data=";
		if (_code == CodeIPv6) {
			// output as IPv6
		} else
		if (_code == CodeSubject) {
			str += reinterpret_cast<const char *>(_data);	// as string
		} else
		if (_code == CodeIPv4) {
			// output as IPv4
		} else {
			for (unsigned i = 0; i < _dlen; i++) {
				str += cvt_hex(_data[i]);
			}
		}
	}
	return str;
}

bool ICMPv6NI_Query::build_body(Buffer &pkt, int phase, Word &pos, Word &checksum)
{
	if (phase == 0) {
		checksum += _qtype;
		checksum += _flags;
		checksum += (_nonce[0]>>16) + (_nonce[0] & 0xffff);
		checksum += (_nonce[1]>>16) + (_nonce[1] & 0xffff);
		for (unsigned i = 0; i < _dlen-1; i+=2) {
			checksum += (_data[i]<<8) + _data[i+1];
		}
		if (_dlen & 1) _checksum += (_data[_dlen-1]<<8);
		pos += 3*4+_dlen;	// XXX roundup?
		return true;
	}
	if (!pkt.add_hshort(_qtype)) return false;
	pos += 2;
	if (!pkt.add_hshort(_flags)) return false;
	pos += 2;
	if (!pkt.add_hlong(_nonce[0])) return false;
	pos += 4;
	if (!pkt.add_hlong(_nonce[0])) return false;
	pos += 4;
	for (unsigned i = 0; i < _dlen; i++) {
		if (!pkt.add_octet(_data[i])) return false;
		pos++;
	}
	return true;
}

bool ICMPv6NI_Query::decode_body(Buffer &pkt)
{
	_dlen = 0;
	if (!pkt.get_hshort(_qtype)) {
		if (debug > 0) cerr << "ICMPv6NI_Query: Missing QType octets" << endl;
		return false;
	}
	if (!pkt.get_hshort(_flags)) {
		if (debug > 0) cerr << "ICMPv6NI_Query: Missing Flags octets" << endl;
		return false;
	}
	if (!pkt.get_hlong(_nonce[0])) {
		if (debug > 0) cerr << "ICMPv6NI_Query: Missing nonce 0 octets" << endl;
		return false;
	}
	if (!pkt.get_hlong(_nonce[1])) {
		if (debug > 0) cerr << "ICMPv6NI_Query: Missing nonce 1 octets" << endl;
		return false;
	}
	while (pkt.get_octet(_data[_dlen])) {
		if (_dlen >= maxdata) {
			if (debug > 0) cerr << "ICMPv6NI_Query: Data too long" << endl;
			return false;
		}
		_dlen++;
	}
	return true;
}

// Node Information Reply
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     Type      |     Code      |           Checksum            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |             Qtype             |             Flags             |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// +                             Nonce                             +
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// /                             Data                              /
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// RFC4620 4.
// response format the same as query

// static part
static Packet *ICMPv6_factory(Buffer &pkt)
{
       Octet type;
       Packet *p;
       if (!pkt.get_octet(type)) return NULL;
       switch (type) {
       case ICMPv6::DestinationUnreachable: p = new ICMPv6DestinationUnreachable(); break;
       case ICMPv6::BufferTooBig: p = new ICMPv6BufferTooBig(); break;
       case ICMPv6::TimeExceeded: p = new ICMPv6TimeExceeded(); break;
       case ICMPv6::ParameterProblem: p = new ICMPv6ParameterProblem(); break;
       case ICMPv6::EchoRequest: p = new ICMPv6EchoRequest(); break;
       case ICMPv6::EchoReply: p = new ICMPv6EchoReply(); break;
       case ICMPv6::MLD_Query:
               if (pkt.left() <= 23) p = new ICMPv6MLD_Query();
               else p = new ICMPv6MLDv2_Query();
               break;
       case ICMPv6::MLD_Report: p = new ICMPv6MLD_Report(); break;
       case ICMPv6::MLD_Done: p = new ICMPv6MLD_Done(); break;
       case ICMPv6::MLDv2_Report: p = new ICMPv6MLDv2_Report(); break;
       case ICMPv6::ND_RS: p = new ICMPv6ND_RS(); break;
       case ICMPv6::ND_RA: p = new ICMPv6ND_RA(); break;
       case ICMPv6::ND_NS: p = new ICMPv6ND_NS(); break;
       case ICMPv6::ND_NA: p = new ICMPv6ND_NA(); break;
       case ICMPv6::ND_Redirect: p = new ICMPv6ND_Redirect(); break;
       case ICMPv6::NI_Query: p = new ICMPv6NI_Query(); break;
       default: p = new ICMPv6(type); break;
       }
       // if (debug > 1) cerr << "new instance " << p->name() << " " << p << endl;
       return p;
}

#endif
