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

#ifndef __NDWATCH_ETHERNET_H__
#define __NDWATCH_ETHERNET_H__

#include <map>
#include <list>
#include "common.h"

// 
// MAC address
//
class MacAddress {
private:
	Octet _addr[6];
	bool _valid;
public:
	MacAddress(): _valid(false) { }
	explicit MacAddress(string str, char delim=':'): _valid(false) { (void)addr(str, delim); }
	// default copy constructor ok
	bool addr(const string &str, char delim=':');
	bool addr(const Octet *mac);
	inline string addr() const { return to_string(); }
	inline bool is_valid() const { return _valid; }
	string to_string(int level=0) const;
	inline bool operator==(const MacAddress &adr) const {
		for (int i = 0; i < 6; i++) {
			if (_addr[i] != adr._addr[i]) return false;
		}
		return true;
	}
	inline bool operator!=(const MacAddress &adr) const {
		for (int i = 0; i < 6; i++) {
			if (_addr[i] != adr._addr[i]) return true;
		}
		return false;
	}
	// needed in STL
	inline bool operator<(const MacAddress &adr) const {
		for (int i = 0; i < 6; i++) {
			if (_addr[i] != adr._addr[i]) return _addr[i] < adr._addr[i];
		}
		return false;
	}
	inline bool is_broadcast() const { return _addr[0] & 1; }
	inline bool is_unspecified() const { return _addr[0] == 0 && _addr[1] == 0 && _addr[2] == 0 && _addr[3] == 0 && _addr[4] == 0 && _addr[5] == 0; }
	bool build(Buffer &pkt, int phase);
	inline Word checksum() const { return ((_addr[0]+_addr[2]+_addr[4])<<8)+_addr[1]+_addr[3]+_addr[5]; }
	inline bool decode(Buffer &pkt) {
		_valid = false;
		for (int i = 0; i < 6; i++) {
			if (!pkt.get_octet(_addr[i])) return false;
		}
		_valid = true;
		return true;
	}
	~MacAddress() { }
};

//
// Ethernet II Frame
//
class Ethernet: public Packet {
private:
	MacAddress _dest;
	MacAddress _src;
	Short _type;
	bool _dest_set;
	bool _src_set;
	bool _type_set;

public:
	typedef bool (Ethernet::*Processor)(Ethernet &pkt);
	typedef std::map<int, std::list<Processor> > CallbacksMap;
	Ethernet(): _dest(), _src(), _type(0), _dest_set(false), _src_set(false), _type_set(false) { }
	explicit Ethernet(MacAddress dest): _dest(dest), _src(), _type(0), _dest_set(true), _src_set(false), _type_set(false) { }
	Ethernet(MacAddress src, MacAddress dest): _dest(dest), _src(src), _type(0), _dest_set(true), _src_set(true), _type_set(false) { }
	Ethernet(MacAddress src, MacAddress dest, int type) : _dest(dest), _src(src), _type(type), _dest_set(true), _src_set(true), _type_set(true) { }
	~Ethernet() { }

	bool src(string str) { return _src.addr(str); }
	MacAddress src() const { return _src; }
	bool dest(string str) { return _dest.addr(str); }
	MacAddress dest() const { return _dest; }
	void type(unsigned type) { _type = type; _type_set = true; }
	unsigned type() const { return _type; }
	string string_type() const;

	string to_string(int level=0) const;
	string name() const { return "Ethernet"; }
	void fixup() { }
	bool decode(Buffer &pkt);
	bool do_build(Buffer &pkt, int phase, Word &pos);
	
	inline static void add_proto(Short code, Packet *(p)()) { 
		_map_code[_map_n] = code;
		_map_p[_map_n] = p;
		++_map_n;
	}
	inline static Packet *proto_factory(Short code) {
		for (int i = 0; i < _map_n; i++) {
			if (_map_code[i] == code) return (*_map_p[i])();
		}
		return NULL;
	}

private:
	// packet decoder map (_map_code[i] => _map_p[i])
	static Packet *(*_map_p[100])();
	static Short _map_code[100];
	static int _map_n;

	// not used currently
	static bool add_callback(unsigned type, Processor proc);
	static CallbacksMap _callbacks;
	static void dispatch(Ethernet &pkt);
};

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

#ifdef _LIB
Packet *(*Ethernet::_map_p[100])();
Short Ethernet::_map_code[100];
int Ethernet::_map_n;

