/*----------------------------------------------------------------------------*
 *                                                                            *
 *                             n w - g t p v 2 u                              *
 *    G P R S    T u n n e l i n g    P r o t o c o l   v 2 u   S t a c k     *
 *                                                                            *
 *                                                                            *
 * Copyright (c) 2010-2011 Amit Chawre                                        *
 * All rights reserved.                                                       *
 *                                                                            *
 * Redistribution and use in source and binary forms, with or without         *
 * modification, are permitted provided that the following conditions         *
 * are met:                                                                   *
 *                                                                            *
 * 1. Redistributions of source code must retain the above copyright          *
 *    notice, this list of conditions and the following disclaimer.           *
 * 2. Redistributions in binary form must reproduce the above copyright       *
 *    notice, this list of conditions and the following disclaimer in the     *
 *    documentation and/or other materials provided with the distribution.    *
 * 3. The name of the author may not be used to endorse or promote products   *
 *    derived from this software without specific prior written permission.   *
 *                                                                            *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR       *
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES  *
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.    *
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,           *
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT   *
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,  *
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY      *
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT        *
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF   *
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.          *
 *----------------------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "NwTypes.h"
#include "NwUtils.h"
#include "NwGtpv1uError.h"
#include "NwGtpv1uPrivate.h"
#include "NwGtpv1uTunnelEndPoint.h"
#include "NwGtpv1uTrxn.h"
#include "NwGtpv1u.h"
#include "NwGtpv1uIe.h"
#include "NwGtpv1uLog.h"

#include "gtpv1u.h"
#if defined(ENB_MODE)
#include "UTIL/LOG/log.h"
#endif

#ifdef __cplusplus
extern "C" {
#endif

/*--------------------------------------------------------------------------*
 *                    P R I V A T E    F U N C T I O N S                    *
 *--------------------------------------------------------------------------*/

#define NW_GTPV1U_EPC_MIN_HEADER_SIZE                                  (8)


void
nwGtpv1uDisplayBanner(void)
{
    printf(" *----------------------------------------------------------------------------*\n");
    printf(" *                                                                            *\n");
    printf(" *                             n w - g t p v 2 u                              *\n");
    printf(" *    G P R S    T u n n e l i n g    P r o t o c o l   v 2 u   S t a c k     *\n");
    printf(" *                                                                            *\n");
    printf(" *                                                                            *\n");
    printf(" * Copyright (c) 2010-2011 Amit Chawre                                        *\n");
    printf(" * All rights reserved.                                                       *\n");
    printf(" *                                                                            *\n");
    printf(" * Redistribution and use in source and binary forms, with or without         *\n");
    printf(" * modification, are permitted provided that the following conditions         *\n");
    printf(" * are met:                                                                   *\n");
    printf(" *                                                                            *\n");
    printf(" * 1. Redistributions of source code must retain the above copyright          *\n");
    printf(" *    notice, this list of conditions and the following disclaimer.           *\n");
    printf(" * 2. Redistributions in binary form must reproduce the above copyright       *\n");
    printf(" *    notice, this list of conditions and the following disclaimer in the     *\n");
    printf(" *    documentation and/or other materials provided with the distribution.    *\n");
    printf(" * 3. The name of the author may not be used to endorse or promote products   *\n");
    printf(" *    derived from this software without specific prior written permission.   *\n");
    printf(" *                                                                            *\n");
    printf(" * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR       *\n");
    printf(" * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES  *\n");
    printf(" * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.    *\n");
    printf(" * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,           *\n");
    printf(" * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT   *\n");
    printf(" * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,  *\n");
    printf(" * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY      *\n");
    printf(" * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT        *\n");
    printf(" * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF   *\n");
    printf(" * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.          *\n");
    printf(" *----------------------------------------------------------------------------*\n\n");

}
/*---------------------------------------------------------------------------
 * RBTree Search Functions
 *--------------------------------------------------------------------------*/

static inline NwS32T
nwGtpv1uCompareTeid(struct NwGtpv1uTunnelEndPoint *a,
                    struct NwGtpv1uTunnelEndPoint *b);

static inline NwS32T
nwGtpv1uCompareSeqNum(struct NwGtpv1uTrxn *a, struct NwGtpv1uTrxn *b);

RB_PROTOTYPE(NwGtpv1uOutstandingTxSeqNumTrxnMap, NwGtpv1uTrxn,
             outstandingTxSeqNumMapRbtNode, nwGtpv1uCompareSeqNum)
