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

This program is free software; you can redistribute it and/or modify it
under the terms and conditions of the GNU General Public License,
version 2, as published by the Free Software Foundation.

This program is distributed in the hope 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
this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.

The full GNU General Public License is included in this distribution in
the file called "COPYING".

Contact Information
Openair Admin: openair_admin@eurecom.fr
Openair Tech : openair_tech@eurecom.fr
Forums       : http://forums.eurecom.fsr/openairinterface
Address      : EURECOM,
               Campus SophiaTech,
               450 Route des Chappes,
               CS 50193
               06904 Biot Sophia Antipolis cedex,
               FRANCE
*******************************************************************************/
/*! \file gtpv1u_eNB.c
 * \brief
 * \author Sebastien ROUX, Lionel GAUTHIER
 * \version 1.0
 * \company Eurecom
 * \email: lionel.gauthier@eurecom.fr
 */
#include <stdio.h>
#include <errno.h>

#include "mme_config.h"

#include "assertions.h"
#include "intertask_interface.h"
#include "timer.h"

#include "gtpv1u.h"
#include "NwGtpv1u.h"
#include "NwGtpv1uMsg.h"
#include "NwLog.h"
#include "gtpv1u_eNB_defs.h"
#include "gtpv1_u_messages_types.h"
#include "udp_eNB_task.h"
#include "UTIL/LOG/log.h"
#include "COMMON/platform_types.h"
#include "COMMON/platform_constants.h"


extern boolean_t pdcp_data_req(
    module_id_t enb_idP,
    module_id_t UE_id,
    frame_t frame,
    eNB_flag_t eNB_flag,
    rb_id_t rb_id,
    mui_t muiP,
    confirm_t confirmP, \
    sdu_size_t sdu_buffer_size,
    unsigned char* sdu_buffer,
    pdcp_transmission_mode_t mode);


static int gtpv1u_eNB_send_init_udp(
        uint16_t port_number);
NwGtpv1uRcT gtpv1u_eNB_log_request(
        NwGtpv1uLogMgrHandleT hLogMgr,
        NwU32T logLevel,
        NwCharT *file,
        NwU32T line,
        NwCharT *logStr);
NwGtpv1uRcT gtpv1u_eNB_send_udp_msg(
    NwGtpv1uUdpHandleT udpHandle,
    NwU8T *buffer,
    NwU32T buffer_len,
    NwU32T peerIpAddr,
    NwU16T peerPort);
NwGtpv1uRcT gtpv1u_eNB_process_stack_req(
    NwGtpv1uUlpHandleT hUlp,
    NwGtpv1uUlpApiT   *pUlpApi);
int data_recv_callback(
        uint16_t  portP,
        uint32_t  address,
        uint8_t  *buffer,
        uint32_t  length,
        void     *arg_p);
//int
//gtpv1u_create_tunnel_endpoint(
//    gtpv1u_data_t *gtpv1u_data_pP,
//    uint8_t        ue_idP,
//    uint8_t        rab_idP,
//    char          *sgw_ip_addr_pP,
//    uint16_t       portP);
static NwGtpv1uRcT gtpv1u_start_timer_wrapper(
    NwGtpv1uTimerMgrHandleT tmrMgrHandle,
    NwU32T                  timeoutSec,
    NwU32T                  timeoutUsec,
    NwU32T                  tmrType,
    void                   *timeoutArg,
    NwGtpv1uTimerHandleT   *hTmr);
static NwGtpv1uRcT gtpv1u_stop_timer_wrapper(
    NwGtpv1uTimerMgrHandleT tmrMgrHandle,
    NwGtpv1uTimerHandleT hTmr);
int
gtpv1u_initial_req(
    gtpv1u_data_t *gtpv1u_data_pP,
    teid_t         teidP,
    tcp_udp_port_t portP,
    uint32_t       address);
int
gtpv1u_new_data_req(
    uint8_t  enb_idP,
    uint8_t  ue_idP,
    uint8_t  rab_idP,
    uint8_t *buffer_pP,
    uint32_t buf_lenP);
static int
gtpv1u_create_s1u_tunnel(
        gtpv1u_enb_create_tunnel_req_t *create_tunnel_req_pP);
static int gtpv1u_delete_s1u_tunnel(gtpv1u_enb_delete_tunnel_req_t *req_pP);
static int gtpv1u_eNB_init(void);
void *gtpv1u_eNB_task(void *args);
static gtpv1u_data_t gtpv1u_data_g;

static int gtpv1u_eNB_send_init_udp(uint16_t port_number)
{
    // Create and alloc new message
    MessageDef *message_p;
    struct in_addr addr;

    message_p = itti_alloc_new_message(TASK_GTPV1_U, UDP_INIT);
    if (message_p == NULL) {
        return -1;
    }

    UDP_INIT(message_p).port = port_number;
    //LG UDP_INIT(message_p).address = "0.0.0.0"; //ANY address

    addr.s_addr = gtpv1u_data_g.enb_ip_address_for_S1u_S12_S4_up;
    UDP_INIT(message_p).address = inet_ntoa(addr);
    GTPU_DEBUG("Tx UDP_INIT IP addr %s\n", UDP_INIT(message_p).address);

    return itti_send_msg_to_task(TASK_UDP, INSTANCE_DEFAULT, message_p);
}

NwGtpv1uRcT gtpv1u_eNB_log_request(NwGtpv1uLogMgrHandleT hLogMgr,
                               NwU32T logLevel,
                               NwCharT *file,
                               NwU32T line,
                               NwCharT *logStr)
{
    GTPU_DEBUG("%s\n", logStr);
    return NW_GTPV1U_OK;
}

