/*
 * protocoltranslator64{cc,hh} -- element that translates an IPv6 packet to
 * an IPv4 packet.
 * When translating an IPv6 packet to IPv4 packet, assuming the addresses has
 * already been IPv4-mapped IPv6 addresses (e.g.,::ffff:18.26.4.17), the IPv4
 * addresses will be the lowest 32 bits of IPv6 addresses.
 *
 *
 * Peilei Fan
 *
 * Copyright (c) 1999-2001 Massachusetts Institute of Technology.
 *
 * This software is being provided by the copyright holders under the GNU
 * General Public License, either version 2 or, at your discretion, any later
 * version. For more information, see the `COPYRIGHT' file in the source
 * distribution.
 */

#include <click/config.h>
#include "protocoltranslator64.hh"
#include <click/confparse.hh>
#include <click/error.hh>
#include <clicknet/ip.h>
#include <clicknet/ip6.h>
#include <clicknet/icmp.h>
#include <clicknet/icmp6.h>
#include <clicknet/tcp.h>
#include <clicknet/udp.h>
CLICK_DECLS

ProtocolTranslator64::ProtocolTranslator64()
{
}


ProtocolTranslator64::~ProtocolTranslator64()
{
}


int
ProtocolTranslator64::configure(Vector<String> &conf, ErrorHandler *errh)
{
   int before = errh->nerrors();
   if (!(conf.size()==0))
    {
      errh->error("there should be no arguments");
    }
  return (before ==errh->nerrors() ? 0: -1);

}


//make the ipv6->ipv4 translation of the packet according to SIIT (RFC 2765)
Packet *
ProtocolTranslator64::make_translate64(IPAddress src,
				     IPAddress dst,
				     click_ip6 * ip6,
				     unsigned char *a)
{
  click_ip *ip;
  click_tcp *tcp;
  click_udp *udp;
  WritablePacket *q = Packet::make(sizeof(*ip) + ntohs(ip6->ip6_plen));

  if (q==0) {
    click_chatter("can not make packet!");
    assert(0);
  }

  memset(q->data(), '\0', q->length());
  ip = (click_ip *)q->data();
  tcp = (click_tcp *)(ip+1);
  udp = (click_udp *)(ip+1);

  //set ipv4 header
  ip->ip_v = 4;
  ip->ip_hl =5;
  ip->ip_tos =0;
  ip->ip_len = htons(sizeof(*ip) + ntohs(ip6->ip6_plen));

  ip->ip_id = htons(0);
  //need to change
  //ip->ip_id[0]=ip6->ip_flow[1];
  //ip->ip_id[1]=ip6->ip_flow[2];

  //set Don't Fragment flag to true, all other flags to false,
  //set fragement offset: 0
  ip->ip_off = htons(IP_DF);
  //need to deal with fragmentation later

  //we do not change the ttl since the packet has to go through v4 routing table
  ip->ip_ttl = ip6->ip6_hlim;


  //set the src and dst address
  ip->ip_src = src.in_addr();
  ip->ip_dst = dst.in_addr();

  //copy the actual payload of packet
  memcpy((unsigned char *)tcp, a, ntohs(ip6->ip6_plen));
  //set the tcp header checksum
  //The tcp checksum for ipv4 packet is include the tcp packet, and the 96 bits
  //TCP pseudoheader, which consists of Source Address, Destination Address,
  //1 byte zero, 1 byte PTCL, 2 byte TCP length.

  if (ip6->ip6_nxt == 6) //TCP
    {
      ip->ip_p = ip6->ip6_nxt;

      //set the ip header checksum
      ip->ip_sum = 0;
      tcp->th_sum = 0;

      uint16_t tlen = ntohs(ip6->ip6_plen);
      uint16_t csum = click_in_cksum((unsigned char *) tcp, tlen);
      tcp->th_sum = click_in_cksum_pseudohdr(csum, ip, tlen);
      ip->ip_sum = click_in_cksum((unsigned char *)ip, sizeof(click_ip));

    }

  else if (ip6->ip6_nxt ==17) //UDP
    {
      ip->ip_p = ip6->ip6_nxt;

      //set the ip header checksum
      ip->ip_sum=0;
      udp->uh_sum = 0;

      uint16_t tlen = ntohs(ip6->ip6_plen);
      uint16_t csum = click_in_cksum((unsigned char *) udp, tlen);
      udp->uh_sum = click_in_cksum_pseudohdr(csum, ip, tlen);
      ip->ip_sum = click_in_cksum((unsigned char *)ip, sizeof(click_ip));

    }

  else if (ip6->ip6_nxt== 58)
    {
      ip->ip_p = 1;
      //icmp 4->6 translation is dealt by caller
    }

  else
    {
      // will deal with other protocols later
    }
  return q;

}


