/******************************************************************************/
// @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.
//   (c) 2023, 3GPP Organizational Partners (ARIB, ATIS, CCSA, ETSI, TSDSI, TTA, TTC).
//   All rights reserved.
// @version: IWD_23wk37
// $Date: 2023-04-12 17:34:38 +0200 (Wed, 12 Apr 2023) $
// $Rev: 35833 $
/******************************************************************************/

module LoopbackIP {

  import from CommonDefs all;
  import from CommonIP all;
  import from Parameters all;
  import from EUTRA_NR_Parameters all;

  type enumerated DataPathCheck_BearerType_Type {dedicatedBearer, defaultBearer};  /* @status    APPROVED (ENDC, NR5GC, NR5GC_IRAT) */

  //----------------------------------------------------------------------------
  /*
   * @desc      auxiliary function
   * @param     p_UseIPv4
   * @param     p_IPv4Addr
   * @param     p_IPv6Addr
   * @return    charstring
   * @status    APPROVED (ENDC, LTE, LTE_A_PRO, LTE_IRAT, NBIOT, NR5GC, NR5GC_IRAT)
   */
  function fl_LoopbackModeB_IPv4IPv6Address(boolean p_UseIPv4,
                                            charstring p_IPv4Addr,
                                            charstring p_IPv6Addr) return charstring
  {
    if (p_UseIPv4) { return p_IPv4Addr; }
    else           { return p_IPv6Addr; }
  }

  /*
   * @desc      returns IP address to be used for IP packets in loopback mode B depending on IPv4/v6 for the given PDN
   * @param     p_UseIPv4           (default value: not pc_IPv6)
   * @param     p_PdnIndex          (default value: PDN_1)
   * @return    charstring
   * @status    APPROVED (ENDC, LTE, LTE_IRAT, NBIOT, NR5GC, NR5GC_IRAT)
   */
  function f_LoopbackModeB_IP_Address_UE(boolean p_UseIPv4 := not pc_IPv6,
                                         PDN_Index_Type p_PdnIndex := PDN_1) return charstring
  { /* @sic R5s130362 - MCC160 Implementation: tsc_PDN_AddressInfo sic@ */
    /* @sic R5s190281 change 1: not pc_IPv6 instead of pc_IPv4 sic@ */
    var PDN_AddressInfo_Type v_PDN_AddressInfo := f_PDN_AddressInfo_Get(p_PdnIndex);
    return fl_LoopbackModeB_IPv4IPv6Address(p_UseIPv4, v_PDN_AddressInfo.UE_IPAddressIPv4, v_PDN_AddressInfo.UE_IPAddressIPv6);
  }

  /*
   * @desc      returns IP address to be used for IP packets in loopback mode B depending on IPv4/v6 for the given PDN
   * @param     p_UseIPv4           (default value: not pc_IPv6)
   * @param     p_PdnIndex          (default value: PDN_1)
   * @return    charstring
   * @status    APPROVED (ENDC, LTE, LTE_A_PRO, NBIOT, NR5GC, NR5GC_IRAT)
   */
  function f_LoopbackModeB_IP_Address_NW(boolean p_UseIPv4 := not pc_IPv6,
                                         PDN_Index_Type p_PdnIndex := PDN_1) return charstring
  { /* @sic R5s130362 - MCC160 Implementation: tsc_PDN_AddressInfo sic@ */
    /* @sic R5s190411: not pc_IPv6 instead of pc_IPv4 sic@ */
    var PDN_AddressInfo_Type v_PDN_AddressInfo := f_PDN_AddressInfo_Get(p_PdnIndex);
    return fl_LoopbackModeB_IPv4IPv6Address(p_UseIPv4, v_PDN_AddressInfo.PCSCF_IPAddressIPv4, v_PDN_AddressInfo.PCSCF_IPAddressIPv6);
  }
  
