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

module IP_PTC_Main {

  import from IP_ASP_TypeDefs all;
  import from IP_PTC_CtrlMsgs all;
  import from IP_PTC_Templates all;
  import from IP_PTC_Component all;
  import from IP_PTC_Routing all;
  import from DHCPv4_Functions all;
  import from ICMPv6_Functions all;
  import from CommonDefs all;

  //****************************************************************************
  // IP PTC: Additional definitions
  //----------------------------------------------------------------------------

  const PortNumber_Type  tsc_PortNumber_DHCP_Server := 67;
  const PortNumber_Type  tsc_PortNumber_DHCP_Client := 68;

  const charstring tsc_INADDR_BROADCAST := "255.255.255.255";      /* broadcast address; used e.g. for DHCP */
  const charstring tsc_INADDR_ANY := "0.0.0.0";                    /* used e.g. for DHCP */

  const O2_Type tsc_MulticastPrefix_FF02 := 'FF02'O;


  // @desc Check whether an address is a IPv6 multicast address
  // @param p_IPv6Addr
  // @return true if the address is a multicast address
  function fl_IPv6AddrIsMulticast(charstring p_IPv6Addr) return boolean
  {
    var O2_Type v_Prefix;
    v_Prefix := str2oct(substr(p_IPv6Addr,0,4));
    if(v_Prefix == tsc_MulticastPrefix_FF02){ // RFC 2375
      return true;
    }
    return false;
  }

  //****************************************************************************
  // IP PTC: Initialisation
  //----------------------------------------------------------------------------

  function fl_IP_PTC_Init() runs on IP_PTC
  { /* initialisation of the IP PTC */
    vc_IP_PTC_DRB_RoutingTable := {};
    vc_IP_PTC_Applications.DHCP_Server    := fl_SocketConnection_Init(udp);
    vc_IP_PTC_Applications.ICMPv6_Server  := fl_SocketConnection_Init(icmpv6);
    vc_IP_PTC_Applications.DNS_UdpServer  := fl_SocketConnection_Init(udp);
    vc_IP_PTC_Applications.DNS_TcpServer  := fl_SocketConnection_Init(tcp);
  }

  function fl_SocketConnection_Init(InternetProtocol_Type p_Protocol,
                                    template (omit) IP_Socket_Type p_LocalSocket := omit,
                                    template (omit) IP_Socket_Type p_RemoteSocket := omit) return IP_Connection_Type
  { /* initialisation of an application's IP connection information */
    return valueof(cs_IP_ConnectionId(p_Protocol, p_LocalSocket, p_RemoteSocket));
  }

  //****************************************************************************
  // IP Connection handling
  //----------------------------------------------------------------------------
  // UDP

  function fl_UDP_Bind(IP_AddrInfo_Type p_LocalIpAddr,
                       template (omit) PortNumber_Type p_LocalPort,
                       boolean p_AllowBroadcast) runs on IP_PTC return IP_Connection_Type
  { /* Note: there is no default behaviour activated for the IP_PTC
     *       => waiting for the SA's response is blocking (what helps to avoid unexpected behaviour) */
    var IP_SOCKET_IND v_IP_SOCKET_IND;

    IP_SOCK.send(cs_UDP_SOCKET_REQ(cs_IP_Socket(p_LocalIpAddr, p_LocalPort), p_AllowBroadcast));
    IP_SOCK.receive(cr_UDP_SOCKET_CNF(cr_IP_Socket(p_LocalIpAddr, ?))) -> value v_IP_SOCKET_IND;
    return v_IP_SOCKET_IND.CTRL.ConnectionId;   /* in case of p_LocalPort=omit we need to get back the ephemeral port */
  }

  function fl_UDP_Close(IP_Socket_Type p_LocalSocket) runs on IP_PTC return IP_Connection_Type
  { /* Just close a UDP socket;
       Note: there is no confirmation needed to close a UDP server */

    IP_SOCK.send(cs_UDP_CLOSE_REQ(p_LocalSocket));
    return fl_SocketConnection_Init(udp);
  }

  function fl_UdpServer_Start(IP_AddrInfo_Type p_LocalIpAddr,
                              template (omit) PortNumber_Type p_LocalPort,
                              IP_DrbInfo_Type p_DrbInfo,
                              boolean p_AllowBroadcast := false) runs on IP_PTC return IP_Connection_Type
  { /* start a UDP socket by binding its address to the given socket;
       the routing table is updated acc. */
    var IP_Connection_Type v_UdpServerInfo;

    // Bind UDP socket
    v_UdpServerInfo := fl_UDP_Bind(p_LocalIpAddr, p_LocalPort, p_AllowBroadcast);

    // Modify routing table
    vc_IP_PTC_DRB_RoutingTable := f_RoutingTable_AddEntry(vc_IP_PTC_DRB_RoutingTable, v_UdpServerInfo, p_DrbInfo);
    f_IP_PTC_SendRoutingTable(vc_IP_PTC_DRB_RoutingTable);

    return  v_UdpServerInfo;
  }

