/******************************************************************************
 * Copyright (c) 2000-2021 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
 ******************************************************************************/
//  File:     EPTF_CLL_TransportIPL2_Functions.ttcn
//  Rev:      <RnXnn>
//  Prodnr:   CNL 113 512
//  Updated:  2012-02-17
//  Contact:  http://ttcn.ericsson.se
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////
//  Module: EPTF_CLL_TransportIPL2_Functions
// 
//  Purpose:
//    Implementation of UDP transport over IP Layer 2 (LANL2 test port).
// 
//  Module Parameters:
//    tsp_EPTF_TransportIPL2_enableLogging - *boolean*
//    tsp_EPTF_TransportIPL2_loggingComponentMask - *charstring*
//    tsp_EPTF_TransportIPL2_enableStats - *boolean*
//    tsp_EPTF_TransportIPL2_multipleInterfacesMode - *boolean*
//    tsp_EPTF_TransportIPL2_loopbackInterface - *charstring*
//    tsp_EPTF_TransportIPL2_loopbackInterfaceAddress - *charstring*
//    tsp_EPTF_TransportIPL2_loopbackInterfaceMask *charstring*
//    tsp_EPTF_TransportIPL2_dstMacAddr - <OCT6>
//    tsp_EPTF_TransportIPL2_srcMacAddr - <OCT6>
//    tsp_EPTF_TransportIPL2_MTU - *integer*
//    tsp_EPTF_TransportIPL2_ipFragmentRemoveTimer - *float*
// 
//  Module depends on:
//    <EPTF_CLL_Base_Functions>
//    <EPTF_CLL_Common_Definitions>
//    <EPTF_CLL_Common_Functions>
//    <EPTF_CLL_FBQ_Definitions>
//    <EPTF_CLL_FBQ_Functions>
//    <EPTF_CLL_HashMapOct2Int_Functions>
//    <EPTF_CLL_HashMapStr2Int_Functions>
//    <EPTF_CLL_Logging_Definitions>
//    <EPTF_CLL_Logging_Functions>
//    <EPTF_CLL_RBTScheduler_Functions>
//    <EPTF_CLL_Scheduler_Definitions>
//    <EPTF_CLL_Transport_CommonDefinitions>
//    <EPTF_CLL_TransportIPL2_Definitions>
//    <EPTF_CLL_Variable_Definitions>
//    <EPTF_CLL_Variable_Functions>
//    <EPTF_CLL_Semaphore_Functions>
//    <General_Types>
//    <IP_Types>
//    <LANL2asp_Types>
//    <TCCConversion_Functions>
//    <TCCInterface_Functions>
//    <TCP_Types>
//    <Socket_API_Definitions>
//    <EPTF_CLL_HashMap_Functions>
// 
//  Current Owner:
//    EJNOSVN, EGBOTAT
// 
//  Last Review Date:
//    -
// 
//  Detailed Comments:
//    -
//
//  Public API:
//    <f_EPTF_Transport_init>
//    <f_EPTF_Transport_registerMsgLenCallback4LGenType>
//    <f_EPTF_Transport_setUserData>
//    <f_EPTF_Transport_getUserData>
//    <f_EPTF_TransportIPL2_setMsgHandler>
//    <f_EPTF_Transport_send>
//    <f_EPTF_Transport_sendTo>
//    <f_EPTF_Transport_listen>
//    <f_EPTF_Transport_connect>
//    <f_EPTF_TransportIPL2_connectById>
//    <f_EPTF_Transport_close>
//    <f_EPTF_TransportIPL2_getLocalHost>
//    <f_EPTF_TransportIPL2_getLocalPort>
//    <f_EPTF_Transport_getProto>
//    <f_EPTF_Transport_registerMsgCallback>
//    <f_EPTF_Transport_getLocalAddress>
//    <f_EPTF_Transport_getRemoteAddress>
//    <f_EPTF_Transport_setUpInterfaces>
//    <f_EPTF_Transport_appendInterfaces>
//    <f_EPTF_Transport_setDownInterfaces>
//    <f_EPTF_SegmentBuffer_initBuffer>
//    <f_EPTF_SegmentBuffer_setFinalSegmentOffset>
//    <f_EPTF_SegmentBuffer_insertSegment>
//    <f_EPTF_SegmentBuffer_getContinousLen>
//    <f_EPTF_SegmentBuffer_getNonContinousLen>
//    <f_EPTF_SegmentBuffer_getBuffer>
//    <f_EPTF_SegmentBuffer_truncBuffer>
//    <f_EPTF_TransportIPL2_getNextHop>
//    <f_EPTF_TransportIPL2_normalizeRoutingTable>
//    <f_EPTF_TransportIPL2_getHostRoutingTable>
//    <f_EPTF_TransportIPL2_getRouteTable>
//    <f_EPTF_TransportIPL2_setRouteTable>
//    <f_EPTF_TransportIPL2_addRoute>
//    <f_EPTF_TransportIPL2_ARP_getMac>
//
///////////////////////////////////////////////////////////
module EPTF_CLL_TransportIPL2_Functions {


//=========================================================================
// Import Part
//=========================================================================

import from EPTF_CLL_Base_Functions all;
import from EPTF_CLL_Common_Definitions all;
import from EPTF_CLL_Common_Functions all;
import from EPTF_CLL_FBQ_Definitions all;
import from EPTF_CLL_FBQ_Functions all;
import from EPTF_CLL_HashMapOct2Int_Functions all;
import from EPTF_CLL_HashMapStr2Int_Functions all;
import from EPTF_CLL_Logging_Definitions all;
import from EPTF_CLL_Logging_Functions all;
import from EPTF_CLL_RBTScheduler_Functions all;
import from EPTF_CLL_Scheduler_Definitions all;
import from EPTF_CLL_Transport_CommonDefinitions all;
import from EPTF_CLL_TransportIPL2_Definitions all;
import from EPTF_CLL_Variable_Definitions all;
import from EPTF_CLL_Variable_Functions all;
import from EPTF_CLL_Semaphore_Functions all;


import from General_Types all;
import from IP_Types all;
import from LANL2asp_Types all;
import from LANL2asp_PortType all;
import from TCCConversion_Functions all;
import from TCCInterface_Functions all;
import from TCP_Types all;

import from Socket_API_Definitions all;
import from EPTF_CLL_Buffer_Functions all;

import from EPTF_CLL_HashMap_Functions all;

friend module EPTF_Transport_Test_Testcases, EPTF_Transport_Test_perfTestcases;
friend module EPTF_CLL_Transport_Functions;

//=========================================================================
// Module parameters
//========================================================================
modulepar boolean tsp_EPTF_TransportIPL2_enableLogging := false;
modulepar charstring tsp_EPTF_TransportIPL2_loggingComponentMask := "EPTF_TransportIPL2";
modulepar boolean tsp_EPTF_TransportIPL2_enableStats := true;

modulepar boolean tsp_EPTF_TransportIPL2_multipleInterfacesMode := true; // note: if this is true, IPL2_PCO will be set to multiple interfaces mode automatically as well
  // in multiple interfaces mode, the interface information parameters passed in to the init function will be used to open interfaces
  // additional interfaces can be later configured using appendInterfaces()
modulepar boolean tsp_EPTF_TransportIPL2_openLoopbackInterface := false; // whether to open the loopback interface automatically (without being added in setUpInterfaces or appendInterfaces!) in multiple interfaces mode
modulepar charstring tsp_EPTF_TransportIPL2_loopbackInterface := "lo"; // loopback interface name, used only in multiple interfaces mode
modulepar charstring tsp_EPTF_TransportIPL2_loopbackInterfaceAddress := "127.0.0.0"; // default subnet address for loopback interface
modulepar charstring tsp_EPTF_TransportIPL2_loopbackInterfaceMask := "255.0.0.0"; // default subnet mask for loopback interface

modulepar OCT6 tsp_EPTF_TransportIPL2_dstMacAddr := cg_EPTF_TransportIPL2_NullMACAddress;
modulepar OCT6 tsp_EPTF_TransportIPL2_srcMacAddr := cg_EPTF_TransportIPL2_NullMACAddress; // needed in single interface mode

modulepar integer tsp_EPTF_TransportIPL2_MTU := 1500; // Maximum Transmission Unit

modulepar boolean tsp_EPTF_TransportIPL2_enableIpAddressFilter := false;

modulepar float tsp_EPTF_TransportIPL2_ipFragmentRemoveTimer := 0.01;

modulepar integer tsp_EPTF_TransportIPL2_tcpMSS := c_EPTF_TransportIPL2_tcpDefaultMSS; // Maximum Segment Size for TCP

modulepar float tsp_EPTF_TransportIPL2_tcpMSL := c_EPTF_TransportIPL2_tcpDefaultMSL; // Maximum Segment Lifetime for TCP

modulepar boolean tsp_EPTF_TransportIPL2_tcpAllowHalfClose := true; // allow half-closed sockets (the standard allows it)

modulepar integer tsp_EPTF_TransportIPL2_tcpDefaultReceiveWindowSize := 4096;
modulepar float tsp_EPTF_TransportIPL2_tcpMaxAckDelay := 0.2;
modulepar float tsp_EPTF_TransportIPL2_tcpConnectionTimeout := c_EPTF_TransportIPL2_tcpDefaultMSL;

modulepar float tsp_EPTF_TransportIPL2_tcpMinRetransmitTime := 0.5;
modulepar float tsp_EPTF_TransportIPL2_tcpMaxRetransmitTime := 30.0;
modulepar boolean tsp_EPTF_TransportIPL2_tcpCalculateRTT := true;
modulepar float tsp_EPTF_TransportIPL2_tcpSmoothAlpha := 0.8; // used for calculating smoothd RTT, see RFC 793
modulepar float tsp_EPTF_TransportIPL2_tcpSmoothBeta := 1.3; // used for calculating retransmit time from smoothd RTT, see RFC 793

modulepar float tsp_EPTF_TransportIPL2_arpMaxWaitTime := 10.0;

modulepar boolean tsp_EPTF_TransportIPL2_sendFifo_useCLLBuffer := false; // sendFifo is an octetstring of CLL_Buffer? 
modulepar boolean tsp_EPTF_SegmentBuffer_useCLLBuffer := false;			 // SegmentBuffer uses octetstring to store the continous part or CLL_Buffer?

external function f_EPTF_TransportIPL2_encodeIP(in IPv4_packet pdu) return octetstring;
external function f_EPTF_TransportIPL2_decodeIP(in octetstring data, out IPv4_packet pdu) return boolean;
external function f_EPTF_TransportIPL2_encodeUDPWithChecksum(
    in octetstring srcaddr,
    in integer srcpotr,
    in octetstring dstaddr,
    in integer dstport,
    in octetstring payload) return octetstring;
external function f_EPTF_TransportIPL2_decodeUDPWithChecksum(
    in octetstring srcaddr,
    in octetstring dstaddr,
    in octetstring udppdu,
    out integer srcpotr,
    out integer dstport,
    out octetstring payload) return boolean; // false if decoding or checksum check fails, true on valid payload
external function f_EPTF_TransportIPL2_encodeTCPWithChecksum(
    in octetstring srcaddr,
    in octetstring dstaddr,
    in EPTF_TransportIPL2_PDU_TCP pdu) return octetstring;
external function f_EPTF_TransportIPL2_verifyTCPChecksum(
                in octetstring srcaddr,
                in octetstring dstaddr,
  		in octetstring tcppdu) return boolean; // false if checksum verification fails
external function f_EPTF_TransportIPL2_decodeTCP(
    in octetstring tcppdu,
    out EPTF_TransportIPL2_PDU_TCP pdu) return boolean; // false if decoding fails, true on valid payload

// bitwise logical operations for integers
external function f_EPTF_and4i(in integer i1, in integer i2) return integer;
external function f_EPTF_or4i(in integer i1, in integer i2) return integer;
external function f_EPTF_xor4i(in integer i1, in integer i2) return integer;
external function f_EPTF_not4i(in integer i) return integer;
external function f_EPTF_intShiftLeft(in integer i, in integer bits) return integer;
external function f_EPTF_intShiftRight(in integer i, in integer bits) return integer;

// integer modulo32 operations
external function f_EPTF_mod32Add(in integer i1, in integer i2) return integer;
external function f_EPTF_mod32Sub(in integer i1, in integer i2) return integer;
external function f_EPTF_mod32Less(in integer i1, in integer i2) return boolean; // returns true if i1 < i2
external function f_EPTF_mod32LessOrEqual(in integer i1, in integer i2) return boolean;
external function f_EPTF_mod32Greater(in integer i1, in integer i2) return boolean; // returns true if i1 < i2
external function f_EPTF_mod32GreaterOrEqual(in integer i1, in integer i2) return boolean;

///////////////////////////////////////////////////////////
//  Altstep: as_EPTF_TransportIPL2_defaultReceive
// 
//  Purpose:
//    Default altstep to handle receiving messages 
///////////////////////////////////////////////////////////
private altstep as_EPTF_TransportIPL2_defaultReceive ()
  runs on EPTF_TransportIPL2_CT
  {
    var ASP_LANL2 vl_message;
    var ASP_v2_LANL2 vl_message_v2;
    var ASP_LANL2_Error vl_error;
    var ASP_v2_LANL2_Error vl_error_v2;

    [] IPL2_PCO.receive (ASP_LANL2 : ?) -> value vl_message
    {
      f_EPTF_TransportIPL2_handleIncomingPacket(-1, vl_message.type_field, vl_message.payload);
      repeat;
    }

    [] IPL2_PCO.receive (ASP_v2_LANL2 : ?) -> value vl_message_v2
    {
      f_EPTF_TransportIPL2_handleIncomingPacket(vl_message_v2.interface_id, vl_message_v2.type_field, vl_message_v2.payload);
      repeat;
    }

    [] IPL2_PCO.receive (ASP_LANL2_Error : ?) -> value vl_error
    {
      if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
        f_EPTF_TransportIPL2_debug(%definitionId&": Got error ASP from IPL2_PCO: " & log2str(vl_error));
      }
      repeat;
    }

    [] IPL2_PCO.receive (ASP_v2_LANL2_Error : ?) -> value vl_error_v2
    {
      if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
        f_EPTF_TransportIPL2_debug(%definitionId&": Got error ASP from IPL2_PCO: " & log2str(vl_error_v2));
      }
      repeat;
    }

    [] IPL2_PCO.receive { repeat; }
  }

///////////////////////////////////////////////////////////
//  Function: f_EPTF_TransportIPL2_handleIncomingPacket
// 
//  Purpose:
//    Function to handle received ethernet packets
///////////////////////////////////////////////////////////
private function f_EPTF_TransportIPL2_handleIncomingPacket(
  integer       interface_id,
//  octetstring   eth_dst_addr,
//  octetstring   eth_src_addr,
  octetstring   type_field,
  octetstring   payload)
runs on EPTF_TransportIPL2_CT
{
  if (type_field == int2oct(c_ip_gre_proto_ipv4, 2)) {
    // if we have an ip packet, decode it.

    if(tsp_EPTF_TransportIPL2_enableIpAddressFilter) {
      // This code is used to filter incoming packets based on the destination IP,
      // IP addresses that we are listening on, are added to this filter in newLocalPort.
      // Without this filter, if more than one LGen is running, they all do the
      // IP and UDP decode, checksum check and hashmap search to see if the packet is for the given LGen.
      var octetstring vl_destIp := substr(payload, 16, 4);
      if(not f_EPTF_TransportIPL2_hasIp(vl_destIp)) {
        if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
          f_EPTF_TransportIPL2_debug(%definitionId&": IP address "&f_EPTF_TransportIPL2_ip2str(vl_destIp)&" not in filter. Dropping message.");
        }
        return;
      }
    }

    var octetstring ck1 := payload[10];
    var octetstring ck2 := payload[11];
    payload[10] := '00'O;
    payload[11] := '00'O;
    var octetstring chksum := f_IPv4_checksum( payload );

    if (ck1 == chksum[0] and ck2 == chksum[1]) {
//          var IPv4_packet vl_ipv4Message := f_IPv4_dec(data := payload);
      var IPv4_packet vl_ipv4Message;
      if(f_EPTF_TransportIPL2_decodeIP(payload, vl_ipv4Message)) {
        f_EPTF_TransportIPL2_incomingIPFragment(
          vl_ipv4Message.header.srcaddr,
          vl_ipv4Message.header.dstaddr,
          vl_ipv4Message.header.proto,
          vl_ipv4Message.header.id,
          vl_ipv4Message.payload,
          vl_ipv4Message.header.foffset * c_EPTF_TransportIPL2_ipFragmentOffsetScale,
          vl_ipv4Message.header.mfrag != '1'B
        );
      } else {

        if (vl_ipv4Message.header.proto == c_ip_proto_udp) {
          f_EPTF_TransportIPL2_incomingUdpPacket(
            vl_ipv4Message.header.srcaddr,
            vl_ipv4Message.header.dstaddr,
            vl_ipv4Message.payload);
        } else if (vl_ipv4Message.header.proto == c_ip_proto_tcp) {
          f_EPTF_TransportIPL2_incomingTcpSegment(
            vl_ipv4Message.header.srcaddr,
            vl_ipv4Message.header.dstaddr,
            vl_ipv4Message.payload);
        } else {
          if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
            f_EPTF_TransportIPL2_debug(%definitionId&": Unsupported protocol in IP datagram: " & int2str(vl_ipv4Message.header.proto));
          }
        }

      }
    } else {
      if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
        f_EPTF_TransportIPL2_debug(%definitionId&": incorrect IP checksum "&oct2str(chksum)&" should be " & oct2str(ck1) & oct2str(ck2));
      }
    }
  } else if (type_field == int2oct(c_ip_gre_proto_arp, 2)) {
    f_EPTF_TransportIPL2_debug(%definitionId&": ARP received");
    var ARP_packet vl_arpPacket;
    f_EPTF_TransportIPL2_decodeARP(payload, vl_arpPacket)

    if (f_EPTF_TransportIPL2_hasIp(vl_arpPacket.targetIPAddr)) { 
      if (vl_arpPacket.arpType == cg_EPTF_TransportIPL2_ARP_TypeReply) {
        // Handle ARP reply message
        if (f_EPTF_TransportIPL2_addMacToGwArpDB(vl_arpPacket)
            or f_EPTF_TransportIPL2_addMacToIpArpDB(vl_arpPacket)) {
          if (c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
            f_EPTF_TransportIPL2_debug(%definitionId&": MAC found for IP: "&log2str(vl_arpPacket));
          }
        }
      } else if (vl_arpPacket.arpType == cg_EPTF_TransportIPL2_ARP_TypeRequest) {
        // Handle ARP request message
        var EPTF_TransportIPL2_MACAddress vl_srcMac := tsp_EPTF_TransportIPL2_srcMacAddr;
        if(interface_id >= 0) {
          vl_srcMac := v_EPTF_TransportIPL2_ethernetInterfaceList[interface_id].macAddress;
        }
        f_EPTF_TransportIPL2_ARP_sendMACReplyForIp(
          interface_id,
          vl_arpPacket.targetIPAddr,
          vl_srcMac,
          vl_arpPacket.senderIPAddr,
          vl_arpPacket.senderMACAddr);

        if (c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
          f_EPTF_TransportIPL2_debug(%definitionId&": Send ARP reply to "&
            f_EPTF_TransportIPL2_ip2str(vl_arpPacket.senderIPAddr));
        }
      } else {
        if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
          f_EPTF_TransportIPL2_debug(%definitionId&": invalid type in ARP message: "&log2str(vl_arpPacket));
        }
      }
    }
  }else if(type_field == int2oct(oct2int('8100'O), 2)){//VLAN packet
    payload := substr(payload, 2, lengthof(payload)-2);
log("MACI1: ", payload);    
while(substr(payload, 0, 2) == c_EPTF_TransportIPL2_VLAN_TPID)
    {
      payload := substr(payload, 4, lengthof(payload)-4 );
    }
log("MACI2: ", payload);    
    f_EPTF_TransportIPL2_handleIncomingPacket(interface_id, substr(payload, 0, 2), substr(payload, 2, lengthof(payload)-2));
  }else {
    if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
      f_EPTF_TransportIPL2_debug(%definitionId&": not an IP packet, type_field: "&log2str(type_field));
    }
  }
}

///////////////////////////////////////////////////////////
//  Function: f_EPTF_TransportIPL2_initStatistics
// 
//  Purpose:
//    Function to initialize the statistics
//
//  Parameters:
//    -
//
//  Return Value:
//    - 
//
//  Errors:
//    -
//
//  Detailed Comments:
//    
///////////////////////////////////////////////////////////  
private function f_EPTF_TransportIPL2_initStatistics ()
runs on EPTF_TransportIPL2_CT
{
  f_EPTF_Var_newInt(c_EPTF_TransportIPL2_varNamePrefix&c_EPTF_TransportIPL2_incMessageName, 0, v_EPTF_TransportIPL2_incStats[c_EPTF_TransportIPL2_incMessage]);
  f_EPTF_Var_newInt(c_EPTF_TransportIPL2_varNamePrefix&c_EPTF_TransportIPL2_incConnOpenedName, 0, v_EPTF_TransportIPL2_incStats[c_EPTF_TransportIPL2_incConnOpened]);
  f_EPTF_Var_newInt(c_EPTF_TransportIPL2_varNamePrefix&c_EPTF_TransportIPL2_incConnClosedName, 0, v_EPTF_TransportIPL2_incStats[c_EPTF_TransportIPL2_incConnClosed]);
  v_EPTF_TransportIPL2_incStatCounters[c_EPTF_TransportIPL2_incMessage] := 0;
  v_EPTF_TransportIPL2_incStatCounters[c_EPTF_TransportIPL2_incConnOpened] := 0;
  v_EPTF_TransportIPL2_incStatCounters[c_EPTF_TransportIPL2_incConnClosed] := 0;

  f_EPTF_Var_newInt(c_EPTF_TransportIPL2_varNamePrefix&c_EPTF_TransportIPL2_outMessageName, 0, v_EPTF_TransportIPL2_outStats[c_EPTF_TransportIPL2_outMessage]);
  f_EPTF_Var_newInt(c_EPTF_TransportIPL2_varNamePrefix&c_EPTF_TransportIPL2_outCloseName, 0, v_EPTF_TransportIPL2_outStats[c_EPTF_TransportIPL2_outClose]);
  f_EPTF_Var_newInt(c_EPTF_TransportIPL2_varNamePrefix&c_EPTF_TransportIPL2_outConnectName, 0, v_EPTF_TransportIPL2_outStats[c_EPTF_TransportIPL2_outConnect]);
  f_EPTF_Var_newInt(c_EPTF_TransportIPL2_varNamePrefix&c_EPTF_TransportIPL2_outListenName, 0, v_EPTF_TransportIPL2_outStats[c_EPTF_TransportIPL2_outListen]);
  v_EPTF_TransportIPL2_outStatCounters[c_EPTF_TransportIPL2_outMessage] := 0;
  v_EPTF_TransportIPL2_outStatCounters[c_EPTF_TransportIPL2_outClose] := 0;
  v_EPTF_TransportIPL2_outStatCounters[c_EPTF_TransportIPL2_outConnect] := 0;
  v_EPTF_TransportIPL2_outStatCounters[c_EPTF_TransportIPL2_outListen] := 0;

  f_EPTF_Var_newInt(c_EPTF_TransportIPL2_varNamePrefix&c_EPTF_TransportIPL2_nofErrorsName,0, v_EPTF_TransportIPL2_nofErrors);
  f_EPTF_Var_newCharstring(c_EPTF_TransportIPL2_varNamePrefix&c_EPTF_TransportIPL2_lastErrorStringName, "",  v_EPTF_TransportIPL2_lastErrorString);
}


///////////////////////////////////////////////////////////
//  Function: f_EPTF_TransportIPL2_resetStatistics
// 
//  Purpose:
//    Function to reset the statistics
//
//  Parameters:
//    -
//
//  Return Value:
//    - 
//
//  Errors:
//    -
//
//  Detailed Comments:
//    
///////////////////////////////////////////////////////////  
private function f_EPTF_TransportIPL2_resetStatistics ()
runs on EPTF_TransportIPL2_CT
{
  if(tsp_EPTF_TransportIPL2_enableStats) {
    f_EPTF_Var_adjustContent(v_EPTF_TransportIPL2_incStats[c_EPTF_TransportIPL2_incMessage], {intVal:=0});
    f_EPTF_Var_adjustContent(v_EPTF_TransportIPL2_incStats[c_EPTF_TransportIPL2_incConnOpened], {intVal:=0});
    f_EPTF_Var_adjustContent(v_EPTF_TransportIPL2_incStats[c_EPTF_TransportIPL2_incConnClosed], {intVal:=0});
    v_EPTF_TransportIPL2_incStatCounters[c_EPTF_TransportIPL2_incMessage] := 0;
    v_EPTF_TransportIPL2_incStatCounters[c_EPTF_TransportIPL2_incConnOpened] := 0;
    v_EPTF_TransportIPL2_incStatCounters[c_EPTF_TransportIPL2_incConnClosed] := 0;

    f_EPTF_Var_adjustContent(v_EPTF_TransportIPL2_outStats[c_EPTF_TransportIPL2_outMessage], {intVal:=0});
    f_EPTF_Var_adjustContent(v_EPTF_TransportIPL2_outStats[c_EPTF_TransportIPL2_outClose], {intVal:=0});
    f_EPTF_Var_adjustContent(v_EPTF_TransportIPL2_outStats[c_EPTF_TransportIPL2_outConnect], {intVal:=0});
    f_EPTF_Var_adjustContent(v_EPTF_TransportIPL2_outStats[c_EPTF_TransportIPL2_outListen], {intVal:=0});
    v_EPTF_TransportIPL2_outStatCounters[c_EPTF_TransportIPL2_outMessage] := 0;
    v_EPTF_TransportIPL2_outStatCounters[c_EPTF_TransportIPL2_outClose] := 0;
    v_EPTF_TransportIPL2_outStatCounters[c_EPTF_TransportIPL2_outConnect] := 0;
    v_EPTF_TransportIPL2_outStatCounters[c_EPTF_TransportIPL2_outListen] := 0;


    f_EPTF_Var_adjustContent(v_EPTF_TransportIPL2_nofErrors, {intVal:=0});
    f_EPTF_Var_adjustContent(v_EPTF_TransportIPL2_lastErrorString, {charstringVal:=""});
  }
}


///////////////////////////////////////////////////////////
//  Function: f_EPTF_TransportIPL2_updateIncomingStatistics
// 
//  Purpose:
//    Function to update the incoming statistics
//
//  Parameters:
//    pl_idx - *in* - *integer* - the index of the statistic
//
//  Return Value:
//    - 
//
//  Errors:
//    -
//
//  Detailed Comments:
//    
///////////////////////////////////////////////////////////  
private function f_EPTF_TransportIPL2_updateIncomingStatistics (
  in integer pl_idx)
runs on EPTF_TransportIPL2_CT
{
  if(tsp_EPTF_TransportIPL2_enableStats) {
    var integer vl_varIdx := v_EPTF_TransportIPL2_incStats[pl_idx];

    v_EPTF_TransportIPL2_incStatCounters[pl_idx] := v_EPTF_TransportIPL2_incStatCounters[pl_idx] + 1;

    f_EPTF_Var_setContent(vl_varIdx, {intVal := v_EPTF_TransportIPL2_incStatCounters[pl_idx]});
  }
}

///////////////////////////////////////////////////////////
//  Function: f_EPTF_TransportIPL2_increaseErrors
// 
//  Purpose:
//    Function to increase the number of the errors
//
//  Parameters:
//    -
//
//  Return Value:
//    - 
//
//  Errors:
//    -
//
//  Detailed Comments:
//    
///////////////////////////////////////////////////////////  
private function f_EPTF_TransportIPL2_increaseErrors ()
runs on EPTF_TransportIPL2_CT
{
  var EPTF_Var_DirectContent vl_prevContent;

  f_EPTF_Var_getContent(v_EPTF_TransportIPL2_nofErrors, vl_prevContent);

  f_EPTF_Var_adjustContent(v_EPTF_TransportIPL2_nofErrors, {intVal := vl_prevContent.intVal + 1 });
}

///////////////////////////////////////////////////////////
//  Function: f_EPTF_TransportIPL2_updateOutgoingStatistics
// 
//  Purpose:
//    Function to update the outgoing statistics
//
//  Parameters:
//    pl_idx - *in* - *integer* - the index of the statistic
//
//  Return Value:
//    - 
//
//  Errors:
//    -
//
//  Detailed Comments:
//    
///////////////////////////////////////////////////////////   
private function f_EPTF_TransportIPL2_updateOutgoingStatistics (
  in integer pl_idx)
runs on EPTF_TransportIPL2_CT
{
  if(tsp_EPTF_TransportIPL2_enableStats) {
    var integer vl_varIdx := v_EPTF_TransportIPL2_outStats[pl_idx];

    v_EPTF_TransportIPL2_outStatCounters[pl_idx] := v_EPTF_TransportIPL2_outStatCounters[pl_idx] + 1;

    f_EPTF_Var_setContent(vl_varIdx, {intVal := v_EPTF_TransportIPL2_outStatCounters[pl_idx]});
  }
}

///////////////////////////////////////////////////////////
//  Function: f_EPTF_TransportIPL2_initExtendedComponents
// 
//  Purpose:
//    Function to initialize extended components
//
//  Parameters:
//    pl_selfName - *in* - *charstring* - The name of the LGen
//
//  Return Value:
//    - 
//
//  Errors:
//    -
//
//  Detailed Comments:
//    
///////////////////////////////////////////////////////////    
private function f_EPTF_TransportIPL2_initExtendedComponents (
  in charstring pl_selfName)
runs on EPTF_TransportIPL2_CT
{
  f_EPTF_Base_init_CT(pl_selfName);
  f_EPTF_FBQ_init_CT(pl_selfName);
  f_EPTF_Logging_init_CT(pl_selfName);
  f_EPTF_Var_init_CT(pl_selfName);
  f_EPTF_Scheduler_init_CT(pl_selfName);
  f_EPTF_Semaphore_init_CT(pl_selfName);
  f_EPTF_SegmentBuffer_init_CT(pl_selfName);
}

private function f_EPTF_TransportIPL2_initLoopbackInterface()
runs on EPTF_TransportIPL2_CT
{
  for(var integer i:=0; i<sizeof(v_EPTF_TransportIPL2_ethernetInterfaceList); i:=i+1) {
    if(v_EPTF_TransportIPL2_ethernetInterfaceList[i].name == tsp_EPTF_TransportIPL2_loopbackInterface) {
      v_EPTF_TransportIPL2_loopbackIF := i;
      break;
    }
  }
  if(v_EPTF_TransportIPL2_loopbackIF < 0) {
    v_EPTF_TransportIPL2_loopbackIF := sizeof(v_EPTF_TransportIPL2_ethernetInterfaceList);
    v_EPTF_TransportIPL2_ethernetInterfaceList[v_EPTF_TransportIPL2_loopbackIF] := c_EPTF_TransportIPL2_EthernetInterface_init;
    v_EPTF_TransportIPL2_ethernetInterfaceList[v_EPTF_TransportIPL2_loopbackIF].name := tsp_EPTF_TransportIPL2_loopbackInterface;
  }
  var octetstring vl_mask := f_EPTF_TransportIPL2_getIpFromHostName(tsp_EPTF_TransportIPL2_loopbackInterfaceMask);
  v_EPTF_TransportIPL2_ethernetInterfaceList[v_EPTF_TransportIPL2_loopbackIF].packetFilter := {
    filterString := "arp or udp or tcp and net "&
      tsp_EPTF_TransportIPL2_loopbackInterfaceAddress & " mask "& tsp_EPTF_TransportIPL2_loopbackInterfaceMask,
    subnets := {
      {
        net := f_EPTF_TransportIPL2_getIpFromHostName(tsp_EPTF_TransportIPL2_loopbackInterfaceAddress),
        mask := vl_mask,
        maskbits := f_EPTF_TransportIPL2_nofBitsInNetmask(vl_mask)
      }
    }
  }
}

