/*******************************************************************************
    OpenAirInterface
    Copyright(c) 1999 - 2014 Eurecom

    OpenAirInterface is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.


    OpenAirInterface is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with OpenAirInterface.The full GNU General Public License is
   included in this distribution in the file called "COPYING". If not,
   see <http://www.gnu.org/licenses/>.

  Contact Information
  OpenAirInterface Admin: openair_admin@eurecom.fr
  OpenAirInterface Tech : openair_tech@eurecom.fr
  OpenAirInterface Dev  : openair4g-devel@eurecom.fr

  Address      : Eurecom, Compus SophiaTech 450, route des chappes, 06451 Biot, France.

 *******************************************************************************/
/*! \file sgi_task.c
* \brief
* \author Lionel Gauthier
* \company Eurecom
* \email: lionel.gauthier@eurecom.fr
*/

#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>
#include <stdlib.h>
#include <unistd.h>

#include <time.h>
#include <net/if.h>

//-----------------------------------------------------------------------------
#include "assertions.h"
#include "mme_config.h"
#include "intertask_interface.h"
#include "sgi.h"

//-----------------------------------------------------------------------------
static void* sgi_task_thread(void *args_p);
static int sgi_create_endpoint_request(sgi_data_t *sgi_dataP, SGICreateEndpointReq *req_p);
static int sgi_update_endpoint_request(sgi_data_t *sgi_dataP, SGIUpdateEndpointReq *req_p);
static int sgi_init_phase2(sgi_data_t *sgi_data_pP);
//-----------------------------------------------------------------------------
static pthread_t fw_2_sgi_task_thread;
//-----------------------------------------------------------------------------
static void* sgi_task_thread(void *args_p)
//-----------------------------------------------------------------------------
{
  sgi_data_t *sgi_data_p;

  if (args_p == NULL) {
    return NULL;
  }

  itti_mark_task_ready(TASK_FW_IP);

  sgi_data_p = (sgi_data_t *)args_p;


  while(1) {
    /* Trying to fetch a message from the message queue.
     * If the queue is empty, this function will block till a
     * message is sent to the task.
     */
    MessageDef *received_message_p;
    itti_receive_msg(TASK_FW_IP, &received_message_p);

    DevAssert(received_message_p != NULL);

    if (sgi_data_p->hss_associated) {
      switch (ITTI_MSG_ID(received_message_p)) {
      case GTPV1U_TUNNEL_DATA_IND: {
        /* We received data from GTPV1_U incoming from an UE.
         * Forward it host adapter.
         */
        Gtpv1uTunnelDataInd *data_ind_p;
        data_ind_p = &received_message_p->ittiMsg.gtpv1uTunnelDataInd;
#ifdef SGI_SOCKET_RAW
        sgi_send_data(&data_ind_p->buffer[data_ind_p->offset], data_ind_p->length, sgi_data_p, data_ind_p->local_S1u_teid);
#else
#ifdef SGI_SOCKET_DGRAM
        sgi_dgram_send_data(&data_ind_p->buffer[data_ind_p->offset], data_ind_p->length, sgi_data_p, data_ind_p->local_S1u_teid);
#endif
#endif
        /* Buffer is no longer needed, free it */
        itti_free(ITTI_MSG_ORIGIN_ID(received_message_p), data_ind_p->buffer);
      }
      break;

      case SGI_CREATE_ENDPOINT_REQUEST: {
        SGICreateEndpointReq *req_p;
        req_p = &received_message_p->ittiMsg.sgiCreateEndpointReq;
        sgi_create_endpoint_request(sgi_data_p, req_p);
      }
      break;

      case SGI_UPDATE_ENDPOINT_REQUEST: {
        SGIUpdateEndpointReq *req_p;
        req_p = &received_message_p->ittiMsg.sgiUpdateEndpointReq;
        sgi_update_endpoint_request(sgi_data_p, req_p);
      }
      break;

      default: {
        SGI_IF_ERROR("Unkwnon message ID %d:%s\n",
                     ITTI_MSG_ID(received_message_p), ITTI_MSG_NAME(received_message_p));
      }
      break;
      }
    } else {
      switch (ITTI_MSG_ID(received_message_p)) {
      case ACTIVATE_MESSAGE: {
        sgi_data_p->hss_associated = 1;
#if !defined(ENABLE_USE_GTPU_IN_KERNEL)
        SGI_IF_DEBUG("HSS ASSOCIATED, CAN START VLANS (bug in freediameter)");
        sgi_init_phase2(sgi_data_p);
#endif
      }
      break;

      default: {
        SGI_IF_ERROR("Unkwnon or ignored message ID %d:%s\n",
                     ITTI_MSG_ID(received_message_p), ITTI_MSG_NAME(received_message_p));
      }
      break;
      }
    }

    itti_free(ITTI_MSG_ORIGIN_ID(received_message_p), received_message_p);
    received_message_p = NULL;
  }

  return NULL;
}


