/******************************************************************************/
// @copyright   Copyright Notification
//   No part may be reproduced except as authorized by written permission.
//   The copyright and the foregoing restriction extend to reproduction in all media.
//   Trademark 2010, 3GPP Organizational Partners (ARIB, ATIS, CCSA, ETSI, TTA, TTC).
//   All rights reserved.
// @version: IWD_10wk37
// $Date: 2010-06-04 14:58:42 +0200 (Fri, 04 Jun 2010) $
// $Rev: 3274 $
/******************************************************************************/

module CommonIP {

  import from CommonDefs all;

  const UInt8_Type tsc_IP_Protocol_ICMP := 1;                     /* @status    APPROVED */
  const UInt8_Type tsc_IP_Protocol_UDP  := 17;                    /* @status    APPROVED */
  const UInt8_Type tsc_ICMP_Type_EchoReply := 0;                  /* @status    APPROVED */
  const UInt8_Type tsc_ICMP_Type_EchoRequest := 8;
  
  const octetstring tsc_IP_AnyData := '00112233445566778899AABBCCDDEEFF'O;  /* @status    APPROVED */

  //----------------------------------------------------------------------------
  /*
   * @desc      convert IP address in dot notation into octetstring;
   *            e.g. "10.11.12.13" results in '0A0B0C0D'O
   * @param     p_IPv4AddrChar
   * @return    O4_Type
   * @status    APPROVED
   */
  function f_Convert_IPv4Addr2OctString(charstring p_IPv4AddrChar) return O4_Type
  {
    var Char1List_Type v_SplitCharList := {"."};
    var CharStringList_Type v_FieldList := f_StringSplit(p_IPv4AddrChar, v_SplitCharList);
    var octetstring v_IPv4AddrOct := ''O;
    var integer v_FieldCnt := lengthof(v_FieldList);
    var integer v_IntVal;
    var integer i;

    if (v_FieldCnt != 4) {
      FatalError(__FILE__, __LINE__, "invalid IP Address");
    }
    for (i:=0; i<v_FieldCnt; i:=i+1) {
      v_IntVal := str2int(v_FieldList[i]);
      v_IPv4AddrOct := v_IPv4AddrOct & int2oct(v_IntVal, 1);
    }
    return v_IPv4AddrOct;
  }

  //----------------------------------------------------------------------------
  /*
   * @desc      Convert a text representation of a IPv6 address in the colon separated hexadecimal format
   *            into an octetstring of length 16 according to RFC3513.
   *            E.g. "2001::1" results in '20010000000000000000000000000001'O
   * @param     p_IPv6AddrChar
   * @return    O16_Type
   * @status    APPROVED
   */
  function f_Convert_IPv6Addr2OctString(charstring p_IPv6AddrChar) return O16_Type
  {
    var Char1List_Type v_SplitCharList := {":"};
    var CharStringList_Type v_FieldList := f_StringSplit(p_IPv6AddrChar, v_SplitCharList);
    var octetstring v_IPv6AddrOct := ''O;
    var integer v_FieldCnt := lengthof(v_FieldList);
    var charstring v_Field;
    var integer v_NoOfEmptyBlocks;
    var integer i;
    var integer k;

    if (v_FieldCnt > 8) {
      FatalError(__FILE__, __LINE__, "invalid IP Address");
    }
    for (i:=0; i<v_FieldCnt; i:=i+1) {
      v_Field := v_FieldList[i];
      select (lengthof(v_Field)) {
        case (0) { // empty block
          v_NoOfEmptyBlocks := 8 - (v_FieldCnt - 1); // "-1" because one empty field is already there
          for (k:=0; k < v_NoOfEmptyBlocks; k:=k+1) {
            v_IPv6AddrOct := v_IPv6AddrOct & '0000'O;
          }
        }
        case (1) {
          v_IPv6AddrOct := v_IPv6AddrOct & str2oct("000" & v_Field);
        }
        case (2) {
          v_IPv6AddrOct := v_IPv6AddrOct & str2oct("00" & v_Field);
        }
        case (3) {
          v_IPv6AddrOct := v_IPv6AddrOct & str2oct("0" & v_Field);
        }
        case (4) {
          v_IPv6AddrOct := v_IPv6AddrOct & str2oct(v_Field);

        }
        case else {
          FatalError(__FILE__, __LINE__, "invalid IP Address");
        }
      }
    }
    return v_IPv6AddrOct;
  }

