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

#ifndef __NDWATCH_BOOTP_H__
#define __NDWATCH_BOOTP_H__

//
// BOOTP/DHCP protocol
// RFC951, RFC1497, RFC1542
//
// BOOTP/DHCP Options
// RFC1497, RFC1533, RFC2132
class BOOTP_Option
{
	Octet _tag;
public:
	// full list http://www.iana.org/assignments/bootp-dhcp-parameters/
	enum {	Pad=0, SubnetMask=1, TimeOffset=2, Gateway=3,
		TimeServer=4, IENNameServer=5, DNS=6, LogServer=7,
		QuoteServer=8, LPRServer=9, ImpressServer=10, RLPServer=11,
		HostName=12, BootFileSize=13, MeritDump=14, DomainName=15,
		SwapServer=16, RootPath=17, Extensions=18, IPForward=19,
		SourceRouting=20, PolicyFilter=21, MaxDataReass=22, DefaultTTL=23,
		PathMTUAging=24, PathMTUPlateu=25, MTU=26, AllSubnetsLocal=27,
		BroadcastIP=28, PerformMaskDisc=29, MaskSupplier=30, PerformRdisc=31,
		RouterSolAddr=32, StaticRoute=33, TrailerEnc=34, ARPTimeout=35,
		EthernetEnc=36, TCPTTL=37, TCPKeepaliveI=38, TCPKeepaliveG=39,
		NISDomain=40, NISServers=41, NTPServers=42, VendorSpec=43,
		NetBIOSNS=44, NetBIOSDDS=45, NetBIOSNodeType=46, NetBIOSScope=47,
		XWindowFS=48, XWindowDM=49, RequestedIP=50, IPLeaseTime=51,
		OptionOverload=52, MessageType=53, ServerId=54, ParamReq=55,
		Message=56, MaxMessage=57, RenewalTime=58, RebindingTime=59,
		ClassId=60, ClientId=61,
		NISPDomain=64, NISPServers=65, TFTPServer=66, BootFilename=67, 
		MobileIpHome=68, SMTPServer=69, POP3Server=70, NNTPServer=71,
		WebServer=72, FingerServer=73, IRCServer=74, StreetTalkServer=75,
		// RFC3004
		UserClass=77,
		// RFC4702
		ClientFQDN=81, 
		// RFC3046
		AgentInfo=82,
		End=255 };
	enum { DHCPDiscover=1, DHCPOffer=2, DHCPRequest=3, DHCPDecline=4, 
		DHCPAck=5, DHCPNak=6, DHCPRelease=7 };
	enum { MAX_ADDR=8 };
	BOOTP_Option(Octet tag): _tag(tag) { }
	Octet type() const { return _tag; }
	virtual string name() const = 0;
	virtual string to_string(int level=0) const = 0;
	virtual bool do_build(Buffer &pkt, int phase, Word &pos) = 0;
	virtual bool decode(Buffer &pkt) = 0;
	virtual ~BOOTP_Option() { };
	static BOOTP_Option *decode_option(Buffer &pkt);
};

class BOOTP_Option_Pad: public BOOTP_Option
{
public:
	BOOTP_Option_Pad(): BOOTP_Option(Pad) { }
	string name() const { return "Pad"; }
	string to_string(int level=0) const { return "Pad"; }
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(Pad)) return false;
		pos++;
		return true;
	}
	bool decode(Buffer &pkt)
	{
		return true;
	}
	~BOOTP_Option_Pad() { }
};

//  Code   Len        Subnet Mask
//  +-----+-----+-----+-----+-----+-----+
//  |  1  |  4  |  m1 |  m2 |  m3 |  m4 |
//  +-----+-----+-----+-----+-----+-----+
class BOOTP_Option_SubnetMask: public BOOTP_Option
{
	IPv4Address _subnet_mask;
public:
	BOOTP_Option_SubnetMask(): BOOTP_Option(SubnetMask),_subnet_mask() { }
	BOOTP_Option_SubnetMask(const IPv4Address &ip): BOOTP_Option(SubnetMask), _subnet_mask(ip) { }
	const IPv4Address& subnet_mask() const { return _subnet_mask; }
	virtual string name() const { return "SubnetMask"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("SubnetMask ");
		str += _subnet_mask.to_string();
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(SubnetMask)) return false;
		if (!pkt.add_octet(4)) return false;
		if (!_subnet_mask.build(pkt, phase)) return false;
		pos += 6;
		return true;
	}
	bool decode(Buffer &pkt)
	{
		Octet l;
		if (!pkt.get_octet(l)) return false;
		if (l != 4) return false;
		if (!_subnet_mask.decode(pkt)) return false;
		return true;
	}
	virtual ~BOOTP_Option_SubnetMask() { }
};

//  Code   Len        Time Offset
// +-----+-----+-----+-----+-----+-----+
// |  2  |  4  |  n1 |  n2 |  n3 |  n4 |
// +-----+-----+-----+-----+-----+-----+
class BOOTP_Option_TimeOffset: public BOOTP_Option
{
	Word _time_offset;
public:
	BOOTP_Option_TimeOffset(): BOOTP_Option(TimeOffset),_time_offset(0) { }
	BOOTP_Option_TimeOffset(Word offset): BOOTP_Option(TimeOffset), _time_offset(offset) { }
	const Word time_offset() const { return _time_offset; }
	virtual string name() const { return "TimeOffset"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("TimeOffset ");
		str += cvt_int(_time_offset);
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(TimeOffset)) return false;
		if (!pkt.add_octet(4)) return false;
		if (!pkt.add_hlong(_time_offset)) return false;
		pos += 6;
		return true;
	}
	bool decode(Buffer &pkt)
	{
		Octet l;
		if (!pkt.get_octet(l)) return false;
		if (l != 4) return false;
		if (!pkt.get_hlong(_time_offset)) {
			return false;
		}
		return true;
	}
	virtual ~BOOTP_Option_TimeOffset() { }
};

//  Code   Len         Address 1               Address 2
// +-----+-----+-----+-----+-----+-----+-----+-----+--
// |  3  |  n  |  a1 |  a2 |  a3 |  a4 |  a1 |  a2 |  ...
// +-----+-----+-----+-----+-----+-----+-----+-----+--
class BOOTP_Option_Gateway: public BOOTP_Option
{
	Octet _ngateways;
	IPv4Address _gateways[MAX_ADDR];
public:
	BOOTP_Option_Gateway(): BOOTP_Option(Gateway),_ngateways(0) { }
	BOOTP_Option_Gateway(const IPv4Address &ip): BOOTP_Option(Gateway), _ngateways(1) { _gateways[0] = ip; }
	bool add_gateway(const IPv4Address &ip) {
		if (_ngateways >= MAX_ADDR) return false;
		_gateways[_ngateways++] = ip;
	}
	const bool get_gateway(unsigned i, IPv4Address &ip) const 
	{ 
		if (i >= _ngateways) return false;
		ip = _gateways[i];
		return true;
	}
	virtual string name() const { return "Gateway"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("Gateway");
		for (unsigned i = 0; i < _ngateways; i++) {
			if (i == 0) str += " ";
			else str += ",";
			str += _gateways[i].to_string();
		}
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(Gateway)) return false;
		pos++;
		if (!pkt.add_octet(_ngateways*4)) return false;
		pos++;
		for (unsigned i = 0; i < _ngateways; i++) {
			if (!_gateways[i].build(pkt, phase)) return false;
			pos += 4;
		}
		return true;
	}
	bool decode(Buffer &pkt)
	{
		Octet l;
		if (!pkt.get_octet(l)) return false;
		while (l >= 4) {
			l -= 4;
			if (!_gateways[_ngateways].decode(pkt)) return false;
			if (_ngateways < MAX_ADDR) _ngateways++;
		}
		if (l != 0) return false;
		return true;
	}
	virtual ~BOOTP_Option_Gateway() { }
};