NwGtpv1uRcT gtpv1u_eNB_send_udp_msg(
    NwGtpv1uUdpHandleT udpHandle,
    NwU8T *buffer,
    NwU32T buffer_len,
    NwU32T peerIpAddr,
    NwU16T peerPort)
{
    // Create and alloc new message
    MessageDef     *message_p       = NULL;
    udp_data_req_t *udp_data_req_p  = NULL;

    message_p = itti_alloc_new_message(TASK_GTPV1_U, UDP_DATA_REQ);

    udp_data_req_p = &message_p->ittiMsg.udp_data_req;

    udp_data_req_p->peer_address  = peerIpAddr;
    udp_data_req_p->peer_port     = peerPort;
    udp_data_req_p->buffer        = buffer;
    udp_data_req_p->buffer_length = buffer_len;

    return itti_send_msg_to_task(TASK_UDP, INSTANCE_DEFAULT, message_p);
}


/* Callback called when a gtpv1u message arrived on UDP interface */
NwGtpv1uRcT gtpv1u_eNB_process_stack_req(
    NwGtpv1uUlpHandleT hUlp,
    NwGtpv1uUlpApiT   *pUlpApi)
{
    int                 result             = 0;
    teid_t              teid               = 0;
    hashtable_rc_t      hash_rc            = HASH_TABLE_KEY_NOT_EXISTS;
    gtpv1u_teid_data_t *gtpv1u_teid_data_p = NULL;

    switch(pUlpApi->apiType) {
            /* Here there are two type of messages handled:
             * - T-PDU
             * - END-MARKER
             */
        case NW_GTPV1U_ULP_API_RECV_TPDU: {
            uint8_t              buffer[4096];
            uint32_t             buffer_len;
            /* Nw-gptv1u stack has processed a PDU. we can schedule it to PDCP
             * for transmission.
             */
            teid = pUlpApi->apiInfo.recvMsgInfo.teid;
            if (NW_GTPV1U_OK != nwGtpv1uMsgGetTpdu(pUlpApi->apiInfo.recvMsgInfo.hMsg,
                                                   buffer, &buffer_len)) {
                GTPU_ERROR("Error while retrieving T-PDU");
            }

            //-----------------------
            // GTPV1U->PDCP mapping
            //-----------------------
            hash_rc = hashtable_get(gtpv1u_data_g.teid_mapping, teid, (void**)&gtpv1u_teid_data_p);
            if (hash_rc == HASH_TABLE_OK) {
                GTPU_DEBUG("Received T-PDU from gtpv1u stack teid  %u size %d -> enb module id %u ue module id %u rab id %u\n",
                		teid,
                		buffer_len,
                		gtpv1u_teid_data_p->enb_id,
                		gtpv1u_teid_data_p->ue_id,
                		gtpv1u_teid_data_p->eps_bearer_id);

                result = pdcp_data_req(
                		gtpv1u_teid_data_p->enb_id,
                		gtpv1u_teid_data_p->ue_id,
                        0, // frame TO DO
                        ENB_FLAG_YES,
                        gtpv1u_teid_data_p->eps_bearer_id,
                                   0, // mui
                                   0, // confirm
                                   buffer_len,
                                   buffer,
                                   PDCP_TRANSMISSION_MODE_DATA);
                AssertError (result == TRUE, return NW_GTPV1U_FAILURE ,"PDCP data request failed!\n");
            } else {
                GTPU_ERROR("Received T-PDU from gtpv1u stack teid %u unknown", teid, buffer_len);
            }
        }
        break;
        default: {
            GTPU_ERROR("Received undefined UlpApi (%02x) from gtpv1u stack!\n",
                       pUlpApi->apiType);
        }
    }
    return NW_GTPV1U_OK;
}


int data_recv_callback(uint16_t  portP,
                       uint32_t  address,
                       uint8_t  *buffer,
                       uint32_t  length,
                       void     *arg_p)
{
    gtpv1u_data_t        *gtpv1u_data_p;

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

    gtpv1u_data_p = (gtpv1u_data_t *)arg_p;

    return nwGtpv1uProcessUdpReq(gtpv1u_data_p->gtpv1u_stack,
           buffer,
           length,
           portP,
           address);
}

