// 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.




#define _LIB

#include <iostream>
#include <sstream>
#include <queue>


extern "C"{
#include <errno.h>
#include <time.h>
#include <sys/un.h>
#include <pcap.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
}

#include <string>

#include "ndwatch/common.h"
#include "ndwatch/ethernet.h"
#include "ndwatch/udp.h"
#include "ndwatch/tcp.h"
#include "ndwatch/ipv6.h"
#include "ndwatch/ipv4.h"
#include "ndwatch/icmpv6.h"

#include "dhcpv6.h"
#include "iricore_socket.h"
#include "lease_table.h"

int debug = 0;
char int_cvt_buf[32];
const char *xdigits = "0123456789abcdef";

typedef std::vector<uint64_t> CodeAndTime;
typedef std::map<int, CodeAndTime> transID_table;

unix_socket *usocket;
Lease_Table *aip_table;

void print_help(){
	std::cout << "DHCPv6 iri module:" << std::endl;
	std::cout << "dhcpv6 [-f filter] [-i interface] [-p file] [-hs]" << std::endl;
	std::cout << "        -f filter         pcap_filter default is 'udp port 546 or udp port 547'" << std::endl;
	std::cout << "        -i interface      capture interface" << std::endl;
	std::cout << "        -p file           load pcap file instead of live capture" << std::endl;
	std::cout << "        -h                show this help" << std::endl;
	std::cout << "        -s                standalone - do not connect to iri-core" << std::endl;
}

void handle_msg(Octet &msg_type, int &transaction_id,
		Short status_code, Lease_table_item &item){

	static transID_table transIDs;
	std::map<int, CodeAndTime>::iterator it;
	std::vector<uint64_t> code_vector;
	uint64_t cur_time;
	cur_time = (uint64_t)time(NULL);
	uint64_t clean_time =0;

	code_vector.clear();
	code_vector.push_back(msg_type);
	code_vector.push_back(cur_time);

	switch(msg_type){
	case DHCPv6::SOLICIT:
		//std::cout << "DHCPv6 SOLICIT" << std::endl;
		usocket->send_report( item, cur_time, "REPORT", "client looked for server (DHCPv6 solicit)");
		break;
	case DHCPv6::ADVERTISE:
		//no report send on advertise
		//std::cout << "DHCPv6 ADVERTISE" << std::endl;
		break;
	case DHCPv6::REQUEST:
		//std::cout << "DHCPv6 REQUEST" << std::endl;
		transIDs[transaction_id] = code_vector;
		usocket->send_report( item, cur_time, "REPORT", "client requested IP address (DHCPv6 request)");
		break;
	case DHCPv6::CONFIRM:
		//std::cout << "DHCPv6 CONFIRM" << std::endl;
		transIDs[transaction_id] = code_vector;
		usocket->send_report(item, cur_time, "REPORT", "client checked if his IP address was still valid (DHCPv6 confirm)");
		break;
	case DHCPv6::RENEW:
		//std::cout << "DHCPv6 RENEW" << std::endl;
		transIDs[transaction_id] = code_vector;
		//ignore for now
		break;
	case DHCPv6::REBIND:
		//std::cout << "DHCPv6 REBIND" << std::endl;
		transIDs[transaction_id] = code_vector;
		//ingore for now
		break;
	case DHCPv6::REPLY:
		//std::cout << "DHCPv6 REPLY" << std::endl;
		if( status_code >= 0){
			it = transIDs.find(transaction_id);
			if(it != transIDs.end()){
				if((*it).second[0] != DHCPv6::DECLINE or (*it).second[0] != DHCPv6::RELEASE){
					aip_table->update(item,usocket);
				}
			}
		}
		break;
	case DHCPv6::RELEASE:
		//std::cout << "DHCPv6 RELEASE" << std::endl;
		transIDs[transaction_id] = code_vector;
		aip_table->remove(item,usocket);
		break;
	case DHCPv6::DECLINE:
		//std::cout << "DHCPv6 DECLINE" << std::endl;
		transIDs[transaction_id] = code_vector;
		aip_table->remove(item,usocket);
		break;
	case DHCPv6::RECONFIGURE:
		//std::cout << "DHCPv6 RECONFIGURE" << std::endl;
		break;
	case DHCPv6::INFORMATION_REQUEST:
		//std::cout << "DHCPv6 INFORMATION_REQUEST" << std::endl;
		break;
	case DHCPv6::RELAY_FORW:
		// it should never get here
		//std::cout << "DHCPv6 RELAY_FORW" << std::endl;
		break;
	case DHCPv6::RELAY_REPL:
		// it should never get here
		//std::cout << "DHCPv6 RELAY_REPL" << std::endl;
		break;
	default:
		//std::cout << "DHCPv6 OTHER MSG" << std::endl;
		break;
	}

	//sometimes clean transaction id table... (remove record older than one min)
	if (cur_time > clean_time){
		clean_time = cur_time + 60;
		for ( it= transIDs.begin() ; it != transIDs.end(); it++ ){
			if(((*it).second)[1] + 60  < cur_time){
				transIDs.erase(it);
				it= transIDs.begin();
			}
		}
	}

}