//  Code   Len         Address 1               Address 2
// +-----+-----+-----+-----+-----+-----+-----+-----+--
// |  6  |  n  |  a1 |  a2 |  a3 |  a4 |  a1 |  a2 |  ...
// +-----+-----+-----+-----+-----+-----+-----+-----+--
class BOOTP_Option_DNS: public BOOTP_Option
{
	Octet _ndns;
	IPv4Address _dns[MAX_ADDR];
public:
	BOOTP_Option_DNS(): BOOTP_Option(DNS),_ndns(0) { }
	BOOTP_Option_DNS(const IPv4Address &ip): BOOTP_Option(DNS), _ndns(1) { _dns[0] = ip; }
	bool add_gateway(const IPv4Address &ip) {
		if (_ndns >= MAX_ADDR) return false;
		_dns[_ndns++] = ip;
	}
	const bool get_gateway(unsigned i, IPv4Address &ip) const 
	{ 
		if (i >= _ndns) return false;
		ip = _dns[i];
		return true;
	}
	virtual string name() const { return "DNS"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("DNS");
		for (unsigned i = 0; i < _ndns; i++) {
			if (i == 0) str += " ";
			else str += ",";
			str += _dns[i].to_string();
		}
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(DNS)) return false;
		pos++;
		if (!pkt.add_octet(_ndns*4)) return false;
		pos++;
		for (unsigned i = 0; i < _ndns; i++) {
			if (!_dns[i].build(pkt, phase)) return false;
			pos += 4;
		}
		return true;
	}
	bool decode(Buffer &pkt)
	{
		Octet l;
		if (!pkt.get_octet(l)) return false;
		if (debug > 0 && (l%4) != 0) cerr << "BOOTP_Option_DNS: option length " << l << " not multiple of 4!" << endl;
		while (l >= 4) {
			l -= 4;
			if (!_dns[_ndns].decode(pkt)) return false;
			if (_ndns < MAX_ADDR) _ndns++;
		}
		if (l != 0) {
			return false;
		}
		return true;
	}
	virtual ~BOOTP_Option_DNS() { }
};

//  Code   Len                 Host Name
// +-----+-----+-----+-----+-----+-----+-----+-----+--
// |  12 |  n  |  h1 |  h2 |  h3 |  h4 |  h5 |  h6 |  ...
// +-----+-----+-----+-----+-----+-----+-----+-----+--
class BOOTP_Option_HostName: public BOOTP_Option
{
	string _hostname;
public:
	BOOTP_Option_HostName(): BOOTP_Option(HostName),_hostname() { }
	BOOTP_Option_HostName(const string &name): BOOTP_Option(HostName),_hostname(name) { }
	const string &hostname() const { return _hostname; }
	virtual string name() const { return "HostName"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("HostName ");
		str += _hostname;
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(HostName)) return false;
		if (!pkt.add_octet(_hostname.length())) return false;
		pos += 2;
		for (unsigned i = 0; i < _hostname.length(); i++) {
			if (!pkt.add_octet(_hostname[i])) return false;
			pos++;
		}
		return true;
	}
	bool decode(Buffer &pkt)
	{
		Octet l,c;
		_hostname = "";
		if (!pkt.get_octet(l)) return false;
		for (unsigned i = 0; i < l; i++) {
			if (!pkt.get_octet(c)) return false;
			_hostname += c;
		}
		return true;
	}
	virtual ~BOOTP_Option_HostName() { }
};

//  Code   Len        Domain Name
// +-----+-----+-----+-----+-----+-----+--
// |  15 |  n  |  d1 |  d2 |  d3 |  d4 |  ...
// +-----+-----+-----+-----+-----+-----+--
class BOOTP_Option_DomainName: public BOOTP_Option
{
	string _domain;
public:
	BOOTP_Option_DomainName(): BOOTP_Option(DomainName),_domain() { }
	BOOTP_Option_DomainName(const string &name): BOOTP_Option(DomainName),_domain(name) { }
	const string &domain() const { return _domain; }
	virtual string name() const { return "DomainName"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("DomainName ");
		str += _domain;
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(DomainName)) return false;
		if (!pkt.add_octet(_domain.length())) return false;
		pos += 2;
		for (unsigned i = 0; i < _domain.length(); i++) {
			if (!pkt.add_octet(_domain[i])) return false;
			pos++;
		}
		return true;
	}
	bool decode(Buffer &pkt)
	{
		Octet l,c;
		_domain = "";
		if (!pkt.get_octet(l)) return false;
		for (unsigned i = 0; i < l; i++) {
			if (!pkt.get_octet(c)) return false;
			_domain += c;
		}
		return true;
	}
	virtual ~BOOTP_Option_DomainName() { }
};

//  Code   Len      Swap Server
//  +-----+-----+-----+-----+-----+-----+
//  |  16 |  4  |  a1 |  a2 |  a3 |  a4 |
//  +-----+-----+-----+-----+-----+-----+
class BOOTP_Option_SwapServer: public BOOTP_Option
{
	IPv4Address _server;
public:
	BOOTP_Option_SwapServer(): BOOTP_Option(SwapServer),_server() { }
	BOOTP_Option_SwapServer(const IPv4Address &ip): BOOTP_Option(SwapServer), _server(ip) { }
	const IPv4Address& server() const { return _server; }
	virtual string name() const { return "SwapServer"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("SwapServer ");
		str += _server.to_string();
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(SwapServer)) return false;
		if (!pkt.add_octet(4)) return false;
		if (!_server.build(pkt, phase)) return false;
		pos += 6;
		return true;
	}
	bool decode(Buffer &pkt)
	{
		Octet l;
		if (!pkt.get_octet(l)) return false;
		if (l != 4) return false;
		if (!_server.decode(pkt)) return false;
		return true;
	}
	virtual ~BOOTP_Option_SwapServer() { }
};

//  Code   Len      Root Disk Pathname
// +-----+-----+-----+-----+-----+-----+--
// |  17 |  n  |  n1 |  n2 |  n3 |  n4 |  ...
// +-----+-----+-----+-----+-----+-----+--
class BOOTP_Option_RootPath: public BOOTP_Option
{
	string _path;
public:
	BOOTP_Option_RootPath(): BOOTP_Option(RootPath),_path() { }
	BOOTP_Option_RootPath(const string &name): BOOTP_Option(RootPath),_path(name) { }
	const string &path() const { return _path; }
	virtual string name() const { return "RootPath"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("RootPath ");
		str += _path;
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(RootPath)) return false;
		if (!pkt.add_octet(_path.length())) return false;
		pos += 2;
		for (unsigned i = 0; i < _path.length(); i++) {
			if (!pkt.add_octet(_path[i])) return false;
			pos++;
		}
		return true;
	}
	bool decode(Buffer &pkt)
	{
		Octet l,c;
		_path = "";
		if (!pkt.get_octet(l)) return false;
		for (unsigned i = 0; i < l; i++) {
			if (!pkt.get_octet(c)) return false;
			_path += c;
		}
		return true;
	}
	virtual ~BOOTP_Option_RootPath() { }
};