RB_PROTOTYPE(NwGtpv1uOutstandingRxSeqNumTrxnMap, NwGtpv1uTrxn,
             outstandingRxSeqNumMapRbtNode, nwGtpv1uCompareSeqNum)
RB_PROTOTYPE(NwGtpv1uTunnelEndPointTMap, NwGtpv1uTunnelEndPoint,
             sessionMapRbtNode, nwGtpv1uCompareTeid)
RB_PROTOTYPE(NwGtpv1uTunnelEndPointIdentifierMap, NwGtpv1uTunnelEndPoint,
             sessionMapRbtNode, nwGtpv1uCompareTeid)

RB_GENERATE(NwGtpv1uOutstandingTxSeqNumTrxnMap, NwGtpv1uTrxn,
            outstandingTxSeqNumMapRbtNode, nwGtpv1uCompareSeqNum)
RB_GENERATE(NwGtpv1uOutstandingRxSeqNumTrxnMap, NwGtpv1uTrxn,
            outstandingRxSeqNumMapRbtNode, nwGtpv1uCompareSeqNum)
RB_GENERATE(NwGtpv1uTunnelEndPointTMap, NwGtpv1uTunnelEndPoint,
            sessionMapRbtNode, nwGtpv1uCompareTeid)
RB_GENERATE(NwGtpv1uTunnelEndPointIdentifierMap, NwGtpv1uTunnelEndPoint,
            sessionMapRbtNode, nwGtpv1uCompareTeid)

/**
  Comparator funtion for comparing two sessions.

  @param[in] a: Pointer to session a.
  @param[in] b: Pointer to session b.
  @return  An integer greater than, equal to or less than zero according to whether the
  object pointed to by a is greater than, equal to or less than the object pointed to by b.
 */

static inline NwS32T
nwGtpv1uCompareTeid(struct NwGtpv1uTunnelEndPoint *a,
                    struct NwGtpv1uTunnelEndPoint *b)
{
    if(a->teid > b->teid) {
        return 1;
    }
    if(a->teid < b->teid) {
        return -1;
    }
    return 0;
}

/**
  Comparator funtion for comparing two sequence number transactions.

  @param[in] a: Pointer to session a.
  @param[in] b: Pointer to session b.
  @return  An integer greater than, equal to or less than zero according to whether the
  object pointed to by a is greater than, equal to or less than the object pointed to by b.
 */

static inline NwS32T
nwGtpv1uCompareSeqNum(struct NwGtpv1uTrxn *a, struct NwGtpv1uTrxn *b)
{
    if(a->seqNum > b->seqNum) {
        return 1;
    }
    if(a->seqNum < b->seqNum) {
        return -1;
    }
    if(a->peerIp > b->peerIp) {
        return 1;
    }
    if(a->peerIp < b->peerIp) {
        return -1;
    }
    return 0;
}

/**
  Create and Send GTPU message.

  @param[in] thiz: Stack handle
  @return NW_GTPV1U_OK on success.
 */

static NwGtpv1uRcT
nwGtpv1uCreateAndSendMsg( NwGtpv1uStackT *thiz, NwU32T peerIp, NwU16T peerPort,
                          NwGtpv1uMsgT *pMsg)
{
    NwGtpv1uRcT rc = NW_GTPV1U_FAILURE;
    NwU8T *msgHdr;

    NW_ASSERT(thiz);
    NW_ASSERT(pMsg);

    NW_ENTER(thiz);

    msgHdr = pMsg->msgBuf;
    NW_ASSERT(msgHdr != NULL);

    *(msgHdr++)         = (pMsg->version << 5)            |
                          (pMsg->protocolType << 4)       |
                          (pMsg->extHdrFlag << 2)         |
                          (pMsg->seqNumFlag << 1)         |
                          (pMsg->npduNumFlag);

    *(msgHdr++)         = (pMsg->msgType);
    *((NwU16T *) msgHdr) = htons(pMsg->msgLen);
    msgHdr += 2;

    *((NwU32T *) msgHdr) = htonl(pMsg->teid);
    msgHdr += 4;
    GTPU_DEBUG("nwGtpv1uCreateAndSendMsg to teid %u length %d\n", pMsg->teid, pMsg->msgLen);

    if(pMsg->seqNumFlag || pMsg->extHdrFlag || pMsg->npduNumFlag) {
        *((NwU16T *) msgHdr) = (pMsg->seqNumFlag ? htons(pMsg->seqNum) : 0x0000);
        msgHdr += 2;

        *((NwU8T *) msgHdr) = (pMsg->npduNumFlag ? htons(pMsg->npduNum) : 0x00);
        msgHdr++;

        *((NwU8T *) msgHdr) = (pMsg->extHdrFlag ? htons(pMsg->nextExtHdrType) : 0x00);
        msgHdr++;
    }

    rc = thiz->udp.udpDataReqCallback(thiz->udp.hUdp,
                                      pMsg->msgBuf,
                                      pMsg->msgLen + NW_GTPV1U_EPC_MIN_HEADER_SIZE,
                                      peerIp,
                                      peerPort);

    NW_LEAVE(thiz);
    return rc;
}