void process_DHCPv6(UDP *udp){
	DHCPv6 *dhcpv6_pkt_ptr = new DHCPv6;
	DHCPv6 *dhcpv6_pkt = dhcpv6_pkt_ptr;
	Buffer mbuf;
	mbuf.buf(udp->get_data(), udp->length());
	dhcpv6_pkt->decode(mbuf, mbuf.left());
	DHCPv6_Option *opt = NULL;
	DHCPv6_Option *opt1 = NULL;
	int valid_lifetime = 0;
	int i=0;
	int j=0;
	IPv6Address *ip_addr = NULL;
	IPv6Address_prefix *ip_prefix;
	Short status_code = -1;
	Octet msg_type = 0;
	int transaction_id = -1;
	Lease_table_item item;

	DUID *duid = NULL;

	msg_type = dhcpv6_pkt->msg_type();

	//check for relay msg
	if(msg_type == DHCPv6::RELAY_FORW  or msg_type == DHCPv6::RELAY_REPL){
		while((opt = dhcpv6_pkt->option(i)) != NULL){
			if(opt->code() == DHCPv6_Option::OPTION_RELAY_MSG){
				dhcpv6_pkt = ((DHCPv6_Relay_msg_Option *) opt)->relayed_DHCPv6();
				msg_type = dhcpv6_pkt->msg_type();
				break;
			}
			i++;
		}
	}
	i=0;
	transaction_id = dhcpv6_pkt->transaction_id();
	//check for duid
	while((opt = dhcpv6_pkt->option(i)) != NULL){
		if(opt->code() == DHCPv6_Option::OPTION_CLIENTID){ //get mac!
			duid = ((DHCPv6_CLIENTID_Option *) opt)->duid();
			/*if(duid){
				if(duid->code() == DUID::DUID_LL){
					duid_addr = ((DUID_LL_Format *)duid)->address();
				}else if(duid->code() == DUID::DUID_LLT){
					duid_addr = ((DUID_LLT_Format *)duid)->address();
				} else {
					duid_addr = NULL;
				}
			}*/
		}
		i++;
	}
	i=0;
	while((opt = dhcpv6_pkt->option(i)) != NULL){
		if(opt->code() == DHCPv6_Option::OPTION_IA_NA){  //get IPv6
			//get status code
			while((opt1 = opt->option(j)) != NULL){
				if(opt1->code() == DHCPv6_Option::OPTION_STATUS_CODE){  //get status code
					status_code = ((DHCPv6_Status_Code_Option *) opt1)->status_code();
				}
				j++;
			}
			j = 0;
			//get all ip addresses
			while((opt1 = opt->option(j)) != NULL){
				if(opt1->code() == DHCPv6_Option::OPTION_IAADDR){
					ip_addr = ((DHCPv6_IAADDR_Option *)opt1)->address();
					valid_lifetime = ((DHCPv6_IAADDR_Option *) opt1)->valid_lifetime();
					if(valid_lifetime == 0){ //unlimited lifetime
						valid_lifetime = 300000000; // set it to far future (around 10 years ahead)
					}
					item.clean();
					item.timestamp(time(NULL) + valid_lifetime);
					if(duid != NULL){
						item.append("DUID",duid->to_string());
					}
					if(ip_addr != NULL and msg_type != DHCPv6::REQUEST and
							msg_type != DHCPv6::CONFIRM and msg_type != DHCPv6::ADVERTISE){
						item.append("IPv6",ip_addr->to_string());
					}
					if(ip_addr != NULL or duid != NULL){
						handle_msg(msg_type, transaction_id, status_code,item);
					}
				}
				j++;
			}
		}else if(opt->code() == DHCPv6_Option::OPTION_IA_PD){ //prefix delegation
			//get status code
			while((opt1 = opt->option(j)) != NULL){
				if(opt1->code() == DHCPv6_Option::OPTION_STATUS_CODE){  //get status code
					status_code = ((DHCPv6_Status_Code_Option *) opt1)->status_code();
				}
				j++;
			}
			j = 0;
			//get all ip preffixes
			while((opt1 = opt->option(j)) != NULL){
				if(opt1->code() == DHCPv6_Option::OPTION_IAPREFIX){
					ip_prefix = ((DHCPv6_IAPREFIX_Option *)opt1)->prefix();
					valid_lifetime = ((DHCPv6_IAADDR_Option *) opt1)->valid_lifetime();
					if(valid_lifetime == 0){ //unlimited lifetime
						valid_lifetime = 300000000; // set it to far future (around 10 years ahead)
					}
					item.clean();
					item.timestamp(time(NULL) + valid_lifetime);
					if(duid != NULL){
						item.append("DUID",duid->to_string());
					}
					if(ip_prefix != NULL and msg_type != DHCPv6::REQUEST and
							msg_type != DHCPv6::CONFIRM and msg_type != DHCPv6::ADVERTISE){
						item.append("IPv6",ip_prefix->to_string());
					}
					if(ip_prefix != NULL or duid != NULL){
						handle_msg(msg_type, transaction_id, status_code,item);
					}
				}
				j++;
			}
		}
		i++;
	}
	delete dhcpv6_pkt_ptr;
}