//  Code   Len     Broadcast Address
// +-----+-----+-----+-----+-----+-----+
// |  28 |  4  |  b1 |  b2 |  b3 |  b4 |
// +-----+-----+-----+-----+-----+-----+
class BOOTP_Option_BroadcastIP: public BOOTP_Option
{
	IPv4Address _addr;
public:
	BOOTP_Option_BroadcastIP(): BOOTP_Option(BroadcastIP),_addr() { }
	BOOTP_Option_BroadcastIP(const IPv4Address &ip): BOOTP_Option(BroadcastIP), _addr(ip) { }
	const IPv4Address& address() const { return _addr; }
	virtual string name() const { return "BroadcastIP"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("BroadcastIP ");
		str += _addr.to_string();
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(BroadcastIP)) return false;
		if (!pkt.add_octet(4)) return false;
		if (!_addr.build(pkt, phase)) return false;
		pos += 6;
		return true;
	}
	bool decode(Buffer &pkt)
	{
		Octet l;
		if (!pkt.get_octet(l)) return false;
		if (l != 4) return false;
		if (!_addr.decode(pkt)) return false;
		return true;
	}
	virtual ~BOOTP_Option_BroadcastIP() { }
};

//  Code   Len  Value
// +-----+-----+-----+
// |  31 |  1  | 0/1 |
// +-----+-----+-----+
class BOOTP_Option_PerformRdisc: public BOOTP_Option
{
	Octet _value;
public:
	BOOTP_Option_PerformRdisc(): BOOTP_Option(PerformRdisc),_value(0) { }
	BOOTP_Option_PerformRdisc(Octet val): BOOTP_Option(PerformRdisc), _value(val) { }
	Octet value() const { return _value; }
	virtual string name() const { return "PerformRdisc"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("PerformRdisc ");
		if (_value == 0) str += "NO";
		else
		if (_value == 1) str += "YES";
		else str += cvt_int(_value);
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(PerformRdisc)) return false;
		if (!pkt.add_octet(1)) return false;
		if (!pkt.add_octet(_value)) return false;
		pos += 3;
		return true;
	}
	bool decode(Buffer &pkt)
	{
		Octet l;
		if (!pkt.get_octet(l)) return false;
		if (l != 1) return false;
		if (!pkt.get_octet(_value)) return false;
		return true;
	}
	virtual ~BOOTP_Option_PerformRdisc() { }
};

//  Code   Len         Address 1               Address 2
// +-----+-----+-----+-----+-----+-----+-----+-----+--
// | 42  |  n  |  a1 |  a2 |  a3 |  a4 |  a1 |  a2 |  ...
// +-----+-----+-----+-----+-----+-----+-----+-----+--
class BOOTP_Option_NTPServers: public BOOTP_Option
{
	Octet _nntp;
	IPv4Address _ntp[MAX_ADDR];
public:
	BOOTP_Option_NTPServers(): BOOTP_Option(NTPServers),_nntp(0) { }
	BOOTP_Option_NTPServers(const IPv4Address &ip): BOOTP_Option(NTPServers), _nntp(1) { _ntp[0] = ip; }
	bool add_server(const IPv4Address &ip) {
		if (_nntp >= MAX_ADDR) return false;
		_ntp[_nntp++] = ip;
	}
	const bool get_server(unsigned i, IPv4Address &ip) const 
	{ 
		if (i >= _nntp) return false;
		ip = _ntp[i];
		return true;
	}
	virtual string name() const { return "NTPServers"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("NTPServers");
		for (unsigned i = 0; i < _nntp; i++) {
			if (i == 0) str += " ";
			else str += ",";
			str += _ntp[i].to_string();
		}
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(NTPServers)) return false;
		pos++;
		if (!pkt.add_octet(_nntp*4)) return false;
		pos++;
		for (unsigned i = 0; i < _nntp; i++) {
			if (!_ntp[i].build(pkt, phase)) return false;
			pos += 4;
		}
		return true;
	}
	bool decode(Buffer &pkt)
	{
		Octet l;
		if (!pkt.get_octet(l)) return false;
		if (debug > 0 && (l%4) != 0) cerr << "BOOTP_Option_NTPServers: option length " << l << " not multiple of 4!" << endl;
		while (l >= 4) {
			l -= 4;
			if (!_ntp[_nntp].decode(pkt)) return false;
			if (_nntp < MAX_ADDR) _nntp++;
		}
		if (l != 0) return false;
		return true;
	}
	virtual ~BOOTP_Option_NTPServers() { }
};

//  Code   Len   Vendor specific 
// +-----+-----+-----+-----+---
// |  43 |  n  |  i1 |  i2 | ...
// +-----+-----+-----+-----+---
class BOOTP_Option_VendorSpec: public BOOTP_Option
{
	Octet _len;
	Octet _info[256];
public:
	BOOTP_Option_VendorSpec(): BOOTP_Option(VendorSpec),_len(0) { }
	BOOTP_Option_VendorSpec(Octet *info, unsigned infolen): BOOTP_Option(VendorSpec),_len(0) { if (infolen < 256) { _len = infolen; memcpy(_info, info, infolen); }  }
	const Octet *info() const { return _info; }
	virtual string name() const { return "VendorSpec"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("VendorSpec ");
		for (unsigned i = 0; i < _len; i++) {
			if (i) str += ":";
			str += cvt_hex(_info[i], true);
		}
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(VendorSpec)) return false;
		if (!pkt.add_octet(_len)) return false;
		pos += 2;
		for (unsigned i = 0; i < _len; i++) {
			if (!pkt.add_octet(_info[i])) return false;
			pos++;
		}
		return true;
	}
	bool decode(Buffer &pkt)
	{
		_len = 0;
		if (!pkt.get_octet(_len)) return false;
		for (unsigned i = 0; i < _len; i++) {
			if (!pkt.get_octet(_info[i])) return false;
		}
		return true;
	}
	virtual ~BOOTP_Option_VendorSpec() { }
};
//  Code   Len         Address 1               Address 2
// +-----+-----+-----+-----+-----+-----+-----+-----+--
// | 44  |  n  |  a1 |  a2 |  a3 |  a4 |  a1 |  a2 |  ...
// +-----+-----+-----+-----+-----+-----+-----+-----+--
class BOOTP_Option_NetBIOSNS: public BOOTP_Option
{
	Octet _nnbns;
	IPv4Address _nbns[MAX_ADDR];
public:
	BOOTP_Option_NetBIOSNS(): BOOTP_Option(NetBIOSNS),_nnbns(0) { }
	BOOTP_Option_NetBIOSNS(const IPv4Address &ip): BOOTP_Option(NetBIOSNS), _nnbns(1) { _nbns[0] = ip; }
	bool add_server(const IPv4Address &ip) {
		if (_nnbns >= MAX_ADDR) return false;
		_nbns[_nnbns++] = ip;
	}
	const bool get_server(unsigned i, IPv4Address &ip) const 
	{ 
		if (i >= _nnbns) return false;
		ip = _nbns[i];
		return true;
	}
	virtual string name() const { return "NetBIOSNS"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("NetBIOSNS");
		for (unsigned i = 0; i < _nnbns; i++) {
			if (i == 0) str += " ";
			else str += ",";
			str += _nbns[i].to_string();
		}
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(NetBIOSNS)) return false;
		pos++;
		if (!pkt.add_octet(_nnbns*4)) return false;
		pos++;
		for (unsigned i = 0; i < _nnbns; i++) {
			if (!_nbns[i].build(pkt, phase)) return false;
			pos += 4;
		}
		return true;
	}
	bool decode(Buffer &pkt)
	{
		Octet l;
		if (!pkt.get_octet(l)) return false;
		if (debug > 0 && (l%4) != 0) cerr << "BOOTP_Option_NetBIOSNS: option length " << l << " not multiple of 4!" << endl;
		while (l >= 4) {
			l -= 4;
			if (!_nbns[_nnbns].decode(pkt)) return false;
			if (_nnbns < MAX_ADDR) _nnbns++;
		}
		if (l != 0) return false;
		return true;
	}
	virtual ~BOOTP_Option_NetBIOSNS() { }
};