/**
 * Send GTPv1u Message Indication to ULP entity.
 *
 * @param[in] hGtpuStackHandle : Stack handle
 * @return NW_GTPV1U_OK on success.
 */

static NwGtpv1uRcT
nwGtpv1uSendUlpMessageIndication( NW_IN NwGtpv1uStackT *thiz,
                                  NW_IN NwU32T  hUlpTrxn,
                                  NW_IN NwU32T  apiType,
                                  NW_IN NwU32T  msgType,
                                  NW_IN NwU32T  peerIp,
                                  NW_IN NwU16T  peerPort,
                                  NW_IN NwU8T  *pMsgBuf,
                                  NW_IN NwU16T  msgLength)
{
    NwGtpv1uRcT rc = NW_GTPV1U_FAILURE;
    NwGtpv1uUlpApiT ulpApi;

    NW_ENTER(thiz);

    ulpApi.apiType                        = apiType;
    ulpApi.apiInfo.recvMsgInfo.msgType    = msgType;
    ulpApi.apiInfo.recvMsgInfo.hUlpTrxn   = hUlpTrxn;
    ulpApi.apiInfo.recvMsgInfo.peerIp     = peerIp;
    ulpApi.apiInfo.recvMsgInfo.peerPort   = peerPort;

    if(pMsgBuf && msgLength) {
        rc = nwGtpv1uMsgFromBufferNew((NwGtpv1uStackHandleT)thiz, pMsgBuf, msgLength,
                                      &(ulpApi.apiInfo.recvMsgInfo.hMsg));
        NW_ASSERT(rc == NW_GTPV1U_OK);
    }

    rc = thiz->ulp.ulpReqCallback(thiz->ulp.hUlp, &ulpApi);
    NW_ASSERT(rc == NW_GTPV1U_OK);

    NW_LEAVE(thiz);

    return rc;
}

NwGtpv1uRcT
nwGtpv1uPeerRspTimeout(void *arg)
{
    NwGtpv1uRcT rc = NW_GTPV1U_OK;
    NwGtpv1uTrxnT *thiz;
    NwGtpv1uTimeoutInfoT *timeoutInfo = arg;

    GTPU_WARNING( "Retransmission timer expired\n");

    thiz = ((NwGtpv1uTrxnT *)timeoutInfo->timeoutArg);
    rc = thiz->pStack->udp.udpDataReqCallback(thiz->pStack->udp.hUdp,
            thiz->pMsg->msgBuf,
            thiz->pMsg->msgLen,
            thiz->peerIp,
            thiz->peerPort);

    if(thiz->maxRetries) {
        rc = thiz->pStack->tmrMgr.tmrStartCallback(thiz->pStack->tmrMgr.tmrMgrHandle,
                5, 0, NW_GTPV1U_TMR_TYPE_ONE_SHOT, (void *)timeoutInfo, &thiz->hRspTmr);
        thiz->maxRetries--;
    } else {
        /* Inform session layer about path fialure */
        GTPU_WARNING("Max retries over!\n");
    }
    return rc;
}

/*---------------------------------------------------------------------------
 * ULP API Processing Functions
 *--------------------------------------------------------------------------*/

/**
  Process NW_GTPV1U_ULP_API_CREATE_TUNNEL_ENDPOINT Request from ULP entity.

  @param[in] hGtpuStackHandle : Stack handle
  @param[in] pUlpReq : Pointer to Ulp Req.
  @return NW_GTPV1U_OK on success.
 */