  //----------------------------------------------------------------------------
  /*
   * @desc      Calculate of the IP checksum acc. RFC 1071
   * @param     p_OctetString
   * @return    O2_Type
   * @status    APPROVED
   */
  function f_IpChecksum(octetstring p_OctetString) return O2_Type
  { /* calculate header checksum; within the given octetstring the header checksum shall be set to '0000'O */
    var integer i;
    var octetstring v_Word;
    var integer v_DataLength := lengthof(p_OctetString);
    var integer v_Sum := 0;

    if (v_DataLength mod 2 != 0) {
      p_OctetString := p_OctetString & '00'O;
    }
    
    // accumulate all words of the IP header
    for (i:=0; i < v_DataLength; i := i+2) {
      v_Word := p_OctetString[i] & p_OctetString[i+1];
      v_Sum := v_Sum + oct2int(v_Word);
    }
    // deal with the
    while (v_Sum > tsc_UInt16Max) {
      v_Sum := (v_Sum mod (tsc_UInt16Max+1)) + (v_Sum / (tsc_UInt16Max+1));
    }
    // get complement
    v_Sum := tsc_UInt16Max - v_Sum;
    return int2oct(v_Sum, 2);
  }

  //----------------------------------------------------------------------------
  /*
   * @desc      create an IPv4 packet ( currently with typical settings for UDP or ICMP packet)
   * @param     p_Identification    (default value: '6d7d'O)
   * @param     p_Protocol
   * @param     p_SourceAddr
   * @param     p_DestAddr
   * @param     p_IPPayload
   * @return    octetstring
   * @status    APPROVED
   */
  function fl_IPv4Packet_Create(O2_Type p_Identification := '6d7d'O, // Identification; random value (can be used to generate different packets
                                UInt8_Type p_Protocol,
                                charstring p_SourceAddr,
                                charstring p_DestAddr,
                                octetstring p_IPPayload)
    return octetstring
  {
    var integer v_TotalLength := lengthof(p_IPPayload) + 20;  // 20 bytes IP header
    var O4_Type v_SourceAddrStr := f_Convert_IPv4Addr2OctString(p_SourceAddr);
    var O4_Type v_DestAddrStr := f_Convert_IPv4Addr2OctString(p_DestAddr);
    var octetstring v_OctetString;
    
    // IP header
    v_OctetString := '45'O;            // version and header length
    v_OctetString := v_OctetString & '00'O;   // Differentiated services (RFC 2474), Explicit Congestion Notification (ECN, RFC 3168), former TOS (RFC 791)
    v_OctetString := v_OctetString & int2oct(v_TotalLength, 2);
    v_OctetString := v_OctetString & p_Identification;
    v_OctetString := v_OctetString & '0000'O; // flags (3 bits; typically 0 for UDP) and fragment offset (13 bits; typically 0 for UDP)
    v_OctetString := v_OctetString & '80'O;   // Time to live (random value)
    v_OctetString := v_OctetString & int2oct(p_Protocol, 1);
    v_OctetString := v_OctetString & f_IpChecksum(v_OctetString & '0000'O & v_SourceAddrStr & v_DestAddrStr);
    v_OctetString := v_OctetString & v_SourceAddrStr;
    v_OctetString := v_OctetString & v_DestAddrStr;
    v_OctetString := v_OctetString & p_IPPayload;
    
    return v_OctetString;
  }