//  Code   Len         Address 1               Address 2
// +-----+-----+-----+-----+-----+-----+-----+-----+--
// | 45  |  n  |  a1 |  a2 |  a3 |  a4 |  a1 |  a2 |  ...
// +-----+-----+-----+-----+-----+-----+-----+-----+--
class BOOTP_Option_NetBIOSDDS: public BOOTP_Option
{
	Octet _nnbdds;
	IPv4Address _nbdds[MAX_ADDR];
public:
	BOOTP_Option_NetBIOSDDS(): BOOTP_Option(NetBIOSDDS),_nnbdds(0) { }
	BOOTP_Option_NetBIOSDDS(const IPv4Address &ip): BOOTP_Option(NetBIOSDDS), _nnbdds(1) { _nbdds[0] = ip; }
	bool add_server(const IPv4Address &ip) {
		if (_nnbdds >= MAX_ADDR) return false;
		_nbdds[_nnbdds++] = ip;
	}
	const bool get_server(unsigned i, IPv4Address &ip) const 
	{ 
		if (i >= _nnbdds) return false;
		ip = _nbdds[i];
		return true;
	}
	virtual string name() const { return "NetBIOSDDS"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("NetBIOSDDS");
		for (unsigned i = 0; i < _nnbdds; i++) {
			if (i == 0) str += " ";
			else str += ",";
			str += _nbdds[i].to_string();
		}
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(NetBIOSDDS)) return false;
		pos++;
		if (!pkt.add_octet(_nnbdds*4)) return false;
		pos++;
		for (unsigned i = 0; i < _nnbdds; i++) {
			if (!_nbdds[i].build(pkt, phase)) return false;
			pos += 4;
		}
		return true;
	}
	bool decode(Buffer &pkt)
	{
		Octet l;
		if (!pkt.get_octet(l)) return false;
		if (debug > 0 && (l%4) != 0) cerr << "BOOTP_Option_NetBIOSDDS: option length " << l << " not multiple of 4!" << endl;
		while (l >= 4) {
			l -= 4;
			if (!_nbdds[_nnbdds].decode(pkt)) return false;
			if (_nnbdds < MAX_ADDR) _nnbdds++;
		}
		if (l != 0) return false;
		return true;
	}
	virtual ~BOOTP_Option_NetBIOSDDS() { }
};

//  Code   Len  Value
// +-----+-----+-----+
// |  46 |  1  | 0-f |
// +-----+-----+-----+
class BOOTP_Option_NetBIOSNodeType: public BOOTP_Option
{
	Octet _value;
public:
	BOOTP_Option_NetBIOSNodeType(): BOOTP_Option(NetBIOSNodeType),_value(0) { }
	BOOTP_Option_NetBIOSNodeType(Octet val): BOOTP_Option(NetBIOSNodeType), _value(val) { }
	Octet value() const { return _value; }
	virtual string name() const { return "NetBIOSNodeType"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("NetBIOSNodeType ");
		if (_value > 0xf) str += cvt_hex(_value&0xf0, false);
		if (_value & 0x1) str += "B";
		if (_value & 0x2) str += "P";
		if (_value & 0x4) str += "M";
		if (_value & 0x8) str += "H";
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(NetBIOSNodeType)) return false;
		if (!pkt.add_octet(1)) return false;
		if (!pkt.add_octet(_value)) return false;
		pos += 3;
		return true;
	}
	bool decode(Buffer &pkt)
	{
		Octet l;
		if (!pkt.get_octet(l)) return false;
		if (l != 1) return false;
		if (!pkt.get_octet(_value)) return false;
		return true;
	}
	virtual ~BOOTP_Option_NetBIOSNodeType() { }
};

//  Code   Len              NetBIOS Scope
// +-----+-----+-----+-----+-----+-----+-----+-----+--
// |  47 |  n  |  s1 |  s2 |  s3 |  s4 |  s5 |  s6 |  ...
// +-----+-----+-----+-----+-----+-----+-----+-----+--
class BOOTP_Option_NetBIOSScope: public BOOTP_Option
{
	string _scope;
public:
	BOOTP_Option_NetBIOSScope(): BOOTP_Option(NetBIOSScope),_scope() { }
	BOOTP_Option_NetBIOSScope(const string &name): BOOTP_Option(NetBIOSScope),_scope(name) { }
	const string &scope() const { return _scope; }
	virtual string name() const { return "NetBIOSScope"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("NetBIOSScope ");
		str += _scope;
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(NetBIOSScope)) return false;
		if (!pkt.add_octet(_scope.length())) return false;
		pos += 2;
		for (unsigned i = 0; i < _scope.length(); i++) {
			if (!pkt.add_octet(_scope[i])) return false;
			pos++;
		}
		return true;
	}
	bool decode(Buffer &pkt)
	{
		Octet l,c;
		_scope = "";
		if (!pkt.get_octet(l)) return false;
		for (unsigned i = 0; i < l; i++) {
			if (!pkt.get_octet(c)) return false;
			_scope += c;
		}
		return true;
	}
	virtual ~BOOTP_Option_NetBIOSScope() { }
};

//  Code   Len          Address
// +-----+-----+-----+-----+-----+-----+
// |  50 |  4  |  a1 |  a2 |  a3 |  a4 |
// +-----+-----+-----+-----+-----+-----+
class BOOTP_Option_RequestedIP: public BOOTP_Option
{
	IPv4Address _requested_ip;
public:
	BOOTP_Option_RequestedIP(): BOOTP_Option(RequestedIP),_requested_ip() { }
	BOOTP_Option_RequestedIP(const IPv4Address &ip): BOOTP_Option(RequestedIP), _requested_ip(ip) { }
	const IPv4Address& requested_ip() const { return _requested_ip; }
	virtual string name() const { return "RequestedIP"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("RequestedIP ");
		str += _requested_ip.to_string();
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(RequestedIP)) return false;
		if (!pkt.add_octet(4)) return false;
		if (!_requested_ip.build(pkt, phase)) return false;
		pos += 6;
		return true;
	}
	bool decode(Buffer &pkt)
	{
		Octet l;
		if (!pkt.get_octet(l)) return false;
		if (l != 4) return false;
		if (!_requested_ip.decode(pkt)) return false;
		return true;
	}
	virtual ~BOOTP_Option_RequestedIP() { }
};

//  Code   Len         Lease Time
// +-----+-----+-----+-----+-----+-----+
// |  51 |  4  |  t1 |  t2 |  t3 |  t4 |
// +-----+-----+-----+-----+-----+-----+
class BOOTP_Option_IPLeaseTime: public BOOTP_Option
{
	Word _time;
public:
	BOOTP_Option_IPLeaseTime(): BOOTP_Option(IPLeaseTime),_time(0) { }
	BOOTP_Option_IPLeaseTime(Word time): BOOTP_Option(IPLeaseTime), _time(time) { }
	Word time() const { return _time; }
	virtual string name() const { return "IPLeaseTime"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("IPLeaseTime ");
		str += cvt_int(_time);
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(IPLeaseTime)) return false;
		if (!pkt.add_octet(4)) return false;
		if (!pkt.add_hlong(_time)) return false;
		pos += 6;
		return true;
	}
	bool decode(Buffer &pkt)
	{
		Octet l;
		if (!pkt.get_octet(l)) return false;
		if (l != 4) return false;
		if (!pkt.get_hlong(_time)) {
			return false;
		}
		return true;
	}
	virtual ~BOOTP_Option_IPLeaseTime() { }
};

