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

#ifndef __NDWATCH_IPV4_H__
#define __NDWATCH_IPV4_H__

// RFC791
class IPv4Address
{
private:
	Octet _addr[4];
public:
	IPv4Address() { _addr[0] = _addr[1] = _addr[2] = _addr[3] = 0; }
	explicit IPv4Address(const string &str) { (void)addr(str); }
	// default copy constructor ok
	int length() const { return 4; }
	bool addr(const string &str);
	const Octet *octet_addr() const { return _addr; }
	inline string addr() const { return to_string(); }
	string to_string(int level = 0) const;
	// rfc4291 
	inline bool is_unspecified() const {	// ::
		for (int i = 0; i < 4; i++) {
			if (_addr[i]) return false;
		}
		return true;
	}
	inline bool is_loopback() const {	// ::1
		if (_addr[0] != 127) return false;
		return true;
	}
	inline bool is_linklocal() const {
		if (_addr[0] != 169 || _addr[1] != 254) return false;
		return true;
	}
	inline bool is_private() const {	// 10, 178, 196
		if (_addr[0] != 10 &&
		    (_addr[0] != 172 || (_addr[1] & 0xf0) != 0x10) &&
		    (_addr[0] != 192 || _addr[1] != 168)) return false;
		return true;
	}
	inline bool is_multicast() const { // ff00::-
		if ((_addr[0] & 0xf0) != 0xe0) return false;
		if (_addr[0] != 255 || _addr[1] != 255 || _addr[2] != 255 || _addr[3] != 255) return false;
		return true;
	}
	inline bool is_prefix(const IPv4Address &pfx, int len) const {
		int i=0;
		if (len > 32) return false;
		while (len > 8) {
			if (_addr[i] != pfx._addr[i]) return false;
			len -= 8; i++;
		}
		if (len == 0) return true;
		Octet mask = 0xffu << len;
		if ((_addr[i] & mask) != (pfx._addr[i] & mask)) return false;
		return true;
	}

	inline bool operator==(const IPv4Address &adr) const {
		for (int i = 0; i < 4; i++) {
			if (_addr[i] != adr._addr[i]) return false;
		}
		return true;
	}
	inline bool operator!=(const IPv4Address &adr) const { return !(*this ==(adr)); }
	inline unsigned checksum() const { 
		return (_addr[0]<<8) + _addr[1] + (_addr[2]<<8) + _addr[3];
	}
	inline bool build(Buffer &pkt, int phase) {
		for (int i = 0; i < 4; i++) pkt.add_octet(_addr[i]);
		return true;
	}
	inline bool decode(Buffer &pkt) {
		for (int i = 0; i < 4; i++) {
			if (!pkt.get_octet(_addr[i])) return false;
		}
		return true;
	}
	~IPv4Address() { }
};

// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |Version|  IHL  |    DSCP   |ECN|          Total Length         |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |         Identification        |Flags|      Fragment Offset    |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |  Time to Live |    Protocol   |         Header Checksum       |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                       Source Address                          |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                    Destination Address                        |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                    Options                    |    Padding    |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// RFC791 3.1
// RFC3168 23.1
class IPv4: public Packet 
{
private:
	Octet _version;	// IP version number = 4
	Octet _hlen;	// header length in 4 octet units
	Octet _tos;	// type of service (DSCP + ECN)
	Short _plen;	// payload length including header
	Short _id;	// identification
	Short _foff;	// fragment offset
	Octet _ttl;	// time to live
	Octet _proto;	// type of header immediately following the IPv4 header
	Short _checksum;	// header checksum
	IPv4Address _src;	// source address
	IPv4Address _dest;	// destination address
	Word _options;		// options
	Octet *_frag;	// fragment data
	bool _own;	
	IPv4(const IPv4&);
	IPv4& operator=(const IPv4&);
public:

	IPv4(): _version(4), _hlen(5), _tos(0), _plen(0), _id(0), _foff(0), _ttl(64), _proto(0), _checksum(0), _src(), _dest(), _options(0), _frag(0), _own(false) { }
	explicit IPv4(const string &dest): _version(4), _hlen(5), _tos(0), _plen(0), _id(0), _foff(0), _ttl(64), _proto(0), _checksum(0), _src(), _dest(dest), _options(0), _frag(0), _own(false) { }
	explicit IPv4(const string &src, const string &dest): _version(4), _hlen(5), _tos(0), _plen(0), _id(0), _foff(0), _ttl(64), _proto(0), _checksum(0), _src(src), _dest(dest), _options(0), _frag(0), _own(false) { }

	inline bool is_fragment() const { return (_foff & 0x1fff) != 0; }
	inline bool is_df() const { return (_foff & 0x4000); }
	inline bool is_mf() const { return (_foff & 0x2000); }
	inline void set_df() { _foff |= 0x4000; }
	inline void set_mf() { _foff |= 0x2000; }
	inline Octet *fragment_data() { return _frag; }
	inline void fragment_data(Octet *data, Short len) {
		_plen = len+20;
		_frag = data;
		_own = false;
	}
	inline Short fragment_length() const { return _plen - _hlen*4; }
	inline Short fragment_offset() const { return (_foff&0x1fff)*8; }
	inline bool fragment_offset(Short off) { 
		if (off > 65535-20) return false;
		if (off & 0x7) return false;
		_foff = (_foff & 0xe000) | ((off>>3) & 0x1fff); 
		return true;
	}
	inline bool dest(const string &str) { return _dest.addr(str); }
	inline const IPv4Address& dest() const { return _dest; }
	inline bool src(const string &str) { return _src.addr(str); }
	inline const IPv4Address& src() const { return _src; }
	inline bool dscp(unsigned val) { 
		if (val > 63) return false;
		_tos = val<<2; return true;
	}
	inline int dscp() const { return _tos>>2; }
	inline bool ident(unsigned id) {
		if (id >= 1<<16) return false;
		_id = id; return true;
	}
	inline int ident() const { return _id; }
	inline bool plen(unsigned len) { 
		if (len > 65535) return false;
		_plen = len; return true;
	}
	inline int plen() const { return _plen; }
	inline bool proto(unsigned proto) { 
		if (proto > 255) return false;
		_proto = proto; return true;
	}
	inline int proto() const { return _proto; }
	inline bool ttl(unsigned ttl) {
		if (ttl > 255) return false;
		_ttl = ttl; return true;
	}
	inline int ttl() const { return _ttl; }

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

	Word pseudo_checksum(Octet proto, Word plen) const;

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

	~IPv4() { if (_frag && _own) delete _frag; }

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

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

Packet *IPv4_factory();

#endif