  //----------------------------------------------------------------------------
  /*
   * @desc      create an IPv6 packet (currently with typical settings for an UDP packet)
   *            For IPv6 packet format, see RFC 2460
   * @param     p_Protocol
   * @param     p_SourceAddr
   * @param     p_DestAddr
   * @param     p_IPPayload
   * @return    octetstring
   * @status    APPROVED
   */
  function fl_IPv6Packet_Create(UInt8_Type p_Protocol,
                                charstring p_SourceAddr,
                                charstring p_DestAddr,
                                octetstring p_IPPayload)
    return octetstring
  {
    var O16_Type v_SourceAddrStr := f_Convert_IPv6Addr2OctString(p_SourceAddr);
    var O16_Type v_DestAddrStr := f_Convert_IPv6Addr2OctString(p_DestAddr);
    var octetstring v_OctetString;
    
    // IP header
    v_OctetString := '60000000'O;  // Version, Traffic Class and Flow Label
    v_OctetString := v_OctetString & int2oct (lengthof(p_IPPayload), 2); // Payload Length
      v_OctetString := v_OctetString & int2oct(p_Protocol, 1);   // Next header
    v_OctetString := v_OctetString & '40'O;   // Hop Limit
    v_OctetString := v_OctetString & v_SourceAddrStr;
    v_OctetString := v_OctetString & v_DestAddrStr;
    v_OctetString := v_OctetString & p_IPPayload;
    return v_OctetString;
  }

  //----------------------------------------------------------------------------
  /*
   * @desc      build an IPv4 UDP datagram
   * @param     p_SourceAddr
   * @param     p_DestAddr
   * @param     p_SourcePort
   * @param     p_DestPort
   * @param     p_UdpPayload
   * @return    octetstring
   * @status    APPROVED
   */
  function fl_IPv4UdpDatagram_Create(charstring p_SourceAddr,
                                     charstring p_DestAddr,
                                     UInt16_Type p_SourcePort,
                                     UInt16_Type p_DestPort,
                                     octetstring p_UdpPayload)
    return octetstring
  { /* @desc create UDP datagram */
    var integer v_UdpDatagramLength := lengthof(p_UdpPayload) + 8;  // 8 bytes UDP header
    // Pseudo Header:
    var O4_Type v_SourceAddrStr := f_Convert_IPv4Addr2OctString(p_SourceAddr);
    var O4_Type v_DestAddrStr := f_Convert_IPv4Addr2OctString(p_DestAddr);
    var O2_Type v_LengthStr := int2oct(v_UdpDatagramLength, 2);
    var O1_Type v_Protocol := '11'O; // UDP
    var octetstring v_PseudoHeader := v_SourceAddrStr & v_DestAddrStr &'00'O & v_Protocol & v_LengthStr;
    var O2_Type v_ChecksumDummy := '0000'O;
    var octetstring v_OctetString := ''O;
    
    v_OctetString := v_OctetString & int2oct(p_SourcePort, 2);
    v_OctetString := v_OctetString & int2oct(p_DestPort, 2);
    v_OctetString := v_OctetString & v_LengthStr;
    v_OctetString := v_OctetString & f_IpChecksum(v_PseudoHeader & v_OctetString & v_ChecksumDummy & p_UdpPayload);  /* Note: the UDP checksum can also be '0000'O what means "no chcksum"; but that is not the usual case */
    v_OctetString := v_OctetString & p_UdpPayload;

    return v_OctetString;
  }