//  Code   Len  Type
// +-----+-----+-----+
// |  53 |  1  | 1-9 |
// +-----+-----+-----+
class BOOTP_Option_MessageType: public BOOTP_Option
{
	Octet _type;
public:
	BOOTP_Option_MessageType(): BOOTP_Option(MessageType),_type(0) { }
	BOOTP_Option_MessageType(Octet type): BOOTP_Option(MessageType), _type(type) { }
	const Word type() const { return _type; }
	virtual string name() const { return "DHCPMessage"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("DHCPMessage ");
		switch (_type) {
		case DHCPDiscover: str += "Discover"; break;
		case DHCPOffer: str += "Offer"; break;
		case DHCPRequest: str += "Request"; break;
		case DHCPDecline: str += "Decline"; break;
		case DHCPAck: str += "Ack"; break;
		case DHCPNak: str += "Nak"; break;
		case DHCPRelease: str += "Release"; break;
		default:
			str += cvt_int(_type);
		}
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(MessageType)) return false;
		if (!pkt.add_octet(1)) return false;
		if (!pkt.add_octet(_type)) return false;
		pos += 3;
		return true;
	}
	bool decode(Buffer &pkt)
	{
		Octet l;
		if (!pkt.get_octet(l)) return false;
		if (l != 1) return false;
		if (!pkt.get_octet(_type)) {
			return false;
		}
		return true;
	}
	virtual ~BOOTP_Option_MessageType() { }
};

//  Code   Len            Address
// +-----+-----+-----+-----+-----+-----+
// |  54 |  4  |  a1 |  a2 |  a3 |  a4 |
// +-----+-----+-----+-----+-----+-----+
class BOOTP_Option_ServerId: public BOOTP_Option
{
	IPv4Address _server_id;
public:
	BOOTP_Option_ServerId(): BOOTP_Option(ServerId),_server_id() { }
	BOOTP_Option_ServerId(const IPv4Address &ip): BOOTP_Option(ServerId), _server_id(ip) { }
	const IPv4Address& server_id() const { return _server_id; }
	virtual string name() const { return "ServerId"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("ServerId ");
		str += _server_id.to_string();
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(ServerId)) return false;
		if (!pkt.add_octet(4)) return false;
		if (!_server_id.build(pkt, phase)) return false;
		pos += 6;
		return true;
	}
	bool decode(Buffer &pkt)
	{
		Octet l;
		if (!pkt.get_octet(l)) return false;
		if (l != 4) return false;
		if (!_server_id.decode(pkt)) return false;
		return true;
	}
	virtual ~BOOTP_Option_ServerId() { }
};

//  Code   Len   Option Codes
// +-----+-----+-----+-----+---
// |  55 |  n  |  c1 |  c2 | ...
// +-----+-----+-----+-----+---
class BOOTP_Option_ParamReq: public BOOTP_Option
{
	Octet _len;
	Octet _id[128];
public:
	BOOTP_Option_ParamReq(): BOOTP_Option(ParamReq),_len(0) { }
	BOOTP_Option_ParamReq(Octet *id, unsigned idlen): BOOTP_Option(ParamReq),_len(0) { if (idlen < 128) { _len = idlen; memcpy(_id, id, idlen); }  }
	const Octet *cliend_id() const { return _id; }
	virtual string name() const { return "ParamReqList"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("ParamReqList ");
		for (unsigned i = 0; i < _len; i++) {
			if (i) str += ":";
			str += cvt_int(_id[i]);
		}
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(ParamReq)) return false;
		if (!pkt.add_octet(_len)) return false;
		pos += 2;
		for (unsigned i = 0; i < _len; i++) {
			if (!pkt.add_octet(_id[i])) return false;
			pos++;
		}
		return true;
	}
	bool decode(Buffer &pkt)
	{
		Octet l;
		_len = 0;
		if (!pkt.get_octet(l)) return false;
		if (l >= 128) return false;
		_len = l;
		for (unsigned i = 0; i < _len; i++) {
			if (!pkt.get_octet(_id[i])) return false;
		}
		return true;
	}
	virtual ~BOOTP_Option_ParamReq() { }
};

//  Code   Len     Length
// +-----+-----+-----+-----+
// |  57 |  2  |  l1 |  l2 |
// +-----+-----+-----+-----+
class BOOTP_Option_MaxMessage: public BOOTP_Option
{
	Short _limit;
public:
	BOOTP_Option_MaxMessage(): BOOTP_Option(MaxMessage),_limit(0) { }
	BOOTP_Option_MaxMessage(Word limit): BOOTP_Option(MaxMessage), _limit(limit) { }
	const Word limit() const { return _limit; }
	virtual string name() const { return "MaxMessage"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("MaxMessage ");
		str += cvt_int(_limit);
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(MaxMessage)) return false;
		if (!pkt.add_octet(2)) return false;
		if (!pkt.add_hshort(_limit)) return false;
		pos += 4;
		return true;
	}
	bool decode(Buffer &pkt)
	{
		Octet l;
		if (!pkt.get_octet(l)) return false;
		if (l != 2) return false;
		if (!pkt.get_hshort(_limit)) {
			return false;
		}
		return true;
	}
	virtual ~BOOTP_Option_MaxMessage() { }
};

//  Code   Len        Time Offset
// +-----+-----+-----+-----+-----+-----+
// |  58 |  4  |  t1 |  t2 |  t3 |  t4 |
// +-----+-----+-----+-----+-----+-----+
class BOOTP_Option_RenewalTime: public BOOTP_Option
{
	Word _time;
public:
	BOOTP_Option_RenewalTime(): BOOTP_Option(RenewalTime),_time(0) { }
	BOOTP_Option_RenewalTime(Word offset): BOOTP_Option(RenewalTime), _time(offset) { }
	const Word time() const { return _time; }
	virtual string name() const { return "RenewalTime"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("RenewalTime ");
		str += cvt_int(_time);
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(RenewalTime)) return false;
		if (!pkt.add_octet(4)) return false;
		if (!pkt.add_hlong(_time)) return false;
		pos += 6;
		return true;
	}
	bool decode(Buffer &pkt)
	{
		Octet l;
		if (!pkt.get_octet(l)) return false;
		if (l != 4) return false;
		if (!pkt.get_hlong(_time)) {
			return false;
		}
		return true;
	}
	virtual ~BOOTP_Option_RenewalTime() { }
};

//  Code   Len        Time Offset
// +-----+-----+-----+-----+-----+-----+
// |  58 |  4  |  t1 |  t2 |  t3 |  t4 |
// +-----+-----+-----+-----+-----+-----+
class BOOTP_Option_RebindingTime: public BOOTP_Option
{
	Word _time;
public:
	BOOTP_Option_RebindingTime(): BOOTP_Option(RebindingTime),_time(0) { }
	BOOTP_Option_RebindingTime(Word offset): BOOTP_Option(RebindingTime), _time(offset) { }
	const Word time() const { return _time; }
	virtual string name() const { return "RebindingTime"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("RebindingTime ");
		str += cvt_int(_time);
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(RebindingTime)) return false;
		if (!pkt.add_octet(4)) return false;
		if (!pkt.add_hlong(_time)) return false;
		pos += 6;
		return true;
	}
	bool decode(Buffer &pkt)
	{
		Octet l;
		if (!pkt.get_octet(l)) return false;
		if (l != 4) return false;
		if (!pkt.get_hlong(_time)) {
			return false;
		}
		return true;
	}
	virtual ~BOOTP_Option_RebindingTime() { }
};

//  Code   Len   Vendor class Identifier
// +-----+-----+-----+-----+---
// |  60 |  n  |  i1 |  i2 | ...
// +-----+-----+-----+-----+---
class BOOTP_Option_ClassId: public BOOTP_Option
{
	Octet _len;
	Octet _id[128];
public:
	BOOTP_Option_ClassId(): BOOTP_Option(ClassId),_len(0) { }
	BOOTP_Option_ClassId(Octet *id, unsigned idlen): BOOTP_Option(ClassId),_len(0) { if (idlen < 128) { _len = idlen; memcpy(_id, id, idlen); }  }
	const Octet *cliend_id() const { return _id; }
	virtual string name() const { return "ClassId"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("ClassId ");
		for (unsigned i = 0; i < _len; i++) {
			if (i) str += ":";
			str += cvt_hex(_id[i], true);
		}
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(ClassId)) return false;
		if (!pkt.add_octet(_len)) return false;
		pos += 2;
		for (unsigned i = 0; i < _len; i++) {
			if (!pkt.add_octet(_id[i])) return false;
			pos++;
		}
		return true;
	}
	bool decode(Buffer &pkt)
	{
		Octet l;
		_len = 0;
		if (!pkt.get_octet(l)) return false;
		if (l >= 128) return false;
		_len = l;
		for (unsigned i = 0; i < _len; i++) {
			if (!pkt.get_octet(_id[i])) return false;
		}
		return true;
	}
	virtual ~BOOTP_Option_ClassId() { }
};