//int
//gtpv1u_create_tunnel_endpoint(
//    gtpv1u_data_t *gtpv1u_data_pP,
//    uint8_t        ue_idP,
//    uint8_t        rab_idP,
//    char          *sgw_ip_addr_pP,
//    uint16_t       portP)
//{
//    uint32_t                     teid;
//    uint8_t                      max_attempt = 100;
//    NwGtpv1uRcT                  rc          = NW_GTPV1U_FAILURE;
//    NwGtpv1uUlpApiT              ulp_req;
//    struct gtpv1u_ue_data_s     *new_ue_p    = NULL;
//    struct gtpv1u_bearer_s      *bearer_p    = NULL;
//    hashtable_rc_t               hash_rc     = HASH_TABLE_KEY_NOT_EXISTS;;
//
//    if (rab_idP > GTPV1U_MAX_BEARERS_PER_UE) {
//        LOG_E(GTPU, "Could not use rab_id %d > max %d\n",
//              rab_idP, GTPV1U_MAX_BEARERS_PER_UE);
//        return -1;
//    }
//
//
//    if ((hash_rc = hashtable_get(gtpv1u_data_pP->ue_mapping, (uint64_t)ue_idP, (void**)&new_ue_p)) == HASH_TABLE_OK) {
//        /* A context for this UE already exist in the tree, use it */
//        /* We check that the tunnel is not already configured */
//        if (new_ue_p->bearers[rab_idP].state != BEARER_DOWN) {
//            LOG_E(GTPU, "Cannot create new end-point over already existing tunnel\n");
//            return -1;
//        }
//    } else {
//        /* Context doesn't exist, create it */
//        if (rab_idP != 0) {
//            /* UE should first establish Default bearer before trying to setup
//             * additional bearers.
//             */
//            LOG_E(GTPU, "UE context is not known and rab_id != 0\n");
//            return -1;
//        }
//        new_ue_p = calloc(1, sizeof(struct gtpv1u_ue_data_s));
//        new_ue_p->ue_id = ue_idP;
//
//        hash_rc = hashtable_insert(gtpv1u_data_pP->ue_mapping, (uint64_t)ue_idP, new_ue_p);
//
//        if ((hash_rc != HASH_TABLE_OK) && (hash_rc != HASH_TABLE_INSERT_OVERWRITTEN_DATA)) {
//            LOG_E(GTPU, "Failed to insert new UE context\n");
//            free(new_ue_p);
//            return -1;
//        }
//    }
//
//    bearer_p = &new_ue_p->bearers[rab_idP];
//
//    /* Configure the bearer */
//    bearer_p->state       = BEARER_IN_CONFIG;
//    bearer_p->sgw_ip_addr = inet_addr(sgw_ip_addr_pP);
//    bearer_p->port        = portP;
//
//    /* Create the new stack api request */
//    memset(&ulp_req, 0, sizeof(NwGtpv1uUlpApiT));
//    ulp_req.apiType = NW_GTPV1U_ULP_API_CREATE_TUNNEL_ENDPOINT;
//
//    /* Try to create new tunnel-endpoint.
//     * If teid generated is already present in the stack, just peek another random
//     * teid. This could be ok for small number of tunnel but more errors could be
//     * thrown if we reached high number of tunnels.
//     * TODO: find a solution for teid
//     */
//    do {
//        /* Request for a new random TEID */
//        teid = gtpv1u_new_teid();
//        ulp_req.apiInfo.createTunnelEndPointInfo.teid = teid;
//
//        rc = nwGtpv1uProcessUlpReq(gtpv1u_data_pP->gtpv1u_stack, &ulp_req);
//
//        if (rc == NW_GTPV1U_OK) {
////             LOG_D(GTPU, "Successfully created new tunnel endpoint for teid 0x%x\n",
////                   teid);
//            bearer_p->teid_eNB = teid;
////             gtpv1u_initial_req(gtpv1u_data_pP, teid, GTPV1U_UDP_PORT,
////                                inet_addr("192.168.56.101"));
//            LOG_I(GTPU, "Created eNB tunnel endpoint %u for ue id %u, rab id %u\n", teid, ue_idP, rab_idP);
//            return 0;
//        } else {
//            LOG_W(GTPU, "Teid %u already in use... %s\n",
//                  teid, (max_attempt > 1) ? "Trying another one" : "Last chance");
//        }
//    } while(max_attempt-- && rc != NW_GTPV1U_OK);
//
//    bearer_p->state = BEARER_DOWN;
//    LOG_I(GTPU, "Failed to created eNB tunnel endpoint %u for ue id %u, rab id %u, bearer down\n", teid, ue_idP, rab_idP);
//
//    return -1;
//}


static NwGtpv1uRcT gtpv1u_start_timer_wrapper(
    NwGtpv1uTimerMgrHandleT tmrMgrHandle,
    NwU32T                  timeoutSec,
    NwU32T                  timeoutUsec,
    NwU32T                  tmrType,
    void                   *timeoutArg,
    NwGtpv1uTimerHandleT   *hTmr)
{

    NwGtpv1uRcT rc = NW_GTPV1U_OK;
    long        timer_id;

    if (tmrType == NW_GTPV1U_TMR_TYPE_ONE_SHOT) {
        timer_setup(timeoutSec,
                    timeoutUsec,
                    TASK_GTPV1_U,
                    INSTANCE_DEFAULT,
                    TIMER_ONE_SHOT,
                    timeoutArg,
                    &timer_id);
    } else {
        timer_setup(timeoutSec,
                    timeoutUsec,
                    TASK_GTPV1_U,
                    INSTANCE_DEFAULT,
                    TIMER_PERIODIC,
                    timeoutArg,
                    &timer_id);
    }

    return rc;
}


static NwGtpv1uRcT gtpv1u_stop_timer_wrapper(
    NwGtpv1uTimerMgrHandleT tmrMgrHandle,
    NwGtpv1uTimerHandleT hTmr)
{

    NwGtpv1uRcT rc = NW_GTPV1U_OK;

    return rc;
}


int
gtpv1u_initial_req(
    gtpv1u_data_t *gtpv1u_data_pP,
    teid_t         teidP,
    tcp_udp_port_t portP,
    uint32_t       address)
{
    NwGtpv1uUlpApiT ulp_req;
    NwGtpv1uRcT     rc = NW_GTPV1U_FAILURE;

    memset(&ulp_req, 0, sizeof(NwGtpv1uUlpApiT));

    ulp_req.apiType = NW_GTPV1U_ULP_API_INITIAL_REQ;
    ulp_req.apiInfo.initialReqInfo.teid     = teidP;
    ulp_req.apiInfo.initialReqInfo.peerPort = portP;
    ulp_req.apiInfo.initialReqInfo.peerIp   = address;

    rc = nwGtpv1uProcessUlpReq(gtpv1u_data_pP->gtpv1u_stack, &ulp_req);
    if (rc == NW_GTPV1U_OK) {
        LOG_D(GTPU, "Successfully sent initial req for teid %u\n", teidP);
    } else {
        LOG_W(GTPU, "Could not send initial req for teid %u\n", teidP);
    }
    return (rc == NW_GTPV1U_OK) ? 0 : -1;
}