private function f_EPTF_TransportIPL2_openInterface(in integer pl_interfaceIdx)
runs on EPTF_TransportIPL2_CT
{
  var ASP_LANL2_open_interface vl_open := {
    interface_name := v_EPTF_TransportIPL2_ethernetInterfaceList[pl_interfaceIdx].name,
    default_src_addr := omit,
    promisc_mode := v_EPTF_TransportIPL2_ethernetInterfaceList[pl_interfaceIdx].promiscousMode,
    packet_filter := f_EPTF_TransportIPL2_getPacketFilterString(pl_interfaceIdx)
  }

  if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
    f_EPTF_TransportIPL2_debug(%definitionId&": interface open: " & log2str(vl_open));
  }
  IPL2_PCO.send(vl_open);

  var ASP_LANL2_open_result vl_res;
  var ASP_v2_LANL2_Error vl_err;
  timer T_guard := 5.0;
  T_guard.start;
  alt {
    [] IPL2_PCO.receive(ASP_LANL2_open_result:?) -> value vl_res { }
    [] IPL2_PCO.receive(ASP_v2_LANL2_Error:?) -> value vl_err {
      f_EPTF_TransportIPL2_error(%definitionId&": could not open interface "& v_EPTF_TransportIPL2_ethernetInterfaceList[pl_interfaceIdx].name);
    }
    [] IPL2_PCO.receive {
      f_EPTF_TransportIPL2_warning(%definitionId&": unexpected message received on IPL2_PCO.");
      repeat;
    }
    [] T_guard.timeout {
      f_EPTF_TransportIPL2_error(%definitionId&": timeout while waiting for interface open result from IPL2_PCO.");
    }
  }
  T_guard.stop;

  if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
    f_EPTF_TransportIPL2_debug(%definitionId&": interface open result: " & log2str(vl_res));
  }
  if(not vl_res.success) {
    f_EPTF_TransportIPL2_error(%definitionId&": could not open interface "& v_EPTF_TransportIPL2_ethernetInterfaceList[pl_interfaceIdx].name);
  }

  v_EPTF_TransportIPL2_ethernetInterfaceList[pl_interfaceIdx].interfaceId := vl_res.interface_id;
  v_EPTF_TransportIPL2_ethernetInterfaceList[pl_interfaceIdx].macAddress := vl_res.default_src_addr;
}

private function f_EPTF_TransportIPL2_closeInterface(in integer pl_interfaceIdx)
runs on EPTF_TransportIPL2_CT
{
  var ASP_LANL2_close_interface vl_close := {
    interface_id := v_EPTF_TransportIPL2_ethernetInterfaceList[pl_interfaceIdx].interfaceId
  }
  if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
    f_EPTF_TransportIPL2_debug(%definitionId&": closing interface " & v_EPTF_TransportIPL2_ethernetInterfaceList[pl_interfaceIdx].name);
  }
  IPL2_PCO.send(vl_close);

  var ASP_v2_LANL2_Error vl_err;
  timer T_dummy := 0.0;
  T_dummy.start;
  alt {
    [] IPL2_PCO.receive(ASP_v2_LANL2_Error:?) -> value vl_err {
      if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
        f_EPTF_TransportIPL2_debug(%definitionId&": interface close failed: " & log2str(vl_err));
      }
    }
    [] T_dummy.timeout { }
  }
  v_EPTF_TransportIPL2_ethernetInterfaceList[pl_interfaceIdx].interfaceId := -1;
}

private function f_EPTF_TransportIPL2_openConfiguredInterfaces()
runs on EPTF_TransportIPL2_CT
{
  for(var integer i:=0; i<sizeof(v_EPTF_TransportIPL2_ethernetInterfaceList); i:=i+1) {
    if( v_EPTF_TransportIPL2_openLoopbackInterface == false and
        i == v_EPTF_TransportIPL2_loopbackIF) {
      f_EPTF_TransportIPL2_debug(%definitionId&": skipping open for loopback interface");
    } else {
      f_EPTF_TransportIPL2_genPacketFilter(i);
      f_EPTF_TransportIPL2_openInterface(i);
    }
  }

  f_EPTF_TransportIPL2_updateRouteTableToEthIF();
}

private function f_EPTF_TransportIPL2_closeConfiguredInterfaces()
runs on EPTF_TransportIPL2_CT
{
  for(var integer i:=0; i<sizeof(v_EPTF_TransportIPL2_ethernetInterfaceList); i:=i+1) {
    if(v_EPTF_TransportIPL2_ethernetInterfaceList[i].interfaceId >= 0) {
      f_EPTF_TransportIPL2_closeInterface(i);
    }
  }
}

///////////////////////////////////////////////////////////
//  Function: f_EPTF_Transport_init
// 
//  Purpose:
//    Function to initialize the IPL2 Communication port component
//
//  Parameters:
//    pl_transportType - *in* - <EPTF_Transport_TransportType> - transport type
//    pl_selfName - *in* - *charstring* - EPTF self name
//    pl_interfaceList - *in* - <EPTF_Transport_InterfaceInformationList> interface list - for setUpInterfaces() and setDownInterfaces(), not used
//    pl_enableBufferManager - *in* - *boolean* - copied from IPL4, not used
//  Return Value:
//    - 
//
//  Errors:
//    -
//
//  Detailed Comments:
//    
///////////////////////////////////////////////////////////  
public function f_EPTF_Transport_init(
  in EPTF_Transport_TransportType pl_transportType,
  in charstring pl_selfName,
  in EPTF_Transport_InterfaceInformationList pl_interfaceList := {},
  in boolean pl_enableBufferManager := true)
runs on EPTF_TransportIPL2_CT 
{
  if (IPL2 != pl_transportType) {
    f_EPTF_Base_assert(%definitionId&": The transport type "&log2str(pl_transportType)&
      " is not supported in the IPL2 transport.", IPL2 == pl_transportType)
  }
  var Result vl_result;
  if (v_EPTF_TransportIPL2_initialized) {
    return;
  }

  v_EPTF_TransportIPL2_openLoopbackInterface := tsp_EPTF_TransportIPL2_openLoopbackInterface;

  if(tsp_EPTF_TransportIPL2_tcpMSS > tsp_EPTF_TransportIPL2_MTU - 40) { // 20 byte IP header, 20 byte TCP header
    f_EPTF_TransportIPL2_error(%definitionId&": TCP MSS is greater than IP MTU - 40, please reconfigure.");
  }
  if(tsp_EPTF_TransportIPL2_tcpMSS > tsp_EPTF_TransportIPL2_tcpDefaultReceiveWindowSize) {
    f_EPTF_TransportIPL2_warning(%definitionId&": TCP MSS is greater than the receive window size, please reconfigure.");
  }

  f_EPTF_TransportIPL2_initExtendedComponents(pl_selfName);
  f_EPTF_HashMap_init_CT (pl_selfName);
  
  f_EPTF_Base_registerCleanup(refers(f_EPTF_TransportIPL2_cleanup_CT));

  v_EPTF_TransportIPL2_loggingMaskId := 
    f_EPTF_Logging_registerComponentMasks(
      tsp_EPTF_TransportIPL2_loggingComponentMask, 
      c_EPTF_TransportIPL2_loggingEventClasses, 
      EPTF_Logging_CLL);

  if (tsp_EPTF_TransportIPL2_enableLogging) 
  {
    f_EPTF_Logging_enableLocalMask(
      v_EPTF_TransportIPL2_loggingMaskId, 
      c_EPTF_TransportIPL2_loggingClassIdx_Debug);
  } 
  else 
  {
    f_EPTF_Logging_disableLocalMask(
      v_EPTF_TransportIPL2_loggingMaskId, 
      c_EPTF_TransportIPL2_loggingClassIdx_Debug);
  }
  v_EPTF_TransportIPL2_LGenTypesHashMapId := 
    f_EPTF_str2int_HashMap_New(c_EPTF_TransportIPL2_LGenTypesHashMapName);
  v_EPTF_CommPort_connectionDatabase.hashmapId := 
    f_EPTF_oct2int_HashMap_New(c_EPTF_TransportIPL2_connectionDBHashMapName);
  v_EPTF_TransportIPL2_nameCache.hashmapId := 
    f_EPTF_str2int_HashMap_New(c_EPTF_TransportIPL2_nameCacheHashMapName);
  v_EPTF_TransportIPL2_ipFragmentBufferDB.hashmapId := 
    f_EPTF_oct2int_HashMap_New(c_EPTF_TransportIPL2_ipFragmentBufferDBHashMapName);
//  if(tsp_EPTF_TransportIPL2_enableIpAddressFilter) {
    v_EPTF_TransportIPL2_ipAddressHashmapId := 
      f_EPTF_oct2int_HashMap_New(c_EPTF_TransportIPL2_ipAddressHashMapName);
//  }

  v_EPTF_TransportIPL2_LGenInfoFreeBusyQueue := c_EPTF_emptyFreeBusyQueue;
  f_EPTF_FBQ_initFreeBusyQueue(v_EPTF_TransportIPL2_LGenInfoFreeBusyQueue);
  v_EPTF_CommPort_connectionDatabase.queue := c_EPTF_emptyFreeBusyQueue;
  f_EPTF_FBQ_initFreeBusyQueue(v_EPTF_CommPort_connectionDatabase.queue);  
  v_EPTF_TransportIPL2_ipFragmentBufferDB.queue := c_EPTF_emptyFreeBusyQueue;
  f_EPTF_FBQ_initFreeBusyQueue(v_EPTF_TransportIPL2_ipFragmentBufferDB.queue);  

  vd_EPTF_TransportIPL2_defaultReceive := activate(as_EPTF_TransportIPL2_defaultReceive());

  v_EPTF_Transport_interfaceInformationList := pl_interfaceList;
  f_EPTF_Transport_setUpInterfaces (IPL2, vl_result);

  f_EPTF_TransportIPL2_initStatistics ();
  
  f_EPTF_TransportIPL2_initRouting();

  map(self:IPL2_PCO, system:IPL2_PCO);

  if(tsp_EPTF_TransportIPL2_multipleInterfacesMode) {
    f_EPTF_TransportIPL2_openConfiguredInterfaces();
  }

  v_EPTF_TransportIPL2_initialized := true;
}

///////////////////////////////////////////////////////////
//  Function: f_EPTF_TransportIPL2_cleanup_CT
// 
//  Purpose:
//    Function to cleanup the IPL2 Communicationport component
//
//  Parameters:
//    - 
//
//  Return Value:
//    - 
//
//  Errors:
//    -
//
//  Detailed Comments:
//    
///////////////////////////////////////////////////////////  
private function f_EPTF_TransportIPL2_cleanup_CT() runs on EPTF_TransportIPL2_CT 
{
  if(not v_EPTF_TransportIPL2_initialized) { return;}

  if(f_EPTF_FBQ_getLengthOfBusyChain(v_EPTF_CommPort_connectionDatabase.queue) > 0) {
    var boolean vl_doWait := false;
    for(var integer i:=0; i<sizeof(v_EPTF_CommPort_connectionDatabase.data); i:=i+1) {
      if(f_EPTF_FBQ_itemIsBusy(i, v_EPTF_CommPort_connectionDatabase.queue)) {
        if( v_EPTF_CommPort_connectionDatabase.data[i].tcpState == ESTABLISHED or
            v_EPTF_CommPort_connectionDatabase.data[i].tcpState == CLOSE_WAIT) {
          var Result vl_res;
          if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
            f_EPTF_TransportIPL2_debug(%definitionId&": closing socket "&
              log2str(v_EPTF_CommPort_connectionDatabase.data[i]));
          }
          f_EPTF_Transport_close(IPL2, i, vl_res);
          vl_doWait := true;
        }
      }
    }
    if(vl_doWait) {
      timer T_minWait := 0.0;
      timer T_maxWait := 5.0;
      T_minWait.start;
      T_maxWait.start;
      alt {
        [f_EPTF_FBQ_getLengthOfBusyChain(v_EPTF_CommPort_connectionDatabase.queue) == 0] T_minWait.timeout { }
        [] T_maxWait.timeout { }
      }
    }
    for(var integer i:=0; i<sizeof(v_EPTF_CommPort_connectionDatabase.data); i:=i+1) {
      if( f_EPTF_FBQ_itemIsBusy(i, v_EPTF_CommPort_connectionDatabase.queue) and
          v_EPTF_CommPort_connectionDatabase.data[i].proto == {tcp:={}} and
          ispresent(v_EPTF_CommPort_connectionDatabase.data[i].remHost)) {
        // TCP, not listen socket
        f_EPTF_TransportIPL2_finalCloseTcp(i); // needed to cancel all scheduled event
      }
    }
  }

  f_EPTF_TransportIPL2_closeConfiguredInterfaces();

  unmap(self:IPL2_PCO, system:IPL2_PCO);

  if(vd_EPTF_TransportIPL2_defaultReceive != null) { 
    deactivate(vd_EPTF_TransportIPL2_defaultReceive);
    vd_EPTF_TransportIPL2_defaultReceive := null;
  }

  v_EPTF_TransportIPL2_LGenInfoList := {};
  f_EPTF_str2int_HashMap_Delete(c_EPTF_TransportIPL2_LGenTypesHashMapName);
  f_EPTF_oct2int_HashMap_Delete(c_EPTF_TransportIPL2_connectionDBHashMapName);
  f_EPTF_str2int_HashMap_Delete(c_EPTF_TransportIPL2_nameCacheHashMapName);
  f_EPTF_oct2int_HashMap_Delete(c_EPTF_TransportIPL2_ipFragmentBufferDBHashMapName);
//  if(tsp_EPTF_TransportIPL2_enableIpAddressFilter) {
    f_EPTF_oct2int_HashMap_Delete(c_EPTF_TransportIPL2_ipAddressHashMapName);
//  }

  v_EPTF_TransportIPL2_LGenTypesHashMapId := -1;
  v_EPTF_TransportIPL2_LGenInfoFreeBusyQueue := c_EPTF_emptyFreeBusyQueue;
  v_EPTF_TransportIPL2_incStats := {-1,-1,-1,-1,-1};
  v_EPTF_TransportIPL2_outStats := {-1,-1,-1,-1};
  v_EPTF_TransportIPL2_incStatCounters := {0,0,0,0,0};
  v_EPTF_TransportIPL2_outStatCounters := {0,0,0,0};
  v_EPTF_TransportIPL2_nofErrors := -1;
  v_EPTF_TransportIPL2_lastErrorString := -1;
  v_EPTF_TransportIPL2_loggingMaskId := -1;

  v_EPTF_CommPort_connectionDatabase := { {}, c_EPTF_emptyFreeBusyQueue, -1 }
  v_EPTF_TransportIPL2_nameCache := { {}, -1 }
  v_EPTF_TransportIPL2_ipPacketId := 0;
  v_EPTF_TransportIPL2_ipFragmentBufferDB := { {}, c_EPTF_emptyFreeBusyQueue, -1 };
  v_EPTF_TransportIPL2_ipAddressHashmapId := -1;
  
  v_EPTF_TransportIPL2_initialized := false;
}

///////////////////////////////////////////////////////////
//  Function: f_EPTF_Transport_registerMsgLenCallback4LGenType
// 
//  Purpose:
//    Sets the message length calclutaion function for an LGen type. 
//
//  Parameters:
//    pl_transportType - *in* - <EPTF_Transport_TransportType> - transport type
//    pl_function - *in* - <EPTF_Transport_GetMsgLen_FT> - the message length calculation function reference
//    pl_msgLenArgs - *in* - <EPTF_IntegerList> - the function arguments
//    pl_LGenType - *in* *charstring* - the name of the LGen type
//
/////////////////////////////////////////////////////////// 
public function f_EPTF_Transport_registerMsgLenCallback4LGenType(
  in EPTF_Transport_TransportType pl_transportType,
  in EPTF_Transport_GetMsgLen_FT pl_function,
  in EPTF_IntegerList pl_msgLenArgs,
  in charstring pl_LGenType) 
runs on EPTF_TransportIPL2_CT
{
  if (IPL2 != pl_transportType) {
    f_EPTF_Base_assert(%definitionId&": The transport type "&log2str(pl_transportType)&
      " is not supported in the IPL2 transport.", IPL2 == pl_transportType)
  }
  var integer vl_fbqId;
  f_EPTF_TransportIPL2_addLGenInfo(pl_LGenType, vl_fbqId);

  v_EPTF_TransportIPL2_LGenInfoList[vl_fbqId].msgLenCalc :=
  {
    getMsgLen := pl_function,
    getMsgLenArgs := pl_msgLenArgs
  }
 
}


private function f_EPTF_TransportIPL2_checkConnId(in charstring pl_caller, in integer pl_connId)
runs on EPTF_TransportIPL2_CT
{ 
  if(c_EPTF_Common_debugSwitch) {
    f_EPTF_Base_assert(pl_caller&": Invalid connection ID " & int2str(pl_connId),
      pl_connId >= 0 and
      pl_connId < sizeof(v_EPTF_CommPort_connectionDatabase.data) and
      f_EPTF_FBQ_itemIsBusy(pl_connId, v_EPTF_CommPort_connectionDatabase.queue));
  }
}

///////////////////////////////////////////////////////////
//  Function: f_EPTF_Transport_setUserData
// 
//  Purpose:
//    Function to set user data 
//
//  Parameters:
//    pl_transportType - *in* - <EPTF_Transport_TransportType> - transport type
//    pl_connId - *in* - <ConnectionId> - The ID of the connection
//    pl_userData - *in* - *integer* - The data information
//    pl result - *out* - <Result> - result
//
//  Return Value:
//    -  
//
//  Errors:
//    -
//
//  Detailed Comments:
//    
/////////////////////////////////////////////////////////// 
public function f_EPTF_Transport_setUserData(
  in EPTF_Transport_TransportType pl_transportType,
  in ConnectionId pl_connId, 
  in integer pl_userData,
  out Result pl_result)
runs on EPTF_TransportIPL2_CT
{
  if (IPL2 != pl_transportType) {
    f_EPTF_Base_assert(%definitionId&": The transport type "&log2str(pl_transportType)&
      " is not supported in the IPL2 transport.", IPL2 == pl_transportType)
  }

  f_EPTF_TransportIPL2_checkConnId(%definitionId, pl_connId);
  v_EPTF_CommPort_connectionDatabase.data[pl_connId].userData := pl_userData;
}

///////////////////////////////////////////////////////////
//  Function: f_EPTF_Transport_getUserData
// 
//  Purpose:
//    Function to get user data 
//
//  Parameters:
//    pl_transportType - *in* - <EPTF_Transport_TransportType> - transport type
//    pl_connId - *in* - <ConnectionId> - The ID of the connection
//    pl_userData - *out* - <integer> - The data information
//    pl_result - *out* - <Result> - result
//
//  Return Value:
//    -
//
//  Errors:
//    -
//
//  Detailed Comments:
//    
/////////////////////////////////////////////////////////// 
public function f_EPTF_Transport_getUserData(
  in EPTF_Transport_TransportType pl_transportType,
  in ConnectionId pl_connId, 
  out integer pl_userData,
  out Result pl_result)
runs on EPTF_TransportIPL2_CT
{
  if (IPL2 != pl_transportType) {
    f_EPTF_Base_assert(%definitionId&": The transport type "&log2str(pl_transportType)&
      " is not supported in the IPL2 transport.", IPL2 == pl_transportType)
  }

  f_EPTF_TransportIPL2_checkConnId(%definitionId, pl_connId);
  pl_userData := v_EPTF_CommPort_connectionDatabase.data[pl_connId].userData;

  pl_result := { errorCode := omit, connId  := omit, os_error_code:=omit, os_error_text:= omit };
}

///////////////////////////////////////////////////////////
//  Function: f_EPTF_TransportIPL2_setMsgHandler
// 
//  Purpose:
//    Function to set callback function for handling incoming messages
//
//  Parameters:
//    pl_msghandler - *in* - <EPTF_Transport_MsgCallback_FT> - The handler function to the receive template
//    pl_LGenType - *in* - *charstring* - the type of the LGen component registered this function
//
//  Return Value:
//    - 
//
//  Errors:
//    pl_LgenType should not be empty string.
//
//  Detailed Comments:
//    
///////////////////////////////////////////////////////////
public function f_EPTF_TransportIPL2_setMsgHandler (
  in EPTF_Transport_MsgCallback_FT pl_msghandler,
  in charstring pl_LGenType)
runs on EPTF_TransportIPL2_CT
{
  if (pl_LGenType=="")
  {    
    f_EPTF_TransportIPL2_error(%definitionId&": LGenType must be specified.");
  }
  else
  {
    var integer vl_fbqId;
   
    f_EPTF_TransportIPL2_addLGenInfo(pl_LGenType, vl_fbqId);
    v_EPTF_TransportIPL2_LGenInfoList[vl_fbqId].msgHandler := pl_msghandler;
    
  }
}

///////////////////////////////////////////////////////////
//  Function: f_EPTF_TransportIPL2_addLGenInfo
// 
//  Purpose:
//    Retreives the index of the database entry belonging to an LGen type.
//    If the entiry does not exist, adds a new entry to the database.
//
//  Parameters:
//    pl_LGenType - *in* *charstring* - the LGen type name
//    pl_idx - *inout* *integer* - the returned index of the database entry
//
///////////////////////////////////////////////////////////
private function f_EPTF_TransportIPL2_addLGenInfo(in charstring pl_LGenType, inout integer pl_idx)
runs on EPTF_TransportIPL2_CT
{
  if (f_EPTF_str2int_HashMap_Find(v_EPTF_TransportIPL2_LGenTypesHashMapId, pl_LGenType, pl_idx))
  {
    return;
  }
  
  pl_idx := f_EPTF_FBQ_getOrCreateFreeSlot(v_EPTF_TransportIPL2_LGenInfoFreeBusyQueue); 
  f_EPTF_FBQ_moveFromFreeToBusyTail(pl_idx,v_EPTF_TransportIPL2_LGenInfoFreeBusyQueue);       

  f_EPTF_str2int_HashMap_Insert(v_EPTF_TransportIPL2_LGenTypesHashMapId, pl_LGenType, pl_idx);
  v_EPTF_TransportIPL2_LGenInfoList[pl_idx] := c_EPTF_TransportIPL2_LGenInfo_init
  
}

///////////////////////////////////////////////////////////
//  Function: f_EPTF_Transport_send
// 
//  Purpose:
//    Function to send a message on a connection
//
//  Parameters:
//    pl_transportType - *in* - <EPTF_Transport_TransportType> - Testport type
//    pl_connId - *in* - <ConnectionId> - connection ID
//    pl_msg - *in* - *octetstring* - the message to send
//    pl_result - *out* - <Result> - result
//    pl_needBuffering - *in* - *boolean* 
//
//  Return Value:
//    *boolean* - true on success, false on error
//
//  Errors:
//    - invalid connection ID
//    - no connection (remote peer) exists for socket
///////////////////////////////////////////////////////////
public function f_EPTF_Transport_send(
  in EPTF_Transport_TransportType pl_transportType,
  in ConnectionId pl_connId,
  in octetstring pl_msg,
  out Result pl_result,
  in boolean pl_needBuffering := false,
  in ProtoTuple pl_proto:={unspecified:={}})
runs on EPTF_TransportIPL2_CT
return boolean
{
  var integer vl_i := 0;
  var octetstring vl_appendMsg := ''O;
  pl_result := { errorCode := omit, connId  := omit, os_error_code:=omit, os_error_text:= omit };
  if (IPL2 != pl_transportType) {
    f_EPTF_Base_assert(%definitionId&": The transport type "&log2str(pl_transportType)&
      " is not supported in the IPL2 transport.", IPL2 == pl_transportType)
  }

  f_EPTF_TransportIPL2_checkConnId(%definitionId, pl_connId);
  if (ispresent(v_EPTF_CommPort_connectionDatabase.data[pl_connId].remHost)){
    if(v_EPTF_CommPort_connectionDatabase.data[pl_connId].proto == {udp:={}}) {
      f_EPTF_TransportIPL2_sendUdpOverL2(
        v_EPTF_CommPort_connectionDatabase.data[pl_connId].locHost,
        v_EPTF_CommPort_connectionDatabase.data[pl_connId].locPort,
        v_EPTF_CommPort_connectionDatabase.data[pl_connId].remHost,
        v_EPTF_CommPort_connectionDatabase.data[pl_connId].remPort,
        pl_msg, pl_connId);
    } else if(v_EPTF_CommPort_connectionDatabase.data[pl_connId].proto == {tcp:={}}) {
      if(not f_EPTF_TransportIPL2_tcpSendMessage(pl_connId, pl_msg, pl_result)) { return false; }
    } else {
      f_EPTF_TransportIPL2_error(%definitionId&": unsupported protocol "&
        log2str(v_EPTF_CommPort_connectionDatabase.data[pl_connId].proto));
    }

    f_EPTF_TransportIPL2_updateOutgoingStatistics(c_EPTF_TransportIPL2_outMessage);

    return true;
  } else {
    pl_result.errorCode := ERROR_INVALID_CONNECTION
    if(v_EPTF_CommPort_connectionDatabase.data[pl_connId].proto == {tcp:={}}) {
      pl_result.os_error_code := c_EPTF_TransportIPL2_errorCantSendOnListenSocket;
      pl_result.os_error_text := "cannot send on TCP listen socket";
    } else {
      pl_result.os_error_code := c_EPTF_TransportIPL2_errorSocketIsNotConnected;
      pl_result.os_error_text := "socket is not connected";
    }
    f_EPTF_TransportIPL2_warning(%definitionId&": "&pl_result.os_error_text);
    f_EPTF_Var_adjustContent(v_EPTF_TransportIPL2_lastErrorString, {charstringVal:=%definitionId&": "&pl_result.os_error_text});
    f_EPTF_TransportIPL2_increaseErrors();
    pl_result.errorCode := ERROR_INVALID_CONNECTION
    return false;
  }
}

///////////////////////////////////////////////////////////
//  Function: f_EPTF_Transport_sendTo
// 
//  Purpose:
//    Function to send a message to a remote peer
//
//  Parameters:
//    pl_transportType - *in* - <EPTF_Transport_TransportType> - Testport type
//    pl_connId - *in* - <ConnectionId> - connection ID
//    pl_remHost - *in* - <HostName> - remote host
//    pl_remotePort - *in* - <PortNumber> - remote port number
//    pl_msg - *in* - *octetstring* - the message to send
//    pl_result - *out* - <Result> - result
//    pl_needBuffering - *in* - <boolean> - is Buffering needed?
//  Return Value:
//    *boolean* - true on success, false on error
//
//  Errors:
//    - invalid connection ID
///////////////////////////////////////////////////////////
public function f_EPTF_Transport_sendTo(
  in EPTF_Transport_TransportType pl_transportType,
  in ConnectionId pl_connId,
  in HostName pl_remoteHost,
  in PortNumber pl_remotePort,
  in octetstring pl_msg,
  out Result pl_result,
  in boolean pl_needBuffering := false)
runs on EPTF_TransportIPL2_CT
return boolean
{
  var integer vl_i := 0;
  var octetstring vl_appendMsg := ''O;
  pl_result := { errorCode := omit, connId  := omit, os_error_code:=omit, os_error_text:= omit };
  if (IPL2 != pl_transportType) {
    f_EPTF_Base_assert(%definitionId&": The transport type "&log2str(pl_transportType)&
      " is not supported in the IPL2 transport.", IPL2 == pl_transportType)
  }
  
  f_EPTF_TransportIPL2_checkConnId(%definitionId, pl_connId);

  var EPTF_TransportIPL2_IpAddress vl_ipAddr := f_EPTF_TransportIPL2_getIpFromHostName(pl_remoteHost);

  if(v_EPTF_CommPort_connectionDatabase.data[pl_connId].proto == {udp:={}}) {
    f_EPTF_TransportIPL2_sendUdpOverL2(
      v_EPTF_CommPort_connectionDatabase.data[pl_connId].locHost,
      v_EPTF_CommPort_connectionDatabase.data[pl_connId].locPort,
      vl_ipAddr,
      pl_remotePort,
      pl_msg, pl_connId);
  } else if(v_EPTF_CommPort_connectionDatabase.data[pl_connId].proto == {tcp:={}}) {
    if(not ispresent(v_EPTF_CommPort_connectionDatabase.data[pl_connId].remHost)) {
      f_EPTF_TransportIPL2_warning(%definitionId&": cannot send on TCP listen socket.");
      pl_result.errorCode := ERROR_INVALID_INPUT_PARAMETER
      pl_result.os_error_code := c_EPTF_TransportIPL2_errorCantSendOnListenSocket;
      pl_result.os_error_text := "cannot send on TCP listen socket";
      return false;
    }
    if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
      //debug-check the remote IP address and port number
      if(v_EPTF_CommPort_connectionDatabase.data[pl_connId].remHost != vl_ipAddr) {
        f_EPTF_TransportIPL2_debug(%definitionId&": the specified remote host "&log2str(vl_ipAddr)&
          " doesn't match the connection's remote address "&
          log2str(v_EPTF_CommPort_connectionDatabase.data[pl_connId].remHost));
      }
      if(v_EPTF_CommPort_connectionDatabase.data[pl_connId].remPort != pl_remotePort) {
        f_EPTF_TransportIPL2_debug(%definitionId&": the specified remote port "&int2str(pl_remotePort)&
          " doesn't match the connection's remote port "&
          int2str(v_EPTF_CommPort_connectionDatabase.data[pl_connId].remPort));
      }
    }
    return f_EPTF_Transport_send(pl_transportType, pl_connId, pl_msg, pl_result, pl_needBuffering);
  } else {
    f_EPTF_TransportIPL2_error(%definitionId&": unsupported protocol "&
      log2str(v_EPTF_CommPort_connectionDatabase.data[pl_connId].proto));
  }

  f_EPTF_TransportIPL2_updateOutgoingStatistics(c_EPTF_TransportIPL2_outMessage);

  return true;
}