//  Code   Len   Type  Client-Identifier
// +-----+-----+-----+-----+-----+---
// |  61 |  n  |  t1 |  i1 |  i2 | ...
// +-----+-----+-----+-----+-----+---
class BOOTP_Option_ClientId: public BOOTP_Option
{
	Octet _len;
	Octet _id[128];
public:
	BOOTP_Option_ClientId(): BOOTP_Option(ClientId),_len(0) { }
	BOOTP_Option_ClientId(Octet *id, unsigned idlen): BOOTP_Option(ClientId),_len(0) { if (idlen < 128) { _len = idlen; memcpy(_id, id, idlen); }  }
	const Octet *cliend_id() const { return _id; }
	virtual string name() const { return "ClientId"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("ClientId ");
		for (unsigned i = 0; i < _len; i++) {
			if (i) str += ":";
			str += cvt_hex(_id[i], true);
		}
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(ClientId)) return false;
		if (!pkt.add_octet(_len)) return false;
		pos += 2;
		for (unsigned i = 0; i < _len; i++) {
			if (!pkt.add_octet(_id[i])) return false;
			pos++;
		}
		return true;
	}
	bool decode(Buffer &pkt)
	{
		Octet l;
		_len = 0;
		if (!pkt.get_octet(l)) return false;
		if (l >= 128) return false;
		_len = l;
		for (unsigned i = 0; i < _len; i++) {
			if (!pkt.get_octet(_id[i])) return false;
		}
		return true;
	}
	virtual ~BOOTP_Option_ClientId() { }
};

//  Code   Len               TFTP server
// +-----+-----+-----+-----+-----+-----+-----+-----+--
// |  66 |  n  |  h1 |  h2 |  h3 |  h4 |  h5 |  h6 |  ...
// +-----+-----+-----+-----+-----+-----+-----+-----+--
class BOOTP_Option_TFTPServer: public BOOTP_Option
{
	string _server;
public:
	BOOTP_Option_TFTPServer(): BOOTP_Option(TFTPServer),_server() { }
	BOOTP_Option_TFTPServer(const string &name): BOOTP_Option(TFTPServer),_server(name) { }
	const string &server() const { return _server; }
	virtual string name() const { return "TFTPServer"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("TFTPServer ");
		str += _server;
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(TFTPServer)) return false;
		if (!pkt.add_octet(_server.length())) return false;
		pos += 2;
		for (unsigned i = 0; i < _server.length(); i++) {
			if (!pkt.add_octet(_server[i])) return false;
			pos++;
		}
		return true;
	}
	bool decode(Buffer &pkt)
	{
		Octet l,c;
		_server = "";
		if (!pkt.get_octet(l)) return false;
		for (unsigned i = 0; i < l; i++) {
			if (!pkt.get_octet(c)) return false;
			_server += c;
		}
		return true;
	}
	virtual ~BOOTP_Option_TFTPServer() { }
};

//  Code   Len   Value
// +-----+-----+---------------------  . . .  --+
// | 77  |  N  | User Class Data (N octets)     |
// +-----+-----+---------------------  . . .  --+
// UC_Len_i     User_Class_Data_i
// +--------+------------------------  . . .  --+
// |  L_i   | Opaque-Data ('UC_Len_i' octets)   |
// +--------+------------------------  . . .  --+
// RFC3004
class BOOTP_Option_UserClass: public BOOTP_Option
{
	Octet _len;
	Octet _id[256];	// TODO - break into User_Class[i]
public:
	BOOTP_Option_UserClass(): BOOTP_Option(UserClass),_len(0) { }
	BOOTP_Option_UserClass(Octet *id, unsigned idlen): BOOTP_Option(UserClass),_len(0) { if (idlen < 256) { _len = idlen; memcpy(_id, id, idlen); }  }
	const Octet *user_class() const { return _id; }
	virtual string name() const { return "UserClass"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("UserClass ");
		for (unsigned i = 0; i < _len; i++) {
			if (i) str += ":";
			str += cvt_hex(_id[i], true);
		}
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(UserClass)) return false;
		if (!pkt.add_octet(_len)) return false;
		pos += 2;
		for (unsigned i = 0; i < _len; i++) {
			if (!pkt.add_octet(_id[i])) return false;
			pos++;
		}
		return true;
	}
	bool decode(Buffer &pkt)
	{
		_len = 0;
		if (!pkt.get_octet(_len)) return false;
		for (unsigned i = 0; i < _len; i++) {
			if (!pkt.get_octet(_id[i])) return false;
		}
		return true;
	}
	virtual ~BOOTP_Option_UserClass() { }
};

//   Code   Len     Agent Information Field
// +------+------+------+------+------+------+--...-+------+
// |  82  |   N  |  i1  |  i2  |  i3  |  i4  |      |  iN  |
// +------+------+------+------+------+------+--...-+------+
//  SubOpt  Len     Sub-option Value
// +------+------+------+------+------+------+--...-+------+
// |  1   |   N  |  s1  |  s2  |  s3  |  s4  |      |  sN  |
// +------+------+------+------+------+------+--...-+------+
// SubOpt  Len     Sub-option Value
// +------+------+------+------+------+------+--...-+------+
// |  2   |   N  |  i1  |  i2  |  i3  |  i4  |      |  iN  |
// +------+------+------+------+------+------+--...-+------+
// RFC3046
class BOOTP_Option_AgentInfo: public BOOTP_Option
{
	Octet _len;
	Octet _id[128];
public:
	BOOTP_Option_AgentInfo(): BOOTP_Option(AgentInfo),_len(0) { }
	BOOTP_Option_AgentInfo(Octet *id, unsigned idlen): BOOTP_Option(AgentInfo),_len(0) { if (idlen < 128) { _len = idlen; memcpy(_id, id, idlen); }  }
	const Octet *cliend_id() const { return _id; }
	virtual string name() const { return "AgentInfo"; }
	virtual string to_string(int level=0) const 
	{ 
		string str("AgentInfo ");
		for (unsigned i = 0; i < _len; i++) {
			if (i) str += ":";
			str += cvt_hex(_id[i], true);
		}
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(AgentInfo)) return false;
		if (!pkt.add_octet(_len)) return false;
		pos += 2;
		for (unsigned i = 0; i < _len; i++) {
			if (!pkt.add_octet(_id[i])) return false;
			pos++;
		}
		return true;
	}
	bool decode(Buffer &pkt)
	{
		Octet l;
		_len = 0;
		if (!pkt.get_octet(l)) return false;
		if (l >= 128) return false;
		_len = l;
		for (unsigned i = 0; i < _len; i++) {
			if (!pkt.get_octet(_id[i])) return false;
		}
		return true;
	}
	virtual ~BOOTP_Option_AgentInfo() { }
};

