/*
 * dhcpv6.cpp
 *
 *  Created on: 7.8.2012
 *      Author: Petr Kramolis
 */
// This program is free software; you can redistribute it and/or
// modify it under the terms of the BUT OPEN SOURCE LICENCE
// Version 1 as published by the Brno University of Technology, Antonínská 548/1,
// 601 90, Czech Republic.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// BUT OPEN SOURCE LICENCE (BUTOSL) for more details.
// 
// You should have received a copy of the BUT OPEN SOURCE LICENCE (BUTOSL)
// along with this program; if not, write to the Brno University of Technology,
// Antonínská 548/1, 601 90, Czech Republic.


#include "dhcpv6.h"

/*
bool DUID_LLT_Format::decode(Buffer &pkt)
{
	pkt.get_hshort(_hw_type);
	pkt.get_hlong(_time);
	_address = DUID_HW_Identifier::decode_DUID_HW_Identifier(pkt, _hw_type);

	return true;
};

bool DUID_LL_Format::decode(Buffer &pkt)
{
	pkt.get_hshort(_hw_type);
	_address = DUID_HW_Identifier::decode_DUID_HW_Identifier(pkt, _hw_type);
	return true;
};

DUID_HW_Identifier *DUID_HW_Identifier::decode_DUID_HW_Identifier(Buffer &pkt, int hw_type)
{
	DUID_HW_Identifier * new_DUID_HW_Identifier;
	switch(hw_type){
	case MAC: new_DUID_HW_Identifier = new DUID_MAC;break;
	case EUI64: new_DUID_HW_Identifier = new DUID_EUI64;break;
	default: return NULL;
	}

	if(new_DUID_HW_Identifier->decode(pkt)){
		return new_DUID_HW_Identifier;
	}
	delete new_DUID_HW_Identifier;
	return NULL;
}*/

DUID::DUID (const DUID& duid){
	_len = duid._len;
	if(_len != 0){
		_duid = new Octet[_len];
		memcpy(_duid,duid._duid,sizeof(Octet)* _len);
	}else{
		_duid = NULL;
	}
}

DUID& DUID::operator= (const DUID& duid){
	if(this != &duid){
		_len = duid._len;
		if(_len != 0){
			_duid = new Octet[_len];
			memcpy(_duid,duid._duid,sizeof(Octet)* _len);
		}else{
			_duid = NULL;
		}
	}
	return *this;
}

bool DUID::decode(Buffer &pkt, int len){
	_len = len;
	_duid = new Octet[len];
	for(int i=0; i < len; i++){
		if(!pkt.get_octet(_duid[i])){
			return false;
		}
	}
	return true;
}

string DUID::to_string(){
	stringstream ss;
	
	ss << std::hex;
	for(int i=0; i < _len; i++){
		if(i != 0){
			ss << ":";
		}
		ss << int(_duid[i]);
	}
	return ss.str();
}

DUID *DUID::decode_duid(Buffer &pkt, int len)
{
	DUID *new_DUID;

	//Short DUID_type = 0;
	//pkt.get_hshort(DUID_type);
	//switch (DUID_type){
	//case DUID_LLT: new_DUID = new DUID_LLT_Format;break;
	//case DUID_LL: new_DUID = new DUID_LL_Format;break;
	//default:
	//	new_DUID = new DUID;
	//}

	new_DUID = new DUID;
	if(new_DUID->decode(pkt,len)){
		return new_DUID;
	}
	delete new_DUID;
	return NULL;
}



bool DHCPv6_Option::decode(Buffer &pkt)
{
	Octet null;
	pkt.get_hshort(_len);

	//skip uknown bytes
	for(int i=0; i<_len;i++){
		pkt.get_octet(null);
	}
	return true;
};


bool DHCPv6_CLIENTID_Option::decode(Buffer &pkt){
	pkt.get_hshort(_len);
	if((_duid = DUID::decode_duid(pkt,_len)) == NULL ){
		return false;
	}
	return true;
};


DHCPv6_Option *DHCPv6_IA_NA_Option::option(int i){
	if (i<MAX_OPTS && i >= 0)
		return _opts[i];
	return NULL;
}

bool DHCPv6_IA_NA_Option::decode(Buffer &pkt)
{
	pkt.get_hshort(_len);
	pkt.get_hlong(_IAID);
	pkt.get_hlong(_T1);
	pkt.get_hlong(_T2);

	int i=0;
	int len = 12;
	while(_len > len){
		if((_opts[i] = DHCPv6_Option::decode_option(pkt)) == NULL ){
			return false;
		}
		len += 4 + _opts[i]->len(); // len and code are not counted in option len!
		i++;
	}
	return true;
};


DHCPv6_Option *DHCPv6_IAADDR_Option::option(int i){
	if (i<MAX_OPTS && i >= 0)
		return _opts[i];
	return NULL;
}

bool DHCPv6_IAADDR_Option::decode(Buffer &pkt){
	pkt.get_hshort(_len);
	_address.decode(pkt);

	pkt.get_hlong(_preferred_lifetime);
	pkt.get_hlong(_valid_lifetime);

	int len = 24;
	int i = 0;
	while(_len > len){
		if((_opts[i] = DHCPv6_Option::decode_option(pkt)) == NULL ){
			return false;
		}
		len += 4 + _opts[i]->len(); // 4 -> len and code are not count in option lenght!
		i++;
	}
	return true;
}

bool DHCPv6_Status_Code_Option::decode(Buffer &pkt){
	pkt.get_hshort(_len);
	pkt.get_hshort(_status_code);

	Octet c = 0;
	int l = 2; // size of status code
	while( l < _len){
		pkt.get_octet(c);
		_status_message.append(1,(char) c);
		l++;
	}
	return true;
};


