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

#include <map>

class NC6
{
public:
	// neighbor cache entry
	struct TNCEnt {
		IPv6Address ipv6;
		MacAddress mac;
		time_t ctime;
		time_t stime;
		time_t atime;
		bool valid;
		Octet flags;
	};
	typedef map<const IPv6Address, TNCEnt *> TNC;

private:
	TNC _nc_ip;
	PCAP &_pcap;

public:
	// open PCAP interface
	NC6(PCAP &pcap);
	bool lookup_mac(const string &dev, const IPv6Address &ipv6, MacAddress &mac, bool retry=false);
	~NC6();
};

#ifdef _LIB
NC6::NC6(PCAP &pcap): _nc_ip(), _pcap(pcap)
{
}

NC6::~NC6()
{
}

bool NC6::lookup_mac(const string &dev, const IPv6Address &ipv6, MacAddress &mac, bool retry)
{
	TNCEnt *pe;
	TNC::iterator iit = _nc_ip.find(ipv6);
	if (iit != _nc_ip.end()) {
		pe = (*iit).second;
		if (!retry && pe->valid) {
			mac = pe->mac;
			return true;
		}
	} else {
		pe = static_cast<TNCEnt *>(operator new(sizeof(TNCEnt)));
		memset(pe, 0, sizeof(TNCEnt));
		pe->ipv6 = ipv6;
		pe->ctime = time(NULL);
		_nc_ip.insert(pair<IPv6Address, TNCEnt *>(ipv6, pe));
	}
	Buffer pkt;
	MacAddress src;
	MacAddress dst;
	
	if (!if_get_mac(dev, src)) {		// get MAC
		cerr << "source mac not found" << endl;
	}
	dst.addr(ipv6.multicast_mac(true));
	Ethernet *e = new Ethernet(src, dst);

	IPv6Address ipsrc, ipdst;
	int plen;
	if_get_ipv6(dev, ipsrc, plen, 0);	// get LLA
	ipdst.solicited_multicast(ipv6);	// destination = solicited-node
	IPv6 *p = new IPv6(ipsrc, ipdst);
	p->hlim(255);
	e->attach(p);

	ICMPv6ND_NS *ns = new ICMPv6ND_NS(ipv6);
	NDOptionSourceLLA *slla = new NDOptionSourceLLA(src);
	ns->add_option(slla);
	p->attach(ns);
	if (!e->build(pkt)) {
		cerr << "NC6::ND NS packet build failed" << endl;
		delete p;
		return false;
	}
	// cout << e << endl;
	// cout << p << endl;
	// cout << ns << endl;

	pe->stime = time(NULL);
	_pcap.set_filter("icmp6 and dst "+ipsrc.to_string());
	_pcap.write(dev, pkt);
	Buffer rpkt;
	Ethernet *re = new Ethernet();
	string rdev;
	while (time(NULL) <= pe->stime + 1*3) { // RETRANS_TIMER=1 * MAX_MULTICAST_SOLICIT
		if (_pcap.read(rpkt, rdev, 1000)) {
			if (!re->decode(rpkt)) continue;
			if (!re->payload()) continue;
			if (dynamic_cast<IPv6 *>(re->payload()) == NULL) continue;
			IPv6 *ripv6 = dynamic_cast<IPv6 *>(re->payload());
			if (!ripv6->payload()) continue;
			if (dynamic_cast<ICMPv6 *>(ripv6->payload()) == NULL) continue;
			ICMPv6 *ricmpv6 = dynamic_cast<ICMPv6 *>(ripv6->payload());
			if (ricmpv6->type() != ICMPv6::ND_NA) continue;
			ICMPv6ND_NA *nd_na = dynamic_cast<ICMPv6ND_NA *>(ricmpv6);
			if (nd_na->target() != ipv6) continue;
			cout << ricmpv6 << endl;
			// check option Source LLA == mac src
			pe->atime = time(NULL);
			pe->flags = nd_na->flags();
			mac = pe->mac = re->src();
			pe->valid = true;
			delete re;
			delete e;
			return true;
		}
		_pcap.write(dev, pkt);
	}
	delete re;
	delete e;
	return false;
}
#endif
