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

module Handle_TCP_Connections
{

import from General_Types all;
import from TCP_Types all;
import from IP_Types all;

modulepar 
{  
  charstring tsp_APN1_Internet_IpAddr1;
  charstring tsp_APN1_MS_IpAddr1; 
}  

// dummy component definition     
type component BASIC_CT
{
 //...
 
 var ConnectionList vc_ConnectionList := {}
 
 //...
};

// data needed to identify a single TCP connection
type record Connection
{
 charstring   srcIpAddr,
 integer      srcPort,
 charstring   dstIpAddr,
 integer      dstPort,
 integer      tCP_SeqNo, 
 integer      tCP_AckNo  
}

type record of Connection ConnectionList;

// function which finds the  TCP connection in a ConnectionList identified by 
// source and destination IP addresses and ports
function f_find_connectionID
(
 charstring   pl_srcIpAddr,
 integer      pl_srcPort,
 charstring   pl_dstIpAddr,
 integer      pl_dstPort     
) runs on BASIC_CT return integer
{
  
  for(var integer i := 0; i < sizeof(vc_ConnectionList); i := i + 1) 
   {
     if 
     (        
       pl_srcIpAddr == vc_ConnectionList[i].srcIpAddr and
       pl_srcPort == vc_ConnectionList[i].srcPort and
       pl_dstIpAddr == vc_ConnectionList[i].dstIpAddr and
       pl_dstPort  == vc_ConnectionList[i].dstPort    
     )
     {return i}    
   }
 
   // Add new element to vc_ConnectionList
   var integer vl_next_element := sizeof(vc_ConnectionList);
   vc_ConnectionList[vl_next_element].srcIpAddr := pl_srcIpAddr;
   vc_ConnectionList[vl_next_element].srcPort := pl_srcPort;
   vc_ConnectionList[vl_next_element].dstIpAddr := pl_dstIpAddr;
   vc_ConnectionList[vl_next_element].dstPort := pl_dstPort;
   vc_ConnectionList[vl_next_element].tCP_SeqNo := 100;
   vc_ConnectionList[vl_next_element].tCP_AckNo := 0;
   
   return vl_next_element; 
}

// function to create a TCP/IP packets carrying the payload "pl_data"
function f_TCP_CreatePayload(
    charstring       pl_srcIpAddr,
    LIN2_BO_LAST     pl_srcPort, 
    charstring       pl_dstIpAddr,
    LIN2_BO_LAST     pl_dstPort,
    TCP_Control_bits pl_tcpctrl,
    octetstring      pl_data,
    octetstring      pl_options := ''O )
runs on BASIC_CT
return IPv4_packet
{
    var integer vl_connectionID := f_find_connectionID(pl_srcIpAddr,pl_srcPort,pl_dstIpAddr,pl_dstPort)

    var octetstring vl_tcp_enc;  
    var template IPv4_packet vl_ip;

    var PDU_TCP vl_tcp_packet :=
    {
        source_port             := pl_srcPort,
        dest_port               := pl_dstPort,
        sequence_number         := vc_ConnectionList[vl_connectionID].tCP_SeqNo,        //v_TCP_SeqNo 
        acknowledgment_number    := vc_ConnectionList[vl_connectionID].tCP_AckNo,       //v_TCP_AckNo
        data_offset             := 0, //dummy,
        reserved                := '000000'B,
        control_bits            := pl_tcpctrl,
        window                  := 3000,              // should not be hardcoded
        checksum                := '0000'O,           // calculated by f_TCP_enc
        urgent_pointer          := 0,                 // only set it URG-bit set in options
        options                 := pl_options,        // f_TCP_enc adds padding to 4 byte boundary if needed
        data                    := pl_data
    }

    //log("### TCP_Functions: TCP packet to be sent: ", vl_tcp_packet);

    // Calculate new SeqNo
    // If in TCP setup/finish phase, increase SeqNo with 1
    // If sending data, increase SeqNo with data length
    if(pl_tcpctrl.syn == '1'B  or pl_tcpctrl.fin == '1'B )
    {
        vc_ConnectionList[vl_connectionID].tCP_SeqNo := vc_ConnectionList[vl_connectionID].tCP_SeqNo + 1;  //v_TCP_SeqNo
    }
    else
    {
        vc_ConnectionList[vl_connectionID].tCP_SeqNo := vc_ConnectionList[vl_connectionID].tCP_SeqNo + lengthof(pl_data); //v_TCP_SeqNo
    }

    vl_tcp_enc := f_enc_PDU_TCP(f_IPv4_addr_enc(pl_srcIpAddr),f_IPv4_addr_enc(pl_dstIpAddr),vl_tcp_packet);

    vl_ip := t_IPv4_Basic(
        f_IPv4_addr_enc(pl_srcIpAddr),
        f_IPv4_addr_enc(pl_dstIpAddr),
        c_ip_proto_tcp,
        vl_tcp_enc);
    
    return valueof(vl_ip);
}


// function to verify TCP and IP headers in a received message,
// TCP checksum is verified, SeqNo and AckNo calculated as if we are the server
// and saved in local ConnectionList variable.
function f_TCP_VerifyHeader(
    charstring  pl_srcIpAddr,
    integer     pl_srcPort,
    charstring  pl_dstIpAddr,
    integer     pl_dstPort,
    TCP_Control_bits  pl_control,
    octetstring pl_data)
runs on BASIC_CT
return octetstring
{
    var integer vl_connectionID := f_find_connectionID(pl_srcIpAddr,pl_srcPort,pl_dstIpAddr,pl_dstPort)  

    var PDU_TCP    vl_tcp_pkt;
    var octetstring   vl_tcp_enc;
    var template integer vl_AckNo := ?;
    var boolean vl_wait_establish := false;
    var boolean vl_wait_terminate := false;
    
    // Check IP header
    vl_tcp_enc := f_IP_VerifyHeader(pl_srcIpAddr, pl_dstIpAddr, c_ip_proto_tcp, pl_data);
    
    // Check TCP header    
    if (f_TCP_verify_checksum(f_IPv4_addr_enc(pl_srcIpAddr), f_IPv4_addr_enc(pl_dstIpAddr), vl_tcp_enc)  == false )
    {
           log( "### f_TCP_dec: ERROR! The TCP checksum of the received TCP packet is not correct." ); 
           setverdict( fail );
           stop;
    }
    
    vl_tcp_pkt := f_dec_PDU_TCP( vl_tcp_enc);
    
    // TCP Connection Establishment, SERVER
    if (vl_tcp_pkt.control_bits.syn == '1'B)
    {
        vl_wait_establish := true;
        vc_ConnectionList[vl_connectionID].tCP_AckNo := vl_tcp_pkt.sequence_number + 1;  //v_TCP_AckNo
    }
    else if(vl_tcp_pkt.control_bits.ack == '1'B and vl_wait_establish)
    {
        vl_wait_establish := false;
    }
    // TCP Connection Termination, SERVER
    else if (vl_tcp_pkt.control_bits.fin == '1'B)
    {
        vl_wait_terminate := true;
        vl_AckNo := vc_ConnectionList[vl_connectionID].tCP_AckNo;    // v_TCP_AckNo
        vc_ConnectionList[vl_connectionID].tCP_AckNo := vc_ConnectionList[vl_connectionID].tCP_AckNo + 1;  // v_TCP_AckNo
    }
    else if (vl_tcp_pkt.control_bits.ack == '1'B and vl_wait_terminate)
    {
        vl_wait_terminate := false;
    }
    // TCP Communication
    else
    {
        vl_AckNo := vc_ConnectionList[vl_connectionID].tCP_AckNo;    //v_TCP_AckNo
        vc_ConnectionList[vl_connectionID].tCP_AckNo := vc_ConnectionList[vl_connectionID].tCP_AckNo + lengthof(vl_tcp_pkt.data); //v_TCP_AckNo
        
        template PDU_TCP tr_tcp := tr_TCP_Pkt(pl_srcPort, pl_dstPort, vc_ConnectionList[vl_connectionID].tCP_SeqNo, vl_AckNo, pl_control, ?); //v_TCP_SeqNo
        var template octetstring vl_SeqNo := ?;
        // Check SeqNo for TCP-retransmission
        vl_SeqNo := int2oct(vc_ConnectionList[vl_connectionID].tCP_AckNo, 4);       //v_TCP_AckNo
        if (vc_ConnectionList[vl_connectionID].tCP_AckNo == vl_tcp_pkt.sequence_number)                                   //normal packet//v_TCP_AckNo
        {
            vc_ConnectionList[vl_connectionID].tCP_AckNo := vc_ConnectionList[vl_connectionID].tCP_AckNo + lengthof(vl_tcp_pkt.data); //v_TCP_AckNo
        }
        else if(vc_ConnectionList[vl_connectionID].tCP_AckNo == vl_tcp_pkt.sequence_number + lengthof(vl_tcp_pkt.data) )   //re-transimitted packet
        {
            vl_SeqNo := int2oct(oct2int(valueof(vl_SeqNo)) - lengthof(vl_tcp_pkt.data), 4);
        }
        else
        {
            setverdict(fail, "### Unexpected TCP received");
            log("### Received: " ,vl_tcp_pkt);
            log("### Wanted  : " ,tr_tcp);
            stop;
        }        
    }
    
    log("### f_TCP_VerifyHeader: Received ", vl_tcp_pkt);
    setverdict(pass);
    return vl_tcp_pkt.data;
}


////////////////////////////////////////////////////
// Misc. definitions
////////////////////////////////////////////////////

// template for a general IPv4 packet
template IPv4_packet t_IPv4_Basic
(
OCT4 pl_srcIpAddr,
OCT4 pl_dstIpAddr,
LIN1 pl_proto,
octetstring pl_tcp_enc

) :=
{
 header :=
   {
     ver :=4,
     hlen :=0,
     tos := 0,
     tlen := 0,
     id := 0,
     res := '0'B,
     dfrag := '0'B,
     mfrag := '0'B,
     foffset := 0,
     ttl := 0,
     proto := pl_proto,
     cksum := 0,
     srcaddr := pl_srcIpAddr,
     dstaddr := pl_dstIpAddr 
    },  
 
 
 ext_headers := omit,
 payload := pl_tcp_enc

}


// template for receiving a general TCP packet
template PDU_TCP tr_TCP_Pkt(
    integer                    pl_srcPort,
    integer                    pl_dstPort,
    template integer              pl_seqNo, 
    template integer              pl_ackNo, 
    template TCP_Control_bits  pl_control,
    template octetstring       pl_payload) :=
{
    source_port             := pl_srcPort,
    dest_port               := pl_dstPort,
    sequence_number         := pl_seqNo,
    acknowledgment_number    := pl_ackNo,
    data_offset             := (5..15),
    reserved                := '000000'B,
    control_bits            := pl_control,
    window                  := ?,
    checksum                := ?,
    urgent_pointer          := ?,
    options                 := *,
    data                    := ?
}


// function for basic matching of header in received IP packet
function f_IP_VerifyHeader(
    charstring   pl_srcIpAddr,
    charstring   pl_dstIpAddr,
    LIN1         pl_protocol,   
    octetstring  pl_data)
return octetstring
{
    var IPv4_packet vl_ip_pkt := f_IPv4_dec(pl_data);
    template IPv4_packet tr_ip := tr_IP_Pkt(pl_srcIpAddr, pl_dstIpAddr, pl_protocol); 

    if (not match(vl_ip_pkt, tr_ip))
    {
        setverdict(fail, "### Received IP incorrect");
        log("### Received: ", vl_ip_pkt);
        log("### Wanted  : ", tr_ip);
    }
    setverdict(pass);
    return vl_ip_pkt.payload;
}


// template for receiving a general IP packet
template IPv4_packet tr_IP_Pkt(
    charstring  pl_srcIpAddr := tsp_APN1_Internet_IpAddr1,
    charstring  pl_dstIpAddr := tsp_APN1_MS_IpAddr1,
    LIN1        pl_protocol := 0) :=
{
    header :=
    {
        ver := c_ip_version_ipv4,
        hlen := (5..15),
        tos := ?,
        tlen := ?,
        id := ?,
        res := '0'B,             //reserved, always 0
        dfrag := ?,
        mfrag := ?,
        foffset := ?,
        ttl := (0..255),         //time to live: 255 hops
        proto := pl_protocol,    //encapsulated protocol
        cksum := ?,
        srcaddr := f_IPv4_addr_enc(pl_srcIpAddr),
        dstaddr := f_IPv4_addr_enc(pl_dstIpAddr)
    },
    ext_headers := omit,
    payload := ?
} 


} // end of file