group EPTF_TransportIPL2_ConnectionHandling
{
  private function f_EPTF_TransportIPL2_getIpFromHostName(in charstring pl_hostName)
  runs on EPTF_TransportIPL2_CT
  return EPTF_TransportIPL2_IpAddress
  {
    var integer vl_idx := -1;
    if(f_EPTF_str2int_HashMap_Find(v_EPTF_TransportIPL2_nameCache.hashmapId, pl_hostName, vl_idx)) {
      return v_EPTF_TransportIPL2_nameCache.addresses[vl_idx];
    } else {
      vl_idx := sizeof(v_EPTF_TransportIPL2_nameCache.addresses);
      f_EPTF_str2int_HashMap_Insert(v_EPTF_TransportIPL2_nameCache.hashmapId, pl_hostName, vl_idx);
      v_EPTF_TransportIPL2_nameCache.addresses[vl_idx] := f_OctetIpv4(pl_hostName);
      if(lengthof(v_EPTF_TransportIPL2_nameCache.addresses[vl_idx]) == 0) {
        v_EPTF_TransportIPL2_nameCache.addresses[vl_idx] := f_OctetIpv4(f_getIpAddr(pl_hostName));
      }
      return v_EPTF_TransportIPL2_nameCache.addresses[vl_idx];
    }
  }

  // FIXME: should be in TCC Useful Functions (TCCConversion_Functions)
/*  function f_EPTF_TransportIPL2_ip2str(in EPTF_TransportIPL2_IpAddress pl_ipAddr)
  return charstring
  {
    return int2str(oct2int(pl_ipAddr[0])) & "." &
           int2str(oct2int(pl_ipAddr[1])) & "." &
           int2str(oct2int(pl_ipAddr[2])) & "." &
           int2str(oct2int(pl_ipAddr[3]));
  }*/
  external function f_EPTF_TransportIPL2_ip2str(in octetstring pl_ipAddr)
  return charstring;

  friend function f_EPTF_Transport_registerConnCallbacks(
    in EPTF_Transport_TransportType pl_transportType,
    in charstring pl_LGenType,
    in EPTF_Transport_ConnectionOpened_FT pl_connOpenedFn,
    in EPTF_Transport_ConnectionClosed_FT pl_connClosedFn)
  runs on EPTF_TransportIPL2_CT
  {
    if (IPL2 != pl_transportType) {
      f_EPTF_Base_assert(%definitionId&": The transport type "&log2str(pl_transportType)&
        " is not supported in the IPL2 transport.", IPL2 == pl_transportType)
    }
    if (pl_LGenType=="") {
      f_EPTF_Base_assert(%definitionId&": LGenType must be specified.", pl_LGenType!="");
    }
    var integer vl_fbqId;
    f_EPTF_TransportIPL2_addLGenInfo(pl_LGenType, vl_fbqId);
    v_EPTF_TransportIPL2_LGenInfoList[vl_fbqId].connOpenedFn := pl_connOpenedFn;
    v_EPTF_TransportIPL2_LGenInfoList[vl_fbqId].connClosedFn := pl_connClosedFn;
  }

  friend function f_EPTF_Transport_setConnectionUniqueId(
    in ConnectionId pl_connId,
    in integer pl_uniqueId)
  runs on EPTF_TransportIPL2_CT
  {
    v_EPTF_CommPort_connectionDatabase.data[pl_connId].uniqueId := pl_uniqueId;
  }

  friend function f_EPTF_Transport_getConnectionUniqueId(
    in ConnectionId pl_connId)
  runs on EPTF_TransportIPL2_CT
  return integer
  {
    return v_EPTF_CommPort_connectionDatabase.data[pl_connId].uniqueId;
  }

  private function f_EPTF_TransportIPL2_newLocalPort(
    in ProtoTuple pl_proto,
    in octetstring pl_hostAddr,
    in integer pl_portNumber,
    in integer pl_lgenIdx,
    in octetstring pl_remoteHostAddr := ''O, // optional, should be empty in case of UDP or TCP listen socket
    in integer pl_remotePortNumber := -1) // optional, should be -1 in case of UDP or TCP listen socket
  runs on EPTF_TransportIPL2_CT
  return integer
  {
    if(c_EPTF_Common_debugSwitch) {
      f_EPTF_Base_assert(%definitionId&": invalid port number "&int2str(pl_portNumber),
        pl_portNumber > 0  and // not >=0 because 0 would mean automatic allocation of a local port number which is not supported
        pl_portNumber <= 65535);
    }
    if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
      f_EPTF_TransportIPL2_debug(%definitionId&"(\""&
        f_EPTF_TransportIPL2_ip2str(pl_hostAddr)&"\", "&int2str(pl_portNumber)&", \""&
        f_EPTF_TransportIPL2_ip2str(pl_remoteHostAddr)&"\", "&int2str(pl_remotePortNumber)&")");
    }

//    if(tsp_EPTF_TransportIPL2_enableIpAddressFilter) {
      f_EPTF_TransportIPL2_addIp(pl_hostAddr);
//    }

    var integer vl_idx := f_EPTF_TransportIPL2_getConnectionIdx(pl_proto, pl_hostAddr, pl_portNumber, pl_remoteHostAddr, pl_remotePortNumber);
    if (vl_idx == -1) {
      // no index found, add new element
        vl_idx := f_EPTF_FBQ_getOrCreateFreeSlot(v_EPTF_CommPort_connectionDatabase.queue);
        f_EPTF_FBQ_moveFromFreeToBusyTail(vl_idx, v_EPTF_CommPort_connectionDatabase.queue);
        v_EPTF_CommPort_connectionDatabase.data[vl_idx] := c_EPTF_TransportIPL2_Connection_init;
        v_EPTF_CommPort_connectionDatabase.data[vl_idx].uniqueId := vl_idx;
        v_EPTF_CommPort_connectionDatabase.data[vl_idx].lgenIdx := pl_lgenIdx;
        if(pl_remoteHostAddr == ''O) {
          v_EPTF_CommPort_connectionDatabase.data[vl_idx].remHost := omit;
        } else {
          v_EPTF_CommPort_connectionDatabase.data[vl_idx].remHost := pl_remoteHostAddr;
        }
        v_EPTF_CommPort_connectionDatabase.data[vl_idx].remPort := pl_remotePortNumber;
        v_EPTF_CommPort_connectionDatabase.data[vl_idx].locHost := pl_hostAddr;
        v_EPTF_CommPort_connectionDatabase.data[vl_idx].locPort := pl_portNumber;
        v_EPTF_CommPort_connectionDatabase.data[vl_idx].proto := pl_proto; 
        if(pl_proto == {udp:={}}){
          v_EPTF_CommPort_connectionDatabase.data[vl_idx].hashKey := f_EPTF_TransportIPL2_getHashKey4UDP(pl_hostAddr, pl_portNumber);
        } else if(pl_proto == {tcp:={}}) {
          v_EPTF_CommPort_connectionDatabase.data[vl_idx].hashKey :=
            f_EPTF_TransportIPL2_getHashKey4TCP(pl_hostAddr, pl_portNumber, pl_remoteHostAddr, pl_remotePortNumber);
          if(pl_remoteHostAddr != ''O) {
            v_EPTF_CommPort_connectionDatabase.data[vl_idx].tcpConnectionData := c_EPTF_TransportIPL2_TcpConnectionData_init;
            v_EPTF_CommPort_connectionDatabase.data[vl_idx].tcpConnectionData.sendFifo_bufferID := f_EPTF_Buffer_new();
            
            v_EPTF_CommPort_connectionDatabase.data[vl_idx].tcpConnectionData.mss := tsp_EPTF_TransportIPL2_tcpMSS; // updated when hadnling incoming SYN segment
            v_EPTF_CommPort_connectionDatabase.data[vl_idx].tcpConnectionData.sendSeqNum := f_EPTF_TransportIPL2_tcpIsn();
            v_EPTF_CommPort_connectionDatabase.data[vl_idx].tcpConnectionData.sendWindowStart := v_EPTF_CommPort_connectionDatabase.data[vl_idx].tcpConnectionData.sendSeqNum;
            f_EPTF_FBQ_initFreeBusyQueue(v_EPTF_CommPort_connectionDatabase.data[vl_idx].tcpConnectionData.sendBuffer.queue);
            f_EPTF_SegmentBuffer_initBuffer(v_EPTF_CommPort_connectionDatabase.data[vl_idx].tcpConnectionData.segmentBuffer);
            if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
              f_EPTF_TransportIPL2_debug(%definitionId&": send window start initialized to "&
                int2str(v_EPTF_CommPort_connectionDatabase.data[vl_idx].tcpConnectionData.sendWindowStart));
            }
            v_EPTF_CommPort_connectionDatabase.data[vl_idx].tcpConnectionData.smoothedRTT := tsp_EPTF_TransportIPL2_tcpMinRetransmitTime
            //v_EPTF_CommPort_connectionDatabase.data[vl_idx].receiveWindowSize := tsp_EPTF_TransportIPL2_tcpDefaultReceiveWindowSize;
          }
        } else {
          f_EPTF_Base_assert(%definitionId&": Unsupported protocol.", false);
        }
        f_EPTF_oct2int_HashMap_Insert(v_EPTF_CommPort_connectionDatabase.hashmapId, v_EPTF_CommPort_connectionDatabase.data[vl_idx].hashKey, vl_idx);
        if(v_EPTF_TransportIPL2_LGenInfoList[pl_lgenIdx].connOpenedFn != null) {
          v_EPTF_TransportIPL2_LGenInfoList[pl_lgenIdx].connOpenedFn.apply(IPL2, vl_idx);
        }
	return v_EPTF_CommPort_connectionDatabase.data[vl_idx].uniqueId;
    } else {
      f_EPTF_TransportIPL2_warning(%definitionId&": already listening on " & f_EPTF_TransportIPL2_ip2str(pl_hostAddr) & ":" & int2str(pl_portNumber));
      return v_EPTF_CommPort_connectionDatabase.data[vl_idx].uniqueId;
    }
  }
  
  ///////////////////////////////////////////////////////////
  //  Function: f_EPTF_TransportIPL2_tcpIsn
  // 
  //  Purpose:
  //    Function to get an Initial Sequence Number
  //
  //  Parameters:
  //  -
  //
  //  Return Value:
  //    *integer* - Initial Sequence Number
  //
  //  Errors:
  //    -
  ///////////////////////////////////////////////////////////
  external function f_EPTF_TransportIPL2_tcpIsn() return integer;

  
  ///////////////////////////////////////////////////////////
  //  Function: f_EPTF_Transport_listen
  // 
  //  Purpose:
  //    Function to listen on a socket
  //
  //  Parameters:
  //    pl_transportType - *in* - <EPTF_Transport_TransportType> - Testport type
  //    pl_proto - *in* - <ProtoTuple> - protocol
  //    pl_hostName - *in* - <HostName> - local host
  //    pl_portNumber - *in* - <PortNumber> - local port number
  //    pl_LGenType - *in* - *charstring* - LGen type
  //    pl_result - *out* - <Result> - result
  //    pl_automaticBuffering - *out* - *boolean* - automatic buffering
  //  Return Value:
  //    *integer* - connection/socket ID, or -1 on error
  //
  //  Errors:
  //    - invalid LGenType
  ///////////////////////////////////////////////////////////
  public function f_EPTF_Transport_listen(
   in EPTF_Transport_TransportType pl_transportType,
   in ProtoTuple pl_proto,
   in HostName pl_hostName,
   in PortNumber pl_portNumber,
   in charstring pl_LGenType,
   out Result pl_result,
   in boolean pl_automaticBuffering := false,
   in EPTF_TransportIPL2_VLAN_TCIs pl_vlanTCIs := {})
  runs on EPTF_TransportIPL2_CT
  return integer
  {
    pl_result := { errorCode := omit, connId  := omit, os_error_code:=omit, os_error_text:= omit };
    if (IPL2 != pl_transportType) {
      f_EPTF_Base_assert(%definitionId&": The transport type "&log2str(pl_transportType)&
        " is not supported in the IPL2 transport.", IPL2 == pl_transportType)
    }

    if(not ischosen(pl_proto.udp) and not ischosen(pl_proto.tcp)) {
      pl_result.errorCode := ERROR_UNSUPPORTED_PROTOCOL;
      pl_result.os_error_code := c_EPTF_TransportIPL2_errorUnsupportedPorotocol;
      pl_result.os_error_text := "unsupported protocol";
      return -1;
    }

    var integer vl_lgenIdx
    if (f_EPTF_str2int_HashMap_Find(v_EPTF_TransportIPL2_LGenTypesHashMapId,pl_LGenType, vl_lgenIdx))
    {
      var integer vl_idx := f_EPTF_TransportIPL2_newLocalPort(pl_proto, f_EPTF_TransportIPL2_getIpFromHostName(pl_hostName), pl_portNumber, vl_lgenIdx);
      v_EPTF_CommPort_connectionDatabase.data[vl_idx].vlanTCIs := pl_vlanTCIs;
      if(pl_proto == {tcp:={}}){
        f_EPTF_TransportIPL2_setSocketState(vl_idx, LISTEN);
      }
      f_EPTF_TransportIPL2_updateOutgoingStatistics(c_EPTF_TransportIPL2_outListen);
      return vl_idx;
    } else {
      f_EPTF_TransportIPL2_warning(%definitionId&": LGenType "&pl_LGenType&" not found in the hashmap");
      f_EPTF_Var_adjustContent(v_EPTF_TransportIPL2_lastErrorString, {charstringVal:="LGenType "&pl_LGenType&" not found in the hashmap"});
      f_EPTF_TransportIPL2_increaseErrors();
      pl_result.errorCode := ERROR_INVALID_INPUT_PARAMETER;
      pl_result.os_error_code := c_EPTF_TransportIPL2_errorInvalidLgenType;
      pl_result.os_error_text := "invalid LGenType "&pl_LGenType;
      return -1;
    }
  }

  ///////////////////////////////////////////////////////////
  //  Function: f_EPTF_Transport_connect
  // 
  //  Purpose:
  //    Function to create a new connection
  //
  //  Parameters:
  //    pl_transportType - *in* - <EPTF_Transport_TransportType> - Testport type
  //    pl_proto - *in* - <ProtoTuple> - protocol
  //    pl_localHost - *in* <HostName> - local host
  //    pl_localPort - *in* <PortNumber> - local port number
  //    pl_remoteHost - *in* <HostName> - remote host
  //    pl_remotePort - *in* <PortNumber> - remote port number
  //    pl_LGenType - *in* - *charstring* - LGen type
  //    pl_result - *out* - <Result> - result
  //    pl_automaticBuffering - *out* - *boolean* - automatic buffering
  //  Return Value:
  //    *integer* - connection/socket ID, or -1 on error
  //
  //  Errors:
  //    - invalid LGenType
  //    - already listening on localHost:localPort
  ///////////////////////////////////////////////////////////
  public function f_EPTF_Transport_connect(
   in EPTF_Transport_TransportType pl_transportType,
   in ProtoTuple pl_proto,
   in HostName pl_localHost,
   in PortNumber pl_localPort,
   in HostName pl_remoteHost,
   in PortNumber pl_remotePort,
   in charstring pl_LGenType,
   out Result pl_result,
   in boolean pl_automaticBuffering := false,
   in EPTF_TransportIPL2_VLAN_TCIs pl_vlanTCIs := {})
  runs on EPTF_TransportIPL2_CT
  return integer
  {
    pl_result := { errorCode := omit, connId  := omit, os_error_code:=omit, os_error_text:= omit };
    if (IPL2 != pl_transportType) {
      f_EPTF_Base_assert(%definitionId&": The transport type "&log2str(pl_transportType)&
        " is not supported in the IPL2 transport.", IPL2 == pl_transportType)
    }

    if(not ischosen(pl_proto.udp) and not ischosen(pl_proto.tcp)) {
      pl_result.errorCode := ERROR_UNSUPPORTED_PROTOCOL;
      pl_result.os_error_code := c_EPTF_TransportIPL2_errorUnsupportedPorotocol;
      pl_result.os_error_text := "unsupported protocol";
      return -1;
    }

    var integer vl_lgenIdx
    if (f_EPTF_str2int_HashMap_Find(v_EPTF_TransportIPL2_LGenTypesHashMapId,pl_LGenType, vl_lgenIdx))
    {
      var integer vl_idx := f_EPTF_TransportIPL2_newLocalPort(
        pl_proto,
        f_EPTF_TransportIPL2_getIpFromHostName(pl_localHost),
        pl_localPort,
        vl_lgenIdx,
        f_EPTF_TransportIPL2_getIpFromHostName(pl_remoteHost),
        pl_remotePort);
        v_EPTF_CommPort_connectionDatabase.data[vl_idx].vlanTCIs := pl_vlanTCIs;
      if(pl_proto == {tcp:={}}){
        // initiate an outgoing connection
        // 1st step of 3-way handshake-> send SYN
        f_EPTF_TransportIPL2_sendTcpSegmentOverL2(
          vl_idx,
          c_EPTF_TransportIPL2_TCPControlBit_syn,
          ''O);
        f_EPTF_TransportIPL2_setSocketState(vl_idx, SYN_SENT);
      }
      f_EPTF_TransportIPL2_updateOutgoingStatistics(c_EPTF_TransportIPL2_outConnect);
      pl_result.connId := vl_idx; 
      return vl_idx;
    } else {
      f_EPTF_TransportIPL2_warning(%definitionId&": LGenType "&pl_LGenType&" not found in the hashmap");
      f_EPTF_Var_adjustContent(v_EPTF_TransportIPL2_lastErrorString, {charstringVal:="LGenType "&pl_LGenType&" not found in the hashmap"});
      f_EPTF_TransportIPL2_increaseErrors();
      pl_result.errorCode := ERROR_INVALID_INPUT_PARAMETER;
      pl_result.os_error_code := c_EPTF_TransportIPL2_errorInvalidLgenType;
      pl_result.os_error_text := "invalid LGenType "&pl_LGenType;
      return -1;
    }
  }

  ///////////////////////////////////////////////////////////
  //  Function: f_EPTF_TransportIPL2_connectById
  // 
  //  Purpose:
  //    Function to connect an existing UDP socket to a remote peer
  //
  //  Parameters:
  //    pl_connId - *in* *integer* - local connection/socket ID
  //    pl_remoteHost - *in* *charstring* - remote host
  //    pl_remotePort - *in* *integer* - remote port number
  //
  //  Return Value:
  //    -
  //
  //  Errors:
  //    - invalid connection/socket ID
  ///////////////////////////////////////////////////////////
  public function f_EPTF_TransportIPL2_connectById(
    in integer pl_connId,
    in charstring pl_remoteHost,
    in integer pl_remotePort)
  runs on EPTF_TransportIPL2_CT
  {
    f_EPTF_TransportIPL2_checkConnId(%definitionId, pl_connId);
    if(v_EPTF_CommPort_connectionDatabase.data[pl_connId].proto != {udp:={}}) {
      f_EPTF_TransportIPL2_error(%definitionId&": called for socket with protocol " &
        log2str(v_EPTF_CommPort_connectionDatabase.data[pl_connId].proto));
    }
    v_EPTF_CommPort_connectionDatabase.data[pl_connId].remHost := f_EPTF_TransportIPL2_getIpFromHostName(pl_remoteHost);
    v_EPTF_CommPort_connectionDatabase.data[pl_connId].remPort := pl_remotePort;
    f_EPTF_TransportIPL2_updateOutgoingStatistics(c_EPTF_TransportIPL2_outConnect);
  }

  private function f_EPTF_TransportIPL2_finalCloseTcp(
    in integer pl_socketId)
  runs on EPTF_TransportIPL2_CT
  {
    if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
      f_EPTF_TransportIPL2_debug(%definitionId&": final-closing socket " & int2str(pl_socketId));
    }
    f_EPTF_TransportIPL2_cancelSendTcpAck(pl_socketId);
    f_EPTF_TransportIPL2_cancelTimeWait(pl_socketId);
    for(var integer i:=0; i<sizeof(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.sendBuffer.segments); i:=i+1) {
      if(f_EPTF_FBQ_itemIsBusy(i, v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.sendBuffer.queue)) {
        f_EPTF_TransportIPL2_cancelTcpRetransmission(pl_socketId, i);
      }
    }
    if(f_EPTF_SegmentBuffer_getContinousLen(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.segmentBuffer) != 0) {
      f_EPTF_TransportIPL2_warning(%definitionId&": discarding "&
        int2str(f_EPTF_SegmentBuffer_getContinousLen(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.segmentBuffer)) &
        " bytes from the receive buffer.");
    }
    f_EPTF_TransportIPL2_setSocketState(pl_socketId, CLOSED);
    if(v_EPTF_TransportIPL2_LGenInfoList[v_EPTF_CommPort_connectionDatabase.data[pl_socketId].lgenIdx].connClosedFn != null) {
      v_EPTF_TransportIPL2_LGenInfoList[v_EPTF_CommPort_connectionDatabase.data[pl_socketId].lgenIdx].connClosedFn.apply(IPL2, v_EPTF_CommPort_connectionDatabase.data[pl_socketId].uniqueId);
    }
    f_EPTF_FBQ_moveFromBusyToFreeTail(pl_socketId, v_EPTF_CommPort_connectionDatabase.queue);
    f_EPTF_oct2int_HashMap_Erase(v_EPTF_CommPort_connectionDatabase.hashmapId, v_EPTF_CommPort_connectionDatabase.data[pl_socketId].hashKey);
    v_EPTF_CommPort_connectionDatabase.data[pl_socketId] := c_EPTF_TransportIPL2_Connection_init;
    f_EPTF_TransportIPL2_updateOutgoingStatistics(c_EPTF_TransportIPL2_outClose);
  }

  ///////////////////////////////////////////////////////////
  //  Function: f_EPTF_Transport_close
  // 
  //  Purpose:
  //    Function to close a connection or listening socket
  //
  //  Parameters:
  //    pl_transportType - *in* - <EPTF_Transport_TransportType> - Testport type
  //    pl_connId - *in* <ConnectionId> - connection/socket ID
  //    pl_result - *out* - <Result> - result
  //  Return Value:
  //    *boolean* - true on success
  //
  //  Errors:
  //    - invalid connection/socket ID
  ///////////////////////////////////////////////////////////
  public function f_EPTF_Transport_close(
   in EPTF_Transport_TransportType pl_transportType,
   in ConnectionId pl_connId,
   out Result pl_result,
   in ProtoTuple pl_proto:={unspecified:={}})
  runs on EPTF_TransportIPL2_CT
  return boolean
  {
    pl_result := { errorCode := omit, connId  := omit, os_error_code:=omit, os_error_text:= omit };
    if (IPL2 != pl_transportType) {
      f_EPTF_Base_assert(%definitionId&": The transport type "&log2str(pl_transportType)&
        " is not supported in the IPL2 transport.", IPL2 == pl_transportType)
    }
    
//    f_EPTF_TransportIPL2_checkConnId(%definitionId, pl_connId);
    if(pl_connId >= 0 and pl_connId < sizeof(v_EPTF_CommPort_connectionDatabase.data) and
       f_EPTF_FBQ_itemIsBusy(pl_connId, v_EPTF_CommPort_connectionDatabase.queue)) {
      if(v_EPTF_CommPort_connectionDatabase.data[pl_connId].proto == {tcp:={}} and
         ispresent(v_EPTF_CommPort_connectionDatabase.data[pl_connId].remHost)) {
      // TCP, not listen socket

   	    if(tsp_EPTF_TransportIPL2_sendFifo_useCLLBuffer){
		  if(f_EPTF_Buffer_get_read_len(v_EPTF_CommPort_connectionDatabase.data[pl_connId].tcpConnectionData.sendFifo_bufferID) != 0)
	  	  {
            // don't close the connection until there's anything in the send FIFO
            // in this case, delay the close until the last segment sent from the FIFO
            v_EPTF_CommPort_connectionDatabase.data[pl_connId].tcpConnectionData.pendingClose := true;
            f_EPTF_TransportIPL2_debug(%definitionId&": there's data in the send FIFO, delaying close");
            return true;
          }
        } else {
          if(lengthof(v_EPTF_CommPort_connectionDatabase.data[pl_connId].tcpConnectionData.sendFifo) != 0 )
		  {
            // don't close the connection until there's anything in the send FIFO
            // in this case, delay the close until the last segment sent from the FIFO
            v_EPTF_CommPort_connectionDatabase.data[pl_connId].tcpConnectionData.pendingClose := true;
            f_EPTF_TransportIPL2_debug(%definitionId&": there's data in the send FIFO, delaying close");
            return true;
          }
        }
        if(v_EPTF_CommPort_connectionDatabase.data[pl_connId].tcpState == ESTABLISHED or
           v_EPTF_CommPort_connectionDatabase.data[pl_connId].tcpState == SYN_RECEIVED) { // active close
          f_EPTF_TransportIPL2_setSocketState(pl_connId, FIN_WAIT1);
          f_EPTF_TransportIPL2_sendTcpFin(pl_connId);
        } else if(v_EPTF_CommPort_connectionDatabase.data[pl_connId].tcpState == CLOSE_WAIT) { // passive close
          f_EPTF_TransportIPL2_setSocketState(pl_connId, LAST_ACK);
          f_EPTF_TransportIPL2_sendTcpFin(pl_connId);
        } else if(v_EPTF_CommPort_connectionDatabase.data[pl_connId].tcpState == SYN_SENT) {
          f_EPTF_TransportIPL2_finalCloseTcp(pl_connId);
          // don't send FIN
        } else {
          f_EPTF_TransportIPL2_warning(%definitionId&": called in invalid state " &
            log2str(v_EPTF_CommPort_connectionDatabase.data[pl_connId].tcpState));
          //f_EPTF_TransportIPL2_sendTcpFin(pl_connId);
          f_EPTF_TransportIPL2_finalCloseTcp(pl_connId);
        }
      } else {
      // UDP or TCP listen socket
        if(v_EPTF_TransportIPL2_LGenInfoList[v_EPTF_CommPort_connectionDatabase.data[pl_connId].lgenIdx].connClosedFn != null) {
          v_EPTF_TransportIPL2_LGenInfoList[v_EPTF_CommPort_connectionDatabase.data[pl_connId].lgenIdx].connClosedFn.apply(IPL2, v_EPTF_CommPort_connectionDatabase.data[pl_connId].uniqueId);
        }
        f_EPTF_FBQ_moveFromBusyToFreeTail(pl_connId, v_EPTF_CommPort_connectionDatabase.queue);
        f_EPTF_oct2int_HashMap_Erase(v_EPTF_CommPort_connectionDatabase.hashmapId, v_EPTF_CommPort_connectionDatabase.data[pl_connId].hashKey);
        v_EPTF_CommPort_connectionDatabase.data[pl_connId] := c_EPTF_TransportIPL2_Connection_init;
        f_EPTF_TransportIPL2_updateOutgoingStatistics(c_EPTF_TransportIPL2_outClose);
      }
      return true;
    } else {
      f_EPTF_TransportIPL2_warning(%definitionId&": invalid connection ID " & int2str(pl_connId) &
        ". Size of connection DB: " & int2str(sizeof(v_EPTF_CommPort_connectionDatabase.data)));
      pl_result.errorCode := ERROR_INVALID_INPUT_PARAMETER
      pl_result.os_error_code := c_EPTF_TransportIPL2_errorInvalidConnectionId;
      pl_result.os_error_text := "invalid connection ID "&int2str(pl_connId);
      return false;
    }

  }

  ///////////////////////////////////////////////////////////
  //  Function: f_EPTF_TransportIPL2_getLocalHost
  // 
  //  Purpose:
  //    Function to get the local host of a socket
  //
  //  Parameters:
  //    pl_connId - *in* *integer* - connection/socket ID
  //
  //  Return Value:
  //    *octetstring* - local host IP address
  //
  //  Errors:
  //    - invalid connection/socket ID
  ///////////////////////////////////////////////////////////
  public function f_EPTF_TransportIPL2_getLocalHost(in integer pl_connId)
  runs on EPTF_TransportIPL2_CT
  return octetstring
  {
    f_EPTF_TransportIPL2_checkConnId(%definitionId, pl_connId);
    return v_EPTF_CommPort_connectionDatabase.data[pl_connId].locHost;
  }

  ///////////////////////////////////////////////////////////
  //  Function: f_EPTF_TransportIPL2_getLocalPort
  // 
  //  Purpose:
  //    Function to get the local port number of a socket
  //
  //  Parameters:
  //    pl_connId - *in* *integer* - connection/socket ID
  //
  //  Return Value:
  //    *integer* - local port number
  //
  //  Errors:
  //    - invalid connection/socket ID
  ///////////////////////////////////////////////////////////
  public function f_EPTF_TransportIPL2_getLocalPort(in integer pl_connId)
  runs on EPTF_TransportIPL2_CT
  return integer
  {
    f_EPTF_TransportIPL2_checkConnId(%definitionId, pl_connId);
    return v_EPTF_CommPort_connectionDatabase.data[pl_connId].locPort;
  }

  ///////////////////////////////////////////////////////////
  //  Function: f_EPTF_Transport_getProto
  // 
  //  Purpose:
  //    Function to get the protocol of a socket
  //
  //  Parameters:
  //    pl_transportType - *in* - <EPTF_Transport_TransportType> - transport type
  //    pl_connId - *in* <ConnectionId> - connection/socket ID
  //    pl_proto - *out* - <ProtoTuple> - protocol
  //    pl_result - *out* - <Result> - result
  //
  //  Return Value:
  //    -
  //
  //  Errors:
  //    - invalid connection/socket ID
  ///////////////////////////////////////////////////////////
  public function f_EPTF_Transport_getProto(
    in EPTF_Transport_TransportType pl_transportType,
    in ConnectionId pl_connId,
    out ProtoTuple pl_proto,
    out Result pl_result)
  runs on EPTF_TransportIPL2_CT
  {
    if (IPL2 != pl_transportType) {
      f_EPTF_Base_assert(%definitionId&": The transport type "&log2str(pl_transportType)&
        " is not supported in the IPL2 transport.", IPL2 == pl_transportType)
    }
  
    pl_result := { errorCode := omit, connId  := omit, os_error_code:=omit, os_error_text:= omit };
    f_EPTF_TransportIPL2_checkConnId(%definitionId, pl_connId);
    pl_proto := v_EPTF_CommPort_connectionDatabase.data[pl_connId].proto;
  }

  external function f_EPTF_TransportIPL2_getHashKey4UDP(
    in octetstring pl_locAddr,
    in integer pl_locPort)
  return octetstring;

  external function f_EPTF_TransportIPL2_getHashKey4TCP(
    in octetstring pl_locAddr,
    in integer pl_locPort,
    in octetstring pl_remAddr,
    in integer pl_remPort)
  return octetstring;

  private function f_EPTF_TransportIPL2_getConnectionIdx(
    in ProtoTuple pl_proto,
    in octetstring pl_locAddr,
    in integer pl_locPort,
    in octetstring pl_remAddr := ''O,
    in integer pl_remPort := -1)
  runs on EPTF_TransportIPL2_CT
  return integer
  {
    var integer vl_idx := -1;
    if(pl_proto == {udp:={}}) {
      if(f_EPTF_oct2int_HashMap_Find(
          v_EPTF_CommPort_connectionDatabase.hashmapId,
          f_EPTF_TransportIPL2_getHashKey4UDP(pl_locAddr, pl_locPort),
          vl_idx)) {
        return vl_idx;
      }
    } else if(pl_proto == {tcp:={}}) {
      if(f_EPTF_oct2int_HashMap_Find(
          v_EPTF_CommPort_connectionDatabase.hashmapId,
          f_EPTF_TransportIPL2_getHashKey4TCP(
            pl_locAddr,
            pl_locPort,
            pl_remAddr,
            pl_remPort),
          vl_idx)) {
        return vl_idx;
      }
    }
    return -1;
  }


  private function f_EPTF_TransportIPL2_sendUdpOverL2 (
    in EPTF_TransportIPL2_IpAddress pl_locHost,
    in integer pl_locPort,
    in EPTF_TransportIPL2_IpAddress pl_remHost,
    in integer pl_remPort,
    in octetstring pl_msg,
    in ConnectionId pl_connId := -1) 
  runs on EPTF_TransportIPL2_CT
  {
    if ( lengthof(pl_msg) > 65535 - 8 ) {
      f_EPTF_TransportIPL2_warning("UDP packet length is greater than (65535 - 8).");
      f_EPTF_Var_adjustContent(v_EPTF_TransportIPL2_lastErrorString, {charstringVal:="UDP packet length is greater than (65535 - 8)."});
      f_EPTF_TransportIPL2_increaseErrors();
      return;
    }

    var octetstring ip_payload := f_EPTF_TransportIPL2_encodeUDPWithChecksum(pl_locHost, pl_locPort, pl_remHost, pl_remPort, pl_msg);

    var integer vl_msgLength := lengthof(ip_payload);
    var integer vl_maxLength := tsp_EPTF_TransportIPL2_MTU - 20;
    if(vl_msgLength < vl_maxLength) {
      f_EPTF_TransportIPL2_sendIpOverL2(pl_locHost, pl_remHost, c_ip_proto_udp, ip_payload, false, 0, pl_connId);
    } else {
      var integer vl_fragmentOffsetInc := vl_maxLength / c_EPTF_TransportIPL2_ipFragmentOffsetScale;
      vl_maxLength := vl_fragmentOffsetInc * c_EPTF_TransportIPL2_ipFragmentOffsetScale;
      var integer vl_fragmentOffset := 0;

      while ( vl_msgLength > 0 ){
        if (vl_msgLength < vl_maxLength){
          f_EPTF_TransportIPL2_sendIpOverL2(pl_locHost, pl_remHost, c_ip_proto_udp, ip_payload, false, vl_fragmentOffset, pl_connId);
          break;
        } else {
          f_EPTF_TransportIPL2_sendIpOverL2(pl_locHost, pl_remHost, c_ip_proto_udp, substr(ip_payload, 0, vl_maxLength ), true, vl_fragmentOffset, pl_connId);
          ip_payload := substr(ip_payload, vl_maxLength, lengthof(ip_payload) - vl_maxLength);
          vl_msgLength := vl_msgLength - vl_maxLength;
          vl_fragmentOffset := vl_fragmentOffset + vl_fragmentOffsetInc;
        }
      } 
    }

    v_EPTF_TransportIPL2_ipPacketId := v_EPTF_TransportIPL2_ipPacketId + 1;
    if(v_EPTF_TransportIPL2_ipPacketId > 65535) { v_EPTF_TransportIPL2_ipPacketId := 0; }
  }

  // fragments pl_message into segments, calls f_EPTF_TransportIPL2_sendTcpSegmentOverL2 for each segment, sets PSH flag for last segment
  private function f_EPTF_TransportIPL2_tcpSendMessage (
    in integer pl_sockId,
    in octetstring pl_message,
    inout Result pl_result) 
  runs on EPTF_TransportIPL2_CT
  return boolean
  {
    if(v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpState != ESTABLISHED and
       v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpState != CLOSE_WAIT) {
      if(v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpState == SYN_SENT or
         v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpState == SYN_RECEIVED) {
/*        pl_result.errorCode := ERROR_TEMPORARILY_UNAVAILABLE;
        pl_result.os_error_code := omit;
        pl_result.os_error_text := omit;
        return false;*/
        if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
          f_EPTF_TransportIPL2_debug(%definitionId&": cannot send in state "&
            log2str(v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpState)&
            ", appending message to fifo.");
        }
		if(tsp_EPTF_TransportIPL2_sendFifo_useCLLBuffer){
		  f_EPTF_Buffer_put_os(v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendFifo_bufferID, pl_message);	
		} else {
          v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendFifo := 
            v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendFifo &
            pl_message; // store the rest of the message to be sent after the ACK of the SYN or SYN+ACK
		}

        return true;
      } else {
        f_EPTF_TransportIPL2_warning(%definitionId&": called in state "&
          log2str(v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpState));
        pl_result.errorCode := ERROR_INVALID_CONNECTION;
        pl_result.os_error_code := c_EPTF_TransportIPL2_errorInvalidSocketState;
        pl_result.os_error_text := "cannot send on socket with state "&log2str(v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpState);
        return false;
      }
    }
    if(v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.pendingClose) {
      f_EPTF_TransportIPL2_warning(%definitionId&": trying to send on a socket which was closed");
      pl_result.errorCode := ERROR_INVALID_CONNECTION;
      pl_result.os_error_code := c_EPTF_TransportIPL2_errorSocketClosed;
      pl_result.os_error_text := "trying to send on a socket which was closed";
      return false;
    }



    if(tsp_EPTF_TransportIPL2_sendFifo_useCLLBuffer){
	  if (f_EPTF_Buffer_get_read_len(v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendFifo_bufferID) > 0){
        f_EPTF_TransportIPL2_debug(%definitionId&": appending the whole message to fifo.");
  	    f_EPTF_Buffer_put_os(v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendFifo_bufferID, pl_message);	
        return true;   
	  }     
	} else {
      if(lengthof(v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendFifo) > 0){             
        f_EPTF_TransportIPL2_debug(%definitionId&": appending the whole message to fifo.");
	    v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendFifo :=
     	    v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendFifo & pl_message; 	  
        return true;
      }    
    }    

    var integer vl_msgLength := lengthof(pl_message);
    var integer vl_msgOffset := 0;
    var integer vl_mss :=  v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.mss;
    var integer vl_flags := c_EPTF_TransportIPL2_TCPControlBit_ack;
    do {
      if(v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.remoteWindowSize < vl_mss) {
        if(v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.remoteWindowSize > 0) {
          vl_mss := v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.remoteWindowSize; // fit the segment in the window
        } else {
          if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
            f_EPTF_TransportIPL2_debug(%definitionId&": remote window full, appending the remaining "&
              int2str(lengthof(pl_message)-vl_msgOffset)&" bytes of the message to fifo.");
          }
          // store the rest of the message to be sent later, when the window becomes non-zero (hopefully the next incoming ACK)
          if(vl_msgOffset > 0) {

  	  		if(tsp_EPTF_TransportIPL2_sendFifo_useCLLBuffer){
  	  		  f_EPTF_Buffer_clear(v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendFifo_bufferID);
			  f_EPTF_Buffer_put_os(v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendFifo_bufferID, substr(pl_message, vl_msgOffset, lengthof(pl_message) - vl_msgOffset));	
		    } else {
              v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendFifo :=
              substr(pl_message, vl_msgOffset, lengthof(pl_message) - vl_msgOffset);
            }


          } else {
  	  		if(tsp_EPTF_TransportIPL2_sendFifo_useCLLBuffer){
  	  		  f_EPTF_Buffer_clear(v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendFifo_bufferID);
			  f_EPTF_Buffer_put_os(v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendFifo_bufferID, pl_message);	
		    } else {
              v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendFifo := pl_message;             
            }
          }
          break;
        }
      }
      if (vl_msgLength <= vl_mss){
        vl_flags := vl_flags + c_EPTF_TransportIPL2_TCPControlBit_psh;
        if(vl_msgOffset > 0) {
          f_EPTF_TransportIPL2_sendTcpSegmentOverL2(pl_sockId, vl_flags, substr(pl_message, vl_msgOffset, vl_msgLength));
        } else {
          f_EPTF_TransportIPL2_sendTcpSegmentOverL2(pl_sockId, vl_flags, pl_message);
        }
        break;
      } else {
        f_EPTF_TransportIPL2_sendTcpSegmentOverL2(pl_sockId, vl_flags, substr(pl_message, vl_msgOffset, vl_mss ));
        vl_msgLength := vl_msgLength - vl_mss;
        vl_msgOffset := vl_msgOffset + vl_mss;
      }
    } while(true);
    return true;
  }

  private function f_EPTF_TransportIPL2_retransmitTcpSegment(
    in EPTF_ScheduledAction pl_action, // pl_action.actionId[0]: socket ID, pl_action.actionId[1]: send-segment buffer
    in integer pl_eventIndex)
  runs on EPTF_TransportIPL2_CT
  return boolean
  {
    f_EPTF_SchedulerComp_refreshSnapshotTime();
    if(f_EPTF_SchedulerComp_snapshotTime() >
        v_EPTF_CommPort_connectionDatabase.data[pl_action.actionId[0]].tcpConnectionData.sendBuffer.segments[pl_action.actionId[1]].timestamp
          + tsp_EPTF_TransportIPL2_tcpConnectionTimeout) {
      // notify the app of connection timeout
      if(v_EPTF_TransportIPL2_LGenInfoList[v_EPTF_CommPort_connectionDatabase.data[pl_action.actionId[0]].lgenIdx].eventHandler != null) {
        var PortError vl_tmp := ERROR_SOCKET;
        v_EPTF_TransportIPL2_LGenInfoList[v_EPTF_CommPort_connectionDatabase.data[pl_action.actionId[0]].lgenIdx].eventHandler.apply(
          IPL2,
          v_EPTF_CommPort_connectionDatabase.data[pl_action.actionId[0]].uniqueId,
//          Event_Result ,
          {result := {
            errorCode := vl_tmp,
            connId := v_EPTF_CommPort_connectionDatabase.data[pl_action.actionId[0]].uniqueId,
            os_error_code := 7,//c_EPTF_TransportIPL2_errorConnectionTimedOut,    FIXME: use the constant instead of the literal, when the FATAL ERROR of the compiler is fixed
            os_error_text := "connection timed out"
          }}
        );
      }
      f_EPTF_TransportIPL2_debug(%definitionId&": connection timed out");
      v_EPTF_CommPort_connectionDatabase.data[pl_action.actionId[0]].tcpConnectionData.sendBuffer.segments[pl_action.actionId[1]].retransmitTimerIdx := -1;
      f_EPTF_TransportIPL2_finalCloseTcp(pl_action.actionId[0]);
      //return false;
      return true;
    }

    f_EPTF_TransportIPL2_sendIpOverL2(
      v_EPTF_CommPort_connectionDatabase.data[pl_action.actionId[0]].locHost,
      v_EPTF_CommPort_connectionDatabase.data[pl_action.actionId[0]].remHost,
      c_ip_proto_tcp,
      v_EPTF_CommPort_connectionDatabase.data[pl_action.actionId[0]].tcpConnectionData.sendBuffer.segments[pl_action.actionId[1]].encodedSegment,
      false, 0, pl_action.actionId[0]);

    v_EPTF_CommPort_connectionDatabase.data[pl_action.actionId[0]].tcpConnectionData.retransmitTime :=
      2.0 * v_EPTF_CommPort_connectionDatabase.data[pl_action.actionId[0]].tcpConnectionData.retransmitTime;
    if(v_EPTF_CommPort_connectionDatabase.data[pl_action.actionId[0]].tcpConnectionData.retransmitTime > tsp_EPTF_TransportIPL2_tcpMaxRetransmitTime) {
      v_EPTF_CommPort_connectionDatabase.data[pl_action.actionId[0]].tcpConnectionData.retransmitTime := tsp_EPTF_TransportIPL2_tcpMaxRetransmitTime;
    }

    if(not f_EPTF_TransportIPL2_scheduleTcpRetransmission(pl_action.actionId[0], pl_action.actionId[1])) {
      return false;
    }
    return true;
  }

  private function f_EPTF_TransportIPL2_scheduleTcpRetransmission(
    in integer pl_socketId,
    in integer pl_segmentIdx)
  runs on EPTF_TransportIPL2_CT
  return boolean
  {
    if( not f_EPTF_SchedulerComp_scheduleAction(
        f_EPTF_SchedulerComp_snapshotTime() + v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.retransmitTime,
        refers(f_EPTF_TransportIPL2_retransmitTcpSegment),
        { pl_socketId, pl_segmentIdx },
        v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.sendBuffer.segments[pl_segmentIdx].retransmitTimerIdx)) {
      f_EPTF_TransportIPL2_error(%definitionId&": f_EPTF_SchedulerComp_scheduleAction failed, could not schedule retransmission of TCP segment.");
      return false;
    }
    return true;
  }

  private function f_EPTF_TransportIPL2_cancelTcpRetransmission(
    in integer pl_socketId,
    in integer pl_segmentIdx)
  runs on EPTF_TransportIPL2_CT
  {
    if(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.sendBuffer.segments[pl_segmentIdx].retransmitTimerIdx >= 0) {
      if(not f_EPTF_SchedulerComp_CancelEvent(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.sendBuffer.segments[pl_segmentIdx].retransmitTimerIdx)) {
        f_EPTF_TransportIPL2_warning(%definitionId&": f_EPTF_SchedulerComp_CancelEvent failed.");
      }
      f_EPTF_FBQ_moveFromBusyToFreeTail(pl_segmentIdx, v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.sendBuffer.queue);
    }
    v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.sendBuffer.segments[pl_segmentIdx] := c_EPTF_TransportIPL2_TcpSendSegment_init;
  }

  friend function f_EPTF_TransportIPL2_hasPendingRetransmission(
    in integer pl_socketId)
  runs on EPTF_TransportIPL2_CT
  return boolean
  {
    return 0 != f_EPTF_FBQ_getLengthOfBusyChain(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.sendBuffer.queue);
  }

  friend function f_EPTF_TransportIPL2_forceRetransmission(
    in integer pl_socketId)
  runs on EPTF_TransportIPL2_CT
  {
    var integer vl_segmentIdx := -1;
    if(f_EPTF_FBQ_getBusyHeadIdx(
        vl_segmentIdx,
        v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.sendBuffer.queue)) {

/*      if(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.sendBuffer.segments[vl_segmentIdx].retransmitTimerIdx >= 0) {
        if(not f_EPTF_SchedulerComp_CancelEvent(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.sendBuffer.segments[vl_segmentIdx].retransmitTimerIdx)) {
          f_EPTF_TransportIPL2_warning(%definitionId&": f_EPTF_SchedulerComp_CancelEvent failed.");
        }
      }*/

      f_EPTF_TransportIPL2_sendIpOverL2(
        v_EPTF_CommPort_connectionDatabase.data[pl_socketId].locHost,
        v_EPTF_CommPort_connectionDatabase.data[pl_socketId].remHost,
        c_ip_proto_tcp,
        v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.sendBuffer.segments[vl_segmentIdx].encodedSegment,
        false, 0, pl_socketId);

//      if(not f_EPTF_TransportIPL2_scheduleTcpRetransmission(pl_socketId, vl_segmentIdx)) {
//      }

    }
  }

  private function f_EPTF_TransportIPL2_sendTcpRstOnClosed(
    in octetstring pl_srcAddr,
    in octetstring pl_dstAddr,
    in EPTF_TransportIPL2_PDU_TCP pl_incomingSegment)
  runs on EPTF_TransportIPL2_CT
  {
    var EPTF_TransportIPL2_PDU_TCP vl_pdu := {
      source_port := pl_incomingSegment.dest_port,
      dest_port := pl_incomingSegment.source_port,
      sequence_number := 0,
      acknowledgment_number := pl_incomingSegment.sequence_number,
      control_bits := c_EPTF_TransportIPL2_TCPControlBit_rst + c_EPTF_TransportIPL2_TCPControlBit_ack,
      window := tsp_EPTF_TransportIPL2_tcpDefaultReceiveWindowSize,
      options := ''O,
      data := ''O
    }
    if(f_EPTF_and4i(pl_incomingSegment.control_bits, c_EPTF_TransportIPL2_TCPControlBit_ack) != 0) {
      vl_pdu.sequence_number := pl_incomingSegment.acknowledgment_number;
    }
    vl_pdu.acknowledgment_number := vl_pdu.acknowledgment_number + lengthof(pl_incomingSegment.data);

/*    var octetstring vl_encPdu := f_enc_PDU_TCP(
      pl_dstAddr, // -> becomes source address
      pl_srcAddr, // -> becomes destination address
      vl_pdu,
      true,
      true);*/
    var octetstring vl_encPdu := f_EPTF_TransportIPL2_encodeTCPWithChecksum(
      pl_dstAddr, // -> becomes source address
      pl_srcAddr, // -> becomes destination address
      vl_pdu);

    f_EPTF_TransportIPL2_sendIpOverL2(pl_dstAddr, pl_srcAddr, c_ip_proto_tcp, vl_encPdu, false, 0);
  }

  private function f_EPTF_TransportIPL2_handleSendTcpAck(
    in EPTF_ScheduledAction pl_action, // pl_action.actionId[0]: socket ID
    in integer pl_eventIndex)
  runs on EPTF_TransportIPL2_CT
  return boolean
  {
    f_EPTF_TransportIPL2_debug(%definitionId&": sending scheduled ACK.");
    v_EPTF_CommPort_connectionDatabase.data[pl_action.actionId[0]].tcpConnectionData.ackTimerIdx := -1;
    f_EPTF_TransportIPL2_sendTcpAck(pl_action.actionId[0]);
    return true;
  }

  private function f_EPTF_TransportIPL2_scheduleSendTcpAck(
    in integer pl_socketId)
  runs on EPTF_TransportIPL2_CT
  {
    if(tsp_EPTF_TransportIPL2_tcpMaxAckDelay < 0.001) {
      f_EPTF_TransportIPL2_sendTcpAck(pl_socketId);
      return;
    }
    if(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.ackTimerIdx < 0) {
//      f_EPTF_SchedulerComp_refreshSnapshotTime();
      if( not f_EPTF_SchedulerComp_scheduleAction(
          f_EPTF_SchedulerComp_snapshotTime() + tsp_EPTF_TransportIPL2_tcpMaxAckDelay,
          refers(f_EPTF_TransportIPL2_handleSendTcpAck),
          { pl_socketId },
          v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.ackTimerIdx)) {
        // scheduling failed -> send the ACK
        f_EPTF_TransportIPL2_warning(%definitionId&": could not schedule ACK of TCP segment, sending it now.");
        v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.ackTimerIdx := -1;
        f_EPTF_TransportIPL2_sendTcpAck(pl_socketId);
        return;
      }
//      f_EPTF_TransportIPL2_debug(%definitionId&": scheduled ACK.");
    } else {
      f_EPTF_TransportIPL2_debug(%definitionId&": ACK already scheduled.");
    }
  }

  private function f_EPTF_TransportIPL2_cancelSendTcpAck(
    in integer pl_socketId)
  runs on EPTF_TransportIPL2_CT
  {
    if(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.ackTimerIdx >= 0) {
      if(not f_EPTF_SchedulerComp_CancelEvent(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.ackTimerIdx)) {
        f_EPTF_TransportIPL2_warning(%definitionId&": f_EPTF_SchedulerComp_CancelEvent failed.");
      }
      v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.ackTimerIdx := -1;
//      f_EPTF_TransportIPL2_debug(%definitionId&": cancelled ACK.");
    } else {
      f_EPTF_TransportIPL2_debug(%definitionId&": no ACK scheduled.");
    }
  }

  private function f_EPTF_TransportIPL2_sendTcpAck (
    in integer pl_socketId) 
  runs on EPTF_TransportIPL2_CT
  {
    var EPTF_TransportIPL2_PDU_TCP vl_pdu := {
      source_port := v_EPTF_CommPort_connectionDatabase.data[pl_socketId].locPort,
      dest_port := v_EPTF_CommPort_connectionDatabase.data[pl_socketId].remPort,
      sequence_number := v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.sendSeqNum,
      acknowledgment_number := v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.expectedSeqNum,
      control_bits := c_EPTF_TransportIPL2_TCPControlBit_ack,
      window := tsp_EPTF_TransportIPL2_tcpDefaultReceiveWindowSize,//v_EPTF_CommPort_connectionDatabase.data[pl_socketId].receiveWindowSize,
      options := ''O,
      data := ''O
    }

    var octetstring vl_encPdu := f_EPTF_TransportIPL2_encodeTCPWithChecksum(
      v_EPTF_CommPort_connectionDatabase.data[pl_socketId].locHost,
      v_EPTF_CommPort_connectionDatabase.data[pl_socketId].remHost,
      vl_pdu);

    f_EPTF_TransportIPL2_sendIpOverL2(
      v_EPTF_CommPort_connectionDatabase.data[pl_socketId].locHost,
      v_EPTF_CommPort_connectionDatabase.data[pl_socketId].remHost,
      c_ip_proto_tcp,
      vl_encPdu,
      false, 0, pl_socketId);
  }

  private function f_EPTF_TransportIPL2_handleTimeWaitTimeout(
    in EPTF_ScheduledAction pl_action, // pl_action.actionId[0]: socket ID
    in integer pl_eventIndex)
  runs on EPTF_TransportIPL2_CT
  return boolean
  {
    f_EPTF_TransportIPL2_debug(%definitionId&": closing socket.");
    v_EPTF_CommPort_connectionDatabase.data[pl_action.actionId[0]].tcpConnectionData.timeWaitTimerIdx := -1;
    f_EPTF_TransportIPL2_finalCloseTcp(pl_action.actionId[0]);
    return true;
  }

  private function f_EPTF_TransportIPL2_scheduleTimeWait(
    in integer pl_socketId)
  runs on EPTF_TransportIPL2_CT
  {
    if(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.timeWaitTimerIdx < 0) {
//      f_EPTF_SchedulerComp_refreshSnapshotTime();
      if( not f_EPTF_SchedulerComp_scheduleAction(
          f_EPTF_SchedulerComp_snapshotTime() + tsp_EPTF_TransportIPL2_tcpMSL * 2.0,
          refers(f_EPTF_TransportIPL2_handleTimeWaitTimeout),
          { pl_socketId },
          v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.timeWaitTimerIdx)) {
        // scheduling failed -> close the socket
        f_EPTF_TransportIPL2_warning(%definitionId&": could not schedule ACK of TCP segment, sending it now.");
        f_EPTF_TransportIPL2_finalCloseTcp(pl_socketId);
        return;
      }
      f_EPTF_TransportIPL2_debug(%definitionId&": scheduled TIME_WAIT.");
    } else {
      f_EPTF_TransportIPL2_debug(%definitionId&": TIME_WAIT already scheduled.");
    }
  }

  private function f_EPTF_TransportIPL2_cancelTimeWait(
    in integer pl_socketId)
  runs on EPTF_TransportIPL2_CT
  {
    if(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.timeWaitTimerIdx >= 0) {
      if(not f_EPTF_SchedulerComp_CancelEvent(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.timeWaitTimerIdx)) {
        f_EPTF_TransportIPL2_warning(%definitionId&": f_EPTF_SchedulerComp_CancelEvent failed.");
      }
      v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.timeWaitTimerIdx := -1;
      f_EPTF_TransportIPL2_debug(%definitionId&": cancelled TIME_WAIT.");
    } else {
      f_EPTF_TransportIPL2_debug(%definitionId&": no TIME_WAIT scheduled.");
    }
  }

  private function f_EPTF_TransportIPL2_sendTcpFin (
    in integer pl_socketId) 
  runs on EPTF_TransportIPL2_CT
  {
    f_EPTF_TransportIPL2_sendTcpSegmentOverL2(
      pl_socketId,
      c_EPTF_TransportIPL2_TCPControlBit_ack + c_EPTF_TransportIPL2_TCPControlBit_fin,
      ''O);
    v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.unackedBytes :=
      v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.unackedBytes +
        1;
  }

  friend function f_EPTF_TransportIPL2_sendTcpSegmentOverL2(
    in integer pl_sockId,
    in integer pl_flags,
    in octetstring pl_segment,
    in boolean pl_doSend := true)
  runs on EPTF_TransportIPL2_CT
  {
    if (lengthof(pl_segment) > v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.mss) {
      f_EPTF_Base_assert(%definitionId&": pl_segment is longer than MSS for the socket.",
        lengthof(pl_segment) <= v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.mss);
    }

    f_EPTF_SchedulerComp_refreshSnapshotTime();

    var EPTF_TransportIPL2_PDU_TCP vl_pdu := {
      source_port := v_EPTF_CommPort_connectionDatabase.data[pl_sockId].locPort,
      dest_port := v_EPTF_CommPort_connectionDatabase.data[pl_sockId].remPort,
      sequence_number := v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendSeqNum,
      acknowledgment_number := v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.expectedSeqNum,
      control_bits := pl_flags,
      window := tsp_EPTF_TransportIPL2_tcpDefaultReceiveWindowSize,//v_EPTF_CommPort_connectionDatabase.data[pl_sockId].receiveWindowSize,
      options := ''O,
      data := pl_segment
    }
    var integer vl_idx := f_EPTF_FBQ_getOrCreateFreeSlot(v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendBuffer.queue);
    f_EPTF_FBQ_moveFromFreeToBusyTail(vl_idx, v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendBuffer.queue);
    v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendBuffer.segments[vl_idx].payloadLength := lengthof(pl_segment);

    v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendSeqNum :=
      f_EPTF_mod32Add(v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendSeqNum, lengthof(pl_segment));
    if(f_EPTF_and4i(pl_flags,c_EPTF_TransportIPL2_TCPControlBit_syn) != 0) {
      v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendSeqNum :=
        f_EPTF_mod32Add(v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendSeqNum, 1);
      if(v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.mss != c_EPTF_TransportIPL2_tcpDefaultMSS) {
        // MSS option
        vl_pdu.options := '0204'O & int2oct(v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.mss, 2);
      }
    }
    if(f_EPTF_and4i(pl_flags, c_EPTF_TransportIPL2_TCPControlBit_fin) != 0) {
      v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendSeqNum :=
        f_EPTF_mod32Add(v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendSeqNum, 1);
    }
    v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendBuffer.segments[vl_idx].ackSeqNum :=
      v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendSeqNum;

    v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.unackedBytes :=
      v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.unackedBytes +
        lengthof(pl_segment);

    v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendBuffer.segments[vl_idx].encodedSegment := f_EPTF_TransportIPL2_encodeTCPWithChecksum(
      v_EPTF_CommPort_connectionDatabase.data[pl_sockId].locHost,
      v_EPTF_CommPort_connectionDatabase.data[pl_sockId].remHost,
      vl_pdu);

    v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendBuffer.segments[vl_idx].timestamp := f_EPTF_SchedulerComp_snapshotTime();
    if(not f_EPTF_TransportIPL2_scheduleTcpRetransmission(pl_sockId, vl_idx)) {
      return;
    }

    v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.remoteWindowSize :=
      v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.remoteWindowSize -
        lengthof(pl_segment);

    if(pl_doSend) {
      f_EPTF_TransportIPL2_cancelSendTcpAck(pl_sockId);
      f_EPTF_TransportIPL2_sendIpOverL2(
        v_EPTF_CommPort_connectionDatabase.data[pl_sockId].locHost,
        v_EPTF_CommPort_connectionDatabase.data[pl_sockId].remHost,
        c_ip_proto_tcp,
        v_EPTF_CommPort_connectionDatabase.data[pl_sockId].tcpConnectionData.sendBuffer.segments[vl_idx].encodedSegment,
        false, 0, pl_sockId);
    }
  }

  private function f_EPTF_TransportIPL2_sendIpOverL2( 
    in EPTF_TransportIPL2_IpAddress pl_locHost,
    in EPTF_TransportIPL2_IpAddress pl_remHost,
    in integer pl_protoId,
    in octetstring pl_payload,
    in boolean pl_moreFragments,
    in integer pl_fragmentOffset,
    in ConnectionId pl_connId := -1)
  runs on EPTF_TransportIPL2_CT
  {
    var IPv4_packet ip_mess := {
      header := {
        ver  := c_ip_version_ipv4,
        hlen := 5,
        tos  := 0,
        tlen := lengthof(pl_payload) + 20,
        id   := v_EPTF_TransportIPL2_ipPacketId,
        res  := '0'B,
        dfrag   := '1'B,
        mfrag   := '0'B,
        foffset := pl_fragmentOffset,
        ttl     := 255,
        proto   := pl_protoId,
        cksum   := 0,
        srcaddr := pl_locHost,
        dstaddr := pl_remHost  
      },
      ext_headers := omit,
      payload := pl_payload
    }

    if (pl_moreFragments){
      ip_mess.header.mfrag := '1'B;
    }

/*    var octetstring data := f_IPv4_enc_eth(ip_mess);
    var octetstring chksumip := f_IPv4_checksum( data );
    data[10] := chksumip[0];
    data[11] := chksumip[1];*/
    var octetstring data := f_EPTF_TransportIPL2_encodeIP(ip_mess);


    var EPTF_TransportIPL2_MACAddress vl_mac;
    var integer vl_ifaceIdx := -1;
    if(sizeof(v_EPTF_TransportIPL2_ethernetInterfaceList) > 0 or
      tsp_EPTF_TransportIPL2_dstMacAddr == cg_EPTF_TransportIPL2_NullMACAddress) {
      // determine destination MAC address from the routing table:
      if(not f_EPTF_TransportIPL2_ARP_getMacPrivate(
        pl_locHost, // sender
        tsp_EPTF_TransportIPL2_srcMacAddr, // sender mac
        pl_remHost,
        vl_ifaceIdx,
        vl_mac)) {
        // destination unreachable
        f_EPTF_TransportIPL2_warning(%definitionId&": Cannot send data: destination unreachable: "&f_EPTF_TransportIPL2_ip2str(pl_remHost));
        return;
      }
      if(v_EPTF_TransportIPL2_ethernetInterfaceList[vl_ifaceIdx].interfaceId < 0) {
        f_EPTF_TransportIPL2_warning(%definitionId&": Cannot send data: interface "&
          v_EPTF_TransportIPL2_ethernetInterfaceList[vl_ifaceIdx].name&" is not open.");
        return;
      }
    } else {
      vl_mac := tsp_EPTF_TransportIPL2_dstMacAddr
    }
    f_EPTF_TransportIPL2_sendOverL2(
     vl_ifaceIdx,
     pl_eth_dst_addr := vl_mac,
     //pl_eth_src_addr := cg_EPTF_TransportIPL2_NullMACAddress,
     pl_type_field   := int2oct(c_ip_gre_proto_ipv4, 2),
     pl_payload      := data,
     pl_connId       := pl_connId);
  }

  private function f_EPTF_TransportIPL2_sendOverL2(
    in integer       pl_interfaceId,
    in OCT6          pl_eth_dst_addr,
    //in OCT6          pl_eth_src_addr := cg_EPTF_TransportIPL2_NullMACAddress,
    in OCT2          pl_type_field,
    in PDU_LANL2     pl_payload,
    in ConnectionId  pl_connId := -1)
  runs on EPTF_TransportIPL2_CT
  {
    //Add 802.1Q VLAN tagging
    var integer vl_i := 0;
    var octetstring vl_appendMsg := ''O;
    if(pl_connId >= 0) {
      if(sizeof(v_EPTF_CommPort_connectionDatabase.data[pl_connId].vlanTCIs) > 0)
      {
        for(vl_i:=0; vl_i<sizeof(v_EPTF_CommPort_connectionDatabase.data[pl_connId].vlanTCIs)-1; vl_i:=vl_i+1)
        {
          vl_appendMsg := vl_appendMsg & bit2oct(int2bit(v_EPTF_CommPort_connectionDatabase.data[pl_connId].vlanTCIs[vl_i].PCP, 3)&
            int2bit(v_EPTF_CommPort_connectionDatabase.data[pl_connId].vlanTCIs[vl_i].CFI, 1)&
            int2bit(v_EPTF_CommPort_connectionDatabase.data[pl_connId].vlanTCIs[vl_i].VID, 12)) & c_EPTF_TransportIPL2_VLAN_TPID;
        }
        //Ethertype has to be set to IPv4 for the last VLAN TCI
        vl_appendMsg := vl_appendMsg & bit2oct(int2bit(v_EPTF_CommPort_connectionDatabase.data[pl_connId].vlanTCIs[vl_i].PCP, 3)&
          int2bit(v_EPTF_CommPort_connectionDatabase.data[pl_connId].vlanTCIs[vl_i].CFI, 1)&
          int2bit(v_EPTF_CommPort_connectionDatabase.data[pl_connId].vlanTCIs[vl_i].VID, 12)) & int2oct(c_ip_gre_proto_ipv4, 2);

        pl_type_field := c_EPTF_TransportIPL2_VLAN_TPID;
        pl_payload := vl_appendMsg&pl_payload;
      }
    }
    
    if(pl_interfaceId < 0) {
      var ASP_LANL2 vl_message2send := {
        eth_dst_addr := pl_eth_dst_addr,
   //     eth_src_addr := pl_eth_src_addr,
        eth_src_addr := omit, // test port determines the source mac address
        type_field   := pl_type_field,
        payload      := pl_payload
      }
  //     if (pl_eth_src_addr == cg_EPTF_TransportIPL2_NullMACAddress) {
  //       vl_message2send.eth_src_addr := omit; // test port determines the source mac address
  //     }
      IPL2_PCO.send(vl_message2send);
    } else {
      var ASP_v2_LANL2 vl_message2send := {
        interface_id := v_EPTF_TransportIPL2_ethernetInterfaceList[pl_interfaceId].interfaceId,
        eth_dst_addr := pl_eth_dst_addr,
   //     eth_src_addr := pl_eth_src_addr,
        eth_src_addr := omit, // test port determines the source mac address
        type_field   := pl_type_field,
        payload      := pl_payload
      }
  //     if (pl_eth_src_addr == cg_EPTF_TransportIPL2_NullMACAddress) {
  //       vl_message2send.eth_src_addr := omit; // test port determines the source mac address
  //     }
      IPL2_PCO.send(vl_message2send);
    }
  }

  private function f_EPTF_TransportIPL2_addIp(in octetstring pl_ipAddr)
  runs on EPTF_TransportIPL2_CT
  {
    var integer vl_dummy := -1;
    if(not f_EPTF_oct2int_HashMap_Find(v_EPTF_TransportIPL2_ipAddressHashmapId, pl_ipAddr, vl_dummy)) {
      f_EPTF_oct2int_HashMap_Insert(v_EPTF_TransportIPL2_ipAddressHashmapId, pl_ipAddr, 1);
    }
  }

  private function f_EPTF_TransportIPL2_hasIp(in octetstring pl_ipAddr)
  runs on EPTF_TransportIPL2_CT
  return boolean
  {
    var integer vl_dummy := -1;
    return f_EPTF_oct2int_HashMap_Find(v_EPTF_TransportIPL2_ipAddressHashmapId, pl_ipAddr, vl_dummy);
  }

