#include "common.h"
#include "ethernet.h"

#define _LIB
#include "ipv4.h"

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

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

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

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

string IPv4Address::to_string(int level) const
{
	char str[INET_ADDRSTRLEN];
	if (inet_ntop(AF_INET, _addr, str, INET_ADDRSTRLEN)) return string(str);
	return "0.0.0.0";
}
#endif



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

string IPv4::to_string(int level) const
{
	string ret("<IPv4");
	if (_version != 4) {
		ret += " version=";
		ret += cvt_int(_version);
	}
	if (_hlen != 5) {
		ret += " hlen=";
		ret += cvt_int(_hlen);
	}
	if (_tos != 0) {
		ret += " dscp=";
		ret += cvt_hex(_tos>>2);
		ret += " ecn=";	//non-ECT,ECT(1),ECT(0),CE
		ret += cvt_hex(_tos&3);
	}
	ret += " plen=";
	ret += cvt_int(_plen);
	if (_id) {
		ret += " id=";
		ret += cvt_int(_id);
	}
	if (is_df()) ret += " DF";
	if (is_mf()) ret += " MF";
	if (is_fragment()) {
		ret += " foff=";
		ret += cvt_int(fragment_offset());
	}
	ret += " ttl=";
	ret += cvt_int(_ttl);
	ret += " proto=";
	ret += cvt_int(_proto);
	ret += " chk=";
	ret += cvt_hex(_proto);
	ret += " src=";
	ret += _src.to_string(level);
	ret += " dst=";
	ret += _dest.to_string(level);
//	for (int i = 0; i < _num_exts; i++) {
//		ret += " | ";
//		ret += _exts[i]->to_string(level);
//	}
	ret += ">";
	return ret;
}

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

// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// +                         Source Address                        +
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// +                      Destination Address                      +
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |    zero       |    protocol   |         payload length        |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Word IPv4::pseudo_checksum(Octet proto, Word plen) const
{
	// TODO - fix plen
	return _src.checksum() + _dest.checksum() + (plen&0xffff) + proto;
}

bool IPv4::do_build(Buffer &pkt, int phase, Word &pos)
{
	Word checksum;
	// get the extension headers size, insert required paddings
	if (phase == 0) {
		pos += 20;
//		for (int i = 0; i < _num_exts; i++) {
//			if (!_exts[i]->build(pkt, phase, pos)) return false;
//		}
		if (payload()) {
			if (!payload()->do_build(pkt, phase, pos)) return false;
		}
		_plen = pos;	// all extension headers + payload
		checksum = ((_version&0xf) << 12) + ((_hlen & 0xf) << 8) + _tos;
		checksum += _plen;
		checksum += _id;
		checksum += _foff;
		checksum += (_ttl<<8) + _proto;
		checksum += _src.checksum();
		checksum += _dest.checksum();
		checksum = (checksum & 0xffff) + (checksum >> 16);
		if (checksum > 65535) checksum -= 65535;
		_checksum = ~checksum;
		// TODO - if _plen > MTU, perform fragmentation
		return true;
	}
	if (!pkt.add_octet(((_version&0xf)<<4)+(_hlen&0xf))) return false;
	if (!pkt.add_octet(_tos)) return false;
	if (!pkt.add_hshort(_plen)) return false;
	if (!pkt.add_hshort(_id)) return false;
	if (!pkt.add_hshort(_foff)) return false;
	if (!pkt.add_octet(_ttl)) return false;
	if (!pkt.add_octet(_proto)) return false;
	if (!pkt.add_hshort(_checksum)) return false;
	if (!_src.build(pkt, phase)) return false;
	if (!_dest.build(pkt, phase)) return false;
	pos += 20;
//	for (int i = 0; i < _num_exts; i++) {
//		if (!_exts[i]->build(pkt, phase, pos)) return false;
//	}
	if (payload()) {	// build upper layer packet
		if (!payload()->do_build(pkt, phase, pos)) return false;
	}
	return true;
}

bool IPv4::decode(Buffer &pkt)
{
	Octet b1;
	if (_frag && _own) delete _frag;
	_own = false;
	_frag = 0;
	release();		// release previous payload
	pkt.set();
	if (!pkt.get_octet(b1) || !pkt.get_octet(_tos) || !pkt.get_hshort(_plen)) {
		pkt.reset();
		return false;
	}
	_version = b1 >> 4;				// 28..31
	if (_version != 4) {
		if (debug > 0) cerr << "IPV4::decode(): invalid IPv4 version " << (int)_version << endl;
		return false;
	}
	_hlen = (b1 & 0xf);
	if (!pkt.get_hshort(_id) || !pkt.get_hshort(_foff)) {
		pkt.reset();
		return false;
	}
	if (!pkt.get_octet(_ttl) || !pkt.get_octet(_proto) || !pkt.get_hshort(_checksum)) {
		pkt.reset();
		return false;
	}
	if (!_src.decode(pkt) || !_dest.decode(pkt)) {
		pkt.reset();
		return false;
	}
	// TODO - process options
	if (_hlen > 5) {
		int l = _hlen*4-20;
		for (int i = 0; i < l; i++) {
			if (!pkt.get_octet(b1)) return false;
		}
	}
	if (_foff & 0x1fff) {	// fragment offset
		int l = _plen - _hlen*4;
		_frag = new Octet[l];
		_own = true;
		for (int i = 0; i < l; i++) {
			if (!pkt.get_octet(_frag[i])) return false;
		}
		return true;
	}
	// decode payload
	Packet *p = proto_factory(_proto, pkt);
	if (p) {
		attach(p, true);
		if (!p->decode(pkt)) return false;
	}
	return true;
}
Packet *IPv4_factory()
{
       Packet *p = new IPv4();
       // if (debug > 1) cerr << "new IPv4 instance " << p << endl;
       return p;
}