  //----------------------------------------------------------------------------
  /*
   * @desc      create UDP datagram
   * @param     p_SourceAddr
   * @param     p_DestAddr
   * @param     p_SourcePort
   * @param     p_DestPort
   * @param     p_UdpPayload
   * @return    octetstring
   * @status    APPROVED
   */
  function fl_IPv6UdpDatagram_Create(charstring p_SourceAddr,
                                     charstring p_DestAddr,
                                     UInt16_Type p_SourcePort,
                                     UInt16_Type p_DestPort,
                                     octetstring p_UdpPayload)
    return octetstring
  {
    var integer v_UdpDatagramLength := lengthof(p_UdpPayload) + 8;  // 8 bytes UDP header
    var O16_Type v_SourceAddrStr := f_Convert_IPv6Addr2OctString(p_SourceAddr);
    var O16_Type v_DestAddrStr := f_Convert_IPv6Addr2OctString(p_DestAddr);
    var O4_Type v_LengthStr := int2oct(v_UdpDatagramLength, 4); // RFC 2460 clause 8.1
    var octetstring v_PseudoHeader;
    var octetstring v_OctetString;
    
    // Prepare the pseudo header, see RFC 2460 and illustration in Wikipedia
    v_PseudoHeader := v_SourceAddrStr & v_DestAddrStr & v_LengthStr;
    v_PseudoHeader := v_PseudoHeader & v_LengthStr; // UDP length
    v_PseudoHeader := v_PseudoHeader & '000000'O & '11'O; // Zeros and Next header (= Protocol)
    v_PseudoHeader := v_PseudoHeader & int2oct(p_SourcePort, 2) & int2oct(p_DestPort, 2) &  int2oct(v_UdpDatagramLength, 2) & '0000'O;
    v_PseudoHeader := v_PseudoHeader & p_UdpPayload;
    
    // Now set the UDP packet
    v_OctetString := int2oct(p_SourcePort, 2);
    v_OctetString := v_OctetString & int2oct(p_DestPort, 2);
    v_OctetString := v_OctetString & int2oct(v_UdpDatagramLength, 2);
    v_OctetString := v_OctetString & f_IpChecksum(v_PseudoHeader);
    v_OctetString := v_OctetString & p_UdpPayload;
    
    return v_OctetString;
  }
  
  //----------------------------------------------------------------------------
  /*
   * @desc      build an IPv4 ICMP datagram
   * @param     p_IcmpType
   * @param     p_SequenceNumber
   * @param     p_Data
   * @return    octetstring
   * @status    APPROVED
   */
  function fl_Icmpv4Datagram_Create(UInt8_Type p_IcmpType,
                                    O2_Type p_SequenceNumber,
                                    octetstring p_Data)
    return octetstring
  { /* @desc create ICMP datagram */
    var O2_Type v_ChecksumDummy := '0000'O;
    var O2_Type v_Id := '0001'O;
    var octetstring v_OctetString := ''O;
    
    v_OctetString := v_OctetString & int2oct(p_IcmpType, 1);   // Type
    v_OctetString := v_OctetString & '00'O;                    // Code
    v_OctetString := v_OctetString & f_IpChecksum(v_OctetString & v_ChecksumDummy & v_Id & p_Data);
    v_OctetString := v_OctetString & v_Id;
    v_OctetString := v_OctetString & p_SequenceNumber;
    v_OctetString := v_OctetString & p_Data;
    
    return v_OctetString;
  }
  
  //****************************************************************************
  
  template (value) octetstring crs_IPv4_AnyUdpPacket(charstring p_SourceAddr,
                                                     charstring p_DestAddr,
                                                     UInt16_Type p_SourcePort,
                                                     UInt16_Type p_DestPort) :=
  /* @status    APPROVED */
    fl_IPv4Packet_Create(-, tsc_IP_Protocol_UDP, p_SourceAddr, p_DestAddr, fl_IPv4UdpDatagram_Create(p_SourceAddr,
                                                                                                     p_DestAddr,
                                                                                                     p_SourcePort,
                                                                                                     p_DestPort,
                                                                                                     tsc_IP_AnyData));
    // WORKAROUND
/*     fl_IPv4Packet_Create('6d7d'O, tsc_IP_Protocol_UDP, p_SourceAddr, p_DestAddr, fl_IPv4UdpDatagram_Create(p_SourceAddr, */
/*                                                                                                            p_DestAddr, */
/*                                                                                                            p_SourcePort, */
/*                                                                                                            p_DestPort, */
/*                                                                                                            tsc_IP_AnyData)); */
    // /WORKAROUND