///////////////////////////////////////////////////////////
//  Function: f_EPTF_Transport_registerMsgCallback
// 
//  Purpose:
//    Function to register Message callback function
//
//  Parameters:
//    pl_transportType - *in* - <EPTF_Transport_TransportType> - transport type
//    pl_LGenType - *in* - *charstring* - LGen type
//    pl_msgHandler - *in* - <EPTF_Transport_MsgCallback_FT> - message handler
//    pl_eventHandler - *in* - <EPTF_Transport_EventCallback_FT> - event handler
//
//  Return Value:
//    -
//
//  Errors:
//    -
//
///////////////////////////////////////////////////////////
  public function f_EPTF_Transport_registerMsgCallback(
    in EPTF_Transport_TransportType pl_transportType,
    in charstring pl_LGenType,
    in EPTF_Transport_MsgCallback_FT pl_msgHandler,
    in EPTF_Transport_EventCallback_FT pl_eventHandler := null)
  runs on EPTF_TransportIPL2_CT
  {
    if (IPL2 != pl_transportType) {
      f_EPTF_Base_assert(%definitionId&": The transport type "&log2str(pl_transportType)&
        " is not supported in the IPL2 transport.", IPL2 == pl_transportType)
    }

    if (pl_LGenType=="")
    {    
      f_EPTF_TransportIPL2_error(%definitionId&": LGenType must be specified.");
    }
    else
    {
      var integer vl_fbqId;

      f_EPTF_TransportIPL2_addLGenInfo(pl_LGenType, vl_fbqId);
      v_EPTF_TransportIPL2_LGenInfoList[vl_fbqId].msgHandler := pl_msgHandler;
      v_EPTF_TransportIPL2_LGenInfoList[vl_fbqId].eventHandler := pl_eventHandler;

    }
  }