static NwGtpv1uRcT
NwGtpv1uCreateTunnelEndPoint( NW_IN  NwGtpv1uStackT *thiz,
                              NW_IN  NwU32T teid,
                              NW_IN  NwGtpv1uUlpSessionHandleT hUlpSession,
                              NW_OUT NwGtpv1uStackSessionHandleT *phStackSession )
{
    NwGtpv1uRcT rc = NW_GTPV1U_OK;
    NwGtpv1uTunnelEndPointT *pTunnelEndPoint;
    NwGtpv1uTunnelEndPointT *pCollision;

    NW_ENTER(thiz);

    pTunnelEndPoint = nwGtpTunnelEndPointNew(thiz);

    if(pTunnelEndPoint) {

        pTunnelEndPoint->teid           = teid;
        pTunnelEndPoint->pStack         = thiz;
        pTunnelEndPoint->hUlpSession    = hUlpSession;

        pCollision = RB_INSERT(NwGtpv1uTunnelEndPointIdentifierMap, &(thiz->teidMap),
                               pTunnelEndPoint);

        if(pCollision) {
            GTPU_ERROR("Tunnel end-point cannot be created for teid 0x%x. "
                   "Tunnel already exists", teid);
            rc = nwGtpTunnelEndPointDestroy(thiz, pTunnelEndPoint);
            NW_ASSERT(rc == NW_GTPV1U_OK);
            *phStackSession = (NwGtpv1uStackSessionHandleT) 0;
            NW_ASSERT(0);
            rc = NW_GTPV1U_FAILURE;
        } else {
            *phStackSession = (NwGtpv1uStackSessionHandleT) pTunnelEndPoint;
            pTunnelEndPoint = RB_FIND(NwGtpv1uTunnelEndPointIdentifierMap,
                                      &(thiz->teidMap), pTunnelEndPoint);
            NW_ASSERT(pTunnelEndPoint);
            GTPU_DEBUG("Tunnel end-point 0x%x creation successful for teid 0x%x %u(dec)",
                   (unsigned int)pTunnelEndPoint, teid, teid);
        }

    } else {
        *phStackSession = (NwGtpv1uStackSessionHandleT) 0;
        rc = NW_GTPV1U_FAILURE;
    }

    NW_LEAVE(thiz);
    return rc;
}

/**
  Process NW_GTPV1U_ULP_API_DESTROY_TUNNEL_ENDPOINT Request from ULP entity.

  @param[in] hGtpuStackHandle : Stack handle
  @param[in] pUlpReq : Pointer to Ulp Req.
  @return NW_GTPV1U_OK on success.
 */

static NwGtpv1uRcT
nwGtpv1uDestroyTunnelEndPoint( NwGtpv1uStackT *thiz,
                               NW_IN NwGtpv1uUlpApiT *pUlpReq)
{
    NwGtpv1uRcT rc = NW_GTPV1U_OK;
    NwGtpv1uTunnelEndPointT *pRemovedTeid;

    if(pUlpReq->apiInfo.destroyTunnelEndPointInfo.hStackSessionHandle) {
        GTPU_DEBUG(  "Destroying Tunnel end-point '%x'",
               pUlpReq->apiInfo.destroyTunnelEndPointInfo.hStackSessionHandle);
        pRemovedTeid = RB_REMOVE(NwGtpv1uTunnelEndPointIdentifierMap, &(thiz->teidMap),
                                 (NwGtpv1uTunnelEndPointT *)(
                                     pUlpReq->apiInfo.destroyTunnelEndPointInfo.hStackSessionHandle));

        NW_ASSERT(pRemovedTeid == (NwGtpv1uTunnelEndPointT *)(
                      pUlpReq->apiInfo.destroyTunnelEndPointInfo.hStackSessionHandle));

        rc = nwGtpTunnelEndPointDestroy(thiz,
                                        (NwGtpv1uTunnelEndPointT *)
                                        pUlpReq->apiInfo.destroyTunnelEndPointInfo.hStackSessionHandle);
    } else {
        GTPU_WARNING("Non-existent Tunnel end-point handle '%x'",
               pUlpReq->apiInfo.destroyTunnelEndPointInfo.hStackSessionHandle);
    }

    return rc;
}

/**
  Process NW_GTPV1U_ULP_API_INITIAL_REQ Request from ULP entity.

  @param[in] hGtpuStackHandle : Stack handle
  @param[in] pUlpReq : Pointer to Ulp Req.
  @return NW_GTPV1U_OK on success.
 */