  template (value) octetstring crs_IPv6_AnyUdpPacket(charstring p_SourceAddr,
                                                     charstring p_DestAddr,
                                                     UInt16_Type p_SourcePort,
                                                     UInt16_Type p_DestPort) :=
    /* @status    APPROVED */
    fl_IPv6Packet_Create(tsc_IP_Protocol_UDP, p_SourceAddr, p_DestAddr, fl_IPv6UdpDatagram_Create(p_SourceAddr,
                                                                                                  p_DestAddr,
                                                                                                  p_SourcePort,
                                                                                                  p_DestPort,
                                                                                                  tsc_IP_AnyData));
  
  template (value) octetstring crs_IPv4_AnyIcmpEchoReply_1(charstring p_SourceAddr,
                                                           charstring p_DestAddr) :=
    /* @status    APPROVED */
    fl_IPv4Packet_Create('1001'O, tsc_IP_Protocol_ICMP, p_SourceAddr, p_DestAddr, fl_Icmpv4Datagram_Create(tsc_ICMP_Type_EchoReply,
                                                                                                           '0001'O,  // SequenceNumber
                                                                                                           tsc_IP_AnyData));
  template (value) octetstring crs_IPv4_AnyIcmpEchoReply_2(charstring p_SourceAddr,
                                                           charstring p_DestAddr) :=
    /* @status    APPROVED */
    fl_IPv4Packet_Create('1002'O, tsc_IP_Protocol_ICMP, p_SourceAddr, p_DestAddr, fl_Icmpv4Datagram_Create(tsc_ICMP_Type_EchoReply,
                                                                                                           '0002'O,  // SequenceNumber
                                                                                                           tsc_IP_AnyData & tsc_IP_AnyData));
  template (value) octetstring crs_IPv4_AnyIcmpEchoReply_3(charstring p_SourceAddr,
                                                           charstring p_DestAddr) :=
    /* @status    APPROVED */
    fl_IPv4Packet_Create('1003'O, tsc_IP_Protocol_ICMP, p_SourceAddr, p_DestAddr, fl_Icmpv4Datagram_Create(tsc_ICMP_Type_EchoReply,
                                                                                                           '0003'O,  // SequenceNumber
                                                                                                           tsc_IP_AnyData & tsc_IP_AnyData & tsc_IP_AnyData));
  template (value) octetstring crs_IPv4_AnyIcmpEchoReply_4(charstring p_SourceAddr,
                                                           charstring p_DestAddr) :=
    /* @status    APPROVED */
    fl_IPv4Packet_Create('1004'O, tsc_IP_Protocol_ICMP, p_SourceAddr, p_DestAddr, fl_Icmpv4Datagram_Create(tsc_ICMP_Type_EchoReply,
                                                                                                           '0004'O,  // SequenceNumber
                                                                                                           tsc_IP_AnyData & tsc_IP_AnyData & tsc_IP_AnyData & tsc_IP_AnyData));
  template (value) octetstring crs_IPv4_AnyIcmpEchoReply_5(charstring p_SourceAddr,
                                                           charstring p_DestAddr) :=
    /* @status    APPROVED */
    fl_IPv4Packet_Create('1005'O, tsc_IP_Protocol_ICMP, p_SourceAddr, p_DestAddr, fl_Icmpv4Datagram_Create(tsc_ICMP_Type_EchoReply,
                                                                                                           '0005'O,  // SequenceNumber
                                                                                                           tsc_IP_AnyData & tsc_IP_AnyData & tsc_IP_AnyData & tsc_IP_AnyData & tsc_IP_AnyData));
}