bool MacAddress::addr(const string &str, char delim) 
{
	_valid = false;
	int i = 0;
	int dig = 0;
	for (string::const_iterator it = str.begin(); it < str.end(); ) {
	    if (isxdigit(*it)) {
		if (dig >= 2) break;	// too many hex digits
		if (dig == 1) _addr[i] = _addr[i]<<4;
		else _addr[i] = 0;
		if (*it >= '0' && *it <= '9') _addr[i] += *it - '0';
		else
		if (*it >= 'a' && *it <= 'f') _addr[i] += *it - 'a' + 10;
		else
		if (*it >= 'A' && *it <= 'F') _addr[i] += *it - 'A' + 10;
		else break;		// unexpected char
		dig++;
	    } else
	    if (*it == delim) {
		if (dig == 0) break;	// no digit
		dig = 0;
		i++;
		if (i >= 6) break;	// too many parts
	    } else break;		// unexpected char
	    if (++it == str.end() && i == 5 && dig) _valid = true;
	}
	return _valid;
}

bool MacAddress::addr(const Octet *mac)
{
        memcpy(_addr, mac, 6);
	_valid = true;
	return true;
}

string MacAddress::to_string(int level) const
{
	string s("00:00:00:00:00:00");
	if (!_valid) return s;
	for (int i = 0; i < 6; i++) {
	    s[i*3] = xdigits[_addr[i]>>4 & 0xf];
	    s[i*3+1] = xdigits[_addr[i] & 0xf];
	}
	return s;
}

bool MacAddress::build(Buffer &pkt, int phase)
{
	for (int i = 0; i < 6; i++) {
		if (!_valid) {
			if (!pkt.add_octet(0)) return false;
		} else {
			if (!pkt.add_octet(_addr[i])) return false;
		}
	}
	return true;
}

bool Ethernet::do_build(Buffer &pkt, int phase, Word &pos)
{
	if (phase == 0) {	// prepare
		pos += 14;
		if (payload()) {
			if (!payload()->do_build(pkt, 0, pos)) return false;
		}
		return true;
	}
	if (!_dest.is_valid()) {
		if (debug > 0) cerr << "Ethernet::build(): destination mac not found" << endl;
		return false;
	}
	if (!_src.is_valid()) {
		if (debug > 0) cerr << "Ethernet::build(): source mac not found" << endl;
		return false;
	}
	if (!_type_set) {
		if (debug > 0) cerr << "Ethernet::build(): type/length not found" << endl;
		return false;
	}
	if (!_dest.build(pkt, phase)) return false;
	if (!_src.build(pkt, phase)) return false;
	if (!pkt.add_hshort(_type)) return false;
	pos += 14;
	if (payload()) {
		if (!payload()->do_build(pkt, 1, pos)) return false;
	}
	return true;
}

string Ethernet::string_type() const
{
	string str("0000");
	for (int i = 0; i < 4; i++) str[i] = xdigits[_type>>((3-i)*4) & 0xf];
	return str;
}

string Ethernet::to_string(int level) const
{
	string str("<Ether src=");
	str += _src.to_string(level);
	str += " dst=" ;
	str += _dest.to_string(level);
	str += " type=0x";
	str += string_type();
	str += ">";
	return str;
}

bool Ethernet::decode(Buffer &pkt)
{
	_src_set = _dest_set = _type_set = false;
	release();		// release previous packet content
	pkt.set();		// set packet pointer to begin

	Short val = 1;


	for(int i=0;i<7;i++){ //check for linux cooked header
		pkt.get_hshort(val);
	}

	pkt.reset();

	if(val == 0){ // its linux cooked header
		for(int i=0;i<3;i++){ //eat 6 bytes
				pkt.get_hshort(val);
		}
		if (!_src.decode(pkt) || !pkt.get_hshort(val) || !pkt.get_hshort(_type)) {
			pkt.reset();
			return false;
		}
	}else{
		if (!_dest.decode(pkt) || !_src.decode(pkt) || !pkt.get_hshort(_type)) {
			pkt.reset();
			return false;
		}
	}
	_src_set = _dest_set = _type_set = true;
	// get upper protocol decoder
	Packet *p = Ethernet::proto_factory(_type);
	if (p) {
		attach(p, true);	// managed packet layer
		if (!p->decode(pkt)) {return false;}
	} else {
		if (debug > 0) {
			cerr << "Ethernet::decode(): Unknown protocol " << cvt_hex(_type) << endl;
		}
		return false;	// ?? dispatch
	}
	// process the packet 
	dispatch(*this);
	return true;
}

Ethernet::CallbacksMap Ethernet::_callbacks;

bool Ethernet::add_callback(unsigned type, Processor proc)
{
	CallbacksMap::iterator cur = _callbacks.find(type);
	if (cur == _callbacks.end()) {
		list<Processor> *n = new list<Processor>();
		cur = _callbacks.insert(cur, CallbacksMap::value_type(type, *n));
	}
	cur->second.push_back(proc);
	return true;
}

void Ethernet::dispatch(Ethernet &pkt)
{
	CallbacksMap::iterator type_list = _callbacks.find(pkt.type());
	if (type_list == _callbacks.end()) return;
	list<Processor>::iterator cur = type_list->second.begin();
	while (cur != type_list->second.end()) {
		(pkt.*(*cur))(pkt);
		cur++;
	}
}

#endif
#endif