int
gtpv1u_new_data_req(
    uint8_t  enb_idP,
    uint8_t  ue_idP,
    uint8_t  rab_idP,
    uint8_t *buffer_pP,
    uint32_t buf_lenP)
{

    NwGtpv1uUlpApiT          stack_req;
    NwGtpv1uRcT              rc            = NW_GTPV1U_FAILURE;
    struct gtpv1u_ue_data_s  ue;
    struct gtpv1u_ue_data_s *ue_inst_p     = NULL;
    struct gtpv1u_bearer_s  *bearer_p      = NULL;
    hashtable_rc_t           hash_rc       = HASH_TABLE_KEY_NOT_EXISTS;;
    gtpv1u_data_t           *gtpv1u_data_p = NULL;

    memset(&ue, 0, sizeof(struct gtpv1u_ue_data_s));
    ue.ue_id = ue_idP;

    AssertFatal(enb_idP >=0, "Bad parameter enb module id %u\n", enb_idP);
    AssertFatal((rab_idP - GTPV1U_BEARER_OFFSET)< GTPV1U_MAX_BEARERS_ID, "Bad parameter rab id %u\n", rab_idP);
    AssertFatal((rab_idP - GTPV1U_BEARER_OFFSET) >= 0 , "Bad parameter rab id %u\n", rab_idP);

    gtpv1u_data_p = &gtpv1u_data_g;
    /* Check that UE context is present in ue map. */
    hash_rc = hashtable_get(gtpv1u_data_p->ue_mapping, (uint64_t)ue_idP, (void**)&ue_inst_p);

    if (hash_rc ==  HASH_TABLE_KEY_NOT_EXISTS ) {
        LOG_E(GTPU, "[UE %d] Trying to send data on non-existing UE context\n", ue_idP);
        return -1;
    }

    bearer_p = &ue_inst_p->bearers[rab_idP - GTPV1U_BEARER_OFFSET];

    /* Ensure the bearer in ready.
     * TODO: handle the cases where the bearer is in HANDOVER state.
     * In such case packets should be placed in FIFO.
     */
    if (bearer_p->state != BEARER_UP) {
        LOG_W(GTPU, "Trying to send data over bearer with state(%u) != BEARER_UP\n",
              bearer_p->state);
#warning  LG: HACK WHILE WAITING FOR NAS, normally return -1
        if (bearer_p->state != BEARER_IN_CONFIG)
            return -1;
    }

    memset(&stack_req, 0, sizeof(NwGtpv1uUlpApiT));

    stack_req.apiType                   = NW_GTPV1U_ULP_API_SEND_TPDU;
    stack_req.apiInfo.sendtoInfo.teid   = bearer_p->teid_sgw;
    stack_req.apiInfo.sendtoInfo.ipAddr = bearer_p->sgw_ip_addr;

    LOG_W(GTPU, "TX TO TEID %u addr 0x%x\n",bearer_p->teid_sgw, bearer_p->sgw_ip_addr);
    rc = nwGtpv1uGpduMsgNew(gtpv1u_data_p->gtpv1u_stack,
            bearer_p->teid_sgw,
            NW_FALSE,
            gtpv1u_data_p->seq_num++,
            buffer_pP,
            buf_lenP,
            &(stack_req.apiInfo.sendtoInfo.hMsg));

    if (rc != NW_GTPV1U_OK) {
        LOG_E(GTPU, "nwGtpv1uGpduMsgNew failed: 0x%x\n", rc);
        return -1;
    }
    rc = nwGtpv1uProcessUlpReq(gtpv1u_data_p->gtpv1u_stack,
                               &stack_req);
    if (rc != NW_GTPV1U_OK) {
        LOG_E(GTPU, "nwGtpv1uProcessUlpReq failed: 0x%x\n", rc);
        return -1;
    }
    rc = nwGtpv1uMsgDelete(gtpv1u_data_p->gtpv1u_stack,
                           stack_req.apiInfo.sendtoInfo.hMsg);
    if (rc != NW_GTPV1U_OK) {
        LOG_E(GTPU, "nwGtpv1uMsgDelete failed: 0x%x\n", rc);
        return -1;
    }
    LOG_E(GTPU, "%s() return code OK\n", __FUNCTION__);
    return 0;
}