static NwGtpv1uRcT
nwGtpv1uInitialReq( NW_IN NwGtpv1uStackT *thiz, NW_IN NwGtpv1uUlpApiT *pUlpReq)
{
    NwGtpv1uRcT rc = NW_GTPV1U_FAILURE;
    NwGtpv1uTrxnT *pTrxn;

    NW_ENTER(thiz);

    /* Create New Transaction */
    rc = nwGtpv1uTrxnNew(thiz, &pTrxn);

    if(pTrxn) {
        rc = nwGtpv1uTrxnCreateAndSendMsg(thiz,
                                          pTrxn,
                                          pUlpReq->apiInfo.initialReqInfo.peerIp,
                                          pUlpReq->apiInfo.initialReqInfo.peerPort,
                                          (NwGtpv1uMsgT *) pUlpReq->hMsg);

        if(rc == NW_GTPV1U_OK) {
            /* Insert into search tree */
            RB_INSERT(NwGtpv1uOutstandingTxSeqNumTrxnMap, &(thiz->outstandingTxSeqNumMap),
                      pTrxn);
        } else {
            rc = nwGtpv1uTrxnDelete(&pTrxn);
            NW_ASSERT(rc == NW_GTPV1U_OK);
        }
    }

    NW_LEAVE(thiz);

    return rc;
}

/**
  Process NW_GTPV1U_ULP_API_SEND_TPDU Request from ULP entity.

  @param[in] thiz: Stack handle
  @param[in] pUlpReq : Pointer to Ulp Req.
  @return NW_GTPV1U_OK on success.
 */

static NwGtpv1uRcT
nwGtpv1uSendto( NwGtpv1uStackT *thiz,  NW_IN NwGtpv1uUlpApiT *pUlpReq)
{
    NwGtpv1uRcT rc = NW_GTPV1U_FAILURE;

    NW_ASSERT(thiz);
    NW_ENTER(thiz);

    (void) nwGtpv1uMsgSetTeid(pUlpReq->apiInfo.sendtoInfo.hMsg,
                              pUlpReq->apiInfo.sendtoInfo.teid);

    rc = nwGtpv1uCreateAndSendMsg(thiz,
                                  pUlpReq->apiInfo.sendtoInfo.ipAddr,
                                  2152,
                                  (NwGtpv1uMsgT *) (NwGtpv1uMsgT *) pUlpReq->apiInfo.sendtoInfo.hMsg);

    NW_LEAVE(thiz);
    return rc;
}

/**
  Process GPDU from UDP entity.

  @param[in] thiz: Stack handle
  @param[in] pUlpReq : Pointer to Ulp Req.
  @return NW_GTPV1U_OK on success.
 */

static NwGtpv1uRcT
nwGtpv1uProcessGpdu( NwGtpv1uStackT *thiz,
                     NW_IN NwU8T *gpdu,
                     NW_IN NwU32T gdpuLen,
                     NW_IN NwU32T peerIp)

{
    NwGtpv1uRcT rc = NW_GTPV1U_FAILURE;
    NwGtpv1uMsgHeaderT *msgHdr;
    NwGtpv1uTunnelEndPointT *pTunnelEndPoint;
    NwGtpv1uTunnelEndPointT tunnelEndPointKey;

    NW_ENTER(thiz);

    msgHdr = (NwGtpv1uMsgHeaderT *) gpdu;

    tunnelEndPointKey.teid = ntohl(msgHdr->teid);
    pTunnelEndPoint = RB_FIND(NwGtpv1uTunnelEndPointIdentifierMap,
                              &(thiz->teidMap), &tunnelEndPointKey);

    if(pTunnelEndPoint) {
        NwGtpv1uMsgHandleT hMsg;

        rc = nwGtpv1uMsgFromBufferNew( (NwGtpv1uStackHandleT)thiz,
                                       (NwU8T *)gpdu,
                                       gdpuLen,
                                       &hMsg);

        if(NW_GTPV1U_OK == rc) {
            NwGtpv1uMsgT *pMsg = (NwGtpv1uMsgT *) hMsg;
            GTPU_DEBUG("Received T-PDU over tunnel end-point '%x' of size %u from "NW_IPV4_ADDR"\n",
                   ntohl(msgHdr->teid), pMsg->msgLen, NW_IPV4_ADDR_FORMAT((peerIp)));
            rc = nwGtpSessionSendMsgApiToUlpEntity(pTunnelEndPoint, pMsg);
        }
    } else {
        GTPU_ERROR("Received T-PDU over non-existent tunnel end-point '%x' from "NW_IPV4_ADDR"\n",
               ntohl(msgHdr->teid), NW_IPV4_ADDR_FORMAT((peerIp)));
    }
    NW_LEAVE(thiz);

    return rc;
}