class BOOTP_Option_Unknown: public BOOTP_Option
{
	Octet _len;
	Octet _val[256];
public:
	BOOTP_Option_Unknown(Octet tag): BOOTP_Option(tag),_len(0) { }
	BOOTP_Option_Unknown(Octet tag, Octet *val, unsigned len): BOOTP_Option(tag),_len(0) { if (len < 256) { _len = len; memcpy(_val, val, len); }  }
	const Octet *value() const { return _val; }
	virtual string name() const { return cvt_int(type()); }
	virtual string to_string(int level=0) const 
	{ 
		string str("tag");
		str += cvt_int(type());
		for (unsigned i = 0; i < _len; i++) {
			if (i) str += ":";
			else str += " ";
			str += cvt_hex(_val[i], true);
		}
		return str;
	}
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(type())) return false;
		if (!pkt.add_octet(_len)) return false;
		pos += 2;
		for (unsigned i = 0; i < _len; i++) {
			if (!pkt.add_octet(_val[i])) return false;
			pos++;
		}
		return true;
	}
	bool decode(Buffer &pkt)
	{
		_len = 0;
		if (!pkt.get_octet(_len)) return false;
		for (unsigned i = 0; i < _len; i++) {
			if (!pkt.get_octet(_val[i])) return false;
		}
		return true;
	}
	virtual ~BOOTP_Option_Unknown() { }
};

class BOOTP_Option_End: public BOOTP_Option
{
public:
	BOOTP_Option_End(): BOOTP_Option(End) { }
	string name() const { return "End"; }
	string to_string(int level=0) const { return "End"; }
	bool do_build(Buffer &pkt, int phase, Word &pos) 
	{
		if (!pkt.add_octet(End)) return false;
		pos++;
		return true;
	}
	bool decode(Buffer &pkt)
	{
		return true;
	}
	~BOOTP_Option_End() { }
};

#ifdef _LIB
BOOTP_Option *BOOTP_Option::decode_option(Buffer &pkt)
{
	Octet tag;
	BOOTP_Option *opt;
	if (!pkt.get_octet(tag)) {
		if (debug > 1) cerr << "BOOTP_Option: missing tag octet" << endl;
		return NULL;
	}
	switch (tag) {
	case Pad: opt = new BOOTP_Option_Pad(); break;
	case SubnetMask: opt = new BOOTP_Option_SubnetMask(); break;
	case TimeOffset: opt = new BOOTP_Option_TimeOffset(); break;
	case Gateway: opt = new BOOTP_Option_Gateway(); break;
	case DNS: opt = new BOOTP_Option_DNS(); break;
	case HostName: opt = new BOOTP_Option_HostName(); break;
	case DomainName: opt = new BOOTP_Option_DomainName(); break;
	case SwapServer: opt = new BOOTP_Option_SwapServer(); break;
	case RootPath: opt = new BOOTP_Option_RootPath(); break;
	case PerformRdisc: opt = new BOOTP_Option_PerformRdisc(); break;
	case NTPServers: opt = new BOOTP_Option_NTPServers(); break;
	case VendorSpec: opt = new BOOTP_Option_VendorSpec(); break;
	case BroadcastIP: opt = new BOOTP_Option_BroadcastIP(); break;
	case NetBIOSNS: opt = new BOOTP_Option_NetBIOSNS(); break;
	case NetBIOSDDS: opt = new BOOTP_Option_NetBIOSDDS(); break;
	case NetBIOSNodeType: opt = new BOOTP_Option_NetBIOSNodeType(); break;
	case NetBIOSScope: opt = new BOOTP_Option_NetBIOSScope(); break;
	case RequestedIP: opt = new BOOTP_Option_RequestedIP(); break;
	case IPLeaseTime: opt = new BOOTP_Option_IPLeaseTime(); break;
	case MessageType: opt = new BOOTP_Option_MessageType(); break;
	case ServerId: opt = new BOOTP_Option_ServerId(); break;
	case ParamReq: opt = new BOOTP_Option_ParamReq(); break;
	case MaxMessage: opt = new BOOTP_Option_MaxMessage(); break;
	case RenewalTime: opt = new BOOTP_Option_RenewalTime(); break;
	case RebindingTime: opt = new BOOTP_Option_RebindingTime(); break;
	case ClassId: opt = new BOOTP_Option_ClassId(); break;
	case ClientId: opt = new BOOTP_Option_ClientId(); break;
	case UserClass: opt = new BOOTP_Option_UserClass(); break;
	case TFTPServer: opt = new BOOTP_Option_TFTPServer(); break;
	case AgentInfo: opt = new BOOTP_Option_AgentInfo(); break;
	case End: opt = new BOOTP_Option_End(); break;
	default: 
		if (debug > 1) cerr << "BOOTP_Option: uknown tag " << (int)tag << endl;
		opt = new BOOTP_Option_Unknown(tag); break;
	}
	if (opt->decode(pkt)) return opt; 
	delete opt; 
	return NULL;
}
#endif
// 0                   1                   2                   3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     op (1)    |   htype (1)   |   hlen (1)    |   hops (1)    |
// +---------------+---------------+---------------+---------------+
// |                            xid (4)                            |
// +-------------------------------+-------------------------------+
// |           secs (2)            |           flags (2)           |
// +-------------------------------+-------------------------------+
// |                           ciaddr (4)                          |
// +---------------------------------------------------------------+
// |                           yiaddr (4)                          |
// +---------------------------------------------------------------+
// |                           siaddr (4)                          |
// +---------------------------------------------------------------+
// |                           giaddr (4)                          |
// +---------------------------------------------------------------+
// |                           chaddr (16)                         |
// +---------------------------------------------------------------+
// |                           sname  (64)                         |
// +---------------------------------------------------------------+
// |                           file   (128)                        |
// +---------------------------------------------------------------+
// |                           vend   (64)                         |
// +---------------------------------------------------------------+
class BOOTP: public Packet
{
public:
	enum { REQUEST = 1, REPLY = 2 };
	enum { MAX_OPTS = 1000 };	// full of pads
private:
	Octet _op;		// REQUEST, REPLY
	Octet _htype;		// media type
	Octet _hlen;		// hardware address length
	Octet _hops;		// client sets to zero (proxy increments)
	Word _xid;		// random ID
	Short _secs;		// seconds since boot (client)
	Short _flags;		// B(roadcast) flag
	IPv4Address _ciaddr;	// client IP (filled by client)
	IPv4Address _yiaddr;	// your IP (filled by server)
	IPv4Address _siaddr;	// server IP (server)
	IPv4Address _giaddr;	// gateway IP
	MacAddress _chaddr;	// client hardware address
	string _sname;		// server host name
	string _sfile;		// boot file name
	Octet _vend[64];		// vendor specific or RFC1497 extension
	unsigned _nopts;
	BOOTP_Option *_opts[MAX_OPTS];	// vendor options

	BOOTP(const BOOTP&);
	BOOTP& operator=(const BOOTP&);
public:
	BOOTP(): _op(REQUEST),_htype(1),_hlen(6),_hops(0),_xid(0),_secs(0),_flags(0),_ciaddr(),_yiaddr(),_siaddr(),_giaddr(),_chaddr(),_sname(),_sfile(),_vend(),_nopts(0) { }

	void opcode(Octet op) { _op = op; }
	void xid(Word xid) { _xid = xid; }
	void client_ip(const IPv4Address &ip) { _ciaddr = ip; }
	void your_ip(const IPv4Address &ip) { _yiaddr = ip; }
	void server_ip(const IPv4Address &ip) { _siaddr = ip; }
	void gateway_ip(const IPv4Address &ip) { _giaddr = ip; }
	void client_mac(const MacAddress &mac) { _chaddr = mac; }
	void server_host(const string &name) { _sname = name; }
	void boot_file(const string &name) { _sfile = name; }
//	void vendor(const string &name) { _vend = name; }
	string name() const { return "BOOTP"; }
	string to_string(int level=0) const;
	void fixup();
	bool do_build(Buffer &pkt, int phase, Word &pos);
	bool decode(Buffer &pkt);
	void clear_opts() { for (unsigned i=0; i < _nopts; i++) delete _opts[i]; _nopts = 0; }
	~BOOTP() { clear_opts(); }
};