  function fl_UdpServer_Stop(IP_Connection_Type p_UdpServerInfo) runs on IP_PTC return IP_Connection_Type
  { /* common function to close a UDP socket acting as UDP server;
       the routing table is updated acc. */
    var IP_Connection_Type v_NewUdpServerInfo;

    // Close UDP socket
    v_NewUdpServerInfo := fl_UDP_Close(p_UdpServerInfo.Local);

    // Modify routing table
    vc_IP_PTC_DRB_RoutingTable := f_RoutingTable_RemoveEntry(vc_IP_PTC_DRB_RoutingTable, p_UdpServerInfo);
    f_IP_PTC_SendRoutingTable(vc_IP_PTC_DRB_RoutingTable);

    return  v_NewUdpServerInfo;
  }

  //----------------------------------------------------------------------------
  // TCP

  //----------------------------------------------------------------------------
  // ICMPv6

  function fl_ICMPv6_Bind(IP_AddrInfo_Type p_LocalIpAddr,
                          boolean p_AllowBroadcast) runs on IP_PTC return IP_Connection_Type
  { /* Note: there is no default behaviour activated for the IP_PTC
     *       => waiting for the SA's response is blocking (what helps to avoid unexpected behaviour) */
    var template (omit) PortNumber_Type v_LocalPort := omit;  // ICMP does not have a port number
    var IP_SOCKET_IND v_IP_SOCKET_IND;
    
    IP_SOCK.send(cs_ICMPv6_SOCKET_REQ(cs_IP_Socket(p_LocalIpAddr, v_LocalPort), p_AllowBroadcast));
    IP_SOCK.receive(cr_ICMPv6_SOCKET_CNF(cr_IP_Socket(p_LocalIpAddr, v_LocalPort))) -> value v_IP_SOCKET_IND;
    return v_IP_SOCKET_IND.CTRL.ConnectionId;
  }

  function fl_ICMPv6_Close(IP_Socket_Type p_LocalSocket) runs on IP_PTC return IP_Connection_Type
  { /* Just close a ICMPv6 socket;
       Note: there is no confirmation needed to close a ICMPv6 server */

    IP_SOCK.send(cs_ICMPv6_CLOSE_REQ(p_LocalSocket));
    return fl_SocketConnection_Init(icmpv6);
  }

  function fl_Icmpv6Server_Start(IP_AddrInfo_Type p_LocalIpAddr,
                                 IP_DrbInfo_Type p_DrbInfo,
                                 boolean p_AllowBroadcast := false) runs on IP_PTC return IP_Connection_Type
  { /* start a ICMPv6 socket by binding its address to the given socket;
       the routing table is updated acc. */
    var IP_Connection_Type v_IcmpServerInfo;

    // Bind ICMPv6 socket
    v_IcmpServerInfo := fl_ICMPv6_Bind(p_LocalIpAddr, p_AllowBroadcast);

    // Modify routing table
    vc_IP_PTC_DRB_RoutingTable := f_RoutingTable_AddEntry(vc_IP_PTC_DRB_RoutingTable, v_IcmpServerInfo, p_DrbInfo);
    f_IP_PTC_SendRoutingTable(vc_IP_PTC_DRB_RoutingTable);

    return  v_IcmpServerInfo;
  }

  function fl_Icmpv6Server_Stop(IP_Connection_Type p_IcmpServerInfo) runs on IP_PTC return IP_Connection_Type
  { /* common function to close a ICMPv6 socket acting as ICMPv6 server;
       the routing table is updated acc. */
    var IP_Connection_Type v_NewIcmpServerInfo;

    // Close ICMPv6 socket
    v_NewIcmpServerInfo := fl_ICMPv6_Close(p_IcmpServerInfo.Local);

    // Modify routing table
    vc_IP_PTC_DRB_RoutingTable := f_RoutingTable_RemoveEntry(vc_IP_PTC_DRB_RoutingTable, p_IcmpServerInfo);
    f_IP_PTC_SendRoutingTable(vc_IP_PTC_DRB_RoutingTable);

    return  v_NewIcmpServerInfo;
  }

  //****************************************************************************
  // Applications
  //----------------------------------------------------------------------------
  // DHCP:

  function fl_DHCP_Start(DHCP_Start_Type p_StartParams) runs on IP_PTC
  { /* start DHCP server */
    var IP_AddrInfo_Type v_ServerAddr := p_StartParams.ServerAddr;
    var IP_DrbInfo_Type v_DrbInfo := p_StartParams.DrbInfo;
    var boolean v_AllowBroadcast := true;

    vc_IP_PTC_Applications.DHCP_Server := fl_UdpServer_Start(v_ServerAddr, tsc_PortNumber_DHCP_Server, v_DrbInfo, v_AllowBroadcast);
  }

  function fl_DHCP_Stop() runs on IP_PTC
  { /* stop DHCP server */
    vc_IP_PTC_Applications.DHCP_Server := fl_UdpServer_Stop(vc_IP_PTC_Applications.DHCP_Server);
  }

  altstep a_DHCP_Handler(IP_Socket_Type p_DHCP_ServerSocket) runs on IP_PTC
  { /* handling of DHCP messages sent by the UE */
    var IP_SOCKET_IND v_IP_SOCKET_IND;
    var IP_Socket_Type v_RemoteSocket;
    var IP_Socket_Type v_LocalSocket;
    var UDP_Data_Type v_ReqData;
    var UDP_Data_Type v_RespData;
    var charstring v_ServerIpAddr := p_DHCP_ServerSocket.IpAddr.V4.Addr;

    [] IP_SOCK.receive(cr_UDP_DATA_IND(cr_IP_Socket(?, p_DHCP_ServerSocket.Port)))   /* allow any IP address to deal with broadcasts */
      -> value  v_IP_SOCKET_IND
      {
        v_RemoteSocket := v_IP_SOCKET_IND.DATA.ConnectionId.Remote;
        v_LocalSocket := v_IP_SOCKET_IND.DATA.ConnectionId.Local;
        v_ReqData  := v_IP_SOCKET_IND.DATA.Ind.UDP.RecvFrom;
        v_RespData := f_DHCP_GetResponse(v_ReqData, v_ServerIpAddr);
        if (lengthof(v_RespData) >  0) {
          if ((v_RemoteSocket.IpAddr.V4.Addr == tsc_INADDR_ANY) or (v_LocalSocket.IpAddr.V4.Addr == tsc_INADDR_BROADCAST)) {
            /* deal with broadcasts */
            v_RemoteSocket.IpAddr.V4.Addr := tsc_INADDR_BROADCAST;
            v_LocalSocket.IpAddr.V4.Addr  := v_ServerIpAddr;
          }
          IP_SOCK.send(cs_UDP_DATA_REQ(v_LocalSocket, v_RemoteSocket, v_RespData));
        }
      }
  }

  //----------------------------------------------------------------------------
  // ICMPv6:

  function fl_ICMPv6_Start(ICMPv6_Start_Type p_StartParams) runs on IP_PTC
  { /* start ICMP server */
    var IP_AddrInfo_Type v_ServerAddr := p_StartParams.ServerAddr;
    var IP_DrbInfo_Type v_DrbInfo := p_StartParams.DrbInfo;
    var IP_Connection_Type v_IcmpServerInfo;

    if (ischosen(v_ServerAddr.V6)) {
      v_IcmpServerInfo := fl_Icmpv6Server_Start(v_ServerAddr, v_DrbInfo);
      vc_IP_PTC_Applications.ICMPv6_Server := v_IcmpServerInfo;
    } else {
      f_IP_PTC_Error(__FILE__, __LINE__, "IPv6 address expected for ICMPv6");
    }
  }

  function fl_ICMPv6_Stop() runs on IP_PTC
  { /* stop ICMPv6 server */
    vc_IP_PTC_Applications.ICMPv6_Server := fl_Icmpv6Server_Stop(vc_IP_PTC_Applications.ICMPv6_Server);
  }