static int gtpv1u_create_s1u_tunnel(gtpv1u_enb_create_tunnel_req_t *create_tunnel_req_pP)
{
    /* Create a new nw-gtpv1-u stack req using API */
    NwGtpv1uUlpApiT          stack_req;
    NwGtpv1uRcT              rc                   = NW_GTPV1U_FAILURE;
    /* Local tunnel end-point identifier */
    teid_t                   s1u_teid             = 0;
    gtpv1u_teid_data_t      *gtpv1u_teid_data_p   = NULL;
    gtpv1u_ue_data_t        *gtpv1u_ue_data_p     = NULL;
    MessageDef              *message_p            = NULL;
    hashtable_rc_t           hash_rc              = HASH_TABLE_KEY_NOT_EXISTS;
    int                      i;
    ebi_t                    eps_bearer_id        = 0;
    int                      ipv4_addr            = 0;
    int                      ip_offset            = 0;
    in_addr_t                in_addr;
    int                      addrs_length_in_bytes= 0;

    message_p = itti_alloc_new_message(TASK_GTPV1_U, GTPV1U_ENB_CREATE_TUNNEL_RESP);
    GTPV1U_ENB_CREATE_TUNNEL_RESP(message_p).ue_index    = create_tunnel_req_pP->ue_index;
    GTPV1U_ENB_CREATE_TUNNEL_RESP(message_p).status      = 0;
    GTPV1U_ENB_CREATE_TUNNEL_RESP(message_p).num_tunnels = 0;

    for (i = 0; i < create_tunnel_req_pP->num_tunnels; i++) {
        ip_offset               = 0;
        eps_bearer_id = create_tunnel_req_pP->eps_bearer_id[i];
        GTPU_DEBUG("Rx GTPV1U_ENB_CREATE_TUNNEL_REQ ue_index %u eps bearer id %u\n",
            create_tunnel_req_pP->ue_index, eps_bearer_id);
        memset(&stack_req, 0, sizeof(NwGtpv1uUlpApiT));

        stack_req.apiType = NW_GTPV1U_ULP_API_CREATE_TUNNEL_ENDPOINT;

        do {
            s1u_teid = gtpv1u_new_teid();
            GTPU_DEBUG("gtpv1u_create_s1u_tunnel() 0x%x %u(dec)\n", s1u_teid, s1u_teid);
            stack_req.apiInfo.createTunnelEndPointInfo.teid          = s1u_teid;
            stack_req.apiInfo.createTunnelEndPointInfo.hUlpSession   = 0;
            stack_req.apiInfo.createTunnelEndPointInfo.hStackSession = 0;

            rc = nwGtpv1uProcessUlpReq(gtpv1u_data_g.gtpv1u_stack, &stack_req);
            GTPU_DEBUG(".\n");
        } while (rc != NW_GTPV1U_OK);

        //-----------------------
        // PDCP->GTPV1U mapping
        //-----------------------
        hash_rc = hashtable_get(gtpv1u_data_g.ue_mapping, create_tunnel_req_pP->ue_index, (void **)&gtpv1u_ue_data_p);
        if ((hash_rc == HASH_TABLE_KEY_NOT_EXISTS) || (hash_rc == HASH_TABLE_OK)) {

            if (hash_rc == HASH_TABLE_KEY_NOT_EXISTS) {
                gtpv1u_ue_data_p = calloc (1, sizeof(gtpv1u_ue_data_t));
                hash_rc = hashtable_insert(gtpv1u_data_g.ue_mapping, create_tunnel_req_pP->ue_index, gtpv1u_ue_data_p);
                AssertFatal(hash_rc == HASH_TABLE_OK, "Error inserting ue_mapping in GTPV1U hashtable");
            }
            gtpv1u_ue_data_p->ue_id       = create_tunnel_req_pP->ue_index;
                gtpv1u_ue_data_p->instance_id = 0; // TO DO
              memcpy(&GTPV1U_ENB_CREATE_TUNNEL_RESP(message_p).enb_addr.buffer,
                      &gtpv1u_data_g.enb_ip_address_for_S1u_S12_S4_up,
                      sizeof (in_addr_t));
              GTPV1U_ENB_CREATE_TUNNEL_RESP(message_p).enb_addr.length = sizeof (in_addr_t);

              addrs_length_in_bytes = create_tunnel_req_pP->sgw_addr[i].length / 8;
              AssertFatal((addrs_length_in_bytes == 4) ||
                      (addrs_length_in_bytes == 16) ||
                      (addrs_length_in_bytes == 20),
                      "Bad transport layer address length %d (bits) %d (bytes)",
                      create_tunnel_req_pP->sgw_addr[i].length, addrs_length_in_bytes);

            if ((addrs_length_in_bytes == 4) ||
                (addrs_length_in_bytes == 20)) {
                in_addr = *((in_addr_t*)create_tunnel_req_pP->sgw_addr[i].buffer);
                ip_offset = 4;
                gtpv1u_ue_data_p->bearers[eps_bearer_id - GTPV1U_BEARER_OFFSET].sgw_ip_addr = in_addr;
            }
            if ((addrs_length_in_bytes == 16) ||
                (addrs_length_in_bytes == 20)) {
                memcpy(gtpv1u_ue_data_p->bearers[eps_bearer_id - GTPV1U_BEARER_OFFSET].sgw_ip6_addr.s6_addr,
                        &create_tunnel_req_pP->sgw_addr[i].buffer[ip_offset],
                        16);
            }
            gtpv1u_ue_data_p->bearers[eps_bearer_id - GTPV1U_BEARER_OFFSET].state    = BEARER_IN_CONFIG;
            gtpv1u_ue_data_p->bearers[eps_bearer_id - GTPV1U_BEARER_OFFSET].teid_eNB = s1u_teid;
            gtpv1u_ue_data_p->bearers[eps_bearer_id - GTPV1U_BEARER_OFFSET].teid_sgw = create_tunnel_req_pP->sgw_S1u_teid[i];
            GTPV1U_ENB_CREATE_TUNNEL_RESP(message_p).enb_S1u_teid[i] = s1u_teid;

        } else {
            GTPV1U_ENB_CREATE_TUNNEL_RESP(message_p).enb_S1u_teid[i] = 0;
            GTPV1U_ENB_CREATE_TUNNEL_RESP(message_p).status         = 0xFF;
        }
        GTPV1U_ENB_CREATE_TUNNEL_RESP(message_p).eps_bearer_id[i] = eps_bearer_id;
        GTPV1U_ENB_CREATE_TUNNEL_RESP(message_p).num_tunnels      += 1;

        //-----------------------
        // GTPV1U->PDCP mapping
        //-----------------------
        hash_rc = hashtable_get(gtpv1u_data_g.teid_mapping, s1u_teid, (void**)&gtpv1u_teid_data_p);
        if (hash_rc == HASH_TABLE_KEY_NOT_EXISTS) {
            gtpv1u_teid_data_p = calloc (1, sizeof(gtpv1u_teid_data_t));
            gtpv1u_teid_data_p->enb_id        = 0; // TO DO
            gtpv1u_teid_data_p->ue_id         = create_tunnel_req_pP->ue_index;
            gtpv1u_teid_data_p->eps_bearer_id = eps_bearer_id;
            hash_rc = hashtable_insert(gtpv1u_data_g.teid_mapping, s1u_teid, gtpv1u_teid_data_p);
            AssertFatal(hash_rc == HASH_TABLE_OK, "Error inserting teid mapping in GTPV1U hashtable");
        } else {
            GTPV1U_ENB_CREATE_TUNNEL_RESP(message_p).enb_S1u_teid[i] = 0;
            GTPV1U_ENB_CREATE_TUNNEL_RESP(message_p).status         = 0xFF;
        }
    }
    GTPU_DEBUG("Tx GTPV1U_ENB_CREATE_TUNNEL_RESP ue_index %u status %d\n",
        create_tunnel_req_pP->ue_index,
        GTPV1U_ENB_CREATE_TUNNEL_RESP(message_p).status);
    return itti_send_msg_to_task(TASK_RRC_ENB, INSTANCE_DEFAULT, message_p);
}