Packet *
ProtocolTranslator64::make_icmp_translate64(unsigned char *a,
			      unsigned char payload_length)
{

  click_ip6 *ip6=0;
  unsigned char *ip=0;
  unsigned char icmp6_type= a[0];
  unsigned char icmp6_code= a[1];
  unsigned char icmp_length;
  WritablePacket *q2 = 0;

  // set type, code, length , and checksum of ICMP header
  switch (icmp6_type) {
   case ICMP6_ECHO: ;
   case ICMP6_ECHOREPLY : {
      icmp_length = payload_length -sizeof(click_icmp6_echo) + sizeof(click_icmp_echo);
      click_icmp6_echo *icmp6 = (click_icmp6_echo *)a;
      ip6 = (click_ip6 *)(icmp6+1);

      q2 = Packet::make(icmp_length);
      memset(q2->data(), '\0', q2->length());
      click_icmp_echo *icmp = (click_icmp_echo *)q2->data();
      ip = (unsigned char *)(icmp+1);

      if (icmp6_type == ICMP6_ECHO ) { // icmp6_type ==128
	icmp->icmp_type = ICMP_ECHO;                 // icmp_type =8
      }
      else if (icmp6_type == ICMP6_ECHOREPLY ) { // icmp6_type ==129
	icmp->icmp_type = ICMP_ECHOREPLY;              // icmp_type = 0
      }

      icmp->icmp_identifier = (icmp6->icmp6_identifier);
      icmp->icmp_sequence = (icmp6->icmp6_sequence);
      memcpy(ip, ip6, (payload_length - sizeof(click_icmp6_echo)));
      icmp->icmp_cksum = click_in_cksum((unsigned char *)icmp, icmp_length);

    }
  break;

  case  ICMP6_UNREACH : {
      // icmp6_type ==11
      icmp_length = payload_length-sizeof(click_icmp6_unreach)+sizeof(click_icmp_unreach);
      click_icmp6_unreach *icmp6 = (click_icmp6_unreach *)a;
      ip6 = (click_ip6 *)(icmp6+1);
      q2 = Packet::make(icmp_length);
      memset(q2->data(), '\0', q2->length());
      click_icmp_unreach *icmp = (click_icmp_unreach *)q2->data();
      ip = (unsigned char *)(icmp+1);
      icmp->icmp_type = ICMP_UNREACH;             // icmp_type =3

      switch (icmp6_code) {
      case 0: icmp->icmp_code =0;  break;
      case 1: icmp->icmp_code =10; break;
      case 2: icmp->icmp_code =5;  break;
      case 3: icmp->icmp_code =1;  break;
      case 4: icmp->icmp_code =3;  break;
      default: ; break;
      }
      memcpy(ip, ip6, (payload_length - sizeof(click_icmp6_unreach)));
      icmp->icmp_cksum = click_in_cksum((unsigned char *)icmp, icmp_length);

    }
  break;

  case ICMP6_PKTTOOBIG : {
      icmp_length = payload_length-sizeof(click_icmp6_pkttoobig)+sizeof(click_icmp_unreach);
      click_icmp6_pkttoobig *icmp6 = (click_icmp6_pkttoobig*)a;
      ip6 = (click_ip6 *)(icmp6+1);
      q2 = Packet::make(icmp_length);
      memset(q2->data(), '\0', q2->length());
      click_icmp_unreach *icmp = (click_icmp_unreach *)q2->data();
      ip = (unsigned char *)(icmp+1);
      icmp->icmp_type = ICMP_UNREACH; //icmp_type = 3
      icmp->icmp_code = 4;
      memcpy(ip, ip6, (payload_length - sizeof(click_icmp6_pkttoobig)));
      icmp->icmp_cksum = click_in_cksum((unsigned char *)icmp, icmp_length);
    }
  break;

  case ICMP6_TIMXCEED : {
      icmp_length = payload_length-sizeof(click_icmp6_timxceed)+sizeof(click_icmp_timxceed);
      click_icmp6_timxceed *icmp6 = (click_icmp6_timxceed *)a;
      ip6 = (click_ip6 *)(icmp6+1);
      q2 = Packet::make(icmp_length);
      memset(q2->data(), '\0', q2->length());
      click_icmp_timxceed *icmp = (click_icmp_timxceed *)q2->data();
      ip = (unsigned char *)(icmp+1);
      icmp->icmp_type =ICMP_TIMXCEED;            //icmp_type = 11
      icmp->icmp_code = icmp6_code;
      memcpy(ip, ip6, (payload_length - sizeof(click_icmp6_timxceed)));
      icmp->icmp_cksum = click_in_cksum((unsigned char *)icmp, icmp_length);
    }
  break;

  case ICMP6_PARAMPROB : { // icmp6_type == 4

      click_icmp6_paramprob *icmp6 = (click_icmp6_paramprob *)a;
      ip6=(click_ip6 *)(icmp6+1);
      if (icmp6_code ==2 || icmp6_code ==0)
	{
	  icmp_length = payload_length-sizeof(click_icmp6_paramprob)+sizeof(click_icmp_paramprob);
	  q2 = Packet::make(icmp_length);
	  memset(q2->data(), '\0', q2->length());
	  click_icmp_paramprob *icmp = (click_icmp_paramprob *)q2->data();
	  ip = (unsigned char *)(icmp +1);
	  icmp->icmp_type = ICMP_PARAMPROB;         // icmp_type = 12
	  icmp->icmp_code = 0;
	  if (icmp6_code ==2) {
	    icmp->icmp_pointer = -1;
	  }
	  else if(icmp6_code ==0)
	    {
	      switch (ntohl(icmp6->icmp6_pointer)) {
	      case 0 : icmp->icmp_pointer = 0;  break;
	      case 4 : icmp->icmp_pointer = 2;  break;
	      case 7 : icmp->icmp_pointer = 8;  break;
	      case 6 : icmp->icmp_pointer = 9;  break;
	      case 8 : icmp->icmp_pointer = 12; break;
	      case 24: icmp->icmp_pointer = -1; break;
	      default: ; break;
	      }

	    }
	  memcpy(ip, ip6, (payload_length - sizeof(click_icmp6_paramprob)));
	  icmp->icmp_cksum = click_in_cksum((unsigned char *)icmp, icmp_length);
	}

      else if (icmp6_code ==1)
	{
	  icmp_length = payload_length-sizeof(click_icmp6_paramprob)+sizeof(click_icmp_unreach);
	  q2 = Packet::make(icmp_length);
	  memset(q2->data(), '\0', q2->length());
	  click_icmp_unreach *icmp = (click_icmp_unreach *)q2->data();

	  ip = (unsigned char *)(icmp +1);
	  icmp->icmp_type = ICMP_UNREACH; // icmp_type = 3
	  icmp->icmp_code = 2;
	  memcpy(ip, ip6, (payload_length - sizeof(click_icmp6_paramprob)));
	  icmp->icmp_cksum = click_in_cksum((unsigned char *)icmp, icmp_length);
	}
  }
  break;

  default: ; break;
  }

  return q2;
}