#ifdef _LIB
string BOOTP::to_string(int level) const
{
	string str("<BOOTP ");
	if (_op == REQUEST) str += "Req";
	else
	if (_op == REPLY) str += "Reply";
	else {
		str += "op=";
		str += cvt_int(_op);
	}
	if (_htype != 1) {
		str += " htype=";
		str += cvt_int(_htype);
	}
	if (_hlen != 6) {
		str += " hlen=";
		str += cvt_int(_hlen);
	}
	if (_hops) {
		str += " hops=";
		str += cvt_int(_hops);
	}
	str += " xid=";
	str += cvt_hex(_xid);
	if (_secs) {
		str += " secs=";
		str += cvt_int(_secs);
	}
	if (_flags) {
		str += " flags=";
		str += cvt_hex(_flags);
	}
	if (!_ciaddr.is_unspecified()) {
		str += " ciaddr=";
		str += _ciaddr.to_string();
	}
	if (!_yiaddr.is_unspecified()) {
		str += " yiaddr=";
		str += _yiaddr.to_string();
	}
	if (!_siaddr.is_unspecified()) {
		str += " siaddr=";
		str += _siaddr.to_string();
	}
	if (!_giaddr.is_unspecified()) {
		str += " giaddr=";
		str += _giaddr.to_string();
	}
	str += " chaddr=";
	str += _chaddr.to_string();
	if (_sname.length()) {
		str += " sname=";
		str += _sname;
	}
	if (_sfile.length()) {
		str += " file=";
		str += _sfile;
	}
	if (_nopts) {
		for (unsigned i = 0; i < _nopts; i++) {
			str += " ";
			str += _opts[i]->to_string();
		}
	} 
//	if (_vend.length()) {
//		str += " vend=";
//		str += _vend;
//	}
	str += ">";
	return str;
}

// called after UDP attach
void BOOTP::fixup()
{
}

bool BOOTP::do_build(Buffer &pkt, int phase, Word &pos)
{
	unsigned i;
	if (phase == 0) {
		pos += 4+4+4+4+4+4+16+64+128+64;
		return true;
	}
	if (!pkt.add_octet(_op)) return false;
	if (!pkt.add_octet(_htype)) return false;
	if (!pkt.add_octet(_hlen)) return false;
	if (!pkt.add_octet(_hops)) return false;
	pos += 4;
	if (!pkt.add_hlong(_xid)) return false;
	pos += 4;
	if (!pkt.add_hshort(_secs)) return false;
	if (!pkt.add_hshort(_flags)) return false;
	pos += 4;
	if (!_ciaddr.build(pkt, phase)) return false;
	pos += 4;
	if (!_yiaddr.build(pkt, phase)) return false;
	pos += 4;
	if (!_siaddr.build(pkt, phase)) return false;
	pos += 4;
	if (!_giaddr.build(pkt, phase)) return false;
	pos += 4;
	if (!_chaddr.build(pkt, phase)) return false;
	for (i = 0; i < 16-6; i++) {
		if (!pkt.add_octet(0)) return false;
	}
	pos += 16;
	for (i = 0; i < 64; i++) {
		if (i < _sname.length()) {
			if (!pkt.add_octet(_sname[i])) return false;
		} else {
			if (!pkt.add_octet(0)) return false;
		}
		++pos;
	}
	for (i = 0; i < 128; i++) {
		if (i < _sfile.length()) {
			if (!pkt.add_octet(_sfile[i])) return false;
		} else {
			if (!pkt.add_octet(0)) return false;
		}
		++pos;
	}
	if (_nopts) {
		if (!pkt.add_octet(0x63)) return false;
		if (!pkt.add_octet(0x82)) return false;
		if (!pkt.add_octet(0x53)) return false;
		if (!pkt.add_octet(0x63)) return false;
		for (i = 0; i < _nopts; i++) {
			if (!_opts[i]->do_build(pkt, phase, pos)) return false;
		}
	} else
	for (i = 0; i < 64; i++) {
		if (!pkt.add_octet(_vend[i])) return false;
		++pos;
	}
	return true;
}

bool BOOTP::decode(Buffer &pkt)
{
	unsigned i;
	Octet p;
	_sname = _sfile = "";
	memset(_vend, 0, 64);
	clear_opts();
	if (!pkt.get_octet(_op)) {
		if (debug > 0) cerr << "BOOTP::decode(): missing op octet" << endl;
		return false;
	}
	if (!pkt.get_octet(_htype)) {
		if (debug > 0) cerr << "BOOTP::decode(): missing htype octet" << endl;
		return false;
	}
	if (!pkt.get_octet(_hlen)) {
		if (debug > 0) cerr << "BOOTP::decode(): missing hlen octet" << endl;
		return false;
	}
	if (!pkt.get_octet(_hops)) {
		if (debug > 0) cerr << "BOOTP::decode(): missing hops octet" << endl;
		return false;
	}
	if (!pkt.get_hlong(_xid)) {
		if (debug > 0) cerr << "BOOTP::decode(): missing xid octets" << endl;
		return false;
	}
	if (!pkt.get_hshort(_secs)) {
		if (debug > 0) cerr << "BOOTP::decode(): missing secs octets" << endl;
		return false;
	}
	if (!pkt.get_hshort(_flags)) {
		if (debug > 0) cerr << "BOOTP::decode(): missing flags octets" << endl;
		return false;
	}
	if (!_ciaddr.decode(pkt)) {
		if (debug > 0) cerr << "BOOTP::decode(): missing ciaddr octets" << endl;
		return false;
	}
	if (!_yiaddr.decode(pkt)) {
		if (debug > 0) cerr << "BOOTP::decode(): missing yiaddr octets" << endl;
		return false;
	}
	if (!_siaddr.decode(pkt)) {
		if (debug > 0) cerr << "BOOTP::decode(): missing siaddr octets" << endl;
		return false;
	}
	if (!_giaddr.decode(pkt)) {
		if (debug > 0) cerr << "BOOTP::decode(): missing giaddr octets" << endl;
		return false;
	}
	if (!_chaddr.decode(pkt)) {
		if (debug > 0) cerr << "BOOTP::decode(): missing chaddr octets" << endl;
		return false;
	}
	for (i = 0; i < 16-6; i++) {
		if (!pkt.get_octet(p)) {
			if (debug > 0) cerr << "BOOTP::decode(): missing chaddr octets" << endl;
			return false;
		}
	}
	for (int i = 0; i < 64; i++) {
		if (!pkt.get_octet(p)) {
			if (debug > 0) cerr << "BOOTP::decode(): missing sname octets" << endl;
			return false;
		}
		if (p) _sname += p;
	}
	for (int i = 0; i < 128; i++) {
		if (!pkt.get_octet(p)) {
			if (debug > 0) cerr << "BOOTP::decode(): missing file octets" << endl;
			return false;
		}
		if (p) _sfile += p;
	}
	for (int i = 0; i < 64; i++) {
		if (!pkt.get_octet(p)) {
			return true;
		}
		_vend[i] = p;
		if (i == 3 && _vend[0] == 0x63 && _vend[1] == 0x82 && _vend[2] == 0x53 && _vend[3] == 0x63) { 
			// RFC1497 vendor extension format
			while ((_opts[_nopts] = BOOTP_Option::decode_option(pkt)) != NULL) {
				if (_opts[_nopts++]->type() == BOOTP_Option::End) break;
				if (_nopts >= MAX_OPTS) {
					if (debug > 0) cerr << "BOOTP::decode(): too many options" << endl;
					return false;
				}
			}
			break;
		}
	}
	return true;
}
#endif
#endif