/**
  Handle Echo Request from Peer Entity.

  @param[in] thiz : Stack context
  @return NW_GTPV1U_OK on success.
 */

static NwGtpv1uRcT
nwGtpv1uHandleEchoReq(NW_IN NwGtpv1uStackT *thiz,
                      NW_IN NwU8T *msgBuf,
                      NW_IN NwU32T msgBufLen,
                      NW_IN NwU16T peerPort,
                      NW_IN NwU32T peerIp)
{
    NwGtpv1uRcT           rc = NW_GTPV1U_FAILURE;
    NwU16T                seqNum = 0;
    NwGtpv1uMsgHandleT    hMsg = 0;

    seqNum = ntohs(*(NwU16T *) (msgBuf + (((*msgBuf) & 0x02) ? 8 : 4)));

    /* Send Echo Response */

    rc = nwGtpv1uMsgNew( (NwGtpv1uStackHandleT)thiz,
                         NW_TRUE,         /* SeqNum flag    */
                         NW_FALSE,
                         NW_FALSE,
                         NW_GTP_ECHO_RSP,  /* Msg Type             */
                         0x00000000U,      /* TEID                 */
                         seqNum,           /* Seq Number           */
                         0,
                         0,
                         (&hMsg));

    NW_ASSERT(NW_GTPV1U_OK == rc);

    /*
     * The Restart Counter value in the Recovery information element shall
     * not be used, i.e. it shall be set to zero by the sender and shall be
     * ignored by the receiver.
     */
    rc = nwGtpv1uMsgAddIeTV1(hMsg, NW_GTPV1U_IE_RECOVERY, 0x00);

    GTPU_INFO("Sending NW_GTP_ECHO_RSP message to %x:%x with seq %u\n",
        peerIp,
        peerPort,
        seqNum);

    rc = nwGtpv1uCreateAndSendMsg(
        thiz,
        peerIp,
        peerPort,
        (NwGtpv1uMsgT *) hMsg);

    rc = nwGtpv1uMsgDelete((NwGtpv1uStackHandleT)thiz, hMsg);
    NW_ASSERT(NW_GTPV1U_OK == rc);

    return rc;
}

/*--------------------------------------------------------------------------*
 *                     P U B L I C   F U N C T I O N S                      *
 *--------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------
 * Constructor
 *--------------------------------------------------------------------------*/

NwGtpv1uRcT
nwGtpv1uInitialize( NW_INOUT NwGtpv1uStackHandleT *hGtpuStackHandle)
{
    NwGtpv1uRcT rc = NW_GTPV1U_FAILURE;
    NwGtpv1uStackT *thiz;

    thiz = (NwGtpv1uStackT *) malloc( sizeof(NwGtpv1uStackT));
    memset(thiz, 0, sizeof(NwGtpv1uStackT));

    if(thiz) {
        thiz->id    = (NwU32T) thiz;
        thiz->seq   = (NwU16T) ((NwU32T)thiz) ;
        RB_INIT(&(thiz->outstandingTxSeqNumMap));
        RB_INIT(&(thiz->outstandingRxSeqNumMap));
        RB_INIT(&(thiz->sessionMap));
        RB_INIT(&(thiz->teidMap));
        if (0 == 1) {
            nwGtpv1uDisplayBanner();
        }

        rc = NW_GTPV1U_OK;
    } else {
        rc = NW_GTPV1U_FAILURE;
    }


    *hGtpuStackHandle = (NwGtpv1uStackHandleT) thiz;
    return rc;
}


/*---------------------------------------------------------------------------
 * Destructor
 *--------------------------------------------------------------------------*/

NwGtpv1uRcT
nwGtpv1uFinalize( NW_IN  NwGtpv1uStackHandleT hGtpuStackHandle)
{
    NwGtpv1uRcT rc = NW_GTPV1U_FAILURE;
    if(hGtpuStackHandle) {
        free((void *)hGtpuStackHandle);
        rc = NW_GTPV1U_OK;
    } else {
        rc = NW_GTPV1U_FAILURE;
    }
    return rc;
}


/*---------------------------------------------------------------------------
 * Configuration Get/Set
 *--------------------------------------------------------------------------*/

NwGtpv1uRcT
nwGtpv1uSetUlpEntity( NW_IN NwGtpv1uStackHandleT hGtpuStackHandle,
                      NW_IN NwGtpv1uUlpEntityT *pUlpEntity)
{
    NwGtpv1uRcT rc = NW_GTPV1U_FAILURE;
    NwGtpv1uStackT *thiz = (NwGtpv1uStackT *) hGtpuStackHandle;

    if(pUlpEntity) {
        thiz->ulp = *(pUlpEntity);
        rc = NW_GTPV1U_OK;
    } else {
        rc = NW_GTPV1U_FAILURE;
    }

    return rc;
}