//------------------------------------------------------
static int sgi_create_endpoint_request(sgi_data_t *sgi_dataP, SGICreateEndpointReq *req_p)
//------------------------------------------------------
{
  SGICreateEndpointResp                     *sgi_create_endpoint_resp_p;
  MessageDef                                *message_p;
  sgi_teid_mapping_t                        *mapping;

  SGI_IF_DEBUG("Rx IP_FW_CREATE_SGI_ENDPOINT_REQUEST, Context: S-GW S11 teid %u, S-GW S1U teid %u EPS bearer id %u\n",
               req_p->context_teid, req_p->sgw_S1u_teid, req_p->eps_bearer_id);

  message_p               = itti_alloc_new_message(TASK_FW_IP, SGI_CREATE_ENDPOINT_RESPONSE);

  if (message_p == NULL) {
    return -1;
  }

  sgi_create_endpoint_resp_p = &message_p->ittiMsg.sgiCreateEndpointResp;
  sgi_create_endpoint_resp_p->context_teid   = req_p->context_teid;
  sgi_create_endpoint_resp_p->sgw_S1u_teid   = req_p->sgw_S1u_teid;
  sgi_create_endpoint_resp_p->eps_bearer_id  = req_p->eps_bearer_id;
  sgi_create_endpoint_resp_p->pdn_type       = req_p->pdn_type;
  sgi_create_endpoint_resp_p->paa            = req_p->paa;
  sgi_create_endpoint_resp_p->status         = SGI_STATUS_OK;

  if (hashtable_is_key_exists(sgi_dataP->teid_mapping, req_p->sgw_S1u_teid) == HASH_TABLE_OK) {
    SGI_IF_ERROR("SGI_STATUS_ERROR_CONTEXT_ALREADY_EXIST Context: S11 teid %u\n", req_p->context_teid);
    sgi_create_endpoint_resp_p->status       = SGI_STATUS_ERROR_CONTEXT_ALREADY_EXIST;
  } else {
    mapping = malloc(sizeof(sgi_teid_mapping_t));

    if (mapping == NULL) {
      sgi_create_endpoint_resp_p->status       = SGI_STATUS_ERROR_NO_MEMORY_AVAILABLE;
    } else {
      memset(mapping, 0 , sizeof(sgi_teid_mapping_t));

      mapping->eps_bearer_id              = req_p->eps_bearer_id;
      mapping->enb_S1U_teid               = -1;

      if (hashtable_insert(sgi_dataP->teid_mapping, req_p->sgw_S1u_teid, mapping) != 0) {
        SGI_IF_ERROR("SGI_STATUS_ERROR_SYSTEM_FAILURE Context: S11 teid %u\n", req_p->context_teid);
        sgi_create_endpoint_resp_p->status  = SGI_STATUS_ERROR_SYSTEM_FAILURE;
        free(mapping);
      } else {
        SGI_IF_ERROR("SGI_STATUS_OK Context: S11 teid %u\n", req_p->context_teid);
        sgi_create_endpoint_resp_p->status  = SGI_STATUS_OK;
      }
    }
  }

  return itti_send_msg_to_task(TASK_SPGW_APP, INSTANCE_DEFAULT, message_p);
}