///////////////////////////////////////////////////////////
//  Function: f_EPTF_Transport_getLocalAddress
// 
//  Purpose:
//    Function to get Local Address for a connection
//
//  Parameters:
//    pl_transportType - *in* - <EPTF_Transport_TransportType> transport type
//    pl_connId - *in* - <ConnectionId> - The ID of the connection
//    pl_hostName - *out* - <HostName> - local host name
//    pl_portNumber - *out* - <PortNumber> - local port numbers
//    pl_result - *out* - <Result> - the value of the connection detail
//
//  Return Value:
//    -
//
//  Errors:
//    -
//
//  Detailed Comments:
//    
/////////////////////////////////////////////////////////// 
public function f_EPTF_Transport_getLocalAddress(
  in EPTF_Transport_TransportType pl_transportType,
  in ConnectionId pl_connId,
  out HostName pl_hostName,
  out PortNumber pl_portNumber,
  out Result pl_result)
runs on EPTF_TransportIPL2_CT
{
   pl_result := { errorCode := omit, connId  := omit, os_error_code:=omit, os_error_text:= omit };
   if (IPL2 != pl_transportType) {
     f_EPTF_Base_assert(%definitionId&": The transport type "&log2str(pl_transportType)&
       " is not supported in the IPL2 transport.", IPL2 == pl_transportType)
   }

   f_EPTF_TransportIPL2_checkConnId(%definitionId, pl_connId);
   pl_hostName := f_EPTF_TransportIPL2_ip2str(v_EPTF_CommPort_connectionDatabase.data[pl_connId].locHost);
   pl_portNumber := v_EPTF_CommPort_connectionDatabase.data[pl_connId].locPort;

}

///////////////////////////////////////////////////////////
//  Function: f_EPTF_Transport_getRemoteAddress
// 
//  Purpose:
//    Function to get remote Address for a connection
//
//  Parameters:
//    pl_transportType - *in* - <EPTF_Transport_TransportType> transport type
//    pl_connId - *in* - <ConnectionId> - The ID of the connection
//    pl_hostName - *out* - <HostName> - remote host name
//    pl_portNumber - *out* - <PortNumber> - remote port number
//    pl_result - *out* - <Result> - the value of the connection detail
//
//  Return Value:
//    -
//
//  Errors:
//    -
//
//  Detailed Comments:
//    
/////////////////////////////////////////////////////////// 
public function f_EPTF_Transport_getRemoteAddress(
  in EPTF_Transport_TransportType pl_transportType,
  in ConnectionId pl_connId,
  out HostName pl_hostName,
  out PortNumber pl_portNumber,
  out Result pl_result)
runs on EPTF_TransportIPL2_CT
{
   pl_result := { errorCode := omit, connId  := omit, os_error_code:=omit, os_error_text:= omit };
   if (IPL2 != pl_transportType) {
     f_EPTF_Base_assert(%definitionId&": The transport type "&log2str(pl_transportType)&
       " is not supported in the IPL2 transport.", IPL2 == pl_transportType)
   }

   f_EPTF_TransportIPL2_checkConnId(%definitionId, pl_connId);
   pl_hostName := f_EPTF_TransportIPL2_ip2str(v_EPTF_CommPort_connectionDatabase.data[pl_connId].remHost);
   pl_portNumber := v_EPTF_CommPort_connectionDatabase.data[pl_connId].remPort;

}

private function f_EPTF_TransportIPL2_getPacketFilterString(in integer pl_ethInterfaceIdx)
runs on EPTF_TransportIPL2_CT
return charstring
{
  return v_EPTF_TransportIPL2_ethernetInterfaceList[pl_ethInterfaceIdx].packetFilter.filterString;
}

///////////////////////////////////////////////////////////
//  Function: f_EPTF_Transport_appendInterfaces
// 
//  Purpose:
//    Function to setup additional interfaces
//
//  Parameters:
//    pl_transportType - *in* <EPTF_Transport_TransportType> - The ID of the connection
//    pl_interfaceList - *in* <EPTF_Transport_InterfaceInformationList> - the list of additional interfaces
//    pl_result - *out* <Result> - result
/////////////////////////////////////////////////////////// 
public function f_EPTF_Transport_appendInterfaces(
  in EPTF_Transport_TransportType pl_transportType,
  in EPTF_Transport_InterfaceInformationList pl_interfaceList,
  out Result pl_result)
runs on EPTF_TransportIPL2_CT
{
  if(tsp_EPTF_TransportIPL2_multipleInterfacesMode == false) {
    f_EPTF_TransportIPL2_debug(%definitionId&": single interface mode.");
    return;
  }
  var integer vl_nofOldInterfaceInformationElems := sizeof(v_EPTF_Transport_interfaceInformationList);
  v_EPTF_Transport_interfaceInformationList := v_EPTF_Transport_interfaceInformationList & pl_interfaceList;

  var integer i;
  var EPTF_IntegerList vl_changed := {}; 
  for (i := 0; i < sizeof(pl_interfaceList); i := i + 1) {
    var integer vl_ethInterfaceIdx := -1;
    for(var integer j := 0; j < sizeof(v_EPTF_TransportIPL2_ethernetInterfaceList); j:=j+1) {
      if(v_EPTF_TransportIPL2_ethernetInterfaceList[j].name == pl_interfaceList[i].ifname) {
        vl_ethInterfaceIdx := j;
        break;
      }
    }
    if(vl_ethInterfaceIdx < 0) {
      // init interface DB elem
      vl_ethInterfaceIdx := sizeof(v_EPTF_TransportIPL2_ethernetInterfaceList);
      v_EPTF_TransportIPL2_ethernetInterfaceList[vl_ethInterfaceIdx] := c_EPTF_TransportIPL2_EthernetInterface_init;
      v_EPTF_TransportIPL2_ethernetInterfaceList[vl_ethInterfaceIdx].name := pl_interfaceList[i].ifname;
    }
    var integer vl_nofIdxs := sizeof(v_EPTF_TransportIPL2_ethernetInterfaceList[vl_ethInterfaceIdx].interfaceInformationIdxs);
    v_EPTF_TransportIPL2_ethernetInterfaceList[vl_ethInterfaceIdx].interfaceInformationIdxs[vl_nofIdxs] := i + vl_nofOldInterfaceInformationElems;
    var integer j;
    for(j:=0; j<sizeof(vl_changed) and vl_changed[j]!=vl_ethInterfaceIdx; j:=j+1) { }
    vl_changed[j] := vl_ethInterfaceIdx;
  }
  f_EPTF_TransportIPL2_debug(%definitionId&": filters will be updated for interfaces: "&log2str(vl_changed));

  for(i:=0; i<sizeof(vl_changed); i:=i+1) {
    var charstring vl_oldFilter := f_EPTF_TransportIPL2_getPacketFilterString(vl_changed[i]);
    f_EPTF_TransportIPL2_genPacketFilter(vl_changed[i]);
    var charstring vl_newFilter := f_EPTF_TransportIPL2_getPacketFilterString(vl_changed[i]);
    if(vl_newFilter != vl_oldFilter) {
      if(v_EPTF_TransportIPL2_ethernetInterfaceList[vl_changed[i]].interfaceId >= 0) {
        f_EPTF_TransportIPL2_debug(%definitionId&": packet filter changed for interface: "&v_EPTF_TransportIPL2_ethernetInterfaceList[vl_changed[i]].name);
        f_EPTF_TransportIPL2_closeInterface(vl_changed[i]);
      } else {
        f_EPTF_TransportIPL2_debug(%definitionId&": opening new interface: "&v_EPTF_TransportIPL2_ethernetInterfaceList[vl_changed[i]].name);
      }
      f_EPTF_TransportIPL2_openInterface(vl_changed[i]);
    } else {
      f_EPTF_TransportIPL2_debug(%definitionId&": packet filter didn't change for interface: "&v_EPTF_TransportIPL2_ethernetInterfaceList[vl_changed[i]].name);
    }
  }
  f_EPTF_TransportIPL2_updateRouteTableToEthIF();
}

///////////////////////////////////////////////////////////
//  Function: f_EPTF_Transport_setUpInterfaces
// 
//  Purpose:
//    Function to setup the interfaces
//
//  Parameters:
//    pl_transportType - *in* - <EPTF_Transport_TransportType> - The ID of the connection
//    pl_result - *out* - <Result> - result
//
//  Return Value:
//    -
//
//  Errors:
//    -
//
//  Detailed Comments:
//    
/////////////////////////////////////////////////////////// 
public function f_EPTF_Transport_setUpInterfaces(
  in EPTF_Transport_TransportType pl_transportType,
  out Result pl_result)
runs on EPTF_TransportIPL2_CT
{
  if (IPL2 != pl_transportType) {
    f_EPTF_Base_assert(%definitionId&": The transport type "&log2str(pl_transportType)&
      " is not supported in the IPL2 transport.", IPL2 == pl_transportType)
  }
   pl_result := { errorCode := omit, connId  := omit, os_error_code:=omit, os_error_text:= omit };

  if(tsp_EPTF_TransportIPL2_multipleInterfacesMode == false) {
    f_EPTF_TransportIPL2_debug(%definitionId&": single interface mode.");
    return;
  } else {
    f_LANL2asp_set_param(IPL2_PCO, "port_mode", "multiple_interface");
  }

  var integer i;
  for (i := 0; i < sizeof(v_EPTF_Transport_interfaceInformationList); i := i + 1) {
    var integer vl_ethInterfaceIdx := -1;
    for(var integer j := 0; j < sizeof(v_EPTF_TransportIPL2_ethernetInterfaceList); j:=j+1) {
      if(v_EPTF_TransportIPL2_ethernetInterfaceList[j].name == v_EPTF_Transport_interfaceInformationList[i].ifname) {
        vl_ethInterfaceIdx := j;
        break;
      }
    }
    if(vl_ethInterfaceIdx < 0) {
      // init interface DB elem
      vl_ethInterfaceIdx := sizeof(v_EPTF_TransportIPL2_ethernetInterfaceList);
      v_EPTF_TransportIPL2_ethernetInterfaceList[vl_ethInterfaceIdx] := c_EPTF_TransportIPL2_EthernetInterface_init;
      v_EPTF_TransportIPL2_ethernetInterfaceList[vl_ethInterfaceIdx].name := v_EPTF_Transport_interfaceInformationList[i].ifname;
    }
    var integer vl_nofIdxs := sizeof(v_EPTF_TransportIPL2_ethernetInterfaceList[vl_ethInterfaceIdx].interfaceInformationIdxs);
    v_EPTF_TransportIPL2_ethernetInterfaceList[vl_ethInterfaceIdx].interfaceInformationIdxs[vl_nofIdxs] := i;
    if(v_EPTF_TransportIPL2_ethernetInterfaceList[i].name == tsp_EPTF_TransportIPL2_loopbackInterface) {
      if(tsp_EPTF_TransportIPL2_openLoopbackInterface == false) {
        f_EPTF_TransportIPL2_debug(%definitionId&": loopback interface was specified, "&
          "overriding tsp_EPTF_TransportIPL2_openLoopbackInterface");
      }
      v_EPTF_TransportIPL2_openLoopbackInterface := true; // override module parameter if the loopback interface was added explicitly
    }
  }

  f_EPTF_TransportIPL2_initLoopbackInterface();
}

private function f_EPTF_TransportIPL2_isAddressInSubnet(
  in EPTF_TransportIPL2_IpAddress pl_addr,
  in EPTF_TransportIPL2_IpAddress pl_net,
  in EPTF_TransportIPL2_IpAddress pl_mask)
return boolean
{
  return pl_net == pl_addr and4b pl_mask;
}

private function f_EPTF_TransportIPL2_nofEqualBits(
  in EPTF_TransportIPL2_IpAddress pl_addr1,
  in EPTF_TransportIPL2_IpAddress pl_addr2)
return integer
{
  var bitstring vl_addr1Bits := oct2bit(pl_addr1);
  var bitstring vl_addr2Bits := oct2bit(pl_addr2);
  for(var integer i := 0; i < lengthof(vl_addr1Bits); i:=i+1) {
    if(vl_addr1Bits[i] != vl_addr2Bits[i]) { return i; }
  }
  return lengthof(vl_addr1Bits);
}

private function f_EPTF_TransportIPL2_nofBitsInNetmask(
  in EPTF_TransportIPL2_IpAddress pl_mask)
return integer
{
  return f_EPTF_TransportIPL2_nofEqualBits(pl_mask, c_EPTF_TransportIPL2_singleHostNetmask);
}

private function f_EPTF_TransportIPL2_ipAddrInc(
  in EPTF_TransportIPL2_IpAddress pl_addr,
  in integer pl_count)
return EPTF_TransportIPL2_IpAddress
{
  var integer vl_tmp := oct2int(pl_addr);
  vl_tmp := vl_tmp + pl_count;
  return int2oct(vl_tmp, 4);
}

private function f_EPTF_TransportIPL2_genNetMask(in integer pl_number)
return EPTF_TransportIPL2_IpAddress {
  if(pl_number > 31) { return c_EPTF_TransportIPL2_singleHostNetmask; }
  var bitstring vl_mask := ''B;
  while(pl_number > 0) {
    vl_mask := vl_mask & '1'B;
    pl_number := pl_number - 1;
  }
  while(lengthof(vl_mask) < 32) {
    vl_mask := vl_mask & '0'B;
  }
  return bit2oct(vl_mask);
}

private function f_EPTF_TransportIPL2_genSubnetFilterStr(
  in EPTF_TransportIPL2_IpAddress pl_net,
  in integer pl_netmaskBits)
return charstring
{
  if(pl_netmaskBits >= 32) {
    return "dst host " & f_EPTF_TransportIPL2_ip2str(pl_net);
  } else {
    return "dst net " & f_EPTF_TransportIPL2_ip2str(pl_net) &
      "/"&int2str(pl_netmaskBits);
  }
}

private function f_EPTF_TransportIPL2_simplifySubnetList(
  in EPTF_TransportIPL2_SubnetList pl_subnets)
return EPTF_TransportIPL2_SubnetList
{
  var EPTF_TransportIPL2_SubnetList vl_ret := {};
  for(var integer i:=0; i<sizeof(pl_subnets); i:=i+1) {
    var boolean vl_simplified := false;
    for(var integer j:=0; j<sizeof(vl_ret); j:=j+1) {
      if(pl_subnets[i] == vl_ret[j]) {
        vl_simplified := true;
        break;
      }
      if(pl_subnets[i].maskbits < vl_ret[j].maskbits) {
        if(f_EPTF_TransportIPL2_isAddressInSubnet(
              pl_addr := vl_ret[j].net,
              pl_net := pl_subnets[i].net,
              pl_mask := pl_subnets[i].mask)) {
          vl_simplified := true;
          vl_ret[j] := pl_subnets[i];
          break;
        }
      } else {
        if(f_EPTF_TransportIPL2_isAddressInSubnet(
              pl_addr := pl_subnets[i].net,
              pl_net := vl_ret[j].net,
              pl_mask := vl_ret[j].mask)) {
          vl_simplified := true;
          break;
        }
      }
    }
    if(not vl_simplified) {
      vl_ret[sizeof(vl_ret)] := pl_subnets[i];
    }
  }
  return vl_ret;
}

private function f_EPTF_TransportIPL2_genFilterStr4Subnets(
  in EPTF_TransportIPL2_SubnetList pl_subnets)
return charstring
{
  if(sizeof(pl_subnets) == 0) { return "arp or udp or tcp"; }
  var charstring vl_ret := "arp or udp or tcp and ";

  if(sizeof(pl_subnets) > 1) {
    vl_ret := vl_ret & "(";
  }

  for(var integer i:=0; i<sizeof(pl_subnets); i:=i+1) {
    if(pl_subnets[i].maskbits == 0) { return "arp or udp or tcp"; }
    if(i>0) {
      vl_ret := vl_ret & " or "
    }
    vl_ret := vl_ret & f_EPTF_TransportIPL2_genSubnetFilterStr(pl_subnets[i].net, pl_subnets[i].maskbits);
  }

  if(sizeof(pl_subnets) > 1) {
    vl_ret := vl_ret & ")";
  }

  return vl_ret;
}

private function f_EPTF_TransportIPL2_genPacketFilter(in integer pl_ethInterfaceIdx)
runs on EPTF_TransportIPL2_CT
{
  if(sizeof(v_EPTF_TransportIPL2_ethernetInterfaceList[pl_ethInterfaceIdx].interfaceInformationIdxs) == 0) {
    if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
      f_EPTF_TransportIPL2_debug(%definitionId&": skipped, packet filter for interface "&
        v_EPTF_TransportIPL2_ethernetInterfaceList[pl_ethInterfaceIdx].name&": " &
        log2str(v_EPTF_TransportIPL2_ethernetInterfaceList[pl_ethInterfaceIdx].packetFilter));
    }
    return;
  }

  if(sizeof(v_EPTF_TransportIPL2_ethernetInterfaceList[pl_ethInterfaceIdx].interfaceInformationIdxs) == 0) {
    v_EPTF_TransportIPL2_ethernetInterfaceList[pl_ethInterfaceIdx].packetFilter.filterString := "arp or tcp or udp";
    v_EPTF_TransportIPL2_ethernetInterfaceList[pl_ethInterfaceIdx].packetFilter.subnets := {};
    return;
  }

  for(var integer i:=0; i<sizeof(v_EPTF_TransportIPL2_ethernetInterfaceList[pl_ethInterfaceIdx].interfaceInformationIdxs); i:=i+1) {
    var integer vl_idx := v_EPTF_TransportIPL2_ethernetInterfaceList[pl_ethInterfaceIdx].interfaceInformationIdxs[i];
    var integer vl_netmaskBits := 32;
    var EPTF_TransportIPL2_IpAddress vl_start := f_EPTF_TransportIPL2_getIpFromHostName(v_EPTF_Transport_interfaceInformationList[vl_idx].HostName);
    var EPTF_TransportIPL2_IpAddress vl_end := vl_start;
    if(v_EPTF_Transport_interfaceInformationList[vl_idx].count > 1) {
      vl_end := f_EPTF_TransportIPL2_ipAddrInc(vl_start, v_EPTF_Transport_interfaceInformationList[vl_idx].count - 1);
    }
    vl_netmaskBits := f_EPTF_TransportIPL2_nofEqualBits(vl_start, vl_end);
    v_EPTF_TransportIPL2_ethernetInterfaceList[pl_ethInterfaceIdx].packetFilter.subnets[i].mask := 
      f_EPTF_TransportIPL2_genNetMask(vl_netmaskBits);
    v_EPTF_TransportIPL2_ethernetInterfaceList[pl_ethInterfaceIdx].packetFilter.subnets[i].net :=
      vl_start and4b v_EPTF_TransportIPL2_ethernetInterfaceList[pl_ethInterfaceIdx].packetFilter.subnets[i].mask;
    v_EPTF_TransportIPL2_ethernetInterfaceList[pl_ethInterfaceIdx].packetFilter.subnets[i].maskbits := vl_netmaskBits;

    if(vl_netmaskBits == 0) {
      v_EPTF_TransportIPL2_ethernetInterfaceList[pl_ethInterfaceIdx].packetFilter.filterString := "arp or tcp or udp";
      return;
    }
  }

  v_EPTF_TransportIPL2_ethernetInterfaceList[pl_ethInterfaceIdx].packetFilter.subnets :=
    f_EPTF_TransportIPL2_simplifySubnetList(v_EPTF_TransportIPL2_ethernetInterfaceList[pl_ethInterfaceIdx].packetFilter.subnets);

  v_EPTF_TransportIPL2_ethernetInterfaceList[pl_ethInterfaceIdx].packetFilter.filterString :=
    f_EPTF_TransportIPL2_genFilterStr4Subnets(v_EPTF_TransportIPL2_ethernetInterfaceList[pl_ethInterfaceIdx].packetFilter.subnets);

  if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
    f_EPTF_TransportIPL2_debug(%definitionId&": packet filter for interface "&
      v_EPTF_TransportIPL2_ethernetInterfaceList[pl_ethInterfaceIdx].name&": " &
      log2str(v_EPTF_TransportIPL2_ethernetInterfaceList[pl_ethInterfaceIdx].packetFilter));
  }
}

///////////////////////////////////////////////////////////
//  Function: f_EPTF_Transport_setDownInterfaces
// 
//  Purpose:
//    Function to set down interfaces
//
//  Parameters:
//    pl_transportType - *in* - <EPTF_Transport_TransportType> - Testport type
//    pl_result - *out* - <Result> - result
//
//  Return Value:
//    -
//
//  Errors:
//    -
//
//  Detailed Comments:
//    
/////////////////////////////////////////////////////////// 
public function f_EPTF_Transport_setDownInterfaces(
  in EPTF_Transport_TransportType pl_transportType,
  out Result pl_result)
runs on EPTF_TransportIPL2_CT
{
  if (IPL2 != pl_transportType) {
    f_EPTF_Base_assert(%definitionId&": The transport type "&log2str(pl_transportType)&
      " is not supported in the IPL2 transport.", IPL2 == pl_transportType)
  }
   pl_result := { errorCode := omit, connId  := omit, os_error_code:=omit, os_error_text:= omit };
 //TODO
/*  uses IPL4asp_Functions, which uses TCCInterface_Functions.
    copy-paste into IPL2 but into where?

  var integer vl_numberofinterfaces := sizeof(v_EPTF_CommPort_IPL4_interfaceInformationList);

  for (var integer i := 0; i < vl_numberofinterfaces; i := i + 1) {
    f_setDownInterface(v_EPTF_CommPort_IPL4_interfaceInformationList[i].ifname,
      v_EPTF_CommPort_IPL4_interfaceInformationList[i].count,
      v_EPTF_CommPort_IPL4_interfaceInformationList[i].virtualIfaceStart);
  }
  */
}






} // end of group EPTF_TransportIPL2_ConnectionHandling

///////////////////////////////////////////////////////////////////////////////
//  Group: EPTF_TransportIPL2_IPReassembly
// 
//  Purpose:
//    Functions for reassembly of fragmented IP packets.
//
///////////////////////////////////////////////////////////////////////////////
group EPTF_TransportIPL2_IPReassembly
{

  external function f_EPTF_TransportIPL2_insertOctets(
    in octetstring pl_buffer,
    in octetstring pl_fragment,
    in integer pl_offset) return octetstring;

  private function f_EPTF_TransportIPL2_removeFragmentBufferEvent(
    in EPTF_ScheduledAction pl_action,
    in integer pl_eventIndex)
  runs on EPTF_TransportIPL2_CT
  return boolean
  {
    if(not f_EPTF_FBQ_itemIsBusy(pl_action.actionId[0], v_EPTF_TransportIPL2_ipFragmentBufferDB.queue)) {
      f_EPTF_TransportIPL2_warning(%definitionId&": item "&int2str(pl_action.actionId[0])&" is not busy.");
      return false;
    }
    if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
      f_EPTF_TransportIPL2_debug(%definitionId&": timeout for buffer with index " & int2str(pl_action.actionId[0]));
    }
    v_EPTF_TransportIPL2_ipFragmentBufferDB.buffers[pl_action.actionId[0]].removeTimerIdx := -1;
    f_EPTF_TransportIPL2_removeFragmentBuffer(pl_action.actionId[0]);
    return true;
  }

  private function f_EPTF_TransportIPL2_removeFragmentBuffer(in integer pl_idx)
  runs on EPTF_TransportIPL2_CT
  {
    v_EPTF_TransportIPL2_ipFragmentBufferDB.buffers[pl_idx].msg := ''O;
    f_EPTF_FBQ_moveFromBusyToFreeTail(pl_idx, v_EPTF_TransportIPL2_ipFragmentBufferDB.queue);
    f_EPTF_oct2int_HashMap_Erase(v_EPTF_TransportIPL2_ipFragmentBufferDB.hashmapId,
      v_EPTF_TransportIPL2_ipFragmentBufferDB.buffers[pl_idx].hashKey);
  }

  private function f_EPTF_TransportIPL2_incomingIPFragment(
    in octetstring pl_srcAddr,
    in octetstring pl_dstAddr,
    in integer pl_proto,
    in integer pl_ipId,
    in octetstring pl_msgFragment,
    in integer pl_offset,
    in boolean pl_isFinal)
  runs on EPTF_TransportIPL2_CT
  {
    if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
      f_EPTF_TransportIPL2_debug(%definitionId&": Incoming IP fragment.");
      f_EPTF_TransportIPL2_debug(%definitionId&": srcAddr = " & f_EPTF_TransportIPL2_ip2str(pl_srcAddr));
      f_EPTF_TransportIPL2_debug(%definitionId&": dstAddr = " & f_EPTF_TransportIPL2_ip2str(pl_dstAddr));
      f_EPTF_TransportIPL2_debug(%definitionId&": ID = " & int2str(pl_ipId));
      f_EPTF_TransportIPL2_debug(%definitionId&": fragment length = " & int2str(lengthof(pl_msgFragment)));
      f_EPTF_TransportIPL2_debug(%definitionId&": offset = " & int2str(pl_offset));
      f_EPTF_TransportIPL2_debug(%definitionId&": isFinal = " & log2str(pl_isFinal));
    }

    var octetstring vl_key := pl_srcAddr & pl_dstAddr & int2oct(pl_proto, 1) & int2oct(pl_ipId, 2);
    var integer vl_idx := -1;

    // try to find buffer with key based on source and destination IP address, protocol and ID field
    if(not f_EPTF_oct2int_HashMap_Find(v_EPTF_TransportIPL2_ipFragmentBufferDB.hashmapId, vl_key, vl_idx)) {
      // not found -> create buffer
      vl_idx := f_EPTF_FBQ_getOrCreateFreeSlot(v_EPTF_TransportIPL2_ipFragmentBufferDB.queue);
      f_EPTF_FBQ_moveFromFreeToBusyTail(vl_idx, v_EPTF_TransportIPL2_ipFragmentBufferDB.queue);
      f_EPTF_oct2int_HashMap_Insert(v_EPTF_TransportIPL2_ipFragmentBufferDB.hashmapId, vl_key, vl_idx);

      if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
        f_EPTF_TransportIPL2_debug(%definitionId&": creating new buffer with index " & int2str(vl_idx));
      }

      // initialize buffer contents:
      v_EPTF_TransportIPL2_ipFragmentBufferDB.buffers[vl_idx] := {
        msg := f_EPTF_TransportIPL2_insertOctets(''O, pl_msgFragment, pl_offset),
        receivedBytes := lengthof(pl_msgFragment),
        totalBytes := -1, // calculated from final packet's fragment offset and length
        removeTimerIdx := -1,
        hashKey := vl_key
      }
    } else {
      // found -> update buffer
      if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
        f_EPTF_TransportIPL2_debug(%definitionId&": found buffer with index " & int2str(vl_idx));
      }
      // insert the fragment into the buffer at offset pl_offset
      v_EPTF_TransportIPL2_ipFragmentBufferDB.buffers[vl_idx].msg :=
        f_EPTF_TransportIPL2_insertOctets(
          v_EPTF_TransportIPL2_ipFragmentBufferDB.buffers[vl_idx].msg,
          pl_msgFragment,
          pl_offset);

      v_EPTF_TransportIPL2_ipFragmentBufferDB.buffers[vl_idx].receivedBytes :=
        v_EPTF_TransportIPL2_ipFragmentBufferDB.buffers[vl_idx].receivedBytes + lengthof(pl_msgFragment);
    }

    if(pl_isFinal) {
      // total bytes needed for the whole IP package is the size of the last fragment + its offset
      v_EPTF_TransportIPL2_ipFragmentBufferDB.buffers[vl_idx].totalBytes := 
        lengthof(pl_msgFragment) + pl_offset;
    }

    if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
      f_EPTF_TransportIPL2_debug(%definitionId&": total bytes needed: " &
        int2str(v_EPTF_TransportIPL2_ipFragmentBufferDB.buffers[vl_idx].totalBytes));
      f_EPTF_TransportIPL2_debug(%definitionId&": bytes in buffer: " &
        int2str(v_EPTF_TransportIPL2_ipFragmentBufferDB.buffers[vl_idx].receivedBytes));
    }

    if( v_EPTF_TransportIPL2_ipFragmentBufferDB.buffers[vl_idx].receivedBytes ==
        v_EPTF_TransportIPL2_ipFragmentBufferDB.buffers[vl_idx].totalBytes) {
      // total bytes == received bytes -> received the complete package

      // cancel timer for removing buffer
      if(v_EPTF_TransportIPL2_ipFragmentBufferDB.buffers[vl_idx].removeTimerIdx >= 0) {
        if(not f_EPTF_SchedulerComp_CancelEvent(v_EPTF_TransportIPL2_ipFragmentBufferDB.buffers[vl_idx].removeTimerIdx)) {
          f_EPTF_TransportIPL2_warning(%definitionId&": f_EPTF_SchedulerComp_CancelEvent failed.");
        }
      }
      v_EPTF_TransportIPL2_ipFragmentBufferDB.buffers[vl_idx].removeTimerIdx := -1;

      if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
        f_EPTF_TransportIPL2_debug(%definitionId&": received whole IP packet.");
      }

      // pass the payload to the upper layer
      if (pl_proto == c_ip_proto_udp) {
        f_EPTF_TransportIPL2_incomingUdpPacket(
          pl_srcAddr,
          pl_dstAddr,
          v_EPTF_TransportIPL2_ipFragmentBufferDB.buffers[vl_idx].msg);
      } else if (pl_proto == c_ip_proto_tcp) {
        f_EPTF_TransportIPL2_incomingTcpSegment(
          pl_srcAddr,
          pl_dstAddr,
          v_EPTF_TransportIPL2_ipFragmentBufferDB.buffers[vl_idx].msg);
      } else {
        if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
          f_EPTF_TransportIPL2_debug(%definitionId&": Unsupported protocol in IP datagram: " & int2str(pl_proto));
        }
      }


      // the buffer is no longer needed -> remove it
      f_EPTF_TransportIPL2_removeFragmentBuffer(vl_idx);
    } else {
      // more fragments are expected

      // re-schedule event to remove the buffer if no more fragments are received
      f_EPTF_SchedulerComp_refreshSnapshotTime();
      if(v_EPTF_TransportIPL2_ipFragmentBufferDB.buffers[vl_idx].removeTimerIdx >= 0) {
        if(not f_EPTF_SchedulerComp_CancelEvent(v_EPTF_TransportIPL2_ipFragmentBufferDB.buffers[vl_idx].removeTimerIdx)) {
          f_EPTF_TransportIPL2_warning(%definitionId&": f_EPTF_SchedulerComp_CancelEvent failed.");
        }
      }
      if( not f_EPTF_SchedulerComp_scheduleAction(
          f_EPTF_SchedulerComp_snapshotTime() + tsp_EPTF_TransportIPL2_ipFragmentRemoveTimer,
          refers(f_EPTF_TransportIPL2_removeFragmentBufferEvent),
          { vl_idx },
          v_EPTF_TransportIPL2_ipFragmentBufferDB.buffers[vl_idx].removeTimerIdx)) {
        // scheduling failed -> remove buffer to prevent memory leak
        f_EPTF_TransportIPL2_warning(%definitionId&": f_EPTF_SchedulerComp_scheduleAction failed, discarding buffer.");
        f_EPTF_TransportIPL2_removeFragmentBuffer(vl_idx);
        return;
      }

    }
  }


} // end of group EPTF_TransportIPL2_IPReassembly

