/******************************************************************************
 * Copyright (c) 2013 IBM Corporation
 * All rights reserved.
 * This program and the accompanying materials
 * are made available under the terms of the BSD License
 * which accompanies this distribution, and is available at
 * http://www.opensource.org/licenses/bsd-license.php
 *
 * Contributors:
 *     IBM Corporation - initial implementation
 *****************************************************************************/

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <time.h>
#include <ctype.h>
#include <sys/socket.h>
#include "ethernet.h"
#include "ipv6.h"
#include "icmpv6.h"
#include "ndp.h"
#include "udp.h"

#undef IPV6_DEBUG
//#define IPV6_DEBUG
#ifdef IPV6_DEBUG
#define dprintf(_x ...) do { printf(_x); } while (0)
#else
#define dprintf(_x ...)
#endif

/****************************** PROTOTYPES *******************************/
int8_t ip6addr_add (struct ip6addr_list_entry *new_address);
static void ipv6_init(int fd);
static int ip6_is_multicast (ip6_addr_t * ip);

/****************************** LOCAL VARIABLES **************************/

/* List of Ipv6 Addresses */
static struct ip6addr_list_entry *first_ip6;
static struct ip6addr_list_entry *last_ip6;

/* Own IPv6 address */
static struct ip6addr_list_entry *own_ip6;

/* All nodes link-local address */
struct ip6addr_list_entry all_nodes_ll;

/* Null IPv6 address */
static ip6_addr_t null_ip6;