  altstep a_ICMPv6_Handler(IP_Socket_Type p_ICMPv6_ServerSocket) runs on IP_PTC
  { /* handling of ICMPv6 messages sent by the UE */
    var IP_SOCKET_IND v_IP_SOCKET_IND;
    var IP_Socket_Type v_RemoteSocket;
    var IP_Socket_Type v_LocalSocket;
    var ICMP_Data_Type v_ReqData;
    var ICMP_Data_Type v_RespData;
    var charstring v_ServerIpAddr := p_ICMPv6_ServerSocket.IpAddr.V6.Addr;

    [] IP_SOCK.receive(cr_ICMPv6_DATA_IND(cr_IP_Socket(?, omit)))   /* allow any IP address to deal with multicasts */
      -> value  v_IP_SOCKET_IND
      {
        v_RemoteSocket := v_IP_SOCKET_IND.DATA.ConnectionId.Remote;
        v_LocalSocket := v_IP_SOCKET_IND.DATA.ConnectionId.Local;
        v_ReqData  := v_IP_SOCKET_IND.DATA.Ind.ICMP.RecvFrom;
        v_RespData := f_ICMPv6_GetResponse(v_ReqData);
        if (lengthof(v_RespData) >  0) {
          if (fl_IPv6AddrIsMulticast(v_LocalSocket.IpAddr.V6.Addr)) {
            // Handle a multicast request
            // SRC address is local link address corresponding to the server (SS), i.e. FE80:xxx
            // DST is the remote socket address (as received in the incoming IP packet)
            // TODO - Make sure that the IP address passed to this alstep is the local link address of the server
            v_LocalSocket.IpAddr.V6.Addr  := v_ServerIpAddr;
          }
          IP_SOCK.send(cs_UDP_DATA_REQ(v_LocalSocket, v_RemoteSocket, v_RespData));
        }
      }

  }

  //----------------------------------------------------------------------------
  // DNS:

/*   altstep a_DNS_Handler() runs on IP_PTC */
/*   { */
/*     var IP_SOCKET_IND v_IP_SOCKET_IND; */

/*     /\* [ispresent(vc_IP_PTC_Applications.DNS_UdpServer.Local)]  */
/*        => deal with UDP data *\/ */

/*     /\* [ispresent(vc_IP_PTC_Applications.DNS_TcpServer.Local) and not ispresent(vc_IP_PTC_Applications.DHCP_TcpServer.Remote)]  */
/*        => deal with accept *\/ */

/*     /\* [ispresent(vc_IP_PTC_Applications.DNS_TcpServer.Local) and ispresent(vc_IP_PTC_Applications.DHCP_TcpServer.Remote)]  */
/*        => deal with data *\/ */

/*     /\* [ispresent(vc_IP_PTC_Applications.DNS_TcpServer.Local) and ispresent(vc_IP_PTC_Applications.DHCP_TcpServer.Remote)]  */
/*        => deal with close *\/ */
/*   } */

  //----------------------------------------------------------------------------
  // Common:

  altstep a_ApplicationCtrl(IP_PTC_CTRL_PORT p_CtrlPort) runs on IP_PTC
  { /* get and handle configuration messages from the test cases (running on EUTRA, GERAN, UTRAN) */
    var IP_ConfigReq_Type v_IP_ConfigReq;

    // configuration of DHCP
    [] p_CtrlPort.receive(cr_DHCP_Req) -> value v_IP_ConfigReq {
      if (ischosen(v_IP_ConfigReq.DHCP.Start)) {
        fl_DHCP_Start(v_IP_ConfigReq.DHCP.Start);

      } else if (ischosen(v_IP_ConfigReq.DHCP.Stop)) {
        fl_DHCP_Stop();
      }
      p_CtrlPort.send(cs_DHCP_Cnf);
    }
    // configuration of ICMPv6
    [] p_CtrlPort.receive(cr_ICMPv6_Req) -> value v_IP_ConfigReq {
      if (ischosen(v_IP_ConfigReq.ICMPv6.Start)) {
        fl_ICMPv6_Start(v_IP_ConfigReq.ICMPv6.Start);

      } else if (ischosen(v_IP_ConfigReq.ICMPv6.Stop)) {
        fl_ICMPv6_Stop();
      }
      p_CtrlPort.send(cs_ICMPv6_Cnf);
    }
  }

  //****************************************************************************
  // Main Loop
  //----------------------------------------------------------------------------

  function f_IP_PTC_MainLoop() runs on IP_PTC
  { /* @desc   IP PTC main loop (started by the MTC) */

    fl_IP_PTC_Init();

    while (true) {
      alt {
        [] a_ApplicationCtrl(EUTRA_CTRL);
        [] a_ApplicationCtrl(UTRAN_CTRL);
        [] a_ApplicationCtrl(GERAN_CTRL);
        [ispresent(vc_IP_PTC_Applications.DHCP_Server.Local)]   a_DHCP_Handler  (vc_IP_PTC_Applications.DHCP_Server.Local);
        [ispresent(vc_IP_PTC_Applications.ICMPv6_Server.Local)] a_ICMPv6_Handler(vc_IP_PTC_Applications.ICMPv6_Server.Local);
        // [] a_DNS_Handler();
        [] any port.receive {
          f_IP_PTC_Error(__FILE__, __LINE__, "unexpected message (skipped)");
          repeat;
        }
      }
    }
  };
}