group EPTF_TransportIPL2_Layer3 {

  private function f_EPTF_TransportIPL2_incomingUdpPacket(
    in octetstring pl_srcAddr,
    in octetstring pl_dstAddr,
    inout octetstring pl_payload)
  runs on EPTF_TransportIPL2_CT
  {
    var octetstring msg;
    var integer srcport, dstport;

    if(f_EPTF_TransportIPL2_decodeUDPWithChecksum(
         pl_srcAddr,
         pl_dstAddr,
         pl_payload,
         srcport,
         dstport,
         msg)) {
      var integer vl_idx := f_EPTF_TransportIPL2_getConnectionIdx({udp:={}}, pl_dstAddr, dstport);

      if (vl_idx != -1) {
        var integer vl_lgenIdx := v_EPTF_CommPort_connectionDatabase.data[vl_idx].lgenIdx;
        if(vl_lgenIdx >= 0) {
          if(v_EPTF_TransportIPL2_LGenInfoList[vl_lgenIdx].msgHandler != null) {
            v_EPTF_TransportIPL2_LGenInfoList[vl_lgenIdx].msgHandler.apply(
              IPL2,
              v_EPTF_CommPort_connectionDatabase.data[vl_idx].uniqueId,
              f_EPTF_TransportIPL2_ip2str(pl_srcAddr),
              srcport,
              f_EPTF_TransportIPL2_ip2str(pl_dstAddr),
              dstport,
              {udp:={}},
              v_EPTF_CommPort_connectionDatabase.data[vl_idx].userData,
              msg
            );
          }
          else { f_EPTF_TransportIPL2_error(%definitionId&": no message handler is set for LGen " & int2str(vl_lgenIdx)); }
        } else {
          f_EPTF_TransportIPL2_warning(%definitionId&": no LGen is set for connection " &
            f_EPTF_TransportIPL2_ip2str(pl_dstAddr) & ":" & int2str(dstport));
        }
      } else {
        if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
          f_EPTF_TransportIPL2_debug(%definitionId&": no connection found for incoming UDP packet.");
          f_EPTF_TransportIPL2_debug(%definitionId&": source IP Addr: " & f_EPTF_TransportIPL2_ip2str(pl_srcAddr));
          f_EPTF_TransportIPL2_debug(%definitionId&": destination IP Addr: " & f_EPTF_TransportIPL2_ip2str(pl_dstAddr));
          f_EPTF_TransportIPL2_debug(%definitionId&": UDP header: " & oct2str(substr(pl_payload, 0, 8)));
        }
      }

    } else {
      if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
        f_EPTF_TransportIPL2_debug(%definitionId&": could not decode UDP PDU.");
      }
    }
  }

  private function f_EPTF_TransportIPL2_handleTcpOptions(
    in octetstring pl_options,
    out integer pl_mss)
  runs on EPTF_TransportIPL2_CT
  {
    var integer i := 0;
    while(i<lengthof(pl_options)) {
      if(pl_options[i] == '00'O) {
        break; // end of options list
      } else if(pl_options[i] == '01'O) {
        // NOP
        i:=i+1;
      } else if(pl_options[i] == '02'O) {
        if(i+3 >= lengthof(pl_options) or pl_options[i+1] != '04'O) {
          f_EPTF_TransportIPL2_debug(%definitionId&": invalid MSS option in incoming SYN segment.");
        } else {
          pl_mss := oct2int(substr(pl_options, i+2, 2));
          if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
            f_EPTF_TransportIPL2_debug(%definitionId&": remote MSS: " & int2str(pl_mss));
          }
        }
        break; // no other option supported at the moment.
      } else if(i+1 < lengthof(pl_options)) {
        i := i + oct2int(pl_options[i+1]);
      } else {
        break;
      }
    }
  }

  private function f_EPTF_TransportIPL2_handleTcpSyn(
    in octetstring pl_srcAddr,
    in octetstring pl_dstAddr,
    in EPTF_TransportIPL2_PDU_TCP pl_pdu)
  runs on EPTF_TransportIPL2_CT
  {
    var integer vl_listenIdx := f_EPTF_TransportIPL2_getConnectionIdx({tcp:={}}, pl_dstAddr, pl_pdu.dest_port); // get listen socket (source addr == ''O)

    if (vl_listenIdx >= 0) {
      var integer vl_lgenIdx := v_EPTF_CommPort_connectionDatabase.data[vl_listenIdx].lgenIdx;
      if(vl_lgenIdx >= 0) {
        var integer vl_connectionIdx := f_EPTF_TransportIPL2_newLocalPort(
          {tcp:={}},
          pl_dstAddr,
          pl_pdu.dest_port,
          vl_lgenIdx,
          pl_srcAddr,
          pl_pdu.source_port);
        v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.expectedSeqNum := pl_pdu.sequence_number + 1;
        f_EPTF_TransportIPL2_sendTcpSegmentOverL2(
          vl_connectionIdx,
          c_EPTF_TransportIPL2_TCPControlBit_syn + c_EPTF_TransportIPL2_TCPControlBit_ack,
          ''O);
        f_EPTF_TransportIPL2_setSocketState(vl_connectionIdx, SYN_RECEIVED);
        var integer vl_remoteMss := c_EPTF_TransportIPL2_tcpDefaultMSS;
        if(lengthof(pl_pdu.options) > 0) {
          f_EPTF_TransportIPL2_handleTcpOptions(pl_pdu.options, vl_remoteMss);
        }
        if(vl_remoteMss < v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.mss) {
          v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.mss := vl_remoteMss;
        }
        if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
          f_EPTF_TransportIPL2_debug(%definitionId&": local MSS: " &
            int2str(v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.mss));
        }
        f_EPTF_TransportIPL2_updateOutgoingStatistics(c_EPTF_TransportIPL2_incConnOpened);
        // call event handler of LGen
        if(v_EPTF_TransportIPL2_LGenInfoList[v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].lgenIdx].eventHandler != null) {
          v_EPTF_TransportIPL2_LGenInfoList[v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].lgenIdx].eventHandler.apply(
            IPL2,
            v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].uniqueId,
//            Event_ConnOpened,
            {connOpened := {
              v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].uniqueId,
              f_EPTF_TransportIPL2_ip2str(v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].remHost),
              v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].remPort,
              f_EPTF_TransportIPL2_ip2str(v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].locHost),
              v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].locPort,
              {tcp := {}},
              v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].userData
            }}
          );
        }
      } else {
        f_EPTF_TransportIPL2_warning(%definitionId&": no LGen is set for TCP listen socket " &
          f_EPTF_TransportIPL2_ip2str(pl_dstAddr) & ":" & int2str(pl_pdu.dest_port));
      }
    } else {
      if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
        f_EPTF_TransportIPL2_debug(%definitionId&": no listen socket found for incoming TCP SYN segment: " & log2str(pl_pdu));
      }
      // send RST segment
      // fixme: send if we have any *TCP* connection on this IP address
/*      if(f_EPTF_TransportIPL2_hasIp(pl_dstAddr)) {
        f_EPTF_TransportIPL2_sendTcpRstOnClosed(
          pl_srcAddr,
          pl_dstAddr,
          pl_pdu);
      }*/
    }
  }

  // handle SYN part of SYN+ACK
  private function f_EPTF_TransportIPL2_handleTcpSynAck(
    in integer pl_connectionIdx,
    in EPTF_TransportIPL2_PDU_TCP pl_pdu)
  runs on EPTF_TransportIPL2_CT
  {
    if(v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpState == SYN_SENT) {
      f_EPTF_TransportIPL2_setSocketState(pl_connectionIdx, ESTABLISHED);
      v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.expectedSeqNum := pl_pdu.sequence_number + 1;
      var integer vl_remoteMss := c_EPTF_TransportIPL2_tcpDefaultMSS;
      if(lengthof(pl_pdu.options) > 0) {
        f_EPTF_TransportIPL2_handleTcpOptions(pl_pdu.options, vl_remoteMss);
      }
      if(vl_remoteMss < v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.mss) {
        v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.mss := vl_remoteMss;
      }
      if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
        f_EPTF_TransportIPL2_debug(%definitionId&": local MSS: " &
          int2str(v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.mss));
      }
    }/* else {
      // retransmitted SYN
    }*/
    f_EPTF_TransportIPL2_sendTcpAck(pl_connectionIdx);
  }

  private function f_EPTF_TransportIPL2_handleTcpPayload(
    in integer pl_connectionIdx,
    inout EPTF_TransportIPL2_PDU_TCP pl_pdu)
  runs on EPTF_TransportIPL2_CT
  {
    var integer vl_conc := 0;
    if(pl_pdu.sequence_number == v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.expectedSeqNum) {
      // most probably we get the new expected segment
      var integer vl_size := 0;
      vl_conc := f_EPTF_SegmentBuffer_insertSegment(
        v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.segmentBuffer,
        pl_pdu.data,
        0, // pl_pdu.sequence_number - v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.expectedSeqNum
        vl_size);
      v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.expectedSeqNum :=
        f_EPTF_mod32Add(v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.expectedSeqNum, vl_conc);
      //f_EPTF_TransportIPL2_scheduleSendTcpAck(pl_connectionIdx);
    } else {
      var integer vl_start, vl_end;
      if(not f_EPTF_TransportIPL2_isSeqNumValid(pl_connectionIdx, pl_pdu.sequence_number, pl_pdu.data, vl_start, vl_end)) {
        f_EPTF_TransportIPL2_debug(%definitionId&": segment dropped.");
        return;
      }
      var integer vl_size := 0;
      vl_conc := f_EPTF_SegmentBuffer_insertSegment(
        v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.segmentBuffer,
        pl_pdu.data,
        f_EPTF_mod32Sub(vl_start, v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.expectedSeqNum),
        vl_size);
      v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.expectedSeqNum :=
        f_EPTF_mod32Add(v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.expectedSeqNum, vl_conc);
      //if(vl_conc > 0) { f_EPTF_TransportIPL2_scheduleSendTcpAck(pl_connectionIdx); }
    }

    var integer vl_seqNumBeforeMsgHandling := v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.sendSeqNum;

    if (v_EPTF_TransportIPL2_LGenInfoList[v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].lgenIdx].msgLenCalc.getMsgLen == null) {
      f_EPTF_Base_assert(%definitionId&": no length calculating function was set for LGen.",
        v_EPTF_TransportIPL2_LGenInfoList[v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].lgenIdx].msgLenCalc.getMsgLen != null);
    }
    if (v_EPTF_TransportIPL2_LGenInfoList[v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].lgenIdx].msgHandler == null) {
      f_EPTF_Base_assert(%definitionId&": no message handler function was set for LGen.",
        v_EPTF_TransportIPL2_LGenInfoList[v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].lgenIdx].msgHandler != null);
    }
    // loop while there's data in the continuous buffer
    var integer vl_msgLen := v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.msgLen;
    var integer vl_contLen := f_EPTF_SegmentBuffer_getContinousLen(v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.segmentBuffer);
    while(vl_contLen > 0) {
      var octetstring vl_msg := ''O;
      f_EPTF_SegmentBuffer_getBuffer(v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.segmentBuffer, vl_msg);
      if(vl_msgLen <= 0) {
        vl_msgLen :=
          v_EPTF_TransportIPL2_LGenInfoList[v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].lgenIdx].msgLenCalc.getMsgLen.apply(
            IPL2,
            vl_msg,
            v_EPTF_TransportIPL2_LGenInfoList[v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].lgenIdx].msgLenCalc.getMsgLenArgs
          );
        if(vl_msgLen <= 0) {
          break; // we don't know the length of the message yet
        }
      }
      if( vl_contLen < vl_msgLen) {
        break; // there's not enough octets in the buffer yet
      }
/*      var octetstring vl_msg;
      f_EPTF_SegmentBuffer_getBuffer(v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.segmentBuffer, vl_msg,
        vl_msgLen);*/
      if(lengthof(vl_msg) > vl_msgLen) { vl_msg := substr(vl_msg, 0, vl_msgLen); }
      // pass the message to the message handler:
      v_EPTF_TransportIPL2_LGenInfoList[v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].lgenIdx].msgHandler.apply(
        IPL2,
        v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].uniqueId,
        f_EPTF_TransportIPL2_ip2str(v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].remHost),
        v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].remPort,
        f_EPTF_TransportIPL2_ip2str(v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].locHost),
        v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].locPort,
        {tcp:={}},
        v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].userData,
        vl_msg);
      // remove the message from the buffer:
      f_EPTF_SegmentBuffer_truncBuffer(v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.segmentBuffer,
        vl_msgLen);
      vl_msgLen := -1;
      vl_contLen := f_EPTF_SegmentBuffer_getContinousLen(v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.segmentBuffer);
    }
    v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.msgLen := vl_msgLen;

    if(v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.segmentBuffer.finSegmentOffset == 0) {
    // a FIN segment was buffered, receive window became empty -> handle FIN
      f_EPTF_TransportIPL2_doHandleTcpFin(pl_connectionIdx); // sends ACK if needed
    } else if(vl_conc > 0 and
      vl_seqNumBeforeMsgHandling == v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.sendSeqNum) { // only schedule the ACK if the message handler didn't send anything
      f_EPTF_TransportIPL2_scheduleSendTcpAck(pl_connectionIdx);
    }
  }

  private function f_EPTF_TransportIPL2_doHandleTcpFin(
    in integer pl_connectionIdx)
  runs on EPTF_TransportIPL2_CT
  {
    v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.expectedSeqNum := f_EPTF_mod32Add(
      v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.expectedSeqNum, 1); // ACK for the incoming FIN = last seq num + 1
    if(v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpState == FIN_WAIT2) {
      f_EPTF_TransportIPL2_setSocketState(pl_connectionIdx, TIME_WAIT);
      // schedule final close of socket
      f_EPTF_TransportIPL2_scheduleTimeWait(pl_connectionIdx);
      f_EPTF_TransportIPL2_sendTcpAck(pl_connectionIdx);
    } else if(v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpState == FIN_WAIT1) {
      f_EPTF_TransportIPL2_setSocketState(pl_connectionIdx, CLOSING);
      f_EPTF_TransportIPL2_sendTcpAck(pl_connectionIdx);
    } else if(v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpState == ESTABLISHED) {
      f_EPTF_TransportIPL2_setSocketState(pl_connectionIdx, CLOSE_WAIT);

      // notify the applib of the remote close, wait for it to close too, unless tsp_EPTF_TransportIPL2_tcpAllowHalfClose is false
      if(v_EPTF_TransportIPL2_LGenInfoList[v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].lgenIdx].eventHandler != null) {
        v_EPTF_TransportIPL2_LGenInfoList[v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].lgenIdx].eventHandler.apply(
          IPL2,
          v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].uniqueId,
//          Event_ConnClosed,
          {connClosed := {
            v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].uniqueId,
            f_EPTF_TransportIPL2_ip2str(v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].remHost),
            v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].remPort,
            f_EPTF_TransportIPL2_ip2str(v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].locHost),
            v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].locPort,
            {tcp := {}},
            v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].userData
          }}
        );
      }

      if(v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpState == CLOSE_WAIT) {
        if(tsp_EPTF_TransportIPL2_tcpAllowHalfClose) {
          // if the app didn't close the socket and half-closed is allowed - schedule an ACK
          f_EPTF_TransportIPL2_scheduleSendTcpAck(pl_connectionIdx);
        } else {
          var Result vl_dummy;
          f_EPTF_Transport_close(IPL2, pl_connectionIdx, vl_dummy);
          f_EPTF_TransportIPL2_debug(%definitionId&": half-closed sockets not allowed. Closing socket.");
        }
      }
    }
  }

  private function f_EPTF_TransportIPL2_handleTcpFin(
    in integer pl_connectionIdx,
    in EPTF_TransportIPL2_PDU_TCP pl_pdu)
  runs on EPTF_TransportIPL2_CT
  {
    if(f_EPTF_SegmentBuffer_getNonContinousLen(v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.segmentBuffer) == 0) {
      // receive window is empty, no more data is expected -> handle FIN immediately
      if(v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.segmentBuffer.finSegmentOffset == 0) {
        f_EPTF_TransportIPL2_debug(%definitionId&": retransmitted FIN.");
        f_EPTF_TransportIPL2_sendTcpAck(pl_connectionIdx);
      } else {
        f_EPTF_SegmentBuffer_setFinalSegmentOffset(
          v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.segmentBuffer, 0); // don't accept more data
        f_EPTF_TransportIPL2_doHandleTcpFin(pl_connectionIdx); // sends ACK if needed
      }
    } else {
      // buffer FIN segment
      var integer vl_dataLen := lengthof(pl_pdu.data);
      f_EPTF_SegmentBuffer_setFinalSegmentOffset(
        v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.segmentBuffer,
          f_EPTF_mod32Sub(pl_pdu.sequence_number, v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpConnectionData.expectedSeqNum) + vl_dataLen);
    }
  }

  private function f_EPTF_TransportIPL2_handleTcpRst(
    in integer pl_connectionIdx,
    in EPTF_TransportIPL2_PDU_TCP pl_pdu)
  runs on EPTF_TransportIPL2_CT
  {
    // check seq and ack numbers
    // we assume ack number was already checked, check the sequence number
    if(v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpState == SYN_SENT or
       v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].tcpState == SYN_RECEIVED) {
      if(pl_pdu.sequence_number != 0) { return; }
    } else {
      var integer vl_start := -1, vl_end := -1;
      var octetstring vl_pdu := ''O;
      if(not f_EPTF_TransportIPL2_isSeqNumValid(pl_connectionIdx, pl_pdu.sequence_number, vl_pdu, vl_start, vl_end)) {
        return;
      }
    }
    f_EPTF_TransportIPL2_debug(%definitionId&": connection reset by peer.");
    // notify the app
    if(v_EPTF_TransportIPL2_LGenInfoList[v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].lgenIdx].eventHandler != null) {
      var PortError vl_tmp := ERROR_SOCKET;
      v_EPTF_TransportIPL2_LGenInfoList[v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].lgenIdx].eventHandler.apply(
        IPL2,
        v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].uniqueId,
    //    Event_Result,
        {result := {
          errorCode := vl_tmp,
          connId := v_EPTF_CommPort_connectionDatabase.data[pl_connectionIdx].uniqueId,
          os_error_code := 8,//c_EPTF_TransportIPL2_errorConnectionReset,    FIXME: use the constant instead of the literal, when the FATAL ERROR of the compiler is fixed
          os_error_text := "connection reset by peer"
        }}
      );
    }
    // close the connection
    f_EPTF_TransportIPL2_finalCloseTcp(pl_connectionIdx);
  }

  private function f_EPTF_TransportIPL2_incomingTcpSegment(
    in octetstring pl_srcAddr,
    in octetstring pl_dstAddr,
    inout octetstring pl_payload)
  runs on EPTF_TransportIPL2_CT
  {
    if(not f_EPTF_TransportIPL2_verifyTCPChecksum(pl_srcAddr, pl_dstAddr, pl_payload)) {
      f_EPTF_TransportIPL2_debug(%definitionId&": TCP checksum verification failed for incoming segment.");
      return;
    }
    var EPTF_TransportIPL2_PDU_TCP vl_pdu;
    f_EPTF_TransportIPL2_decodeTCP(pl_payload, vl_pdu);

    var integer vl_unAcked := 0;

    f_EPTF_SchedulerComp_refreshSnapshotTime();

    var integer vl_connectionIdx := f_EPTF_TransportIPL2_getConnectionIdx(
      {tcp:={}},
      pl_dstAddr, vl_pdu.dest_port,
      pl_srcAddr, vl_pdu.source_port);
    if(vl_connectionIdx < 0) {
      // connection does not exist
      if(f_EPTF_and4i(vl_pdu.control_bits, c_EPTF_TransportIPL2_TCPControlBit_syn) != 0) {
        // incoming connection
        // 2nd step of 3-way handshake -> create new connection, send SYN+ACK
        f_EPTF_TransportIPL2_handleTcpSyn(pl_srcAddr, pl_dstAddr, vl_pdu);
      } else {
        if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
          f_EPTF_TransportIPL2_debug(%definitionId&": no connection found for incoming TCP segment: " & log2str(vl_pdu));
        }
        // send RST segment, *unless* it's just an ACK segment, or if we don't have this IP address (don't send RSTs on connections handled by the kernel)
        // fixme: send if we have any *TCP* connection on this IP address
/*        if((vl_pdu.control_bits != c_EPTF_TransportIPL2_TCPControlBit_ack or
            lengthof(vl_pdu.data)>0) and
            f_EPTF_TransportIPL2_hasIp(pl_dstAddr)) {
          f_EPTF_TransportIPL2_sendTcpRstOnClosed(
            pl_srcAddr,
            pl_dstAddr,
            vl_pdu);
        }*/
      }

    } else {
      // connection exists
      // handle ACK
      if(f_EPTF_and4i(vl_pdu.control_bits, c_EPTF_TransportIPL2_TCPControlBit_ack) != 0) {
        if(not f_EPTF_TransportIPL2_isAckValid(vl_connectionIdx, vl_pdu.acknowledgment_number)) {
          f_EPTF_TransportIPL2_debug(%definitionId&": segment dropped.");
          return;
        } else {
          vl_unAcked := f_EPTF_TransportIPL2_handleTcpAck(vl_connectionIdx, vl_pdu.acknowledgment_number);
          if(vl_unAcked < 0) { return; } // LAST_ACK -> CLOSED
        }
      }
      if(f_EPTF_and4i(vl_pdu.control_bits, c_EPTF_TransportIPL2_TCPControlBit_rst) != 0) {
        // connection reset by peer
        f_EPTF_TransportIPL2_handleTcpRst(vl_connectionIdx, vl_pdu)
        return; // no data is expected in a RST segment
      }
      // handle SYN part of SYN+ACK
      if(f_EPTF_and4i(vl_pdu.control_bits, c_EPTF_TransportIPL2_TCPControlBit_syn) != 0) {
        // this was an outgoing connection
        // 3rd step of 3-way handshake-> connection established, send ACK on incoming SYN
        f_EPTF_TransportIPL2_handleTcpSynAck(vl_connectionIdx, vl_pdu);
      }
    }

    if(vl_connectionIdx >= 0) {
      // connections exists (could be just created in the previous if branch, thus the separate if statement)
      if(lengthof(vl_pdu.data) > 0) {
        f_EPTF_TransportIPL2_handleTcpPayload(vl_connectionIdx, vl_pdu);
      }

      if(f_EPTF_and4i(vl_pdu.control_bits, c_EPTF_TransportIPL2_TCPControlBit_fin) != 0) {
        f_EPTF_TransportIPL2_handleTcpFin(vl_connectionIdx, vl_pdu);
      }

      if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
        f_EPTF_TransportIPL2_debug(%definitionId&": unacknowledged outgoing bytes: "&int2str(vl_unAcked));
        f_EPTF_TransportIPL2_debug(%definitionId&": remote receive window: " &int2str(vl_pdu.window));
        if(vl_unAcked > 0) {
          f_EPTF_TransportIPL2_debug(%definitionId&": remote receive window decreased: " &int2str(vl_pdu.window - vl_unAcked));
        }
      }
      v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.remoteWindowSize := vl_pdu.window - vl_unAcked;
      // if there's anything in the send FIFO and the window is non-zero, try to send it

	  var integer vl_fifoLength;
  	  if(tsp_EPTF_TransportIPL2_sendFifo_useCLLBuffer){
		vl_fifoLength := f_EPTF_Buffer_get_read_len(v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.sendFifo_bufferID);	
  	  } else {
        vl_fifoLength := lengthof(v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.sendFifo);      
      }

      var integer vl_fifoOffset := 0;
      if(vl_fifoLength > 0 and
         (v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpState == ESTABLISHED or
          v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpState == CLOSE_WAIT)) {
        var integer vl_mss :=  v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.mss;
        var integer vl_flags := c_EPTF_TransportIPL2_TCPControlBit_ack;
        do {
          if(v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.remoteWindowSize < vl_mss) {
            if(v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.remoteWindowSize > 0) {
              vl_mss := v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.remoteWindowSize; // fit the segment in the window
            } else {
              if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
                f_EPTF_TransportIPL2_debug(%definitionId&": remote window full, "&
                  int2str(vl_fifoLength)&" bytes remained in the fifo.");
              }

		  	  if(tsp_EPTF_TransportIPL2_sendFifo_useCLLBuffer){
  	  		    f_EPTF_Buffer_set_pos(v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.sendFifo_bufferID, vl_fifoOffset);
				f_EPTF_Buffer_cut(v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.sendFifo_bufferID);		
		  	  } else {
              	v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.sendFifo :=
                substr(v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.sendFifo,
                       vl_fifoOffset,
                       lengthof(v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.sendFifo) - vl_fifoOffset);
              }
              break;
            }
          }
          if (vl_fifoLength <= vl_mss){
            vl_flags := vl_flags + c_EPTF_TransportIPL2_TCPControlBit_psh;
            if(vl_fifoOffset > 0) {
		  	  if(tsp_EPTF_TransportIPL2_sendFifo_useCLLBuffer){
  	  		    f_EPTF_Buffer_set_pos(v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.sendFifo_bufferID, vl_fifoOffset);
				f_EPTF_Buffer_cut(v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.sendFifo_bufferID);		
		  	  } else {
                v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.sendFifo :=
                  substr(v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.sendFifo,
                         vl_fifoOffset,
                         lengthof(v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.sendFifo) - vl_fifoOffset);
              }
            }  
		  	if(tsp_EPTF_TransportIPL2_sendFifo_useCLLBuffer){
			  var octetstring vl_sendFifo;
  	  		  f_EPTF_Buffer_get_read_data(v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.sendFifo_bufferID, vl_sendFifo);
			  f_EPTF_TransportIPL2_sendTcpSegmentOverL2(vl_connectionIdx, vl_flags, vl_sendFifo);		
	  	    } else {
              f_EPTF_TransportIPL2_sendTcpSegmentOverL2(vl_connectionIdx, vl_flags, v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.sendFifo);
            }
            if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
              f_EPTF_TransportIPL2_debug(%definitionId&": sent "&int2str(vl_fifoLength)&" bytes from send FIFO, FIFO is now empty.");
            }
            if(tsp_EPTF_TransportIPL2_sendFifo_useCLLBuffer){
              f_EPTF_Buffer_clear(v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.sendFifo_bufferID);
            } else {
              v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.sendFifo := ''O;
            }
            if(v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.pendingClose) {
              v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.pendingClose := false;
              f_EPTF_TransportIPL2_debug(%definitionId&": close pending, calling close().");
              var Result vl_res;
              f_EPTF_Transport_close(IPL2, vl_connectionIdx, vl_res);
            }
            break;
          } else {
            if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
              f_EPTF_TransportIPL2_debug(%definitionId&": sending "&int2str(vl_mss)&" bytes from send FIFO.");
            }

		  	if(tsp_EPTF_TransportIPL2_sendFifo_useCLLBuffer){
			  var octetstring vl_sendFifo;
  	  		  f_EPTF_Buffer_get_read_data(v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.sendFifo_bufferID, vl_sendFifo);
              f_EPTF_TransportIPL2_sendTcpSegmentOverL2(vl_connectionIdx, vl_flags, substr(vl_sendFifo, vl_fifoOffset, vl_mss ));
	  	    } else {
              f_EPTF_TransportIPL2_sendTcpSegmentOverL2(vl_connectionIdx, vl_flags, substr(v_EPTF_CommPort_connectionDatabase.data[vl_connectionIdx].tcpConnectionData.sendFifo, vl_fifoOffset, vl_mss ));
            }

            vl_fifoOffset := vl_fifoOffset + vl_mss;
            vl_fifoLength := vl_fifoLength - vl_mss;
          }
        } while(true);
        
      }
    }
  }

  private function f_EPTF_TransportIPL2_isSeqNumValid(
    in integer pl_socketId,
    in integer pl_seqNum,
    inout octetstring pl_pduData,
    out integer pl_validStart,
    out integer pl_validEnd)
  runs on EPTF_TransportIPL2_CT
  return boolean
  {
/*    if(pl_seqNum == v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.expectedSeqNum) {
      pl_validStart := pl_seqNum;
      pl_validEnd := f_EPTF_mod32Add(pl_seqNum, pl_payloadLength);
      f_EPTF_TransportIPL2_debug(%definitionId&": segment sequence number equals start of window.");
      return true; -- not needed, handled in a different if branch
    }*/
    var integer vl_lastSeqNum := f_EPTF_mod32Add(pl_seqNum, lengthof(pl_pduData) - 1);

    var integer vl_endOfWindow := v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.expectedSeqNum;
    if(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.segmentBuffer.finSegmentOffset >= 0) {
      vl_endOfWindow := f_EPTF_mod32Add(vl_endOfWindow,
        v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.segmentBuffer.finSegmentOffset);
    } else {
      vl_endOfWindow := f_EPTF_mod32Add(vl_endOfWindow,
//        v_EPTF_CommPort_connectionDatabase.data[pl_socketId].receiveWindowSize - 1);
        tsp_EPTF_TransportIPL2_tcpDefaultReceiveWindowSize - 1);
    }

    if(f_EPTF_mod32Less(vl_lastSeqNum, v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.expectedSeqNum) or
       f_EPTF_mod32Greater(pl_seqNum, vl_endOfWindow)) {
      f_EPTF_TransportIPL2_debug(%definitionId&": segment falls outside the receive window.");
      return false;
    } else if(lengthof(pl_pduData) == 0) {
      pl_validStart := pl_seqNum;
      pl_validEnd := pl_seqNum;
      return true;
    }
    if(f_EPTF_mod32Less(pl_seqNum, v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.expectedSeqNum)) {
      var integer vl_trunc := f_EPTF_mod32Sub(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.expectedSeqNum, pl_seqNum);
      pl_pduData := substr(pl_pduData, vl_trunc, lengthof(pl_pduData) - vl_trunc);
      pl_validStart := v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.expectedSeqNum;
      f_EPTF_TransportIPL2_debug(%definitionId&": segment overlaps with start of window.");
    } else {
      pl_validStart := pl_seqNum;
    }
    if(f_EPTF_mod32Greater(vl_lastSeqNum, vl_endOfWindow)) {
      var integer vl_trunc := f_EPTF_mod32Sub(vl_lastSeqNum, vl_endOfWindow);
      pl_pduData := substr(pl_pduData, 0, lengthof(pl_pduData) - vl_trunc);
      pl_validEnd := vl_endOfWindow;
      f_EPTF_TransportIPL2_debug(%definitionId&": segment overlaps with end of window.");
    } else {
      pl_validEnd := vl_lastSeqNum;
    }
    return true;
  }

  private function f_EPTF_TransportIPL2_isAckValid(
    in integer pl_socketId,
    in integer pl_ack)
  runs on EPTF_TransportIPL2_CT
  return boolean
  {
    var integer vl_lastValidAck := f_EPTF_mod32Add(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.sendWindowStart,
      v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.unackedBytes + 1);
    if(f_EPTF_mod32GreaterOrEqual(pl_ack, v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.sendWindowStart) and
       f_EPTF_mod32LessOrEqual(pl_ack, vl_lastValidAck)) {
      if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
        f_EPTF_TransportIPL2_debug(%definitionId&": ack: "&int2str(pl_ack)&
          ", send window start: "&int2str(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.sendWindowStart)&
          ", last valid ack: "&int2str(vl_lastValidAck));
      }
      return true;
    } else {
      if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
        f_EPTF_TransportIPL2_debug(%definitionId&": invalid ack "&int2str(pl_ack)&
          ", send window start: "&int2str(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.sendWindowStart)&
          ", last valid ack: "&int2str(vl_lastValidAck));
      }
      return false;
    }
  }

  private function f_EPTF_TransportIPL2_handleTcpAck(
    in integer pl_socketId,
    in integer pl_ack)
  runs on EPTF_TransportIPL2_CT
  return integer // number of non acked payload bytes or -1 if the socket was closed (LAST_ACK -> CLOSED)
  {
    // find out if the ack is valid
//    if(not f_EPTF_TransportIPL2_isAckValid(pl_socketId, pl_ack)) { return; }

    var integer vl_iter;
    var integer vl_unAcked := 0;
    if(f_EPTF_FBQ_getBusyHeadIdx(vl_iter, v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.sendBuffer.queue)) {
      if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
        f_EPTF_TransportIPL2_debug(%definitionId&": incoming ack: "&int2str(pl_ack));
      }
      var boolean vl_loop := true;
      var integer vl_next := vl_iter;
      do {
        vl_loop := f_EPTF_FBQ_getFwdBusyItemIdx(vl_next, v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.sendBuffer.queue);
        if(f_EPTF_mod32GreaterOrEqual(pl_ack,
          v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.sendBuffer.segments[vl_iter].ackSeqNum)) {
          if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
            f_EPTF_TransportIPL2_debug(%definitionId&": segment acked: "&
              log2str(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.sendBuffer.segments[vl_iter]));
          }
          v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.unackedBytes :=
            v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.unackedBytes -
              v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.sendBuffer.segments[vl_iter].payloadLength;
          v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.sendWindowStart :=
            v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.sendBuffer.segments[vl_iter].ackSeqNum;
          if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
            f_EPTF_TransportIPL2_debug(%definitionId&": send window start updated to "&
              int2str(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.sendWindowStart));
          }

          if(tsp_EPTF_TransportIPL2_tcpCalculateRTT) {
            // round trip time:
            var float vl_rtt := f_EPTF_SchedulerComp_snapshotTime() -
              v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.sendBuffer.segments[vl_iter].timestamp;
            if( v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpState == SYN_RECEIVED or
                v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpState == SYN_SENT) {
              v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.smoothedRTT := vl_rtt;
            } else {
              v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.smoothedRTT :=
                tsp_EPTF_TransportIPL2_tcpSmoothAlpha * v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.smoothedRTT +
                (1.0 - tsp_EPTF_TransportIPL2_tcpSmoothAlpha) * vl_rtt;
            }
            v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.retransmitTime :=
              tsp_EPTF_TransportIPL2_tcpSmoothBeta *v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.smoothedRTT;
            if(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.retransmitTime < tsp_EPTF_TransportIPL2_tcpMinRetransmitTime) {
              v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.retransmitTime := tsp_EPTF_TransportIPL2_tcpMinRetransmitTime;
            } else if(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.retransmitTime > tsp_EPTF_TransportIPL2_tcpMaxRetransmitTime) {
              v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.retransmitTime := tsp_EPTF_TransportIPL2_tcpMaxRetransmitTime;
            }
            if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
              f_EPTF_TransportIPL2_debug(%definitionId&": round trip time: "& float2str(vl_rtt));
              f_EPTF_TransportIPL2_debug(%definitionId&": smoothed round trip time: "& float2str(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.smoothedRTT));
              f_EPTF_TransportIPL2_debug(%definitionId&": retransmit time: "& float2str(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.retransmitTime));
            }
          } else {
            v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.retransmitTime := tsp_EPTF_TransportIPL2_tcpMinRetransmitTime;
          }
          
          f_EPTF_TransportIPL2_cancelTcpRetransmission(pl_socketId, vl_iter);
        } else {
          // the remote receive window size must be decreased with the unacknowledged segments
          // because these segments may be retransmitted (unless acked in a subsequent incoming segment)
          vl_unAcked := vl_unAcked +
              v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpConnectionData.sendBuffer.segments[vl_iter].payloadLength;
        }
        vl_iter := vl_next;
      } while(vl_loop);
    }

    // note: skip the select/case (i.e. if/elseif/else) in the most common case, the ESTABLISHED state
    if(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpState != ESTABLISHED) {
      select(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpState) {
        case(SYN_RECEIVED) {
          f_EPTF_TransportIPL2_setSocketState(pl_socketId, ESTABLISHED);
        }
        case(FIN_WAIT1) {
          if(vl_unAcked == 0) { // everything acked - including the FIN
            // in this state transition, the retransmission of the FIN we sent is cancelled, but we still wait for the FIN of the peer
            // the FIN may be in the same incoming segment, and will be handled after this ACK in f_EPTF_TransportIPL2_incomingTcpSegment
            f_EPTF_TransportIPL2_setSocketState(pl_socketId, FIN_WAIT2);
          }
        }
        case(CLOSING) {
          if(vl_unAcked == 0) { // everything acked - including the FIN
            // in this state transition, the retransmission of the FIN we sent is cancelled
            // we wait for some time before finally closing the socket, so we can still ack incoming retransmitted segments
            f_EPTF_TransportIPL2_setSocketState(pl_socketId, TIME_WAIT);
            // schedule final close of socket
            f_EPTF_TransportIPL2_scheduleTimeWait(pl_socketId);
          }
        }
        case(LAST_ACK) {
          // we got the ACK for our part of the FIN in passive close -> final-close the socket
          f_EPTF_TransportIPL2_finalCloseTcp(pl_socketId);
          return -1;
        }
      }
    }

    return vl_unAcked;
  }

  private function f_EPTF_TransportIPL2_setSocketState(
    in integer pl_socketId,
    in EPTF_TransportIPL2_TCPState pl_newState)
  runs on EPTF_TransportIPL2_CT
  {
    if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
      f_EPTF_TransportIPL2_debug(%definitionId&": state changed for socket "&int2str(pl_socketId)&
        " from "&log2str(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpState)&
        " to "&log2str(pl_newState));
      if(not match({v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpState, pl_newState}, t_EPTF_TransportIPL2_validStateTransitions)) {
        f_EPTF_TransportIPL2_warning(%definitionId&": invalid socket state transition " &
          log2str(v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpState) &
          " -> " & log2str(pl_newState));
      }
    }
    v_EPTF_CommPort_connectionDatabase.data[pl_socketId].tcpState := pl_newState;
  }

} // group EPTF_TransportIPL2_Layer3