NwGtpv1uRcT
nwGtpv1uSetUdpEntity( NW_IN NwGtpv1uStackHandleT hGtpuStackHandle,
                      NW_IN NwGtpv1uUdpEntityT *pUdpEntity)
{
    NwGtpv1uRcT rc = NW_GTPV1U_FAILURE;
    NwGtpv1uStackT *thiz = (NwGtpv1uStackT *) hGtpuStackHandle;

    if(pUdpEntity) {
        thiz->udp = *(pUdpEntity);
        rc = NW_GTPV1U_OK;
    } else {
        rc = NW_GTPV1U_FAILURE;
    }

    return rc;
}

NwGtpv1uRcT
nwGtpv1uSetMemMgrEntity( NW_IN NwGtpv1uStackHandleT hGtpuStackHandle,
                         NW_IN NwGtpv1uMemMgrEntityT *pMemMgrEntity)
{
    NwGtpv1uRcT rc = NW_GTPV1U_FAILURE;
    NwGtpv1uStackT *thiz = (NwGtpv1uStackT *) hGtpuStackHandle;

    if(pMemMgrEntity) {
        thiz->memMgr = *(pMemMgrEntity);
        rc = NW_GTPV1U_OK;
    } else {
        rc = NW_GTPV1U_FAILURE;
    }

    return rc;
}


NwGtpv1uRcT
nwGtpv1uSetTimerMgrEntity( NW_IN NwGtpv1uStackHandleT hGtpuStackHandle,
                           NW_IN NwGtpv1uTimerMgrEntityT *pTmrMgrEntity)
{
    NwGtpv1uRcT rc = NW_GTPV1U_FAILURE;
    NwGtpv1uStackT *thiz = (NwGtpv1uStackT *) hGtpuStackHandle;

    if(pTmrMgrEntity) {
        thiz->tmrMgr = *(pTmrMgrEntity);
        rc = NW_GTPV1U_OK;
    } else {
        rc = NW_GTPV1U_FAILURE;
    }

    return rc;
}


NwGtpv1uRcT
nwGtpv1uSetLogMgrEntity( NW_IN NwGtpv1uStackHandleT hGtpuStackHandle,
                         NW_IN NwGtpv1uLogMgrEntityT *pLogMgrEntity)
{
    NwGtpv1uRcT rc = NW_GTPV1U_FAILURE;
    NwGtpv1uStackT *thiz = (NwGtpv1uStackT *) hGtpuStackHandle;

    if(pLogMgrEntity) {
        thiz->logMgr = *(pLogMgrEntity);
        rc = NW_GTPV1U_OK;
    } else {
        rc = NW_GTPV1U_FAILURE;
    }
    return rc;
}

NwGtpv1uRcT
nwGtpv1uSetLogLevel( NW_IN NwGtpv1uStackHandleT hGtpuStackHandle,
                     NW_IN NwU32T logLevel)
{
    NwGtpv1uStackT *thiz = (NwGtpv1uStackT *) hGtpuStackHandle;
    thiz->logLevel = logLevel;
    return NW_GTPV1U_OK;
}

/*---------------------------------------------------------------------------
 * Process Request from Udp Layer
 *--------------------------------------------------------------------------*/