//------------------------------------------------------
static int sgi_update_endpoint_request(sgi_data_t *sgi_dataP, SGIUpdateEndpointReq *req_p)
//------------------------------------------------------
{
  SGIUpdateEndpointResp                     *sgi_update_endpoint_resp_p = NULL;
  MessageDef                                *message_p = NULL;
  sgi_teid_mapping_t                        *mapping   = NULL;

  SGI_IF_DEBUG("Rx IP_FW_UPDATE_SGI_ENDPOINT_REQUEST, Context: S-GW S11 teid %u, S-GW S1U teid %u EPS bearer id %u\n",
               req_p->context_teid, req_p->sgw_S1u_teid, req_p->eps_bearer_id);

  message_p               = itti_alloc_new_message(TASK_FW_IP, SGI_UPDATE_ENDPOINT_RESPONSE);

  if (message_p == NULL) {
    return -1;
  }

  sgi_update_endpoint_resp_p = &message_p->ittiMsg.sgiUpdateEndpointResp;
  sgi_update_endpoint_resp_p->context_teid   = req_p->context_teid;
  sgi_update_endpoint_resp_p->sgw_S1u_teid   = req_p->sgw_S1u_teid;
  sgi_update_endpoint_resp_p->eps_bearer_id  = req_p->eps_bearer_id;
  sgi_update_endpoint_resp_p->enb_S1u_teid   = req_p->enb_S1u_teid;
  sgi_update_endpoint_resp_p->status         = SGI_STATUS_OK;

  if (hashtable_get(sgi_dataP->teid_mapping, req_p->sgw_S1u_teid, (void**)&mapping) == HASH_TABLE_OK) {
    mapping->enb_S1U_teid = req_p->enb_S1u_teid;
  } else {
    SGI_IF_ERROR("SGI_STATUS_ERROR_CONTEXT_NOT_FOUND Context: S11 teid %u\n", req_p->context_teid);
    sgi_update_endpoint_resp_p->status       = SGI_STATUS_ERROR_CONTEXT_NOT_FOUND;
  }

  return itti_send_msg_to_task(TASK_SPGW_APP, INSTANCE_DEFAULT, message_p);
}

//-----------------------------------------------------------------------------
int sgi_init(const pgw_config_t *pgw_config_p)
//-----------------------------------------------------------------------------
{

  volatile sgi_data_t *sgi_data_p;
  int len;
  int i;

  SGI_IF_DEBUG("Initializing FW_IP task interface\n");

  sgi_data_p = malloc(sizeof(sgi_data_t));
  memset((void *)sgi_data_p, 0, sizeof(sgi_data_t));

  for (i=0; i < SGI_MAX_EPS_BEARERS_PER_USER; i++) {
    sgi_data_p->sd[i] = -1;
  }

  sgi_data_p->thread_started = 0;
  pthread_mutex_init (&sgi_data_p->thread_started_mutex, NULL);

  sgi_data_p->teid_mapping = hashtable_create (SGI_MAX_EPS_BEARERS, NULL, NULL);

  if (sgi_data_p->teid_mapping == NULL) {
    SGI_IF_ERROR("Failed to create SGI hashtable teid_mapping\n");
    return -1;
  }

  sgi_data_p->addr_v4_mapping = hashtable_create (SGI_MAX_SERVED_USERS_PER_PGW, NULL, NULL);

  if (sgi_data_p->addr_v4_mapping == NULL) {
    SGI_IF_ERROR("Failed to create SGI hashtable addr_v4_mapping\n");
    return -1;
  }

  sgi_data_p->addr_v6_mapping = obj_hashtable_create (SGI_MAX_SERVED_USERS_PER_PGW, NULL, NULL, NULL);

  if (sgi_data_p->addr_v6_mapping == NULL) {
    SGI_IF_ERROR("Failed to create SGI hashtable addr_v6_mapping\n");
    return -1;
  }


  len = strlen(pgw_config_p->ipv4.pgw_interface_name_for_SGI);
  sgi_data_p->interface_name = calloc(len + 1, sizeof(char));
  memcpy(sgi_data_p->interface_name, pgw_config_p->ipv4.pgw_interface_name_for_SGI, len);
  sgi_data_p->interface_name[len] = '\0';
  sgi_data_p->ipv4_addr = pgw_config_p->ipv4.pgw_ipv4_address_for_SGI;


  if (pthread_create(&fw_2_sgi_task_thread, NULL, &sgi_task_thread, (void *)sgi_data_p) < 0) {
    SGI_IF_ERROR("sgi_task_thread pthread_create: %s", strerror(errno));
    return -1;
  }

  return 0;
}