static int gtpv1u_delete_s1u_tunnel(gtpv1u_enb_delete_tunnel_req_t *req_pP)
{
    NwGtpv1uUlpApiT          stack_req;
    NwGtpv1uRcT              rc                   = NW_GTPV1U_FAILURE;
    MessageDef              *message_p = NULL;
    gtpv1u_ue_data_t        *gtpv1u_ue_data_p     = NULL;
    hashtable_rc_t           hash_rc              = HASH_TABLE_KEY_NOT_EXISTS;

    GTPU_DEBUG("Rx GTPV1U_ENB_DELETE_TUNNEL user index %u eNB S1U teid %u eps bearer id %u\n", req_pP->ue_index, req_pP->enb_S1u_teid, req_pP->eps_bearer_id);
    message_p = itti_alloc_new_message(TASK_GTPV1_U, GTPV1U_ENB_DELETE_TUNNEL_RESP);

    GTPV1U_ENB_DELETE_TUNNEL_RESP(message_p).enb_S1u_teid = req_pP->enb_S1u_teid;
    GTPV1U_ENB_DELETE_TUNNEL_RESP(message_p).ue_index     = req_pP->ue_index;
    GTPV1U_ENB_CREATE_TUNNEL_RESP(message_p).status       = 0;

    {
        memset(&stack_req, 0, sizeof(NwGtpv1uUlpApiT));
        stack_req.apiType = NW_GTPV1U_ULP_API_DESTROY_TUNNEL_ENDPOINT;
        GTPU_DEBUG("gtpv1u_delte_s1u_tunnel() %u\n", req_pP->enb_S1u_teid);
        stack_req.apiInfo.createTunnelEndPointInfo.teid          = req_pP->enb_S1u_teid;
        stack_req.apiInfo.createTunnelEndPointInfo.hUlpSession   = 0;
        stack_req.apiInfo.createTunnelEndPointInfo.hStackSession = 0;

        rc = nwGtpv1uProcessUlpReq(gtpv1u_data_g.gtpv1u_stack, &stack_req);
        GTPU_DEBUG(".\n");
    }
    if (rc != NW_GTPV1U_OK) {
        GTPV1U_ENB_CREATE_TUNNEL_RESP(message_p).status       = 0xFF;
        GTPU_ERROR("NW_GTPV1U_ULP_API_DESTROY_TUNNEL_ENDPOINT failed");
    }

    //-----------------------
    // PDCP->GTPV1U mapping
    //-----------------------
    hash_rc = hashtable_get(gtpv1u_data_g.ue_mapping, req_pP->ue_index, (void**)&gtpv1u_ue_data_p);
    if (hash_rc == HASH_TABLE_OK) {
        gtpv1u_ue_data_p->bearers[req_pP->eps_bearer_id - GTPV1U_BEARER_OFFSET].state       = BEARER_DOWN;
        gtpv1u_ue_data_p->bearers[req_pP->eps_bearer_id - GTPV1U_BEARER_OFFSET].teid_eNB    = 0;
        gtpv1u_ue_data_p->bearers[req_pP->eps_bearer_id - GTPV1U_BEARER_OFFSET].teid_sgw    = 0;
        gtpv1u_ue_data_p->bearers[req_pP->eps_bearer_id - GTPV1U_BEARER_OFFSET].sgw_ip_addr = 0;
        gtpv1u_ue_data_p->num_bearers -= 1;

        if (gtpv1u_ue_data_p->num_bearers == 0) {
            hash_rc = hashtable_remove(gtpv1u_data_g.ue_mapping, req_pP->ue_index);
            GTPU_DEBUG("Removed user index %u,no more bearers configured\n", req_pP->ue_index);
        }
    }// else silently do nothing

    //-----------------------
    // GTPV1U->PDCP mapping
    //-----------------------
    hash_rc = hashtable_remove(gtpv1u_data_g.teid_mapping, req_pP->enb_S1u_teid);
    if (hash_rc != HASH_TABLE_OK) {
        GTPU_DEBUG("Removed user index %u , enb S1U teid %u not found\n", req_pP->ue_index, req_pP->enb_S1u_teid);
    }

    LOG_D(GTPU, "Tx GTPV1U_ENB_DELETE_TUNNEL_RESP user index %u eNB S1U teid %u status %u\n",
        GTPV1U_ENB_DELETE_TUNNEL_RESP(message_p).ue_index,
        GTPV1U_ENB_DELETE_TUNNEL_RESP(message_p).enb_S1u_teid,
        GTPV1U_ENB_CREATE_TUNNEL_RESP(message_p).status);
    return itti_send_msg_to_task(TASK_RRC_ENB, INSTANCE_DEFAULT, message_p);
}