NwGtpv1uRcT
nwGtpv1uProcessUdpReq( NW_IN NwGtpv1uStackHandleT hGtpuStackHandle,
                       NW_IN NwU8T *udpData,
                       NW_IN NwU32T udpDataLen,
                       NW_IN NwU16T peerPort,
                       NW_IN NwU32T peerIp)
{
    NwGtpv1uRcT           ret = NW_GTPV1U_FAILURE;
    NwGtpv1uStackT       *thiz;
    NwU16T                msgType;

    thiz = (NwGtpv1uStackT *) hGtpuStackHandle;

    NW_ASSERT(thiz);

    msgType = *((NwU8T *)(udpData + 1));

    switch(msgType) {
        case NW_GTP_ECHO_REQ:
            GTPU_DEBUG("NW_GTP_ECHO_REQ\n");
            ret = nwGtpv1uHandleEchoReq( thiz, udpData, udpDataLen, peerPort, peerIp);
            break;

        case NW_GTP_ERROR_INDICATION:
            GTPU_DEBUG("NW_GTP_ERROR_INDICATION\n");
            ret = nwGtpv1uSendUlpMessageIndication( thiz,
                                                    0,
                                                    NW_GTPV1U_ULP_API_RECV_MSG,
                                                    msgType,
                                                    peerIp,
                                                    peerPort,
                                                    udpData,
                                                    udpDataLen);

            NW_ASSERT(ret == NW_GTPV1U_OK);
            break;

        case NW_GTP_ECHO_RSP:
            GTPU_DEBUG("NW_GTP_ECHO_RSP\n");
            ret = NW_GTPV1U_OK;
            break;

        case NW_GTP_GPDU:
            GTPU_DEBUG("NW_GTP_GPDU: DATA COMING FROM UDP\n");
            ret = nwGtpv1uProcessGpdu(thiz, udpData, udpDataLen, peerIp);
            break;

        default:
            ret = NW_GTPV1U_FAILURE;
            NW_ASSERT(0);
            break;
    }

    NW_LEAVE(thiz);
    return ret;
}


/*---------------------------------------------------------------------------
 * Process Request from Upper Layer
 *--------------------------------------------------------------------------*/

NwGtpv1uRcT
nwGtpv1uProcessUlpReq( NW_IN NwGtpv1uStackHandleT hGtpuStackHandle,
                       NW_IN NwGtpv1uUlpApiT *pUlpReq)
{
    NwGtpv1uRcT rc = NW_GTPV1U_FAILURE;
    NwGtpv1uStackT *thiz = (NwGtpv1uStackT *) hGtpuStackHandle;

    NW_ASSERT(thiz);
    NW_ASSERT(pUlpReq != NULL);

    NW_ENTER(thiz);

    switch(pUlpReq->apiType) {
        case NW_GTPV1U_ULP_API_CREATE_TUNNEL_ENDPOINT: {
            GTPU_DEBUG("Received NW_GTPV1U_ULP_API_CREATE_TUNNEL_ENDPOINT from ulp\n");
            rc = NwGtpv1uCreateTunnelEndPoint(thiz,
                                              pUlpReq->apiInfo.createTunnelEndPointInfo.teid,
                                              pUlpReq->apiInfo.createTunnelEndPointInfo.hUlpSession,
                                              &(pUlpReq->apiInfo.createTunnelEndPointInfo.hStackSession));
        }
        break;

        case NW_GTPV1U_ULP_API_DESTROY_TUNNEL_ENDPOINT: {
            GTPU_DEBUG("Received destroy session req from ulp\n");
            rc = nwGtpv1uDestroyTunnelEndPoint(thiz,  pUlpReq);
        }
        break;

        case NW_GTPV1U_ULP_API_INITIAL_REQ: {
            GTPU_DEBUG("Received initial req from ulp\n");
            rc = nwGtpv1uInitialReq(thiz, pUlpReq);
        }
        break;

        case NW_GTPV1U_ULP_API_SEND_TPDU: {
            GTPU_DEBUG("Received send tpdu req from ulp\n");
            rc = nwGtpv1uSendto(thiz,  pUlpReq);
        }
        break;

        default:
            GTPU_DEBUG("Unsupported API received from ulp\n");
            rc = NW_GTPV1U_FAILURE;
            break;
    }

    NW_LEAVE(thiz);

    return rc;
}

/*---------------------------------------------------------------------------
 * Process Timer timeout Request from Timer Manager
 *--------------------------------------------------------------------------*/

NwGtpv1uRcT
nwGtpv1uProcessTimeout(void *timeoutInfo)
{
    NwGtpv1uRcT rc = NW_GTPV1U_FAILURE;
    NwGtpv1uStackT *thiz;

    NW_ASSERT(timeoutInfo != NULL);

    thiz = (NwGtpv1uStackT *) (((NwGtpv1uTimeoutInfoT *) timeoutInfo)->hStack);

    NW_ASSERT(thiz != NULL);

    NW_ENTER(thiz);
    GTPU_DEBUG("Received timeout event from ULP with timeoutInfo %x!\n",
           (unsigned int)timeoutInfo);

    rc = (((NwGtpv1uTimeoutInfoT *) timeoutInfo)->timeoutCallbackFunc) (timeoutInfo);

    NW_LEAVE(thiz);

    return rc;
}

#ifdef __cplusplus
}
#endif

/*--------------------------------------------------------------------------*
 *                      E N D     O F    F I L E                            *
 *--------------------------------------------------------------------------*/