int sgi_init_phase2(sgi_data_t *sgi_data_pP)
{
  int i;

  if (strcmp(sgi_data_pP->interface_name, PGW_CONFIG_STRING_INTERFACE_DISABLED) != 0) {
    sgi_data_pP->interface_index = if_nametoindex(sgi_data_pP->interface_name);

    if (sgi_create_sockets(sgi_data_pP) < 0) {
      SGI_IF_ERROR("Could not create socket, leaving thread %s\n", __FUNCTION__);
      free(sgi_data_pP);
      return -1;
    }
  } else {
    SGI_IF_WARNING("SGI interface disabled by config file\n");
  }


  if (strcmp(sgi_data_pP->interface_name, PGW_CONFIG_STRING_INTERFACE_DISABLED) != 0) {
#ifdef ENABLE_USE_PCAP_FOR_SGI

    if (pthread_create(&sgi_data_pP->capture_on_sgi_if_thread, NULL, &sgi_pcap_fw_2_gtpv1u_thread, (void *)sgi_data_pP) < 0) {
      SGI_IF_ERROR("sgi_pcap_fw_2_gtpv1u_thread pthread_create: %s", strerror(errno));
      return -1;
    }

#endif
#ifdef ENABLE_USE_NETFILTER_FOR_SGI

    if (pthread_create(&sgi_data_pP->capture_on_sgi_if_thread, NULL, &sgi_nf_fw_2_gtpv1u_thread, (void *)sgi_data_pP) < 0) {
      SGI_IF_ERROR("sgi_nf_fw_2_gtpv1u_thread pthread_create: %s", strerror(errno));
      return -1;
    }

#endif
    //#if 0
#ifdef ENABLE_USE_RAW_SOCKET_FOR_SGI

    for (i=0; i < SGI_MAX_EPS_BEARERS_PER_USER; i++) {
      sgi_read_thread_args_t *args_p = malloc(sizeof(sgi_read_thread_args_t));
      args_p->sgi_data      = sgi_data_pP;
      args_p->socket_index  = i;

      if (pthread_create(&sgi_data_pP->capture_on_sgi_if_thread, NULL, &sgi_sock_raw_fw_2_gtpv1u_thread, (void *)args_p) < 0) {
        SGI_IF_ERROR("sgi_sock_raw_fw_2_gtpv1u_thread pthread_create: %s", strerror(errno));
        return -1;
      }
    }

#endif
    //#endif
  }

  while (sgi_data_pP->thread_started != SGI_MAX_EPS_BEARERS_PER_USER ) {
    usleep(1000);
  }

  /*SGI_IF_DEBUG("ARP RESOLVING ROUTER...\n");
  while (sgi_data_p->hw_address_of_router_captured == 0) {
      sgi_send_arp_request(sgi_data_p, "router.eur");
      sleep(2);
  }*/
  SGI_IF_DEBUG("Initializing FW_IP task interface: DONE\n");
  return 0;
}