static int gtpv1u_eNB_init(void)
{
  int                     ret;
  NwGtpv1uRcT             rc = NW_GTPV1U_FAILURE;
  NwGtpv1uUlpEntityT      ulp;
  NwGtpv1uUdpEntityT      udp;
  NwGtpv1uLogMgrEntityT   log;
  NwGtpv1uTimerMgrEntityT tmr;
  Enb_properties_t       *enb_properties_p  = NULL;

    enb_properties_p = enb_config_get()->properties[0];


    memset(&gtpv1u_data_g, 0, sizeof(gtpv1u_data_g));

    GTPU_INFO("Initializing GTPU stack %p\n",&gtpv1u_data_g);
    //gtpv1u_data_g.gtpv1u_stack;
    /* Initialize UE hashtable */
    gtpv1u_data_g.ue_mapping      = hashtable_create (32, NULL, NULL);
    AssertFatal(gtpv1u_data_g.ue_mapping != NULL, " ERROR Initializing TASK_GTPV1_U task interface: in hashtable_create returned %p\n", gtpv1u_data_g.ue_mapping);
    gtpv1u_data_g.teid_mapping    = hashtable_create (256, NULL, NULL);
    AssertFatal(gtpv1u_data_g.teid_mapping != NULL, " ERROR Initializing TASK_GTPV1_U task interface: in hashtable_create\n");
    gtpv1u_data_g.enb_ip_address_for_S1u_S12_S4_up         = enb_properties_p->enb_ipv4_address_for_S1U;
    gtpv1u_data_g.ip_addr         = NULL;
    gtpv1u_data_g.enb_port_for_S1u_S12_S4_up = GTPV1U_UDP_PORT;
    //gtpv1u_data_g.udp_data;
    gtpv1u_data_g.seq_num         = 0;
    gtpv1u_data_g.restart_counter = 0;

    /* Initializing GTPv1-U stack */
    if ((rc = nwGtpv1uInitialize(&gtpv1u_data_g.gtpv1u_stack)) != NW_GTPV1U_OK) {
        LOG_E(GTPU, "Failed to setup nwGtpv1u stack %x\n", rc);
        return -1;
    }

    if ((rc = nwGtpv1uSetLogLevel(gtpv1u_data_g.gtpv1u_stack,
                                  NW_LOG_LEVEL_DEBG)) != NW_GTPV1U_OK) {
        LOG_E(GTPU, "Failed to setup loglevel for stack %x\n", rc);
        return -1;
    }


    /* Set the ULP API callback. Called once message have been processed by the
     * nw-gtpv1u stack.
     */
    ulp.ulpReqCallback = gtpv1u_eNB_process_stack_req;

    if ((rc = nwGtpv1uSetUlpEntity(gtpv1u_data_g.gtpv1u_stack, &ulp)) != NW_GTPV1U_OK) {
        LOG_E(GTPU, "nwGtpv1uSetUlpEntity: %x", rc);
        return -1;
    }

    /* nw-gtpv1u stack requires an udp callback to send data over UDP.
     * We provide a wrapper to UDP task.
     */
    udp.udpDataReqCallback = gtpv1u_eNB_send_udp_msg;

    if ((rc = nwGtpv1uSetUdpEntity(gtpv1u_data_g.gtpv1u_stack, &udp)) != NW_GTPV1U_OK) {
        GTPU_ERROR("nwGtpv1uSetUdpEntity: %x", rc);
        return -1;
    }

    log.logReqCallback = gtpv1u_eNB_log_request;

    if ((rc = nwGtpv1uSetLogMgrEntity(gtpv1u_data_g.gtpv1u_stack, &log)) != NW_GTPV1U_OK) {
        LOG_E(GTPU, "nwGtpv1uSetLogMgrEntity: %x", rc);
        return -1;
    }

    /* Timer interface is more complicated as both wrappers doesn't send a message
     * to the timer task but call the timer API functions start/stop timer.
     */
    tmr.tmrMgrHandle     = 0;
    tmr.tmrStartCallback = gtpv1u_start_timer_wrapper;
    tmr.tmrStopCallback  = gtpv1u_stop_timer_wrapper;

    if ((rc = nwGtpv1uSetTimerMgrEntity(gtpv1u_data_g.gtpv1u_stack, &tmr)) != NW_GTPV1U_OK) {
        LOG_E(GTPU, "nwGtpv1uSetTimerMgrEntity: %x", rc);
        return -1;
    }

/*    if (itti_create_task(TASK_GTPV1_U, &gtpv1u_eNB_thread, NULL) < 0) {
        LOG_E(GTPU, "gtpv1u phtread_create: %s", strerror(errno));
        return -1;
    }
*/
    ret = gtpv1u_eNB_send_init_udp(gtpv1u_data_g.enb_port_for_S1u_S12_S4_up);
    if (ret < 0) {
        return ret;
    }

    LOG_D(GTPU, "Initializing GTPV1U interface for eNB: DONE\n");
    return 0;
}