void
ProtocolTranslator64::push(int, Packet *p)
{
  handle_ip6(p);
}


void
ProtocolTranslator64::handle_ip6(Packet *p)
{
  click_ip6 *ip6 = (click_ip6 *) p->data();
  IP6Address ip6_dst = IP6Address(ip6->ip6_dst);
  IP6Address ip6_src = IP6Address(ip6->ip6_src);

  IPAddress ipa_dst, ipa_src;
  if (ip6_dst.ip4_address(ipa_dst) && ip6_src.ip4_address(ipa_src))
    {

       //translate protocol according to SIIT
       unsigned char * start_of_p= (unsigned char *)(ip6+1);
       Packet *q = 0;
       q = make_translate64(ipa_src, ipa_dst, ip6, start_of_p);

       //see if it is an icmp6 packet, if it is, translate the icmp6 packet
       if (ip6->ip6_nxt == 0x3a)
	 {
	   click_ip * ip4= (click_ip *)q->data();
	   unsigned char * icmp6 = (unsigned char *)(ip4+1);

	   Packet *q2 = 0;
	   q2 = make_icmp_translate64(icmp6, (q->length()-sizeof(click_ip)));
	   WritablePacket *q3=Packet::make(sizeof(click_ip)+q2->length());
	   memset(q3->data(), '\0', q3->length());
	   click_ip *ip = (click_ip *)q3->data();
	   memcpy(ip, q->data(), sizeof(click_ip));
	   unsigned char *start_of_icmp = (unsigned char *)(ip+1);
	   memcpy(start_of_icmp, q2->data(), q2->length());
	   ip->ip_len = htons(q3->length());
	   ip->ip_sum=0;
	   ip->ip_sum = click_in_cksum((unsigned char *)ip, q3->length());

	   p->kill();
	   q->kill();
	   q2->kill();
	   output(0).push(q3);
	 }
       else
	 {
	   p->kill();
	   output(0).push(q);
	 }
    }

  else
    {
      p->kill();
      return;
    }
}

CLICK_ENDDECLS
EXPORT_ELEMENT(ProtocolTranslator64)