/* helper variables */
static uint8_t null_mac[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

struct ip6_config ip6_state;

/****************************** IMPLEMENTATION ***************************/

/**
 * IPv6: Set the own IPv6 address.
 *
 * @param  fd            Socket descriptor
 * @param  _own_ip       client IPv6 address (e.g. ::1)
 */
void set_ipv6_address(int fd, ip6_addr_t *_own_ip6)
{
	struct ip6addr_list_entry *ile;

	ile = malloc(sizeof(struct ip6addr_list_entry));
	if (!ile)
		return;
	memset(ile, 0, sizeof(struct ip6addr_list_entry));
	own_ip6 = ile;

	/* If no address was passed as a parameter generate a link-local
	 * address from our MAC address.*/
	if (_own_ip6 == NULL)
		ip6_create_ll_address(get_mac_address(), &own_ip6->addr);
	else
		memcpy (&(own_ip6->addr.addr), _own_ip6, 16);

	/* Add to our list of IPv6 addresses */
	ip6addr_add (own_ip6);

	ipv6_init(fd);

	/*
	 * Check whether we've got a non-link-local address during
	 * ipv6_init() and use that as preferred address if possible
	 */
	if (_own_ip6 == NULL) {
		for (ile = first_ip6; ile != NULL ; ile = ile->next) {
			if (!ip6_is_multicast(&ile->addr) &&
			    !ip6_is_linklocal(&ile->addr)) {
				own_ip6 = ile;
				break;
			}
		}
	}
}

/**
 * IPv6: Get pointer to own IPv6 address.
 *
 * @return pointer to client IPv6 address (e.g. ::1)
 */
ip6_addr_t *get_ipv6_address(void)
{
	return (ip6_addr_t *) &(own_ip6->addr);
}

/**
 * IPv6: Search for IPv6 address in list
 *
 * @return 0 - IPv6 address is not in list
 *         1 - IPv6 address is in list
 */
static int8_t find_ip6addr(ip6_addr_t *ip)
{
	struct ip6addr_list_entry *n = NULL;

	if (ip == NULL)
	    return 0;

	for (n = first_ip6; n != NULL ; n=n->next)
		if (ip6_cmp (&(n->addr), ip))
			return 1; /* IPv6 address is in  our list*/

	return 0; /* not one of our IPv6 addresses*/
}

/**
 * NET: Handles IPv6-packets
 *
 * @param  fd         - Socket descriptor
 * @param  ip6_packet - Pointer to IPv6 header
 * @param  packetsize - Size of Ipv6 packet
 * @return ERROR      - -1 if packet is too small or unknown protocol
 *			return value of handle_udp
 *
 * @see handle_udp
 * @see ip6hdr
 */
int8_t handle_ipv6(int fd, uint8_t * ip6_packet, uint32_t packetsize)
{

	struct ip6hdr *ip6 = NULL;
	ip6 = (struct ip6hdr *) ip6_packet;

	/* Only handle packets which are for us */
	if (! find_ip6addr(&(ip6->dst)))
		return -1;

	if (packetsize < sizeof(struct ip6hdr))
		return -1; // packet is too small

	switch (ip6->nh) {
		case IPTYPE_UDP:
			return handle_udp (fd, ip6_packet + sizeof (struct ip6hdr),
					ip6->pl);
		case IPTYPE_ICMPV6:
			return handle_icmpv6 (fd, (struct ethhdr *) ip6_packet - sizeof(struct ethhdr),
					      ip6_packet);
	}

	return -1; // unknown protocol
}

 /**
 * NET: Creates IPv6-packet. Places IPv6-header in a packet and fills it
 *      with corresponding information.
 *      <p>
 *      Use this function with similar functions for other network layers
 *      (fill_ethhdr, fill_udphdr, fill_dnshdr, fill_btphdr).
 *
 * @param  packet      Points to the place where IPv6-header must be placed.
 * @param  packetsize  Size of payload (i.e. excluding ethhdr and ip6hdr)
 * @param  ip_proto    Type of the next level protocol (e.g. UDP).
 * @param  ip6_src     Sender IPv6 address
 * @param  ip6_dst     Receiver IPv6 address
 * @see                ip6hdr
 * @see                fill_iphdr
 * @see                fill_ethhdr
 * @see                fill_udphdr
 * @see                fill_dnshdr
 * @see                fill_btphdr
 */
void fill_ip6hdr(uint8_t * packet, uint16_t packetsize,
		 uint8_t ip_proto, ip6_addr_t *ip6_src, ip6_addr_t *ip6_dst)
{
	struct ip6hdr * ip6h = (struct ip6hdr *) packet;

	ip6h->ver_tc_fl = 6 << 28;	// set version to 6
	ip6h->pl = packetsize;		// IPv6 payload size
	ip6h->nh = ip_proto;
	ip6h->hl = 255;
	memcpy (&(ip6h->src), ip6_src, IPV6_ADDR_LENGTH);
	memcpy (&(ip6h->dst), ip6_dst, IPV6_ADDR_LENGTH);
}

/**
 * NET: For a given MAC calculates EUI64-Identifier.
 *      See RFC 4291 "IP Version 6 Addressing Architecture"
 *
 */
uint64_t mac2eui64(const uint8_t *mac)
{
	uint8_t eui64id[8];
	uint64_t retid;

	memcpy (eui64id, mac, 3);
	memcpy (eui64id + 5, mac + 3, 3);
	eui64id[3] = 0xff;
	eui64id[4] = 0xfe;

	memcpy(&retid, eui64id, 8);
	return retid;
}

/**
 * NET: create link-local IPv6 address
 *
 * @param  own_mac    MAC of NIC
 * @param ll_addr     pointer to link-local address which should be created
 */
void ip6_create_ll_address(const uint8_t *own_mac, ip6_addr_t *ll_addr)
{
	ll_addr->part.prefix = IPV6_LL_PREFIX;
	ll_addr->part.interface_id = mac2eui64((uint8_t *) own_mac);
}

/*
 * NET: check if we already have an address with the same prefix.
 * @param  struct ip6_addr_list_entry *ip6
 * @return true or false
 */
int8_t unknown_prefix(ip6_addr_t *ip)
{
	struct ip6addr_list_entry *node;

	for( node = first_ip6; node != NULL; node=node->next )
		if( node->addr.part.prefix == ip->part.prefix )
			return 0; /* address is one of ours */

	return 1; /* prefix not yet in our list */
}

/*
 * NET: Create empty element for prefix list and return a pointer to it;
 * @return NULL - malloc failed
 *	   ! NULL - pointer to new prefix_info
 */
struct prefix_info *ip6_create_prefix_info()
{
	struct prefix_info *prfx_info;

	prfx_info = malloc (sizeof(struct prefix_info));
	if (!prfx_info)
		return NULL;
	memset(prfx_info, 0, sizeof(struct prefix_info));

	return prfx_info;
}

/*
 * NET: create a new IPv6 address with a given network prefix
 *	and add it to our IPv6 address list
 *
 * @param  ip6_addr prefix (as received in RA)
 * @return NULL - pointer to new ip6addr_list entry
 */
void *ip6_prefix2addr(ip6_addr_t prefix)
{
	struct ip6addr_list_entry *new_address;
	uint64_t interface_id;

	new_address = malloc (sizeof(struct ip6addr_list_entry));
	if( !new_address )
		return NULL;
	memset(new_address, 0, sizeof(struct ip6addr_list_entry));

	/* fill new addr struct */
	/* extract prefix from Router Advertisement */
	memcpy (&(new_address->addr.part.prefix), &prefix, 8 );

	/* interface id is generated from MAC address */
	interface_id = mac2eui64 (get_mac_address());
	memcpy (&(new_address->addr.part.interface_id), &interface_id, 8);

	return new_address;
}

/**
 * NET: add new IPv6 adress to list
 *
 * @param   ip6_addr *new_address
 * @return  0 - passed pointer = NULL;
 *	    1 - ok
 */
int8_t ip6addr_add(struct ip6addr_list_entry *new_address)
{
	struct ip6addr_list_entry *solicited_node;


	if (new_address == NULL)
		return 0;

	 /* Don't add the same address twice */
	if (find_ip6addr (&(new_address->addr)))
		return 0;

	/* If address is a unicast address, we also have to process packets
	 * for its solicited-node multicast address.
	 * See RFC 2373 - IP Version 6 Adressing Architecture */
	if (! ip6_is_multicast(&(new_address->addr))) {
		solicited_node = malloc(sizeof(struct ip6addr_list_entry));
		if (! solicited_node)
			return 0;
		memset(solicited_node, 0, sizeof(struct ip6addr_list_entry));

		solicited_node->addr.part.prefix       = IPV6_SOLIC_NODE_PREFIX;
		solicited_node->addr.part.interface_id = IPV6_SOLIC_NODE_IFACE_ID;
		solicited_node->addr.addr[13] = new_address->addr.addr[13];
		solicited_node->addr.addr[14] = new_address->addr.addr[14];
		solicited_node->addr.addr[15] = new_address->addr.addr[15];
		ip6addr_add (solicited_node);
	}

	if (first_ip6 == NULL)
		first_ip6 = new_address;
	else
		last_ip6->next = new_address;
	last_ip6 = new_address;
	last_ip6->next = NULL;

	return 1; /* no error */
}

/**
 * NET: Initialize IPv6
 *
 * @param  fd            socket fd
 */
static void ipv6_init(int fd)
{
	int i = 0;

	send_ip = &send_ipv6;

	/* Address configuration parameters */
	ip6_state.managed_mode = 0;

	/* Null IPv6 address */
	null_ip6.part.prefix       = 0;
	null_ip6.part.interface_id = 0;

	/* Multicast addresses */
	all_nodes_ll.addr.part.prefix         = 0xff02000000000000;
	all_nodes_ll.addr.part.interface_id   = 1;
	ip6addr_add(&all_nodes_ll);

	ndp_init();

	send_router_solicitation (fd);
	for(i=0; i < 4 && !is_ra_received(); i++) {
		set_timer(TICKS_SEC);
		do {
			receive_ether(fd);
			if (is_ra_received())
				break;
		} while (get_timer() > 0);
	}
}

/**
 * NET: compare IPv6 adresses
 *
 * @param  ip6_addr ip_1
 * @param  ip6_addr ip_2
 */
int8_t ip6_cmp(ip6_addr_t *ip_1, ip6_addr_t *ip_2)
{
	return ((int8_t) !memcmp( &(ip_1->addr[0]), &(ip_2->addr[0]),
		IPV6_ADDR_LENGTH ));
}

/**
 * NET: Calculate checksum over IPv6 header and upper-layer protocol
 *      (e.g. UDP or ICMPv6)
 *
 * @param  *ip    - pointer to IPv6 address
 * @return true or false
 */
int ip6_is_multicast(ip6_addr_t * ip)
{
	return ip->addr[0] == 0xFF;
}

/**
 * NET: Generate multicast MAC address from IPv6 address
 *      (e.g. UDP or ICMPv6)
 *
 * @param  *ip    - pointer to IPv6 address
 * @param  *mc_mac  pointer to an array with 6 bytes (for the MAC address)
 * @return pointer to Multicast MAC address
 */
static uint8_t *ip6_to_multicast_mac(ip6_addr_t * ip, uint8_t *mc_mac)
{
	mc_mac[0] = 0x33;
	mc_mac[1] = 0x33;
	memcpy (mc_mac+2, (uint8_t *) &(ip->addr)+12, 4);

	return mc_mac;
}

/**
 * Check whether an IPv6 address is on the same network as we are
 */
static bool is_ip6addr_in_my_net(ip6_addr_t *ip)
{
	struct ip6addr_list_entry *n = NULL;

	for (n = first_ip6; n != NULL; n = n->next) {
		if (n->addr.part.prefix == ip->part.prefix)
			return true;  /* IPv6 address is in our neighborhood */
	}

	return false;    /* not in our neighborhood */
}

/**
 * NET: calculate checksum over IPv6 header and upper-layer protocol
 *      (e.g. UDP or ICMPv6)
 *
 * @param  struct ip6hdr *ip6h    - pointer to IPv6 header
 * @param  unsigned short *packet - pointer to header of upper-layer
 *				    protocol
 * @param  int words              - number of words (as in 2 bytes)
 *				    starting from *packet
 * @return checksum
 */
static unsigned short ip6_checksum(struct ip6hdr *ip6h, unsigned short *packet,
				   int words)
{
	int i=0;
	unsigned long checksum;
	struct ip6hdr pseudo_ip6h;
	unsigned short *pip6h;

	memcpy (&pseudo_ip6h, ip6h, sizeof(struct ip6hdr));
	pseudo_ip6h.hl	      = ip6h->nh;
	pseudo_ip6h.ver_tc_fl = 0;
	pseudo_ip6h.nh	      = 0;
	pip6h = (unsigned short *) &pseudo_ip6h;

	for (checksum = 0; words > 0; words--) {
		checksum += *packet++;
		i++;
	}

	for (i = 0; i < 20; i++) {
		checksum += *pip6h++;
	}

	checksum = (checksum >> 16) + (checksum & 0xffff);
	checksum += (checksum >> 16);

	return ~checksum;
}

/**
 * NET: Handles IPv6-packets
 *
 * @param fd          socket fd
 * @param ip6_packet  Pointer to IPv6 header in packet
 * @param packetsize  Size of IPv6 packet
 * @return -1 : Some error occured
 *          0 : packet stored (NDP request sent - packet will be sent if
 *                             NDP response is received)
 *         >0 : packet sent   (number of transmitted bytes is returned)
 *
 * @see receive_ether
 * @see ip6hdr
 */
int send_ipv6(int fd, void* buffer, int len)
{
	struct ip6hdr *ip6h;
	struct udphdr *udph;
	struct icmp6hdr *icmp6h;
	ip6_addr_t ip_dst;
	uint8_t *mac_addr, mc_mac[6];
	static uint8_t ethframe[ETH_MTU_SIZE];

	mac_addr = null_mac;

	ip6h    = (struct ip6hdr *) buffer;
	udph   = (struct udphdr *)   ((uint8_t *) ip6h + sizeof (struct ip6hdr));
	icmp6h = (struct icmp6hdr *) ((uint8_t *) ip6h + sizeof (struct ip6hdr));

	memcpy(&ip_dst, &ip6h->dst, 16);

	if(len + sizeof(struct ethhdr) > ETH_MTU_SIZE)
		return -1;

	if ( ip6_cmp (&ip6h->src, &null_ip6))
		memcpy (&(ip6h->src), get_ipv6_address(), IPV6_ADDR_LENGTH);

	if (ip6h->nh == 17) {//UDP
		udph->uh_sum = ip6_checksum (ip6h, (unsigned short *) udph ,
					     ip6h->pl >> 1);
		/* As per RFC 768, if the computed  checksum  is zero,
		 * it is transmitted as all ones (the equivalent in
		 * one's complement arithmetic).
		 */
		if (udph->uh_sum == 0)
			udph->uh_sum = ~udph->uh_sum;
	}
	else if (ip6h->nh == 0x3a) //ICMPv6
		icmp6h->checksum = ip6_checksum (ip6h,
						 (unsigned short *) icmp6h,
						 ip6h->pl >> 1);

	if (ip6_is_multicast (&ip_dst)) {
		/* If multicast, then create a proper multicast mac address */
		mac_addr = ip6_to_multicast_mac (&ip_dst, mc_mac);
	} else if (!is_ip6addr_in_my_net(&ip_dst)) {
		/* If server is not in same subnet, user MAC of the router */
		struct router *gw;
		gw = ipv6_get_default_router(&ip6h->src);
		mac_addr = gw ? gw->mac : null_mac;
	} else {
		/* Normal unicast, so use neighbor cache to look up MAC */
		struct neighbor *n = find_neighbor (&ip_dst);
		if (n) {				/* Already cached ? */
			if (memcmp(n->mac, null_mac, ETH_ALEN) != 0)
				mac_addr = n->mac;		/* found it */
		} else {
			mac_addr = null_mac;
			n = malloc(sizeof(struct neighbor));
			if (!n)
				return -1;
			memset(n, 0, sizeof(struct neighbor));
			memcpy(&(n->ip.addr[0]), &ip_dst, 16);
			n->status = NB_PROBE;
			n->times_asked += 1;
			neighbor_add(n);
		}

		if (! memcmp (mac_addr, &null_mac, 6)) {
			if (n->eth_len == 0) {
				send_neighbour_solicitation (fd, &ip_dst);

				// Store the packet until we know the MAC address
				fill_ethhdr (n->eth_frame,
					     htons(ETHERTYPE_IPv6),
					     get_mac_address(),
					     mac_addr);
				memcpy (&(n->eth_frame[sizeof(struct ethhdr)]),
				       buffer, len);
				n->eth_len = len;
				set_timer(TICKS_SEC);
				do {
					receive_ether(fd);
					if (n->status == NB_REACHABLE)
						return len;
				} while (get_timer() > 0);
				return 0;
			}
		}
	}

	if (mac_addr == null_mac)
		return -1;

	fill_ethhdr(ethframe, htons(ETHERTYPE_IPv6), get_mac_address(),
	            mac_addr);
	memcpy(&ethframe[sizeof(struct ethhdr)], buffer, len);

	return send_ether(fd, ethframe, len + sizeof(struct ethhdr));
}

static int check_colons(const char *str)
{
	char *pch, *prv;
	int col = 0;
	int dcol = 0;

	dprintf("str : %s\n",str);
	pch = strchr(str, ':');
	while(pch != NULL){
		prv = pch;
		pch = strchr(pch+1, ':');
		if((pch-prv) != 1) {
			col++;
		} else {
			col--; /* Its part of double colon */
			dcol++;
		}
	}

	dprintf("The number of  col : %d \n",col);
	dprintf("The number of dcol : %d \n",dcol);

	if((dcol > 1) ||                      /* Cannot have 2 "::" */
	   ((dcol == 1) && (col > 5)) ||      /* Too many ':'s */
	   ((dcol == 0) && (col != 7)) ) {    /* Too few ':'s */
		dprintf(" exiting for check_colons \n");
		return 0;
	}

	return (col+dcol);
}

static int ipv6str_to_bytes(const char *str, char *ip)
{
	char block[5];
	int res;
	char *pos;
	uint32_t cnt = 0, len;

	dprintf("str : %s \n",str);

	while (*str != 0) {
		if (cnt > 15 || !isxdigit(*str)){
			return 0;
		}
		if ((pos = strchr(str, ':')) != NULL) {
			len = (int16_t) (pos - str);
			dprintf("\t len  is : %d \n",len);
			if (len > 4)
				return 0;
			strncpy(block, str, len);
			block[len] = 0;
			dprintf("\t str   : %s \n",str);
			dprintf("\t block : %s \n",block);
			str += len;
		} else {
			strncpy(block, str, 4);
			block[4] = 0;
			dprintf("\t str   : %s \n",str);
			dprintf("\t block : %s \n",block);
			str += strlen(block);
		}
		res = strtol(block, NULL, 16);
		dprintf("\t res : %x \n",res);
		if ((res > 0xFFFF) || (res < 0))
			return 0;
		ip[cnt++] = (res & 0xFF00) >> 8;
		ip[cnt++] = (res & 0x00FF);
		if (*str == ':'){
			str++;
		}
	}

	dprintf("cnt : %d\n",cnt);
	return cnt;
}

int str_to_ipv6(const char *str, uint8_t *ip)
{
	int i, k;
	uint16_t len;
	char *ptr;
	char tmp[30], buf[16];

	memset(ip,0,16);

	if(!check_colons(str))
		return 0;

	if ((ptr = strstr(str, "::")) != NULL) {
		/* Handle the ::1 IPv6 loopback */
		if(!strcmp(str,"::1")) {
			ip[15] = 1;
			return 16;
		}
		len = (ptr-str);
		dprintf(" len : %d \n",len);
		if (len >= sizeof(tmp))
			return 0;
		strncpy(tmp, str, len);
		tmp[len] = 0;
		ptr += 2;

		i = ipv6str_to_bytes(ptr, buf);
		if(i == 0)
		return i;

		#if defined(ARGS_DEBUG)
		int j;
		dprintf("=========== bottom part i : %d \n",i);
		for(j=0; j<i; j++)
			dprintf("%02x \t",buf[j]);
		#endif

		/* Copy the bottom part i.e bytes following "::" */
		memcpy(ip+(16-i), buf, i);

		k = ipv6str_to_bytes(tmp, buf);
		if(k == 0)
			return k;

		#if defined(ARGS_DEBUG)
		dprintf("=========== top part k : %d \n",k);
		for(j=0; j<k; j++)
			printf("%02x \t",buf[j]);
		#endif

		/* Copy the top part i.e bytes before "::"  */
		memcpy(ip, buf, k);
		#if defined(ARGS_DEBUG)
		dprintf("\n");
		for(j=0; j<16; j++)
			dprintf("%02x \t",ip[j]);
		#endif

	} else {
		i = ipv6str_to_bytes(str, (char *)ip);
	}
	return i;
}

void ipv6_to_str(const uint8_t *ip, char *str)
{
	int i, len;
	uint8_t byte_even, byte_odd;
	char *consec_zero, *strptr;

	*str = 0;
	for (i = 0; i < 16; i+=2) {
		byte_even = ip[i];
		byte_odd = ip[i+1];
		if (byte_even)
			sprintf(str, "%s%x%02x", str, byte_even, byte_odd);
		else if (byte_odd)
			sprintf(str, "%s%x", str, byte_odd);
		else
			strcat(str, "0");
		if (i != 14)
			strcat(str, ":");
	}
	strptr = str;
	do {
		consec_zero = strstr(strptr, "0:0:");
		if (consec_zero) {
			len = consec_zero - strptr;
			if (!len)
				break;
			else if (strptr[len-1] == ':')
				break;
			else
				strptr = consec_zero + 2;
		}
	} while (consec_zero);
	if (consec_zero) {
		len = consec_zero - str;
		str[len] = 0;
		if (len)
			strcat(str, ":");
		else
			strcat(str, "::");
		strptr = consec_zero + 4;
		while (*strptr) {
			if (!strncmp(strptr, "0:", 2))
				strptr += 2;
			else
				break;
		}
		strcat(str, strptr);
		if (!strcmp(str, "::0"))
			strcpy(str, "::");
	}
}