group EPTF_TransportIPL2_SegmentBuffer
{
   
  ///////////////////////////////////////////////////////////
  //  Function: f_EPTF_SegmentBuffer_initBuffer
  //
  //  Purpose:
  //    (Re-)Initializes the segment buffer
  //
  //  Parameters:
  //    - pl_buffer - *inout* <EPTF_SegmentBuffer> - the segment buffer
  //
  //  Purpose:
  //    Initializes a new segment buffer. Can also be called to clear/reset the whole buffer.
  //
  ///////////////////////////////////////////////////////////
  public function f_EPTF_SegmentBuffer_initBuffer(
    inout EPTF_SegmentBuffer pl_buffer)
  {
    pl_buffer := c_EPTF_SegmentBuffer_init;
    f_EPTF_FBQ_initFreeBusyQueue(pl_buffer.nonContSegments.queue);
    if (tsp_EPTF_SegmentBuffer_useCLLBuffer == true) {
	  // TODO kell-e vajon a vegere pozicionalni? : f_EPTF_Buffer_set_pos(pl_buffer.continuousBufferID, ???);  	
		pl_buffer.continuousBufferID := f_EPTF_Buffer_new();
    }
  }

  ///////////////////////////////////////////////////////////
  //  Function: f_EPTF_SegmentBuffer_init_CT
  //
  //  Purpose:
  //    (Re-)Initializes the segment buffer component
  //
  //  Parameters:
  //    - pl_selfName - *in* <charstring> - name of the component
  //
  //  Purpose:
  //    Initializes the segment buffer component.
  //
  ///////////////////////////////////////////////////////////
  public function f_EPTF_SegmentBuffer_init_CT(
    in charstring pl_selfName)
    runs on EPTF_SegmentBuffer_CT
  {
	f_EPTF_Buffer_init_CT(pl_selfName);
        f_EPTF_FBQ_init_CT(pl_selfName);
  }


  private function f_EPTF_SegmentBuffer_logSegments(
    in charstring pl_caller,
    inout EPTF_SegmentBuffer pl_buffer)
  {
    if(c_EPTF_Common_debugSwitch and tsp_EPTF_TransportIPL2_enableLogging) {
      var integer vl_iter;
      if(f_EPTF_FBQ_getBusyHeadIdx(vl_iter, pl_buffer.nonContSegments.queue)) {
        f_EPTF_Common_user(pl_caller&": Segment buffer:");
        do {
          f_EPTF_Common_user(pl_caller&": "&log2str(pl_buffer.nonContSegments.segments[vl_iter]));
        } while(f_EPTF_FBQ_getFwdBusyItemIdx(vl_iter, pl_buffer.nonContSegments.queue));
      } else {
        f_EPTF_Common_user(pl_caller&": Segment buffer is empty.");
      }
      if(pl_buffer.finSegmentOffset < 0) {
        f_EPTF_Common_user(pl_caller&": final segment not yet received.");
      } else if(pl_buffer.finSegmentOffset == 0) {
        f_EPTF_Common_user(pl_caller&": final segment handled.");
      } else {
        f_EPTF_Common_user(pl_caller&": final segment offset " & int2str(pl_buffer.finSegmentOffset));
      }
    }
   
    
  }

  public function f_EPTF_SegmentBuffer_setFinalSegmentOffset(
    inout EPTF_SegmentBuffer pl_buffer,
    in integer pl_finSegmentOffset)
//  return integer
  {
//    if(pl_buffer.finSegmentOffset < 0) {
      pl_buffer.finSegmentOffset := pl_finSegmentOffset;
      if(c_EPTF_Common_debugSwitch and tsp_EPTF_TransportIPL2_enableLogging) {
        f_EPTF_Common_user(%definitionId&": final segment offset set to "&int2str(pl_finSegmentOffset));
      }
/*    } else {
      f_EPTF_Common_warning(%definitionId&": final segment offset already set: ", pl_buffer.finSegmentOffset);
    }*/
  }

  ///////////////////////////////////////////////////////////
  //  Function: f_EPTF_SegmentBuffer_insertSegment
  //
  //  Purpose:
  //    Inserts a new segment into the buffer of the open socket
  //
  //  Parameters:
  //    - pl_buffer - *inout* <EPTF_SegmentBuffer> - the segment buffer
  //    - pl_payload - *in* *octetstring* - the string to be inserted
  //    - pl_seqNum - *in* *integer* - sequence number of the segment, relative from the start of the window
  //    - pl_size - *out* *integer* - the size of the continuous part
  //  Return Value:
  //     *integer* - the number of bytes concatenated to the continuous part
  //
  ///////////////////////////////////////////////////////////
  public function f_EPTF_SegmentBuffer_insertSegment(
    inout EPTF_SegmentBuffer pl_buffer,
    in octetstring pl_payload,
    in integer pl_seqNum,
    out integer pl_size)
  return integer
  {
    var integer vl_iter;

    if (pl_seqNum == 0){
      var integer vl_payloadLen := lengthof(pl_payload);

      if(pl_buffer.finSegmentOffset > 0 and vl_payloadLen > pl_buffer.finSegmentOffset) {
        f_EPTF_Common_warning(%definitionId&": incoming segment has data with segment number greater than the FIN segment. ");
        f_EPTF_Common_warning(%definitionId&": Length of data: "&int2str(vl_payloadLen));
        f_EPTF_Common_warning(%definitionId&": FIN segment offset: "&int2str(pl_buffer.finSegmentOffset));
        vl_payloadLen := pl_buffer.finSegmentOffset;
		if (tsp_EPTF_SegmentBuffer_useCLLBuffer == true) {
			// TODO kell-e vajon a vegere pozicionalni? : f_EPTF_Buffer_set_pos(pl_buffer.continuousBufferID, ???);			
			f_EPTF_Buffer_put_os(pl_buffer.continuousBufferID, substr(pl_payload, 0, vl_payloadLen));
		} else {
        	pl_buffer.continuousBuffer := pl_buffer.continuousBuffer&substr(pl_payload, 0, vl_payloadLen);
        }
      } else {
		if (tsp_EPTF_SegmentBuffer_useCLLBuffer == true) {
			// TODO kell-e vajon a vegere pozicionalni? : f_EPTF_Buffer_set_pos(pl_buffer.continuousBufferID, ???);	
			f_EPTF_Buffer_put_os(pl_buffer.continuousBufferID, pl_payload);
		} else {
	        pl_buffer.continuousBuffer := pl_buffer.continuousBuffer&pl_payload;
		}
      }

      var integer vl_concatenated := vl_payloadLen;

      if(c_EPTF_Common_debugSwitch and tsp_EPTF_TransportIPL2_enableLogging) {
        f_EPTF_Common_user(%definitionId&": Concatenated "&int2str(vl_payloadLen)&" bytes of payload to continuous buffer.");
      }
      if(f_EPTF_FBQ_getBusyHeadIdx(vl_iter, pl_buffer.nonContSegments.queue)) {
        var boolean vl_loop := true;
        var integer vl_next := vl_iter;
        do {
          vl_loop := f_EPTF_FBQ_getFwdBusyItemIdx(vl_next, pl_buffer.nonContSegments.queue);

          pl_buffer.nonContSegments.segments[vl_iter].seqNum :=
            pl_buffer.nonContSegments.segments[vl_iter].seqNum - vl_concatenated;

          if(pl_buffer.nonContSegments.segments[vl_iter].seqNum <= 0)  {
            if(pl_buffer.nonContSegments.segments[vl_iter].seqNum == 0) {

				if (tsp_EPTF_SegmentBuffer_useCLLBuffer == true) {
					// TODO kell-e vajon a vegere pozicionalni? : f_EPTF_Buffer_set_pos(pl_buffer.continuousBufferID, ???);	
					f_EPTF_Buffer_put_os(pl_buffer.continuousBufferID, pl_buffer.nonContSegments.segments[vl_iter].data);
				} else {
	              pl_buffer.continuousBuffer := pl_buffer.continuousBuffer&pl_buffer.nonContSegments.segments[vl_iter].data;
				}
    	        vl_concatenated := vl_concatenated + lengthof(pl_buffer.nonContSegments.segments[vl_iter].data);

            } else if((lengthof(pl_buffer.nonContSegments.segments[vl_iter].data) + pl_buffer.nonContSegments.segments[vl_iter].seqNum) > 0) {
				if (tsp_EPTF_SegmentBuffer_useCLLBuffer == true) {
					// TODO kell-e vajon a vegere pozicionalni? : f_EPTF_Buffer_set_pos(pl_buffer.continuousBufferID, ???);	
					f_EPTF_Buffer_put_os(pl_buffer.continuousBufferID, 
										 substr(pl_buffer.nonContSegments.segments[vl_iter].data,
					        	               -pl_buffer.nonContSegments.segments[vl_iter].seqNum,
            	    					       lengthof(pl_buffer.nonContSegments.segments[vl_iter].data) + pl_buffer.nonContSegments.segments[vl_iter].seqNum));
				} else {
	              pl_buffer.continuousBuffer := pl_buffer.continuousBuffer&
    	            substr(pl_buffer.nonContSegments.segments[vl_iter].data,
        	               -pl_buffer.nonContSegments.segments[vl_iter].seqNum,
            	           lengthof(pl_buffer.nonContSegments.segments[vl_iter].data) + pl_buffer.nonContSegments.segments[vl_iter].seqNum);
				}

              vl_concatenated := vl_concatenated + lengthof(pl_buffer.nonContSegments.segments[vl_iter].data) + pl_buffer.nonContSegments.segments[vl_iter].seqNum;
            }
            pl_buffer.nonContSegments.nofBytes := 
              pl_buffer.nonContSegments.nofBytes - lengthof(pl_buffer.nonContSegments.segments[vl_iter].data);
            pl_buffer.nonContSegments.segments[vl_iter].data := ''O;
            f_EPTF_FBQ_moveFromBusyToFreeTail(vl_iter, pl_buffer.nonContSegments.queue);
          }

          vl_iter := vl_next;
        } while(vl_loop);
        if(c_EPTF_Common_debugSwitch and tsp_EPTF_TransportIPL2_enableLogging) {
          if(vl_concatenated > vl_payloadLen) {
            f_EPTF_Common_user(%definitionId&": Concatenated "&int2str(vl_concatenated-vl_payloadLen)&" bytes from segment buffer to continuous buffer.");
          } else {
            f_EPTF_Common_user(%definitionId&": No data was concatenated from segment buffer to continuous buffer.");
          }
        }
      }
      if(pl_buffer.finSegmentOffset > 0) {
        pl_buffer.finSegmentOffset :=
          pl_buffer.finSegmentOffset - vl_concatenated;
        if(pl_buffer.finSegmentOffset < 0) { pl_buffer.finSegmentOffset := 0; }
      }
      if(c_EPTF_Common_debugSwitch and tsp_EPTF_TransportIPL2_enableLogging) {
		if (tsp_EPTF_SegmentBuffer_useCLLBuffer == true) {
		    var octetstring vl_outData;
			f_EPTF_Buffer_get_read_data(pl_buffer.continuousBufferID, vl_outData);
			f_EPTF_Common_user(%definitionId&": Continuous buffer: "& log2str(vl_outData));
		} else {
	        f_EPTF_Common_user(%definitionId&": Continuous buffer: "& log2str(pl_buffer.continuousBuffer));     
        }

        f_EPTF_SegmentBuffer_logSegments(%definitionId, pl_buffer);
      }
  	  if (tsp_EPTF_SegmentBuffer_useCLLBuffer == true) {
		pl_size := f_EPTF_Buffer_get_read_len(pl_buffer.continuousBufferID);
	  } else {
        pl_size := lengthof(pl_buffer.continuousBuffer);
      }

      return vl_concatenated;

    } else {
      var integer vl_new := f_EPTF_FBQ_getOrCreateFreeSlot(pl_buffer.nonContSegments.queue);
      pl_buffer.nonContSegments.segments[vl_new] := {
        seqNum := pl_seqNum,
        data := pl_payload
      }
      pl_buffer.nonContSegments.nofBytes := 
        pl_buffer.nonContSegments.nofBytes + lengthof(pl_payload);
      if (f_EPTF_FBQ_getBusyHeadIdx(vl_iter, pl_buffer.nonContSegments.queue)) {
        if(pl_buffer.nonContSegments.segments[vl_iter].seqNum >= pl_seqNum) {
          f_EPTF_FBQ_moveFromFreeHeadToBusy(
            vl_new,
            head,
            vl_iter,
            vl_iter,
            pl_buffer.nonContSegments.queue);
        } else {
          // skip segments with lower sequence number than the new segment
          var integer vl_insertAfter := vl_iter;
          while(pl_buffer.nonContSegments.segments[vl_iter].seqNum < pl_seqNum) {
            vl_insertAfter := vl_iter;
            if(not f_EPTF_FBQ_getFwdBusyItemIdx(vl_iter, pl_buffer.nonContSegments.queue)) {
              break;
            }
          }
          // insert new segment after it
          var integer vl_beforeIdx := vl_insertAfter;
          if(not f_EPTF_FBQ_getFwdBusyItemIdx(vl_beforeIdx, pl_buffer.nonContSegments.queue)) {
            f_EPTF_FBQ_moveFromFreeHeadToBusyTail(pl_buffer.nonContSegments.queue);
          } else {
            f_EPTF_FBQ_moveFromFreeHeadToBusy(
              vl_new,
              middle,
              vl_insertAfter,
              vl_beforeIdx,
              pl_buffer.nonContSegments.queue);
          }
        }
      } else {
        f_EPTF_FBQ_moveFromFreeHeadToBusyTail(pl_buffer.nonContSegments.queue);
      }
      if(c_EPTF_Common_debugSwitch and tsp_EPTF_TransportIPL2_enableLogging) {
        f_EPTF_Common_user(%definitionId&": Stored data with offset "&int2str(pl_seqNum)&" in segment buffer.");
        f_EPTF_SegmentBuffer_logSegments(%definitionId, pl_buffer);
      }

      if (tsp_EPTF_SegmentBuffer_useCLLBuffer == true) {
		pl_size := f_EPTF_Buffer_get_read_len(pl_buffer.continuousBufferID);
	  } else {
        pl_size := lengthof(pl_buffer.continuousBuffer);
      }

      return 0;
    }
  }


  ///////////////////////////////////////////////////////////
  //  Function: f_EPTF_SegmentBuffer_getContinousLen
  //
  //  Purpose:
  //    Retrieves the length of the continuous buffer
  //
  //  Parameters:
  //    - pl_buffer - *inout* <EPTF_SegmentBuffer> - the segment buffer
  //
  //  Return Value:
  //     Returns the length of the continuous buffer
  //
  ///////////////////////////////////////////////////////////
  public function f_EPTF_SegmentBuffer_getContinousLen(inout EPTF_SegmentBuffer pl_buffer)
  return integer
  {
    if (tsp_EPTF_SegmentBuffer_useCLLBuffer == true) {
	  return f_EPTF_Buffer_get_read_len(pl_buffer.continuousBufferID);
    } else {
      return lengthof(pl_buffer.continuousBuffer);
    }
  }

  ///////////////////////////////////////////////////////////
  //  Function: f_EPTF_SegmentBuffer_getNonContinousLen
  //
  //  Purpose:
  //    Retrieves the length of the non-continuous buffer
  //
  //  Parameters:
  //    - pl_buffer - *inout* <EPTF_SegmentBuffer> - the segment buffer
  //
  //  Return Value:
  //     Returns the number of bytes stored in the non-continuous buffer
  //
  ///////////////////////////////////////////////////////////
  public function f_EPTF_SegmentBuffer_getNonContinousLen(inout EPTF_SegmentBuffer pl_buffer)
  return integer
  {
    return pl_buffer.nonContSegments.nofBytes;
  }


  ///////////////////////////////////////////////////////////
  //  Function: f_EPTF_SegmentBuffer_getBuffer
  //
  //  Purpose:
  //    Retrieves the content of the continuous buffer
  //
  //  Parameters:
  //    - pl_buffer - *inout* <EPTF_SegmentBuffer> - the segment buffer
  //    - pl_content - *out* *octetstring* - the place of the string to be retrieved
  //    - pl_size - *in* *integer* - Length of the string to be retrieved
  //      the default -1 means the full available buffer length
  //
  //  Return Value:
  //     -
  //
  ///////////////////////////////////////////////////////////
  public function f_EPTF_SegmentBuffer_getBuffer(
    inout EPTF_SegmentBuffer pl_buffer,
    out octetstring pl_content,
    in integer pl_size := -1)
  {
    if (tsp_EPTF_SegmentBuffer_useCLLBuffer == true) {
	  if (pl_size < 0 or pl_size >= f_EPTF_Buffer_get_read_len(pl_buffer.continuousBufferID))
	  {
	    f_EPTF_Buffer_get_read_data(pl_buffer.continuousBufferID, pl_content);
	  }  else {
	    f_EPTF_Buffer_get_read_data(pl_buffer.continuousBufferID, pl_content);
	    pl_content := substr(pl_content, 0, pl_size);
	  }  
    } else {      
      if (pl_size < 0 or pl_size >= lengthof(pl_buffer.continuousBuffer))
      {
        pl_content := pl_buffer.continuousBuffer;
      } else {
      	pl_content := substr(pl_buffer.continuousBuffer, 0, pl_size);
      }   
    }
    
    
  }


  ///////////////////////////////////////////////////////////
  //  Function: f_EPTF_SegmentBuffer_truncBuffer
  //
  //  Purpose:
  //    Removes the specified amount from the beginning of the buffer
  //
  //  Parameters:
  //    - pl_buffer - *inout* <EPTF_SegmentBuffer> - the segment buffer
  //    - pl_size - *in* *integer* - Length of the string to be deleted.
  //      The default -1 means the full buffer
  //
  //  Return Value:
  //     *integer* - the length of the deleted string
  //
  ///////////////////////////////////////////////////////////
  public function f_EPTF_SegmentBuffer_truncBuffer(
    inout EPTF_SegmentBuffer pl_buffer,
    in integer pl_size := -1)
  return integer
  {
    if (tsp_EPTF_SegmentBuffer_useCLLBuffer == true) {
       var integer vl_len := f_EPTF_Buffer_get_read_len(pl_buffer.continuousBufferID);
    	if (pl_size < 0 or pl_size >= vl_len)
	    {
	      f_EPTF_Buffer_cut_end(pl_buffer.continuousBufferID);
	      f_EPTF_Buffer_cut(pl_buffer.continuousBufferID);
    	} else {
		  f_EPTF_Buffer_set_pos(pl_buffer.continuousBufferID, pl_size);
    	  f_EPTF_Buffer_cut(pl_buffer.continuousBufferID);
	      vl_len := pl_size;
    	}
	    return vl_len;
    } else {
    	var integer vl_len := lengthof(pl_buffer.continuousBuffer)
    	if (pl_size < 0 or pl_size >= vl_len)
	    {
	      pl_buffer.continuousBuffer := ''O;
    	} else {
    	  pl_buffer.continuousBuffer := substr(pl_buffer.continuousBuffer, pl_size, vl_len-pl_size);
	      vl_len := pl_size;
    	}
	    return vl_len;
    }


  }

} // group EPTF_TransportIPL2_SegmentBuffer