  //----------------------------------------------------------------------------
  /*
   * @desc      create an IPv4 packet ( currently with typical settings for UDP or ICMP packet)
   * @param     p_Identification    (default value: '6d7d'O)
   * @param     p_TOS               (default value: '00'O)
   * @param     p_Protocol
   * @param     p_SourceAddr
   * @param     p_DestAddr
   * @param     p_IPPayload
   * @return    octetstring
   * @status    APPROVED (ENDC, LTE, LTE_A_PRO, LTE_IRAT, NBIOT, NR5GC, NR5GC_IRAT, UTRAN)
   */
  function f_IPv4Packet_Create(O2_Type p_Identification := '6d7d'O, // Identification; random value (can be used to generate different packets
                               O1_Type p_TOS := '00'O,              // Differentiated services (RFC 2474), Explicit Congestion Notification (ECN, RFC 3168), former TOS (RFC 791)
                               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 & p_TOS;
    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      build an IPv4 UDP datagram
   * @param     p_SourceAddr
   * @param     p_DestAddr
   * @param     p_SourcePort
   * @param     p_DestPort
   * @param     p_UdpPayload
   * @return    octetstring
   * @status    APPROVED (LTE, LTE_A_PRO, NBIOT, NR5GC, UTRAN)
   */
  function f_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 (LTE, LTE_A_PRO, NBIOT, NR5GC, UTRAN)
   */
  function f_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      create TCP datagram
   * @param     p_SourceAddr
   * @param     p_DestAddr
   * @param     p_SourcePort
   * @param     p_DestPort
   * @param     p_TcpPayload
   * @return    octetstring
   * @status    APPROVED (LTE, NBIOT, NR5GC, UTRAN)
   */
  function f_IPv4TcpDatagram_Create(charstring p_SourceAddr,
                                    charstring p_DestAddr,
                                    UInt16_Type p_SourcePort,
                                    UInt16_Type p_DestPort,
                                    octetstring p_TcpPayload)
    return octetstring
  {
    var integer v_TcpDatagramLength := lengthof(p_TcpPayload) + 20;  // 20 bytes TCP 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_TcpDatagramLength, 2);
    var O1_Type v_Protocol := int2oct(tsc_IP_Protocol_TCP, 1);
    var octetstring v_PseudoHeader := v_SourceAddrStr & v_DestAddrStr &'00'O & v_Protocol & v_LengthStr;
    var O2_Type v_ChecksumDummy := '0000'O;
    var octetstring v_UrgPointer := '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 & int2oct(0, 4);   // Sequence Number
    v_OctetString := v_OctetString & int2oct(0, 4);   // Acknowledgment Number
    v_OctetString := v_OctetString & '5011'O;         // 4bits HeaderLen/6bits Reserved/URG/ACK/PSH/RST/SYN/FIN
    v_OctetString := v_OctetString & int2oct(256, 4); // How to set the windows size?
    v_OctetString := v_OctetString & f_IpChecksum(v_PseudoHeader & v_OctetString & v_ChecksumDummy & v_UrgPointer & p_TcpPayload);  /* Note: the TCP checksum can also be '0000'O what means "no chcksum"; but that is not the usual case */
    v_OctetString := v_OctetString & v_UrgPointer;
    v_OctetString := v_OctetString & p_TcpPayload;

    return v_OctetString;
  }

  //----------------------------------------------------------------------------
  /*
   * @desc      Datagram acc. RFC2406
   * @param     p_NextHeader
   * @param     p_SPI
   * @param     p_Payload
   * @return    octetstring
   * @status    APPROVED (LTE, NBIOT, NR5GC, UTRAN)
   */
  function f_IPSecESPDatagram_Create(integer     p_NextHeader,
                                     O4_Type     p_SPI,
                                     octetstring p_Payload)
    return octetstring
  {
    var octetstring v_OctetString := ''O;
    var O4_Type v_SequenceNumber := int2oct(1, 4);
    var integer v_PayloadLen := lengthof(p_Payload);
    var integer v_PaddingLen := 0;
    var integer i;

    v_OctetString := v_OctetString & p_SPI;              // Security Parameters Index
    v_OctetString := v_OctetString & v_SequenceNumber;   // Sequence Number
    v_OctetString := v_OctetString & p_Payload;

    // Padding to ensure that the resulting text terminates on a 4-byte boundary. RFC 2406 cl2.4
    select (v_PayloadLen mod 4) {
      case (0) { v_PaddingLen := 2; }
      case (1) { v_PaddingLen := 1; }
      case (2) { v_PaddingLen := 0; }
      case (3) { v_PaddingLen := 3; }
    }
    for (i:=0; i<v_PaddingLen; i:=i+1) {
      v_OctetString := v_OctetString & int2oct(i+1, 1);        // The padding bytes when the encryption algorithm does not specify the padding contents
    }
    
    v_OctetString := v_OctetString & int2oct(v_PaddingLen, 1); // The padding lenght and next header are mandatory
    v_OctetString := v_OctetString & int2oct(p_NextHeader, 1);
    
    // Ciphering and authentication are not done. No authentication data present.
    
    return v_OctetString;
  }

  //----------------------------------------------------------------------------
  /*
   * @desc      create TCP datagram
   * @param     p_SourceAddr
   * @param     p_DestAddr
   * @param     p_SourcePort
   * @param     p_DestPort
   * @param     p_TcpPayload
   * @return    octetstring
   * @status    APPROVED (LTE, NBIOT, NR5GC, UTRAN)
   */
  function f_IPv6TcpDatagram_Create(charstring p_SourceAddr,
                                    charstring p_DestAddr,
                                    UInt16_Type p_SourcePort,
                                    UInt16_Type p_DestPort,
                                    octetstring p_TcpPayload)
    return octetstring
  { /* @sic R5s110645 change 4: IPv6 address instead of IPv4 sic@ */
    var integer v_TcpDatagramLength := lengthof(p_TcpPayload) + 20;  // 20 bytes TCP header
    // Pseudo 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_TcpDatagramLength, 4); //  2460 clause 8.1
    var O1_Type v_Protocol := int2oct(tsc_IP_Protocol_TCP, 1);
    var octetstring v_PseudoHeader;
    var O2_Type v_ChecksumDummy := '0000'O;
    var octetstring v_UrgPointer := '0000'O;
    var octetstring v_OctetString := ''O;
    
    // Prepare the pseudo header, see RFC 2460 and illustration in Wikipedia
    v_PseudoHeader := v_SourceAddrStr & v_DestAddrStr & v_LengthStr;
    v_PseudoHeader := v_PseudoHeader & '000000'O & v_Protocol; // Zeros and Next header (= Protocol)
    
    v_OctetString := v_OctetString & int2oct(p_SourcePort, 2);
    v_OctetString := v_OctetString & int2oct(p_DestPort, 2);
    v_OctetString := v_OctetString & int2oct(0, 4);   // Sequence Number
    v_OctetString := v_OctetString & int2oct(0, 4);   // Acknowledgment Number
    v_OctetString := v_OctetString & '5011'O;         // 4bits HeaderLen/6bits Reserved/URG/ACK/PSH/RST/SYN/FIN
    v_OctetString := v_OctetString & int2oct(256, 4); // How to set the windows size?
    v_OctetString := v_OctetString & f_IpChecksum(v_PseudoHeader & v_OctetString & v_ChecksumDummy & v_UrgPointer & p_TcpPayload);  /* Note: the TCP checksum can also be '0000'O what means "no chcksum"; but that is not the usual case */
    v_OctetString := v_OctetString & v_UrgPointer;
    v_OctetString := v_OctetString & p_TcpPayload;

    return v_OctetString;
  }

  //----------------------------------------------------------------------------
  /*
   * @desc      build an ICMP datagram
   *            (NOTE: ICMP applies for IPv4 and IPv6 wheres in general ICMPv6 is used upon IPv6 only)
   * @param     p_IcmpType
   * @param     p_SequenceNumber
   * @param     p_Data
   * @return    octetstring
   * @status    APPROVED (ENDC, LTE, LTE_IRAT, NBIOT, NR5GC, NR5GC_IRAT)
   */
  function fl_IcmpDatagram_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_SequenceNumber & p_Data);  // @sic R5s190117: p_SequenceNumber sic@
    v_OctetString := v_OctetString & v_Id;
    v_OctetString := v_OctetString & p_SequenceNumber;
    v_OctetString := v_OctetString & p_Data;
    