DHCPv6_Option *DHCPv6_IA_PD_Option::option(int i){
	if (i<MAX_OPTS && i >= 0)
		return _opts[i];
	return NULL;
}

bool DHCPv6_IA_PD_Option::decode(Buffer &pkt)
{
	pkt.get_hshort(_len);
	pkt.get_hlong(_IAID);
	pkt.get_hlong(_T1);
	pkt.get_hlong(_T2);

	int i=0;
	int len = 12;
	while(_len > len){
		if((_opts[i] = DHCPv6_Option::decode_option(pkt)) == NULL ){
			return false;
		}
		len += 4 + _opts[i]->len(); // len and code are not counted in option len!
		i++;
	}
	return true;
};

DHCPv6_Option *DHCPv6_IAPREFIX_Option::option(int i){
	if (i<MAX_OPTS && i >= 0)
		return _opts[i];
	return NULL;
}

bool DHCPv6_IAPREFIX_Option::decode(Buffer &pkt){
	pkt.get_hshort(_len);

	pkt.get_hlong(_preferred_lifetime);
	pkt.get_hlong(_valid_lifetime);
	pkt.get_octet(_prefix_length);
	_prefix.decode(pkt);
	_prefix.length(_prefix_length);


	int len = 25;
	int i = 0;
	while(_len > len){
		if((_opts[i] = DHCPv6_Option::decode_option(pkt)) == NULL ){
			return false;
		}
		len += 4 + _opts[i]->len(); // 4 -> len and code are not count in option lenght!
		i++;
	}
	return true;
}


DHCPv6_Option *DHCPv6_Option::decode_option(Buffer &pkt)
{
	DHCPv6_Option *opt=NULL;
	Short code = 0;
	pkt.get_hshort(code);

	switch (code){
	case OPTION_CLIENTID: opt = new DHCPv6_CLIENTID_Option;break;
	case OPTION_SERVERID: opt = new DHCPv6_Option;break;
	case OPTION_IA_NA: opt = new DHCPv6_IA_NA_Option;break;
	case OPTION_IA_TA: opt = new DHCPv6_Option;break;
	case OPTION_IAADDR: opt = new DHCPv6_IAADDR_Option;break;
	case OPTION_ORO: opt = new DHCPv6_Option;break;
	case OPTION_PREFERENCE: opt = new DHCPv6_Option;break;
	case OPTION_ELAPSED_TIME: opt = new DHCPv6_Option;break;
	case OPTION_RELAY_MSG: opt = new DHCPv6_Relay_msg_Option;break;
	case OPTION_AUTH: opt = new DHCPv6_Option;break;
	case OPTION_UNICAST: opt = new DHCPv6_Option;break;
	case OPTION_STATUS_CODE: opt = new DHCPv6_Status_Code_Option;break;
	case OPTION_RAPID_COMMIT: opt = new DHCPv6_Option;break;
	case OPTION_USER_CLASS: opt = new DHCPv6_Option;break;
	case OPTION_VENDOR_CLASS: opt = new DHCPv6_Option;break;
	case OPTION_VENDOR_OPTS: opt = new DHCPv6_Option;break;
	case OPTION_INTERFACE_ID: opt = new DHCPv6_Option;break;
	case OPTION_RECONF_MSG: opt = new DHCPv6_Option;break;
	case OPTION_RECONF_ACCEPT: opt = new DHCPv6_Option;break;
	case OPTION_IA_PD: opt = new DHCPv6_IA_PD_Option;break;
	case OPTION_IAPREFIX: opt = new DHCPv6_IAPREFIX_Option;break;
	default:
		return NULL;//unknow option lets eat it
		opt = new DHCPv6_Option;
		break;
	}

	if(opt->decode(pkt)){
		return opt;
	}
	delete opt;
	return NULL;

};



DHCPv6_Option *DHCPv6::option(int i){
	if (i<MAX_OPTS && i >= 0)
		return _opts[i];
	return NULL;
}

int DHCPv6::transaction_id()
{
	int tmp;
	tmp = _transaction_id[0];
	tmp = tmp << 1;
	tmp += _transaction_id[1];
	tmp = tmp << 1;
	tmp += _transaction_id[2];
	return tmp;
};

bool DHCPv6::decode(Buffer &pkt, int left)
{
	int i=0;
	int pkt_end;
	pkt_end = pkt.left() - left;
	pkt.get_octet(_msg_type);
	if(_msg_type == RELAY_FORW or _msg_type == RELAY_REPL){
		pkt.get_octet(_hop_count);
		_link_address.decode(pkt);
		_pear_address.decode(pkt);
	}else{
		pkt.get_octet(_transaction_id[0]);
		pkt.get_octet(_transaction_id[1]);
		pkt.get_octet(_transaction_id[2]);
	}

	while(pkt.left() > pkt_end ){
		if((_opts[i] = DHCPv6_Option::decode_option(pkt)) == NULL ){
			return false;
		}
		i++;
	}
	return true;
};

bool DHCPv6_Relay_msg_Option::decode(Buffer &pkt){
	pkt.get_hshort(_len);
	_relayed_DHCPv6 = new class DHCPv6;
	return _relayed_DHCPv6->decode(pkt, _len);
};

DHCPv6_Relay_msg_Option::~DHCPv6_Relay_msg_Option(){
	if(_relayed_DHCPv6 != NULL){
		delete _relayed_DHCPv6;
	}
}