group EPTF_TransportIPL2_RouteHandler {
  
  ///////////////////////////////////////////////////////////
  //  Function: f_EPTF_TransportIPL2_initRouting
  //
  //  Purpose:
  //    To initilize the routing handler
  //
  //  Parameters:
  //    -
  //
  //  Return Value:
  //     -
  //
  //  Detailed Comments:
  //    It initializes the routing table database, host routing table
  //    is loaded into the database.
  //
  ///////////////////////////////////////////////////////////
  private function f_EPTF_TransportIPL2_initRouting()
  runs on EPTF_TransportIPL2_CT
  {
    v_EPTF_TransportIPL2_routeTable := {};
    v_EPTF_TransportIPL2_gwArpDB := {};
    v_EPTF_TransportIPL2_ipArpDB := {
      {}, -1
    }
    
    v_EPTF_TransportIPL2_ipArpDB.hashmapId := f_EPTF_oct2int_HashMap_New("ipArpDB");

    var EPTF_TransportIPL2_RouteTable vl_table;
    f_EPTF_TransportIPL2_getHostRoutingTable(vl_table);
    f_EPTF_TransportIPL2_setRouteTable(vl_table);
    if(sizeof(v_EPTF_TransportIPL2_ethernetInterfaceList) > 0) {
      f_EPTF_TransportIPL2_addRoute(
        f_EPTF_TransportIPL2_getIpFromHostName(tsp_EPTF_TransportIPL2_loopbackInterfaceAddress),
        c_EPTF_TransportIPL2_NullIpAddress,
        f_EPTF_TransportIPL2_getIpFromHostName(tsp_EPTF_TransportIPL2_loopbackInterfaceMask),
        tsp_EPTF_TransportIPL2_loopbackInterface);
    }
  }
  
  ///////////////////////////////////////////////////////////////////////////////
  //  Function: f_EPTF_TransportIPL2_getNextHop
  // 
  //  Purpose:
  //    Returns the next hop IP address, interface for the given target IP address.
  //
  //  Parameters:
  //    pl_ipAddr - *in* <EPTF_TransportIPL2_IpAddress> - the target IP address
  //    pl_nextHopIpAddr - *out* <EPTF_TransportIPL2_IpAddress> - the next-hop IP address
  //    pl_nextHopIface - *out charstring* - the interface name of the next hop
  //    pl_isGw - *out boolean* - true if the next hop IP is a gateway, false if not
  //               false value means direct access.
  //
  //  Return Value:
  //    boolean - true if next hop found, false if there is no route to the target IP
  //
  //  Errors:
  //    -
  //
  //  Detailed Comments:
  //    -
  //    
  ///////////////////////////////////////////////////////////////////////////////
  public function f_EPTF_TransportIPL2_getNextHop(
//    in EPTF_TransportIPL2_RouteTable pl_routeTable,
    in EPTF_TransportIPL2_IpAddress pl_ipAddr,
    out EPTF_TransportIPL2_IpAddress pl_nextHopIpAddr,
    out charstring pl_nextHopIface,
    out boolean pl_isGw)
  runs on EPTF_TransportIPL2_CT
  return boolean {
    var integer vl_nextHopIfaceIdx;
    return f_EPTF_TransportIPL2_getNextHopPrivate(pl_ipAddr, pl_nextHopIpAddr, pl_nextHopIface, vl_nextHopIfaceIdx, pl_isGw);
  }

  private function f_EPTF_TransportIPL2_getNextHopPrivate(
//    in EPTF_TransportIPL2_RouteTable pl_routeTable,
    in EPTF_TransportIPL2_IpAddress pl_ipAddr,
    out EPTF_TransportIPL2_IpAddress pl_nextHopIpAddr,
    out charstring pl_nextHopIface,
    out integer pl_nextHopIfaceIdx,
    out boolean pl_isGw)
  runs on EPTF_TransportIPL2_CT
  return boolean {
    pl_nextHopIpAddr := c_EPTF_TransportIPL2_NullIpAddress;
    pl_nextHopIface := "";
    pl_isGw := false;
    
    for (var integer i:=0; i<sizeof(v_EPTF_TransportIPL2_routeTable); i:=i+1) {
      if (v_EPTF_TransportIPL2_routeTable[i].destination == pl_ipAddr and4b v_EPTF_TransportIPL2_routeTable[i].genmask) {
        pl_nextHopIface := v_EPTF_TransportIPL2_routeTable[i].iface;
        pl_nextHopIfaceIdx := v_EPTF_TransportIPL2_routeTableToEthIF[i];
        if (v_EPTF_TransportIPL2_routeTable[i].gateway != c_EPTF_TransportIPL2_NullIpAddress) {
          pl_nextHopIpAddr := v_EPTF_TransportIPL2_routeTable[i].gateway;
          pl_isGw := true;
        } else {
          pl_nextHopIpAddr := pl_ipAddr;
        }
        return true; // for
      }
    }
    
    return false;
  }
  
  ///////////////////////////////////////////////////////////////////////////////
  //  Function: f_EPTF_TransportIPL2_normalizeRoutingTable
  // 
  //  Purpose:
  //    It normalizes the routing table based on netmask.
  //
  //  Parameters:
  //    -
  //
  //  Return Value:
  //    -
  //
  //  Errors:
  //    -
  //
  //  Detailed Comments:
  //    See more detailes at: http://www.softpanorama.org/Algorithms/Sorting/bubblesort.shtml.
  //    
  ///////////////////////////////////////////////////////////////////////////////
  public function f_EPTF_TransportIPL2_normalizeRoutingTable()
  runs on EPTF_TransportIPL2_CT
  {
    var integer n := sizeof(v_EPTF_TransportIPL2_routeTable);
    var integer k;
    var integer bound := n-1;
    var EPTF_TransportIPL2_RouteTableEntry t;
    var integer last_swap;
  
    while (bound!=0) {
      last_swap := 0;
      for ( k:=0; k<bound; k:=k+1 ) {
         t := v_EPTF_TransportIPL2_routeTable[k]; // t is a minimum of A[0]..A[k] 
         if ( oct2int(t.genmask) < oct2int(v_EPTF_TransportIPL2_routeTable[k+1].genmask) ) {
           v_EPTF_TransportIPL2_routeTable[k] := v_EPTF_TransportIPL2_routeTable[k+1]; v_EPTF_TransportIPL2_routeTable[k+1] := t; //swap
           last_swap := k; // mark the last swap position 
         }//if
      }//for
      bound:=last_swap; // elements after bound already sorted        
    }//while

    f_EPTF_TransportIPL2_updateRouteTableToEthIF();
  }

  private function f_EPTF_TransportIPL2_updateRouteTableToEthIF()
  runs on EPTF_TransportIPL2_CT
  {
    v_EPTF_TransportIPL2_routeTableToEthIF := {};
    for(var integer i:=0; i<sizeof(v_EPTF_TransportIPL2_routeTable); i:=i+1) {
      v_EPTF_TransportIPL2_routeTableToEthIF[i] := -1;
      for(var integer j:=0; j<sizeof(v_EPTF_TransportIPL2_ethernetInterfaceList); j:=j+1) {
        if(v_EPTF_TransportIPL2_routeTable[i].iface == v_EPTF_TransportIPL2_ethernetInterfaceList[j].name) {
          v_EPTF_TransportIPL2_routeTableToEthIF[i] := j;
          break;
        }
      }
    }
    if(c_EPTF_Common_debugSwitch and f_EPTF_TransportIPL2_debugEnabled()) {
      f_EPTF_TransportIPL2_debug(%definitionId&": ethernet interface DB: " & log2str(v_EPTF_TransportIPL2_ethernetInterfaceList));
      f_EPTF_TransportIPL2_debug(%definitionId&": route table: " & log2str(v_EPTF_TransportIPL2_routeTable));
      f_EPTF_TransportIPL2_debug(%definitionId&": route table -> interface association list: " & log2str(v_EPTF_TransportIPL2_routeTableToEthIF));
    }
  }

  ///////////////////////////////////////////////////////////
  //  Function: f_EPTF_TransportIPL2_getHostRoutingTable
  //
  //  Purpose:
  //    To retrieve the host system's routing table.
  //
  //  Parameters:
  //    - pl_table - *out* <EPTF_TransportIPL2_RouteTable> - the routing table
  //
  //  Return Value:
  //     -
  //
  //  Detailed Comments:
  //    It retrieves the host system's routing table and saves it into pl_table.
  //
  ///////////////////////////////////////////////////////////
  public external function f_EPTF_TransportIPL2_getHostRoutingTable(
    out EPTF_TransportIPL2_RouteTable pl_table);
    
  ///////////////////////////////////////////////////////////
  //  Function: f_EPTF_TransportIPL2_getRouteTable
  //
  //  Purpose:
  //    To retrieve the current routing table from the database.
  //
  //  Parameters:
  //    - 
  //
  //  Return Value:
  //     <EPTF_TransportIPL2_RouteTable> - the routing table
  //
  //  Detailed Comments:
  //    It retrieves the current routing table and returns it.
  //
  ///////////////////////////////////////////////////////////
  public function f_EPTF_TransportIPL2_getRouteTable()
  runs on EPTF_TransportIPL2_CT
  return EPTF_TransportIPL2_RouteTable
  {
    return v_EPTF_TransportIPL2_routeTable;
  }
  
  ///////////////////////////////////////////////////////////
  //  Function: f_EPTF_TransportIPL2_setRouteTable
  //
  //  Purpose:
  //    To set the current routing table into the database.
  //
  //  Parameters:
  //    - pl_routeTable - *in* <EPTF_TransportIPL2_RouteTable> - the routing
  //      table
  //
  //  Return Value:
  //     <EPTF_TransportIPL2_RouteTable> - the routing table
  //
  //  Detailed Comments:
  //    It loads the specified routing table into the database and normalizes it
  //
  ///////////////////////////////////////////////////////////
  public function f_EPTF_TransportIPL2_setRouteTable(
    in EPTF_TransportIPL2_RouteTable pl_routeTable
  )
  runs on EPTF_TransportIPL2_CT
  {
    v_EPTF_TransportIPL2_routeTable := pl_routeTable;
    f_EPTF_TransportIPL2_normalizeRoutingTable();
  }
  
  ///////////////////////////////////////////////////////////
  //  Function: f_EPTF_TransportIPL2_addRoute
  //
  //  Purpose:
  //    To add a route to the routing table.
  //
  //  Parameters:
  //    - pl_destination - *in* <EPTF_TransportIPL2_IpAddress> - the destination
  //      network
  //    - pl_gateway - *in* <EPTF_TransportIPL2_IpAddress> - the gateway address
  //    - pl_genmask - *in* <EPTF_TransportIPL2_IpAddress> - the subnet mask of
  //      destination network
  //    - pl_iface - *in* *charstring* - the destination interface
  //      (currently not used)
  //
  //  Detailed Comments:
  //    It adds the specified route to the routing table and normalizes it
  //
  ///////////////////////////////////////////////////////////
  public function f_EPTF_TransportIPL2_addRoute(
    in EPTF_TransportIPL2_IpAddress pl_destination,
    in EPTF_TransportIPL2_IpAddress pl_gateway,
    in EPTF_TransportIPL2_IpAddress pl_genmask,
    in charstring pl_iface
    )
  runs on EPTF_TransportIPL2_CT
  {
    var integer vl_idx := sizeof(v_EPTF_TransportIPL2_routeTable);
    v_EPTF_TransportIPL2_routeTable[vl_idx] := {
      pl_destination,
      pl_gateway,
      pl_genmask,
      pl_iface
    }
    
    f_EPTF_TransportIPL2_normalizeRoutingTable();
  }
  
} // group EPTF_TransportIPL2_RouteHandler

group EPTF_TransportIPL2_ARP {
    
  private function f_EPTF_TransportIPL2_createARPRequest(
    in EPTF_TransportIPL2_IpAddress pl_targetIPaddr,
    in EPTF_TransportIPL2_IpAddress pl_senderIPaddr,
    in EPTF_TransportIPL2_MACAddress pl_senderMACaddr )
  return octetstring
  {
    return cg_EPTF_TransportIPL2_ARP_prefix&'0001'O&pl_senderMACaddr&
        pl_senderIPaddr&cg_EPTF_TransportIPL2_NullMACAddress&pl_targetIPaddr;
  }

  private function f_EPTF_TransportIPL2_createARPReply(
    in EPTF_TransportIPL2_IpAddress pl_targetIPaddr,
    in EPTF_TransportIPL2_MACAddress pl_targetMACaddr,
    in EPTF_TransportIPL2_IpAddress pl_senderIPaddr,
    in EPTF_TransportIPL2_MACAddress pl_senderMACaddr )
  return octetstring
  {
    return cg_EPTF_TransportIPL2_ARP_prefix&'0002'O&pl_senderMACaddr&
        pl_senderIPaddr&pl_targetMACaddr&pl_targetIPaddr;
  }
  
  private function f_EPTF_TransportIPL2_decodeARP(
    in octetstring pl_pdu,
    inout ARP_packet pl_arpPacket )
  {
    var integer vl_pos := lengthof(cg_EPTF_TransportIPL2_ARP_prefix);
    
    pl_arpPacket.arpType := substr(pl_pdu, vl_pos, 2);
    vl_pos := vl_pos + 2;
    pl_arpPacket.senderMACAddr := substr(pl_pdu, vl_pos, 6);
    vl_pos := vl_pos + 6;
    pl_arpPacket.senderIPAddr := substr(pl_pdu, vl_pos, 4);
    vl_pos := vl_pos + 4;
    pl_arpPacket.targetMACAddr := substr(pl_pdu, vl_pos, 6);
    vl_pos := vl_pos + 6;
    pl_arpPacket.targetIPAddr := substr(pl_pdu, vl_pos, 4);
  }
  
  ///////////////////////////////////////////////////////////
  //  Function: f_EPTF_TransportIPL2_ARP_getMac
  //
  //  Purpose:
  //    To determine the MAC address for the specified IP address.
  //
  //  Parameters:
  //    - pl_sourceIpAddr - *in* <EPTF_TransportIPL2_IpAddress> - the source 
  //      IP address
  //    - pl_sourceMACAddr - *in* <EPTF_TransportIPL2_MACAddress> - the source
  //      MAC address
  //    - pl_targetIpAddr - *in* <EPTF_TransportIPL2_IpAddress> - the IP address
  //      for which the next hop MAC address should be determined
  //
  //  Return Value:
  //     <EPTF_TransportIPL2_MACAddress> - the MAC address of the next hop
  //     for the target IP address.
  //
  //  Detailed Comments:
  //    The returned MAC address belongs to the next hop of the specified IP
  //    address.
  //
  ///////////////////////////////////////////////////////////
  public function f_EPTF_TransportIPL2_ARP_getMac(
    in EPTF_TransportIPL2_IpAddress pl_sourceIpAddr,
    in EPTF_TransportIPL2_MACAddress pl_sourceMACAddr,
    in EPTF_TransportIPL2_IpAddress pl_targetIpAddr)
  runs on EPTF_TransportIPL2_CT
  return EPTF_TransportIPL2_MACAddress
  {
    var integer vl_nextHopIfaceIdx;
    var EPTF_TransportIPL2_MACAddress vl_nextHopMacAddress;
    if(f_EPTF_TransportIPL2_ARP_getMacPrivate(pl_sourceIpAddr, pl_sourceMACAddr, pl_targetIpAddr, vl_nextHopIfaceIdx, vl_nextHopMacAddress)) {
      return vl_nextHopMacAddress;
    } else {
      return cg_EPTF_TransportIPL2_NullMACAddress;
    }
  }

  private function f_EPTF_TransportIPL2_ARP_getMacPrivate(
    in EPTF_TransportIPL2_IpAddress pl_sourceIpAddr,
    in EPTF_TransportIPL2_MACAddress pl_sourceMACAddr,
    in EPTF_TransportIPL2_IpAddress pl_targetIpAddr,
    out integer pl_nextHopIfaceIdx,
    out EPTF_TransportIPL2_MACAddress pl_nextHopMacAddress)
  runs on EPTF_TransportIPL2_CT
  return boolean // true on success
  {
    var boolean vl_isGw;
    var EPTF_TransportIPL2_IpAddress vl_nextHopIpAddr;
    var charstring vl_nextHopIface;
    var EPTF_TransportIPL2_MACAddress vl_srcMacAddr := pl_sourceMACAddr;
    
    if (f_EPTF_TransportIPL2_hasIp(pl_targetIpAddr)) {
      pl_nextHopMacAddress := vl_srcMacAddr; // note: this can be the null MAC address in case of the loopback IF, thus the boolean return type!
      pl_nextHopIfaceIdx := v_EPTF_TransportIPL2_loopbackIF;
      return true;
    }
    
    if (not f_EPTF_TransportIPL2_getNextHopPrivate(pl_targetIpAddr, vl_nextHopIpAddr, vl_nextHopIface, pl_nextHopIfaceIdx, vl_isGw)) {
      f_EPTF_TransportIPL2_warning(%definitionId&": No route to host: "&f_EPTF_TransportIPL2_ip2str(pl_targetIpAddr));
      pl_nextHopMacAddress := cg_EPTF_TransportIPL2_NullMACAddress;
      return false;
    }
    if(sizeof(v_EPTF_TransportIPL2_ethernetInterfaceList) > 0) { // multiple interface mode
      if(pl_nextHopIfaceIdx < 0) {
        f_EPTF_TransportIPL2_warning(%definitionId&": multiple interface mode, interface was not opened: "&vl_nextHopIface);
        pl_nextHopMacAddress := cg_EPTF_TransportIPL2_NullMACAddress;
        return false;
      }
      vl_srcMacAddr := v_EPTF_TransportIPL2_ethernetInterfaceList[pl_nextHopIfaceIdx].macAddress;
    }

    if (vl_isGw) {
      pl_nextHopMacAddress := f_EPTF_TransportIPL2_ARP_getMacForGw(pl_nextHopIfaceIdx, pl_sourceIpAddr, vl_srcMacAddr, vl_nextHopIpAddr);
    } else {
      pl_nextHopMacAddress := f_EPTF_TransportIPL2_ARP_getMacForIp(pl_nextHopIfaceIdx, pl_sourceIpAddr, vl_srcMacAddr, vl_nextHopIpAddr);
    }
    if(cg_EPTF_TransportIPL2_NullMACAddress == pl_nextHopMacAddress) {
      return false;
    } else {
      return true;
    }
  }
  
  // Determines the MAC address of the pl_gwIpAddr
  private function f_EPTF_TransportIPL2_ARP_getMacForGw(
    in integer pl_interfaceId,
    in EPTF_TransportIPL2_IpAddress pl_sourceIpAddr,
    in EPTF_TransportIPL2_MACAddress pl_sourceMACAddr,
    in EPTF_TransportIPL2_IpAddress pl_gwIpAddr)
  runs on EPTF_TransportIPL2_CT
  return EPTF_TransportIPL2_MACAddress
  {
    var boolean vl_needInsert := true;
    var integer vl_idx := -1;
    
    for ( var integer i := 0; i < sizeof(v_EPTF_TransportIPL2_gwArpDB) ; i := i+1 ) {
      if (v_EPTF_TransportIPL2_gwArpDB[i].gateway != pl_gwIpAddr) {
        continue;
      }
      vl_needInsert := false;
      vl_idx := i;
      if (v_EPTF_TransportIPL2_gwArpDB[i].macaddr != cg_EPTF_TransportIPL2_NullMACAddress) {
        return v_EPTF_TransportIPL2_gwArpDB[i].macaddr;
      }
      break;
    }
    
    if (vl_idx == -1) {
      vl_idx := sizeof(v_EPTF_TransportIPL2_gwArpDB);
      v_EPTF_TransportIPL2_gwArpDB[vl_idx].gateway := pl_gwIpAddr;
      v_EPTF_TransportIPL2_gwArpDB[vl_idx].macaddr := cg_EPTF_TransportIPL2_NullMACAddress;
      v_EPTF_TransportIPL2_gwArpDB[vl_idx].semaphoreIdx := -1;
    }
    
    // if the mac was already requested, but response is not arrived yet, then
    // wait until response comes (or timeout):
    if (v_EPTF_TransportIPL2_gwArpDB[vl_idx].semaphoreIdx != -1) {
      timer t_wait := 0.0;
      t_wait.start;
      alt {
        [v_EPTF_TransportIPL2_gwArpDB[vl_idx].semaphoreIdx == -1] t_wait.timeout {
          return v_EPTF_TransportIPL2_gwArpDB[vl_idx].macaddr;
        }
      }
    }
    f_EPTF_TransportIPL2_ARP_sendMACRequestForIp(pl_interfaceId, pl_sourceIpAddr, pl_sourceMACAddr, pl_gwIpAddr); // TODO
    
    v_EPTF_TransportIPL2_gwArpDB[vl_idx].semaphoreIdx := f_EPTF_Semaphore_new();
    var boolean vl_timeout := f_EPTF_Semaphore_waitForUnlock(v_EPTF_TransportIPL2_gwArpDB[vl_idx].semaphoreIdx, tsp_EPTF_TransportIPL2_arpMaxWaitTime);
    v_EPTF_TransportIPL2_gwArpDB[vl_idx].semaphoreIdx := -1;
    
    if (vl_timeout) {
      return cg_EPTF_TransportIPL2_NullMACAddress;
    }
    
    return v_EPTF_TransportIPL2_gwArpDB[vl_idx].macaddr;
  }
  
    // Determines the MAC address of the pl_gwIpAddr
  private function f_EPTF_TransportIPL2_ARP_getMacForIp(
    in integer pl_interfaceId,
    in EPTF_TransportIPL2_IpAddress pl_sourceIpAddr,
    in EPTF_TransportIPL2_MACAddress pl_sourceMACAddr,
    in EPTF_TransportIPL2_IpAddress pl_targetIpAddr)
  runs on EPTF_TransportIPL2_CT
  return EPTF_TransportIPL2_MACAddress
  {
    var integer vl_idx;
    
    if (f_EPTF_oct2int_HashMap_Find(v_EPTF_TransportIPL2_ipArpDB.hashmapId, pl_targetIpAddr, vl_idx)) {
      if ( v_EPTF_TransportIPL2_ipArpDB.data[vl_idx].macaddr != cg_EPTF_TransportIPL2_NullMACAddress) {
        return v_EPTF_TransportIPL2_ipArpDB.data[vl_idx].macaddr;
      }
    } else {
      vl_idx := -1;
    }
    
    if (vl_idx == -1) {
      vl_idx := sizeof(v_EPTF_TransportIPL2_ipArpDB.data);
      f_EPTF_oct2int_HashMap_Insert(v_EPTF_TransportIPL2_ipArpDB.hashmapId, pl_targetIpAddr, vl_idx);
      v_EPTF_TransportIPL2_ipArpDB.data[vl_idx].macaddr := cg_EPTF_TransportIPL2_NullMACAddress;
      v_EPTF_TransportIPL2_ipArpDB.data[vl_idx].semaphoreIdx := -1;
    }
    
    // if the mac was already requested, but response is not arrived yet, then
    // wait until response comes (or timeout):
    if (v_EPTF_TransportIPL2_ipArpDB.data[vl_idx].semaphoreIdx != -1) {
      timer t_wait := 0.0;
      t_wait.start;
      alt {
        [v_EPTF_TransportIPL2_ipArpDB.data[vl_idx].semaphoreIdx == -1] t_wait.timeout {
          return v_EPTF_TransportIPL2_ipArpDB.data[vl_idx].macaddr;
        }
      }
    }
    
    f_EPTF_TransportIPL2_ARP_sendMACRequestForIp(pl_interfaceId, pl_sourceIpAddr, pl_sourceMACAddr, pl_targetIpAddr); // TODO
    
    v_EPTF_TransportIPL2_ipArpDB.data[vl_idx].semaphoreIdx := f_EPTF_Semaphore_new();
    var boolean vl_timeout := f_EPTF_Semaphore_waitForUnlock(v_EPTF_TransportIPL2_ipArpDB.data[vl_idx].semaphoreIdx, tsp_EPTF_TransportIPL2_arpMaxWaitTime);
    v_EPTF_TransportIPL2_ipArpDB.data[vl_idx].semaphoreIdx := -1;
    
    if (vl_timeout) {
      return cg_EPTF_TransportIPL2_NullMACAddress;
    }
    
    return v_EPTF_TransportIPL2_ipArpDB.data[vl_idx].macaddr;
  }
  
  ///////////////////////////////////////////////////////////
  //  Function: f_EPTF_TransportIPL2_ARP_sendMACRequestForIp
  //
  //  Purpose:
  //    To send an ARP request for the specified IP address.
  //
  //  Parameters:
  //    - pl_sourceIpAddr - *in* <EPTF_TransportIPL2_IpAddress> - the source 
  //      IP address
  //    - pl_sourceMACAddr - *in* <EPTF_TransportIPL2_MACAddress> - the source
  //      MAC address
  //    - pl_targetIpAddr - *in* <EPTF_TransportIPL2_IpAddress> - the IP address
  //      for which the MAC address should be determined
  //
  //  Detailed Comments:
  //    The ARP reply handled by the <as_EPTF_TransportIPL2_defaultReceive> 
  //    altstep.
  //
  ///////////////////////////////////////////////////////////
  private function f_EPTF_TransportIPL2_ARP_sendMACRequestForIp(
    in integer pl_interfaceId,
    in EPTF_TransportIPL2_IpAddress pl_sourceIpAddr,
    in EPTF_TransportIPL2_MACAddress pl_sourceMACAddr,
    in EPTF_TransportIPL2_IpAddress pl_targetIpAddr)
  runs on EPTF_TransportIPL2_CT
  {
    var octetstring vl_data := f_EPTF_TransportIPL2_createARPRequest(
      pl_targetIpAddr, pl_sourceIpAddr, pl_sourceMACAddr);
    
    f_EPTF_TransportIPL2_sendOverL2(
      pl_interfaceId,
      pl_eth_dst_addr := cg_EPTF_TransportIPL2_BroadcastMACAddress,
      //pl_eth_src_addr := pl_sourceMACAddr,
      pl_type_field   := int2oct(c_ip_gre_proto_arp, 2),
      pl_payload      := vl_data);
  }
   
  ///////////////////////////////////////////////////////////
  //  Function: f_EPTF_TransportIPL2_ARP_sendMACReplyForIp
  //
  //  Purpose:
  //    To send an ARP reply.
  //
  //  Parameters:
  //    - pl_interfaceId - *in* *integer* - ethernet interface ID
  //    - pl_sourceIpAddr - *in* <EPTF_TransportIPL2_IpAddress> - the source 
  //      IP address
  //    - pl_sourceMACAddr - *in* <EPTF_TransportIPL2_MACAddress> - the source
  //      MAC address
  //    - pl_targetIpAddr - *in* <EPTF_TransportIPL2_IpAddress> - the target 
  //      IP address
  //    - pl_targetMACAddr - *in* <EPTF_TransportIPL2_MACAddress> - the target
  //      MAC address
  //
  //  Detailed Comments:
  //    -
  ///////////////////////////////////////////////////////////
  private function f_EPTF_TransportIPL2_ARP_sendMACReplyForIp(
    in integer pl_interfaceId,
    in EPTF_TransportIPL2_IpAddress pl_sourceIpAddr,
    in EPTF_TransportIPL2_MACAddress pl_sourceMACAddr,
    in EPTF_TransportIPL2_IpAddress pl_targetIpAddr,
    in EPTF_TransportIPL2_MACAddress pl_targetMACAddr)
  runs on EPTF_TransportIPL2_CT
  {
    var octetstring vl_arpReply := f_EPTF_TransportIPL2_createARPReply(
      pl_targetIpAddr, //target
      pl_targetMACAddr,
      pl_sourceIpAddr, // sender
      pl_sourceMACAddr);
      
    f_EPTF_TransportIPL2_sendOverL2(
      pl_interfaceId,
      pl_targetMACAddr,
      //pl_sourceMACAddr,
      int2oct(c_ip_gre_proto_arp, 2),
      vl_arpReply);
  }
  
  private function f_EPTF_TransportIPL2_addMacToGwArpDB(
    in ARP_packet pl_arpPacket )
  runs on EPTF_TransportIPL2_CT
  return boolean
  {
    for ( var integer i := 0; i < sizeof(v_EPTF_TransportIPL2_gwArpDB) ; i := i+1 ) {
      if (v_EPTF_TransportIPL2_gwArpDB[i].gateway == pl_arpPacket.senderIPAddr) {
        v_EPTF_TransportIPL2_gwArpDB[i].macaddr := pl_arpPacket.senderMACAddr;
        if (v_EPTF_TransportIPL2_gwArpDB[i].semaphoreIdx != -1) {
          f_EPTF_Semaphore_unlock(v_EPTF_TransportIPL2_gwArpDB[i].semaphoreIdx);
        }
        return true;
      }
    }
    
    return false;
  }

  private function f_EPTF_TransportIPL2_addMacToIpArpDB(
    in ARP_packet pl_arpPacket )
  runs on EPTF_TransportIPL2_CT
  return boolean
  {
    var integer vl_idx;
    
    if (f_EPTF_oct2int_HashMap_Find(v_EPTF_TransportIPL2_ipArpDB.hashmapId, pl_arpPacket.senderIPAddr, vl_idx)) {
      v_EPTF_TransportIPL2_ipArpDB.data[vl_idx].macaddr := pl_arpPacket.senderMACAddr;
      if (v_EPTF_TransportIPL2_ipArpDB.data[vl_idx].semaphoreIdx != -1) {
        f_EPTF_Semaphore_unlock(v_EPTF_TransportIPL2_ipArpDB.data[vl_idx].semaphoreIdx);
      }
      return true;
    }
        
    return false;
  }
  
  
  
} //group ARP

///////////////////////////////////////////////////////////////////////////////
//  Group: EPTF_TransportIPL2_Logging
// 
//  Purpose:
//    The functions of the EPTF CommPort IPL2 Logging
//
///////////////////////////////////////////////////////////////////////////////
group EPTF_TransportIPL2_Logging 
{

///////////////////////////////////////////////////////////
  //  Function: f_EPTF_TransportIPL2_error
  // 
  //  Purpose:
  //    Function to log an error from StatMeasure feature.
  //
  //  Parameters:
  //    - pl_message - *in* *charstring* - the message to log
  //
  //  Return Value:
  //    -
  //
  //  Errors & assertions:
  //    - 
  //
  //  Detailed Comments:
  //    -
  //
///////////////////////////////////////////////////////////
  private function f_EPTF_TransportIPL2_error(
    in charstring pl_message)
  runs on EPTF_TransportIPL2_CT
  {
    f_EPTF_Logging_error(true, tsp_EPTF_TransportIPL2_loggingComponentMask&": "&pl_message);
    f_EPTF_Base_stopAll();
  }

///////////////////////////////////////////////////////////
  //  Function: f_EPTF_TransportIPL2_warning
  // 
  //  Purpose:
  //    Function to log a warning from StatMeasure feature.
  //
  //  Parameters:
  //    - pl_message - *in* *charstring* - the message to log
  //
  //  Return Value:
  //    -
  //
  //  Errors & assertions:
  //    - 
  //
  //  Detailed Comments:
  //    -
  //
///////////////////////////////////////////////////////////
  private function f_EPTF_TransportIPL2_warning(
    in charstring pl_message)
  runs on EPTF_TransportIPL2_CT
  {
    f_EPTF_Logging_warningV2(pl_message, 
            v_EPTF_TransportIPL2_loggingMaskId, 
      {c_EPTF_TransportIPL2_loggingClassIdx_Warning});
  }

///////////////////////////////////////////////////////////
  //  Function: f_EPTF_TransportIPL2_debug
  // 
  //  Purpose:
  //    Function to log a debug message from StatMeasure feature.
  //
  //  Parameters:
  //    - pl_message - *in* *charstring* - the message to log
  //
  //  Return Value:
  //    -
  //
  //  Errors & assertions:
  //    - 
  //
  //  Detailed Comments:
  //    -
  //
///////////////////////////////////////////////////////////
  private function f_EPTF_TransportIPL2_debug(
    in charstring pl_message)
  runs on EPTF_TransportIPL2_CT
  {
    f_EPTF_Logging_debugV2(pl_message, 
            v_EPTF_TransportIPL2_loggingMaskId,  
      {c_EPTF_TransportIPL2_loggingClassIdx_Debug});
  }

///////////////////////////////////////////////////////////
  //  Function: f_EPTF_TransportIPL2_debugEnabled
  // 
  //  Purpose:
  //    Function to check if debug is enabled for StatMeasure
  //
  //  Parameters:
  //    -
  //
  //  Return Value:
  //    *boolean* - true if debug enalbed
  //
  //  Errors & assertions:
  //    - 
  //
  //  Detailed Comments:
  //    -
  //
///////////////////////////////////////////////////////////
  private function f_EPTF_TransportIPL2_debugEnabled()
  runs on EPTF_TransportIPL2_CT
  return boolean
  {
    return f_EPTF_Logging_isEnabled(
            v_EPTF_TransportIPL2_loggingMaskId,  
      c_EPTF_TransportIPL2_loggingClassIdx_Debug);
  }
  
} // group Logging

}