    return v_OctetString;
  }

  //****************************************************************************
  /*
   * @desc      common function to build IP packet with ICMP echo request or ICMP echo reply
   * @param     p_IcmpMsgType
   * @param     p_SourceAddr
   * @param     p_DestAddr
   * @param     p_SequenceNumber
   * @param     p_MaxPayloadSize    (default value: 75)
   * @return    octetstring
   * @status    APPROVED (ENDC, LTE, LTE_IRAT, NBIOT, NR5GC, NR5GC_IRAT)
   */
  function fl_IPv4IPv6_IcmpEchoRequestReply(UInt8_Type p_IcmpMsgType,
                                            charstring p_SourceAddr,
                                            charstring p_DestAddr,
                                            integer p_SequenceNumber,
                                            integer p_MaxPayloadSize := 75) return octetstring
  { /* @sic R5s200653: p_MaxPayloadSize for eMTC (max TBS=1000); per default there is no need to use greater TBS for other cases sic@ */
    var O2_Type v_IcmpSequenceNumber := int2oct(p_SequenceNumber, 2);
    var octetstring v_IcmpPayload;
    var octetstring v_Data := ''O;
    var octetstring v_Payload;
    var octetstring v_IpPacket;
    var integer i := p_SequenceNumber - 1;
    var integer v_BlockSize := lengthof(tsc_IP_AnyData);
    var integer n := p_MaxPayloadSize - v_BlockSize + 1;
    var integer v_Start := (i / n) mod v_BlockSize;    /* => v_Start = 0 .. v_BlockSize-1 */
    var integer v_Length := v_BlockSize + (i mod n);   /* => v_Length = v_BlockSize .. p_MaxPayloadSize */

    while (lengthof(v_Data) < p_MaxPayloadSize + v_BlockSize) {
      v_Data := v_Data & tsc_IP_AnyData;
    }
    v_IcmpPayload := substr(v_Data, v_Start, v_Length);    /* @sic R5s200653 sic@ */

    v_Payload := fl_IcmpDatagram_Create(p_IcmpMsgType, v_IcmpSequenceNumber, v_IcmpPayload);

    select (p_IcmpMsgType) {
      case (tsc_ICMP_Type_EchoRequest, tsc_ICMP_Type_EchoReply) {
        v_IpPacket := f_IPv4Packet_Create('10'O & int2oct(p_SequenceNumber, 1), -, tsc_IP_Protocol_ICMP, p_SourceAddr, p_DestAddr,  v_Payload);   /* @sic R5s120642 sic@ */
      }
      case (tsc_ICMPv6_Type_EchoRequest, tsc_ICMPv6_Type_EchoReply) {
        v_Payload := f_ICMPv6_UpdateCRCChecksum(p_SourceAddr, p_DestAddr, v_Payload); /* @sic R5s190117 - Additional change sic@ */
        v_IpPacket := f_IPv6Packet_Create(-, -, tsc_IP_Protocol_ICMPv6, -, p_SourceAddr, p_DestAddr, v_Payload);
      }
    }
    return v_IpPacket;
  }

  /*
   * @desc      depending on whether p_UE_Addr is an IPv4 or an IPv6 address an IPv4 or IPv6 packet is returned with an IcmpEchoReply as payload;
   *            when p_DestAddr is empty p_SourceAddr for both source and destination (that is mostly the case for loopback mode)
   * @param     p_SourceAddr
   * @param     p_DestAddr          (default value: "")
   * @param     p_SequenceNumber    (default value: 1)
   * @return    octetstring
   * @status    APPROVED (ENDC, LTE, LTE_IRAT, NBIOT, NR5GC, NR5GC_IRAT)
   */
  function f_IPv4IPv6_IcmpEchoReply(charstring p_SourceAddr,
                                    charstring p_DestAddr := "",
                                    integer p_SequenceNumber := 1) return octetstring
  {
    var charstring v_DestAddr:= p_DestAddr;
    var UInt8_Type v_IcmpMsgType;
    var octetstring v_IpPacket;
    
    if (v_DestAddr == "") { v_DestAddr := p_SourceAddr; }

    if (f_IpAddressIsIPv4(p_SourceAddr)) {
      v_IcmpMsgType := tsc_ICMP_Type_EchoReply;
    } else {
      v_IcmpMsgType := tsc_ICMPv6_Type_EchoReply;
    }
    v_IpPacket := fl_IPv4IPv6_IcmpEchoRequestReply(v_IcmpMsgType, p_SourceAddr, v_DestAddr, p_SequenceNumber);
    return v_IpPacket;
  }

  /*
   * @desc      depending on whether p_UE_Addr is an IPv4 or an IPv6 address an IPv4 or IPv6 packet is returned with an IcmpEchoRequest as payload
   * @param     p_SourceAddr
   * @param     p_DestAddr
   * @param     p_SequenceNumber
   * @return    octetstring
   * @status    APPROVED (ENDC, NR5GC, NR5GC_IRAT)
   */
  function f_IPv4IPv6_IcmpEchoRequest(charstring p_SourceAddr,
                                      charstring p_DestAddr,
                                      integer p_SequenceNumber) return octetstring
  {
    var UInt8_Type v_IcmpMsgType;
    var octetstring v_IpPacket;
    
    if (f_IpAddressIsIPv4(p_SourceAddr)) {
      v_IcmpMsgType := tsc_ICMP_Type_EchoRequest;
    } else {
      v_IcmpMsgType := tsc_ICMPv6_Type_EchoRequest;
    }
    v_IpPacket := fl_IPv4IPv6_IcmpEchoRequestReply(v_IcmpMsgType, p_SourceAddr, p_DestAddr, p_SequenceNumber);
    return v_IpPacket;
  }

  /*
   * @desc      depending on whether p_UE_Addr is an IPv4 or an IPv6 address an IPv4 or IPv6 packet is returned with a UDP datagram as payload
   * @param     p_SourceAddr
   * @param     p_DestAddr
   * @param     p_SourcePort
   * @param     p_DestPort
   * @param     p_Payload           (default value: tsc_IP_AnyData)
   * @return    octetstring
   * @status    APPROVED (LTE, LTE_A_PRO, NBIOT)
   */
  function f_IPv4IPv6_AnyUdpPacket(charstring p_SourceAddr,
                                   charstring p_DestAddr,
                                   UInt16_Type p_SourcePort,
                                   UInt16_Type p_DestPort,
                                   octetstring p_Payload := tsc_IP_AnyData) return octetstring
  {
    var UInt8_Type v_Protocol := tsc_IP_Protocol_UDP;
    var octetstring v_IpPacket;

    if (f_IpAddressIsIPv4(p_SourceAddr)) {
      v_IpPacket := f_IPv4Packet_Create(-, -, v_Protocol, p_SourceAddr, p_DestAddr, f_IPv4UdpDatagram_Create(p_SourceAddr, p_DestAddr, p_SourcePort, p_DestPort, p_Payload));
    } else {
      v_IpPacket := f_IPv6Packet_Create(-, -, v_Protocol, -, p_SourceAddr, p_DestAddr, f_IPv6UdpDatagram_Create(p_SourceAddr, p_DestAddr, p_SourcePort, p_DestPort, p_Payload));
    }
    return v_IpPacket;
  }
  
  //****************************************************************************

  type record IPv4IPv6PacketInfo_Type { /* @status    APPROVED (ENDC, NR5GC, NR5GC_IRAT) */
    UInt8_Type Protocol,
    charstring SourceAddr,
    charstring DestAddr,
    octetstring Payload
  };

  /*
   * @desc      Get relevant info of IP packet (protocol, source/destination address, payload)
   * @param     p_IpPacket
   * @return    IPv4IPv6PacketInfo_Type
   * @status    APPROVED (ENDC, NR5GC, NR5GC_IRAT)
   */
  function f_IPv4IPv6Packet_GetInfo(octetstring p_IpPacket) return IPv4IPv6PacketInfo_Type
  {
    var IPv4IPv6PacketInfo_Type v_IPv4IPv6PacketInfo;
    var UInt8_Type v_Protocol;
    var bitstring v_IpVersion;
    var bitstring v_IHL;
    var integer v_HeaderLength;
    var integer v_PayloadLength;
    var octetstring v_SourceAddrString;
    var octetstring v_DestAddrString;
    var charstring v_SourceAddr;
    var charstring v_DestAddr;

    v_IpVersion := substr(oct2bit(substr(p_IpPacket, 0, 1)), 0, 4);
    select (v_IpVersion) {
      case ('0100'B) {   // IPv4
        v_IHL := substr(oct2bit(substr(p_IpPacket, 0, 1)), 4, 4); //IHL is the 4 last bits of the first Octet.
        v_HeaderLength := bit2int(v_IHL) * 4;    /* length in bytes */
        v_Protocol:= oct2int(substr(p_IpPacket,   9, 1));
        v_SourceAddrString := substr(p_IpPacket, 12, 4);
        v_DestAddrString := substr(p_IpPacket,   16, 4);
        v_SourceAddr := f_Convert_OctString2IPv4Addr(v_SourceAddrString);
        v_DestAddr := f_Convert_OctString2IPv4Addr(v_DestAddrString);
      }
      case ('0110'B) {   // IPv6
        v_HeaderLength := 40;
        v_Protocol:= oct2int(substr(p_IpPacket,  6, 1));
        v_SourceAddrString := substr(p_IpPacket, 8, 16);
        v_DestAddrString := substr(p_IpPacket,  24, 16);
        v_SourceAddr := f_Convert_OctString2IPv6Addr(v_SourceAddrString);
        v_DestAddr := f_Convert_OctString2IPv6Addr(v_DestAddrString);
      }
    }
    v_PayloadLength := lengthof(p_IpPacket) - v_HeaderLength;
    v_IPv4IPv6PacketInfo.Protocol := v_Protocol;
    v_IPv4IPv6PacketInfo.SourceAddr := v_SourceAddr;
    v_IPv4IPv6PacketInfo.DestAddr := v_DestAddr;
    v_IPv4IPv6PacketInfo.Payload := substr(p_IpPacket, v_HeaderLength, v_PayloadLength);
    return v_IPv4IPv6PacketInfo;
  }

  //----------------------------------------------------------------------------
  /*
   * @desc      Check received IP packet against sent IP packet for check of user plane connectivity according to 38.508-1 clause 4.9.1
   * @param     p_IpPacketTX
   * @param     p_IpPacketRX
   * @param     p_CheckPING         (default value: true)
   * @return    boolean
   * @status    APPROVED (ENDC, NR5GC, NR5GC_IRAT)
   */
  function f_LoopbackModeBorPing_CheckDataRX(octetstring p_IpPacketTX,
                                             octetstring p_IpPacketRX,
                                             boolean p_CheckPING := true) return boolean
  {
    var IPv4IPv6PacketInfo_Type v_PacketInfoTX;
    var IPv4IPv6PacketInfo_Type v_PacketInfoRX;
    var IPv4IPv6PacketInfo_Type v_PacketInfoExpected;
    var integer v_SequenceNumber;
    var octetstring v_ExpectedIcmpEchoReplyPacket;
    var boolean v_UsePING := p_CheckPING and pc_IP_Ping; // @sic R5-206332 only use PING if required by test case sic@

    if (v_UsePING) {
      v_PacketInfoTX := f_IPv4IPv6Packet_GetInfo(p_IpPacketTX);
      v_PacketInfoRX := f_IPv4IPv6Packet_GetInfo(p_IpPacketRX);
      v_SequenceNumber := oct2int(substr(v_PacketInfoTX.Payload, 6, 2));

      v_ExpectedIcmpEchoReplyPacket := f_IPv4IPv6_IcmpEchoReply(v_PacketInfoTX.DestAddr, v_PacketInfoTX.SourceAddr, v_SequenceNumber);
      v_PacketInfoExpected := f_IPv4IPv6Packet_GetInfo(v_ExpectedIcmpEchoReplyPacket);

      return ((v_PacketInfoRX.Protocol == v_PacketInfoExpected.Protocol) and
              (v_PacketInfoRX.SourceAddr == v_PacketInfoExpected.SourceAddr) and
              (v_PacketInfoRX.DestAddr == v_PacketInfoExpected.DestAddr) and
              (v_PacketInfoRX.Payload == v_PacketInfoExpected.Payload));
    }
    else {
      return (p_IpPacketTX == p_IpPacketRX);
    }
  }

  //----------------------------------------------------------------------------
  /*
   * @desc      get IP packet to be sent to the UE for check of user plane connectivity according to 38.508-1 clause 4.9.1
   * @param     p_PdnType
   * @param     p_BearerType
   * @param     p_SequenceNumber
   * @param     p_CheckPING         (default value: true)
   * @return    octetstring
   * @status    APPROVED (ENDC, NR5GC, NR5GC_IRAT)
   */
  function f_LoopbackModeBorPing_GetDataTX(PDN_Index_Type p_PdnType,
                                           DataPathCheck_BearerType_Type p_BearerType,
                                           integer p_SequenceNumber,
                                           boolean p_CheckPING := true) return octetstring
  { /* @sic R5-193981, R5-193983: p_BearerType sic@ */
    var charstring v_IP_AddressUE := f_LoopbackModeB_IP_Address_UE(-, p_PdnType);
    var PDN_Index_Type v_PdnIndex := p_PdnType;
    var charstring v_SourceAddr;
    var charstring v_DestAddr;
    var octetstring v_IpPacket;
    var boolean v_UsePING := p_CheckPING and pc_IP_Ping; // @sic R5s190554 only use PING if required by test case sic@

    select (p_BearerType) {
      case (dedicatedBearer) {                                          /* Dedicated bearer */
        v_SourceAddr := f_LoopbackModeB_IP_Address_NW(-, v_PdnIndex);   /* source address is the NW address of the PDN which the DRB belongs to */
        if (v_UsePING) {
          v_DestAddr := v_IP_AddressUE;
        } else {
          v_DestAddr := v_SourceAddr;                                   /* same IP address for source and destination to match UL and DL packet filters */
        }
      }
      case (defaultBearer) {                                            /* Default bearer */
        v_DestAddr := v_IP_AddressUE;
        if (v_UsePING) {
          if (p_PdnType == PDN_1) {
            v_PdnIndex := PDN_2;
          } else {
            v_PdnIndex := PDN_1;
          }
          v_SourceAddr := f_LoopbackModeB_IP_Address_NW(-, v_PdnIndex); /* different address than NW address of the PDN which the DRB belongs to
                                                                           => IP packet does not match packet filter of dedicated bearer (if any) */
        } else {
          v_SourceAddr := v_DestAddr;                                   /* the UE's IP address as source and destination address as according to 36.523-3 clause 7.14.2 */
        }
      }
    }
    if (v_UsePING) {
      v_IpPacket := f_IPv4IPv6_IcmpEchoRequest(v_SourceAddr, v_DestAddr, p_SequenceNumber);
    } else {
      v_IpPacket := f_IPv4IPv6_IcmpEchoReply(v_SourceAddr, v_DestAddr, p_SequenceNumber);
    }
    return v_IpPacket;
  }

}
