/******************************************************************************
* Copyright (c) 2000-2019 Ericsson Telecom AB
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
*
* Contributors:
*   Endre Kulcsar - initial implementation and initial documentation
******************************************************************************/
//
//  File:               ICMPv6_EncDec.cc
//  Rev:                R2A
//  Prodnr:             CNL 113 631
//  Reference:          RFC 4443          


#include "ICMPv6_Types.hh"

namespace ICMPv6__Types {


#define Get_MSB_ICMPv6(val) (((val) & 0xff00) >> 8)
#define Get_LSB_ICMPv6(val) ((val) & 0x00ff)

// copied from IP protocol module
General__Types::OCT2
Calculate_cksum(const unsigned char *ptr, int datalen)
{
  unsigned long sum = 0;
  unsigned char ret[2];
  
 // Log_function_name_on_enter();
  
  for (int i = 0; i <= datalen - 2; i = i + 2)
    sum += (ptr[i + 1] << 8) + ptr[i]; 

  if (datalen % 2) // datalen is odd
  {
    sum += ptr[datalen - 1];
  }

  sum = (sum & 0xFFFF) + (sum >> 16);
  sum = (sum & 0xFFFF) + (sum >> 16);
  sum = ~sum;

  ret[0] = Get_LSB_ICMPv6(sum);
  ret[1] = Get_MSB_ICMPv6(sum);
  return OCTETSTRING(2, &ret[0]);
}

OCTETSTRING f__enc__PDU__ICMPv6(const PDU__ICMPv6& pdu,
                             const OCTETSTRING& srcaddr, // from IPv6 header
                             const OCTETSTRING& dstaddr)  //, from IPv6 header
{ 
 TTCN_Buffer buf;
 pdu.encode(PDU__ICMPv6_descr_, buf, TTCN_EncDec::CT_RAW);
 OCTETSTRING ret_val(buf.get_len(), buf.get_data());
 
 // if user gave checksum field as '0000'O then calculate actual checksum, otherwise use what user gave
 if (oct2int(substr(ret_val,2,2)) == 0)
 {   
   //generate pseudo header for checksum calculation
   OCTETSTRING PseudoHeader;  
   PseudoHeader =  srcaddr + dstaddr + int2oct(ret_val.lengthof(),4) + int2oct (0,3) + int2oct(58,1);      
   // RFC 4443  2 -> "The Next Header value used in the pseudo-header is 58.  ('3A'O

   // calculate checksum    
   OCTETSTRING CHECKSUM = Calculate_cksum ( (const unsigned char *) (PseudoHeader + ret_val), 
                    ret_val.lengthof() + 40 ); //  40 is pseudoheader length
                             
   ret_val = substr(ret_val,0,2) + CHECKSUM + substr(ret_val,4,ret_val.lengthof()- 4);
  }
 return ret_val;
} 

BOOLEAN f__ICMPv6__verify__checksum
(
 const OCTETSTRING& stream,
 const OCTETSTRING& srcaddr, // from IPv6 header
 const OCTETSTRING& dstaddr // from IPv6 header
)
{
  OCTETSTRING PseudoHeader;
  PseudoHeader =  srcaddr + dstaddr + int2oct(stream.lengthof(),4) + int2oct (0,3) + int2oct(58,1); 
    
  // stream with checksum field as 0000
  OCTETSTRING stream_w_zero_checksum;
  stream_w_zero_checksum = substr(stream,0,2) + int2oct(0,2) + substr(stream,4,stream.lengthof()- 4);  
  
  // calculate checksum    
  OCTETSTRING CHECKSUM = Calculate_cksum ( (const unsigned char *) (PseudoHeader + stream_w_zero_checksum), 
                     stream_w_zero_checksum.lengthof() + 40 ); 
  
  OCTETSTRING RECEIVED_CHECKSUM = substr(stream,2,2);
    
  if (CHECKSUM !=  RECEIVED_CHECKSUM)
  {    
    TTCN_warning("Incorrect checksum received! \n Expected checksum: %x %x \n Received checksum: %x %x ",
    ((const unsigned char *)CHECKSUM)[0],
    ((const unsigned char *)CHECKSUM)[1],
    ((const unsigned char *) RECEIVED_CHECKSUM)[0],
    ((const unsigned char *) RECEIVED_CHECKSUM)[1]
   ); 
   return FALSE;
  }
  else
  {
   return TRUE;
  }    

}

} // end of namespace ICMPv6__Types