int main(int argc, char **argv)
{
	Ethernet *eth_ptr = NULL;
	Ethernet *eth = NULL;
	Buffer pkt;
	string dev;
	char errbuf[PCAP_ERRBUF_SIZE];
	pcap_t *handle;
	struct pcap_pkthdr *header;
	const u_char *tmp;
	int stop=0;
	std::string pcap_file;
	std::string interface;
	bool connect_to_iri = true;
	struct bpf_program fp;
	int c,status;
	char filter_exp[] = "udp port 546 or udp port 547";
	char *ptr_filter;
	pcap_file = "";
	interface = "any";
	Ethernet::add_proto(0x0800, IPv4_factory);
	Ethernet::add_proto(0x86dd, IPv6_factory);
	IPv4::add_proto(ProtoTCP, TCP_factory);
	IPv6::add_proto(ProtoTCP, TCP_factory);
	IPv4::add_proto(ProtoUDP, UDP_factory);
	IPv6::add_proto(ProtoUDP, UDP_factory);
	IPv6::add_proto(ProtoICMPv6, ICMPv6_factory);

	ptr_filter = filter_exp;

	while ((c = getopt (argc, argv, "f:p:i:hs")) != -1){
		switch (c)
		{
		case 'f':
			ptr_filter = optarg;
			break;
		case 'i':
			interface = optarg;
			break;
		case 'p':
			pcap_file = optarg;
			break;
		case 's':
			connect_to_iri = false;
			break;
		case 'h':
			print_help();
			return 0;
			break;
		default:
			break;
		}
	}


	usocket= new unix_socket("DHCPv6",connect_to_iri);
	aip_table = new Lease_Table(usocket);

	if(pcap_file != ""){
		handle = pcap_open_offline(pcap_file.c_str(), errbuf);
		if (handle == NULL) {
			fprintf(stderr, "Couldn't open file: %s\n", errbuf);
			return(1);
		}
	} else {
		handle = pcap_open_live(interface.c_str(), BUFSIZ, 1, 0, errbuf);
		if (handle == NULL) {
			fprintf(stderr, "Couldn't open device: %s\n", errbuf);
			return(1);
		}
	}

	if (pcap_compile(handle, &fp, ptr_filter, 0, 0) == -1) {
		 fprintf(stderr, "Couldn't parse filter %s: %s\n", ptr_filter, pcap_geterr(handle));
		 return(1);
	}
	if (pcap_setfilter(handle, &fp) == -1) {
		fprintf(stderr, "Couldn't install filter %s: %s\n", ptr_filter, pcap_geterr(handle));
		return(1);
	}
	while(!stop){
		//read packet
		status = pcap_next_ex(handle, &header, &tmp);
		if(status == -2){
			stop = 1;
			continue;
		}else if(status == -1){
			pcap_perror(handle,NULL);
		}

		//store packet in buffer 
		pkt.buf((const Octet *) tmp, header->len);

		if(eth_ptr != NULL){
			delete eth_ptr;
		}
		eth_ptr = new Ethernet();
		eth = eth_ptr;

		//parse buffer data and get Eth layer
		if(!eth->decode(pkt)) {
			//cout << "cant decode packet! (skiped)\n";
			continue;
		}
		if (!eth->payload()){
			//cout  << "ethernet with no payload (skiped)\n" ;
			continue;
		}
		UDP *udp=NULL;
		if (dynamic_cast<IPv6 *>(eth->payload()) != NULL){
			IPv6 *ipv6 = dynamic_cast<IPv6 *>(eth->payload());
			if (ipv6->is_fragment()){
				//cout <<"IPv6 fragment (skiped)\n";
				continue;
			}

			if (dynamic_cast<UDP *>(ipv6->payload()) != NULL){
				udp = dynamic_cast<UDP *>(ipv6->payload());

			}
		}
		if (dynamic_cast<IPv4 *>(eth->payload()) != NULL){
			IPv4 *ipv4 = dynamic_cast<IPv4 *>(eth->payload());
			if (ipv4->is_fragment()){
				//cout << "IPv4 fragment (skiped)\n";
				continue;
			}
			if (dynamic_cast<UDP *>(ipv4->payload()) != NULL){
				udp = dynamic_cast<UDP *>(ipv4->payload());
			}
		}
		if(udp != NULL){
			if(udp->dst_port()==547 or udp->dst_port() == 546){ //ports for DHCPv6
				process_DHCPv6(udp);
			}
		}
	}
	pcap_close(handle);
	if(eth_ptr != NULL){
		delete eth_ptr;
	}
	delete usocket;
	delete aip_table;
}