void *gtpv1u_eNB_task(void *args)
{
    int rc = 0;
    rc = gtpv1u_eNB_init();
    AssertFatal(rc == 0, "gtpv1u_eNB_init Failed");
    itti_mark_task_ready(TASK_GTPV1_U);
    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 = NULL;
        itti_receive_msg(TASK_GTPV1_U, &received_message_p);
        DevAssert(received_message_p != NULL);

        switch (ITTI_MSG_ID(received_message_p))
        {
            case GTPV1U_ENB_CREATE_TUNNEL_REQ: {
                gtpv1u_create_s1u_tunnel(&received_message_p->ittiMsg.Gtpv1uCreateTunnelReq);
            }
            break;

            case GTPV1U_ENB_DELETE_TUNNEL_REQ: {
                gtpv1u_delete_s1u_tunnel(&received_message_p->ittiMsg.Gtpv1uDeleteTunnelReq);
            }
            break;

            // DATA COMING FROM UDP
            case UDP_DATA_IND: {
                udp_data_ind_t *udp_data_ind_p;
                udp_data_ind_p = &received_message_p->ittiMsg.udp_data_ind;
                nwGtpv1uProcessUdpReq(gtpv1u_data_g.gtpv1u_stack,
                                      udp_data_ind_p->buffer,
                                      udp_data_ind_p->buffer_length,
                                      udp_data_ind_p->peer_port,
                                      udp_data_ind_p->peer_address);
                free(udp_data_ind_p->buffer);
            }
            break;

            // DATA TO BE SENT TO UDP
            case GTPV1U_ENB_TUNNEL_DATA_REQ: {
            	gtpv1u_enb_tunnel_data_req_t *data_req_p           = NULL;
                NwGtpv1uUlpApiT          stack_req;
                NwGtpv1uRcT                   rc                   = NW_GTPV1U_FAILURE;
                hashtable_rc_t                hash_rc              = HASH_TABLE_KEY_NOT_EXISTS;
                gtpv1u_ue_data_t             *gtpv1u_ue_data_p     = NULL;
                teid_t                        enb_s1u_teid         = 0;
                teid_t                        sgw_s1u_teid         = 0;

                data_req_p = &GTPV1U_ENB_TUNNEL_DATA_REQ(received_message_p);
                //ipv4_send_data(ipv4_data_p->sd, data_ind_p->buffer, data_ind_p->length);

                memset(&stack_req, 0, sizeof(NwGtpv1uUlpApiT));

                hash_rc = hashtable_get(gtpv1u_data_g.ue_mapping, (uint64_t)data_req_p->ue_index, (void**)&gtpv1u_ue_data_p);

                if (hash_rc == HASH_TABLE_KEY_NOT_EXISTS) {
                    GTPU_ERROR("nwGtpv1uProcessUlpReq failed: while getting ue_index %u in hashtable ue_mapping\n", data_req_p->ue_index);
                } else {
                    if ((data_req_p->rab_id >= GTPV1U_BEARER_OFFSET) && (data_req_p->rab_id <= max_val_DRB_Identity)) {
                        enb_s1u_teid                        = gtpv1u_ue_data_p->bearers[data_req_p->rab_id - GTPV1U_BEARER_OFFSET].teid_eNB;
                        sgw_s1u_teid                        = gtpv1u_ue_data_p->bearers[data_req_p->rab_id - GTPV1U_BEARER_OFFSET].teid_sgw;
                        stack_req.apiType                   = NW_GTPV1U_ULP_API_SEND_TPDU;
                        stack_req.apiInfo.sendtoInfo.teid   = sgw_s1u_teid;
                        stack_req.apiInfo.sendtoInfo.ipAddr = gtpv1u_ue_data_p->bearers[data_req_p->rab_id - GTPV1U_BEARER_OFFSET].sgw_ip_addr;

                        rc = nwGtpv1uGpduMsgNew(
                                 gtpv1u_data_g.gtpv1u_stack,
                                 sgw_s1u_teid,
                                 NW_FALSE,
                                 gtpv1u_data_g.seq_num++,
                                 data_req_p->buffer,
                                 data_req_p->length,
                                 &(stack_req.apiInfo.sendtoInfo.hMsg));

                    if (rc != NW_GTPV1U_OK) {
                        GTPU_ERROR("nwGtpv1uGpduMsgNew failed: 0x%x\n", rc);
                    } else {
                        rc = nwGtpv1uProcessUlpReq(gtpv1u_data_g.gtpv1u_stack, &stack_req);
                        if (rc != NW_GTPV1U_OK) {
                            GTPU_ERROR("nwGtpv1uProcessUlpReq failed: 0x%x\n", rc);
                        }
                        rc = nwGtpv1uMsgDelete(gtpv1u_data_g.gtpv1u_stack,
                                    stack_req.apiInfo.sendtoInfo.hMsg);
                        if (rc != NW_GTPV1U_OK) {
                            GTPU_ERROR("nwGtpv1uMsgDelete failed: 0x%x\n", rc);
                        	}
                        }
                    }
                }
                /* Buffer is no longer needed, free it */
                free(data_req_p->buffer);
            }
            break;

            case TERMINATE_MESSAGE: {
                if (gtpv1u_data_g.ue_mapping != NULL) {
                    hashtable_destroy (gtpv1u_data_g.ue_mapping);
                }
                if (gtpv1u_data_g.teid_mapping != NULL) {
                    hashtable_destroy (gtpv1u_data_g.teid_mapping);
                }
                itti_exit_task();
            } break;

            case TIMER_HAS_EXPIRED:
                nwGtpv1uProcessTimeout(&received_message_p->ittiMsg.timer_has_expired.arg);
                break;
            default: {
                GTPU_ERROR("Unkwnon message ID %d:%s\n",
                           ITTI_MSG_ID(received_message_p),
                           ITTI_MSG_NAME(received_message_p));
            }
            break;
        }
        rc = itti_free(ITTI_MSG_ORIGIN_ID(received_message_p), received_message_p);
        AssertFatal(rc == EXIT_SUCCESS, "Failed to free memory (%d)!\n", rc);
        received_message_p = NULL;
    }
    return NULL;
}