/*************************************************************************************************
    Filename:       gatt_server.c
    Revised:
    Revision:

    Description:    This file contains the Generic Attribute Profile Server.

	SDK_LICENSE

**************************************************************************************************/


/*********************************************************************
    INCLUDES
*/
#include "bcomdef.h"
#include "linkdb.h"

#include "gatt.h"
#include "gatt_uuid.h"
#include "gatt_internal.h"


/*********************************************************************
    MACROS
*/
// Parse and process request function pointers
#define gattParseReq( method )     ( serverReqTbl[((method)/2)-1].pfnParseReq )
#define gattProcessReq( method )   ( serverReqTbl[((method)/2)-1].pfnProcessReq )

/*********************************************************************
    CONSTANTS
*/

/*********************************************************************
    TYPEDEFS
*/
// Service record list item
typedef struct _attAttrList
{
    struct _attAttrList* next;  // pointer to next service record
    gattService_t service;      // service record
} gattServiceList_t;

// Structure to keep Attribute Server info
typedef struct
{
    // Info maintained for Handle Value Confirmation message
    uint16 connHandle;    // connection message was sent on
    uint8 timerId;        // confirmation timeout timer id
    uint8 taskId;         // task to be notified of confirmation
} gattServerInfo_t;

// Structure to keep the Parse and Process function pointers for Requests
typedef struct
{
    gattParseReq_t pfnParseReq;     // function to parse an incoming request
    gattProcessReq_t pfnProcessReq; // function to process an incoming request
} gattHandleReq_t;

/*********************************************************************
    GLOBAL VARIABLES
*/


// Server Info table (one entry per each physical link)
gattServerInfo_t serverInfoTbl[GATT_MAX_NUM_CONN];

// Task to be notified of requests
uint8 reqTaskId = INVALID_TASK_ID;

/*********************************************************************
    EXTERNAL VARIABLES
*/

/*********************************************************************
    EXTERNAL FUNCTIONS
*/

/*********************************************************************
    LOCAL VARIABLES
*/
// GATT Service List
static gattServiceList_t* pServiceList = NULL;

// Next available attribute handle
static uint16 nextHandle = GATT_MIN_HANDLE;

/*********************************************************************
    LOCAL FUNCTIONS
*/
static bStatus_t gattProcessExchangeMTUReq( uint16 connHandle, attMsg_t* pMsg );
static bStatus_t gattProcessFindInfoReq( uint16 connHandle, attMsg_t* pMsg );
static bStatus_t gattProcessFindByTypeValueReq( uint16 connHandle, attMsg_t* pMsg );
static bStatus_t gattProcessReadByTypeReq( uint16 connHandle, attMsg_t* pMsg );
static bStatus_t gattProcessReadReq( uint16 connHandle, attMsg_t* pMsg );
static bStatus_t gattProcessReadMultiReq( uint16 connHandle, attMsg_t* pMsg );
static bStatus_t gattProcessReadByGrpTypeReq( uint16 connHandle, attMsg_t* pMsg );
static bStatus_t gattProcessWriteReq( uint16 connHandle, attMsg_t* pMsg );
static bStatus_t gattProcessExecuteWriteReq( uint16 connHandle, attMsg_t* pMsg );

static void gattStoreServerInfo( gattServerInfo_t* pServer, uint8 taskId );
static bStatus_t gattGetServerStatus( uint16 connHandle, gattServerInfo_t** p2pServer );
static gattServerInfo_t* gattFindServerInfo( uint16 connHandle );
static void gattResetServerInfo( gattServerInfo_t* pServer );
static void gattServerStartTimer( uint8* pData, uint16 timeout, uint8* pTimerId );
static uint16 gattServiceLastHandle( uint16 handle );

// Callback functions
static bStatus_t gattServerProcessMsgCB( uint16 connHandle, attPacket_t* pPkt );
static void gattServerHandleTimerCB( uint8* pData );
static void gattServerHandleConnStatusCB( uint16 connectionHandle, uint8 changeType );

/*********************************************************************
    Server Handle Request Table
*/
static CONST gattHandleReq_t serverReqTbl[] =
{
    { ATT_ParseExchangeMTUReq,     gattProcessExchangeMTUReq     }, /* ATT_EXCHANGE_MTU_REQ */
    { ATT_ParseFindInfoReq,        gattProcessFindInfoReq        }, /* ATT_FIND_INFO_REQ */
    { ATT_ParseFindByTypeValueReq, gattProcessFindByTypeValueReq }, /* ATT_FIND_BY_TYPE_VALUE_REQ */
    { ATT_ParseReadByTypeReq,      gattProcessReadByTypeReq      }, /* ATT_READ_BY_TYPE_REQ */
    { ATT_ParseReadReq,            gattProcessReadReq            }, /* ATT_READ_REQ */
    { ATT_ParseReadBlobReq,        gattProcessReadReq            }, /* ATT_READ_BLOB_REQ */
    { ATT_ParseReadMultiReq,       gattProcessReadMultiReq       }, /* ATT_READ_MULTI_REQ */
    { ATT_ParseReadByTypeReq,      gattProcessReadByGrpTypeReq   }, /* ATT_READ_BY_GRP_TYPE_REQ */
    { ATT_ParseWriteReq,           gattProcessWriteReq           }, /* ATT_WRITE_REQ */
    { NULL,                        NULL                          }, /* Undefined */
    { ATT_ParsePrepareWriteReq,    gattProcessWriteReq           }, /* ATT_PREPARE_WRITE_REQ */
    { ATT_ParseExecuteWriteReq,    gattProcessExecuteWriteReq    }  /* ATT_EXECUTE_WRITE_REQ */
};

/*********************************************************************
    API FUNCTIONS
*/

/*  -------------------------------------------------------------------
    GATT Server Public APIs
*/

/******************************************************************************
    @fn      GATT_InitServer

    @brief   Initialize the Generic Attribute Profile Server.

    @return  SUCCESS: Server initialized successfully.
*/
bStatus_t GATT_InitServer( void )
{
    // Mark all Server records as unused
    for ( uint8 i = 0; i < GATT_MAX_NUM_CONN; i++ )
    {
        gattServerInfo_t* pServer = &serverInfoTbl[i];

        // Initialize connection handle
        if ( i == 0 )
        {
            pServer->connHandle = LOOPBACK_CONNHANDLE;
        }
        else
        {
            pServer->connHandle = INVALID_CONNHANDLE;
        }

        // Initialize Handle Value Confirmation info
        pServer->taskId = INVALID_TASK_ID;
        pServer->timerId = INVALID_TIMER_ID;
    }

    // Set up the server's processing function
    gattRegisterServer( gattServerProcessMsgCB );
    // Register with Link DB to receive link status change callback
    linkDB_Register( gattServerHandleConnStatusCB );
    return ( SUCCESS );
}

/******************************************************************************
    @fn      GATT_RegisterService

    @brief   Register a service attribute list with the GATT Server. A service
            is composed of characteristics or references to other services.
            Each characteristic contains a value and may contain optional
            information about the value. There are two types of services:
            primary service and secondary service.

            A service definition begins with a service declaration and ends
            before the next service declaration or the maximum Attribute Handle.

            A characteristic definition begins with a characteristic declaration
            and ends before the next characteristic or service declaration or
            maximum Attribute Handle.

            The attribute server will only keep a pointer to the attribute
            list, so the calling application will have to maintain the code
            and RAM associated with this list.

    @param   pService - pointer to service attribute list to be registered

    @return  SUCCESS: Service registered successfully.
            INVALIDPARAMETER: Invalid service field.
            FAILURE: Not enough attribute handles available.
            bleMemAllocError: Memory allocation error occurred.
*/
bStatus_t GATT_RegisterService( gattService_t* pService )
{
    gattServiceList_t* pNewItem;

    // Make sure the service attribute list begins with a service declaration
    if ( ( pService->numAttrs == 0 ) || !gattServiceType( pService->attrs[0].type ) )
    {
        return ( INVALIDPARAMETER );
    }

    // Make sure we have enough attribute handles available for this service
    if ( ( nextHandle == 0 ) || ( pService->numAttrs > ( GATT_MAX_HANDLE - nextHandle ) + 1 ) )
    {
        return ( FAILURE );
    }

    // Allocate space for the new service item
    pNewItem = (gattServiceList_t*)osal_mem_alloc( sizeof( gattServiceList_t ) );

    if ( pNewItem == NULL )
    {
        // Not enough memory
        return ( bleMemAllocError );
    }

    // Assign attribute handles
    for ( uint16 i = 0; i < pService->numAttrs; i++ )
    {
        pService->attrs[i].handle = nextHandle++;
    }

    // Set up new service item
    pNewItem->next = NULL;
    osal_memcpy( &(pNewItem->service), pService, sizeof( gattService_t ) );

    // Find spot in list
    if ( pServiceList == NULL )
    {
        // First item in list
        pServiceList = pNewItem;
    }
    else
    {
        gattServiceList_t* pLoop = pServiceList;

        // Look for end of list
        while ( pLoop->next != NULL )
        {
            pLoop = pLoop->next;
        }

        // Put new item at end of list
        pLoop->next = pNewItem;
    }

    return ( SUCCESS );
}

/******************************************************************************
    @fn      GATT_DeregisterService

    @brief   Deregister a service attribute list with the GATT Server.

            NOTE: It's the caller's responsibility to free the service attribute
            list returned from this API.

    @param   handle - handle of service to be deregistered
    @param   pService - pointer to deregistered service (to be returned)

    @return  SUCCESS: Service deregistered successfully.
            FAILURE: Service not found.
*/
bStatus_t GATT_DeregisterService( uint16 handle, gattService_t* pService )
{
    gattServiceList_t* pLoop = pServiceList;
    gattServiceList_t* pPrev = NULL;

    // Look for service
    while ( pLoop != NULL )
    {
        if ( pLoop->service.attrs[0].handle == handle )
        {
            // Service found; unlink it
            if ( pPrev == NULL )
            {
                // First item in list
                pServiceList = pLoop->next;
            }
            else
            {
                pPrev->next = pLoop->next;
            }

            // Application will free the service attribute list
            if ( pService != NULL )
            {
                VOID osal_memcpy( pService, &(pLoop->service), sizeof( gattService_t ) );
            }

            // Free the service record
            osal_mem_free( pLoop );
            return ( SUCCESS );
        }

        pPrev = pLoop;
        pLoop = pLoop->next;
    }

    // Service not found
    return ( FAILURE );
}

/******************************************************************************
    @fn      GATT_RegisterForReq

    @brief   Register to receive incoming ATT Requests.

    @param   taskId ? task to forward requests to

    @return  void
*/
void GATT_RegisterForReq( uint8 taskId )
{
    reqTaskId = taskId;
}

/*********************************************************************
    @fn      GATT_VerifyReadPermissions

    @brief   Verify the permissions of an attribute for reading.

    @param   connHandle - connection to use
    @param   permissions - attribute permissions

    @return  SUCCESS: Attribute can be read
            ATT_ERR_READ_NOT_PERMITTED: Attribute cannot be read
            ATT_ERR_INSUFFICIENT_AUTHEN: Attribute requires authentication
            ATT_ERR_INSUFFICIENT_KEY_SIZE: Key Size used for encrypting is insufficient
            ATT_ERR_INSUFFICIENT_ENCRYPT: Attribute requires encryption
*/
bStatus_t GATT_VerifyReadPermissions( uint16 connHandle, uint8 permissions )
{
    // Make sure the requesting device has sufficient security
    if ( gattPermitAuthorRead( permissions ) || gattPermitAuthenRead( permissions ) )
    {
        // Authorization is handled by the application/profile but make sure the
        // requesting device is authenticated and the link is encrypted.
        return ( linkDB_Authen( connHandle, GATT_ENCRYPT_KEY_SIZE, TRUE ) );
    }

    if ( gattPermitEncryptRead( permissions ) )
    {
        // Read operation requires an encrypted link (unauthenticated)
        return ( linkDB_Authen( connHandle, GATT_ENCRYPT_KEY_SIZE, FALSE ) );
    }

    // Make sure the attribute has sufficient permissions to allow reading
    if ( !gattPermitRead( permissions ) )
    {
        return ( ATT_ERR_READ_NOT_PERMITTED );
    }

    return ( SUCCESS );
}

/*********************************************************************
    @fn      GATT_VerifyWritePermissions

    @brief   Verify the permissions of an attribute for writing.

    @param   connHandle - connection to use
    @param   permissions - attribute permissions
    @param   pReq - pointer to write request

    @return  SUCCESS: Attribute can be written
            ATT_ERR_READ_NOT_PERMITTED: Attribute cannot be written
            ATT_ERR_INSUFFICIENT_AUTHEN: Attribute requires authentication
            ATT_ERR_INSUFFICIENT_KEY_SIZE: Key Size used for encrypting is insufficient
            ATT_ERR_INSUFFICIENT_ENCRYPT: Attribute requires encryption
*/
bStatus_t GATT_VerifyWritePermissions( uint16 connHandle, uint8 permissions, attWriteReq_t* pReq )
{
    // Make sure the requesting device has sufficient security
    if ( gattPermitAuthorWrite( permissions ) )
    {
        // Authorization is handled by the application/profile but make sure the
        // requesting device is authenticated and the link is encrypted.
        return ( linkDB_Authen( connHandle, GATT_ENCRYPT_KEY_SIZE, TRUE ) );
    }

    if ( gattPermitAuthenWrite( permissions ) || gattPermitEncryptWrite( permissions ) )
    {
        uint8 status = linkDB_Authen( connHandle, GATT_ENCRYPT_KEY_SIZE, gattPermitAuthenWrite( permissions ) );

        // Write operation requires an encrypted link or an authenticated signed command
        if ( ( status != SUCCESS ) && ( ( pReq->cmd == FALSE ) || ( pReq->sig != ATT_SIG_VALID ) ) )
        {
            return ( status );
        }
    }
    // Make sure the attribute has sufficient permissions to allow writing
    else if ( !gattPermitWrite( permissions ) )
    {
        return ( ATT_ERR_WRITE_NOT_PERMITTED );
    }

    return ( SUCCESS );
}

/*********************************************************************
    @fn      GATT_ServiceChangedInd

    @brief   Send out a Service Changed Indication.

    @param   connHandle - connection to use
    @param   taskId - task to be notified of confirmation

    @return  SUCCESS: Indication was sent successfully.
            FAILURE: Service Changed attribute not found.
            INVALIDPARAMETER: Invalid connection handle or request field.
            MSG_BUFFER_NOT_AVAIL: No HCI buffer is available.
            bleNotConnected: Connection is down.
            blePending: A confirmation is pending with this client.
*/
uint8 GATT_ServiceChangedInd( uint16 connHandle, uint8 taskId )
{
    gattAttribute_t* pAttr;
    // Find the Service Changed attribute record
    pAttr = GATT_FindHandleUUID( GATT_MIN_HANDLE, GATT_MAX_HANDLE,
                                 serviceChangedUUID, ATT_BT_UUID_SIZE, NULL );

    if ( pAttr != NULL )
    {
        attHandleValueInd_t ind;
        ind.handle = pAttr->handle;
        // Set the affected Attribute Handle range to 0x0001 to 0xFFFF to
        // indicate to the client to rediscover the entire set of Attribute
        // Handles on the server.
        ind.len = 4;
        ind.value[0] = LO_UINT16( GATT_MIN_HANDLE );
        ind.value[1] = HI_UINT16( GATT_MIN_HANDLE );
        ind.value[2] = LO_UINT16( GATT_MAX_HANDLE );
        ind.value[3] = HI_UINT16( GATT_MAX_HANDLE );
        return ( GATT_Indication( connHandle, &ind, FALSE, taskId ) );
    }

    return ( FAILURE );
}

/*********************************************************************
    @fn      GATT_FindHandleUUID

    @brief   Find the attribute record for a given handle and UUID.

    @param   startHandle - first handle to look for
    @param   endHandle - last handle to look for
    @param   pUUID - pointer to UUID to look for
    @param   len - length of UUID
    @param   pHandle - handle of owner of attribute (to be returned)

    @return  Pointer to attribute record. NULL, otherwise.
*/
gattAttribute_t* GATT_FindHandleUUID( uint16 startHandle, uint16 endHandle, const uint8* pUUID,
                                      uint16 len, uint16* pHandle )
{
    gattServiceList_t* pLoop = pServiceList;

    while ( pLoop != NULL )
    {
        for ( uint16 i = 0; i < pLoop->service.numAttrs; i++ )
        {
            gattAttribute_t* pAttr = &(pLoop->service.attrs[i]);

            // Check to see if this handle falls within the starting and ending handles
            if ( ( pAttr->handle >= startHandle ) && ( pAttr->handle <= endHandle ) )
            {
                // Compare UUIDs if one is provided
                if ( ( len == 0 ) ||
                        ( ATT_CompareUUID( pAttr->type.uuid, pAttr->type.len,
                                           pUUID, len ) ) )
                {
                    // Entry found
                    if ( pHandle != NULL )
                    {
                        // Handle of the service that the attribute belongs to
                        *pHandle = pLoop->service.attrs[0].handle;
                    }

                    return ( pAttr );
                }
            }
        }

        // Try next service
        pLoop = pLoop->next;
    }

    return ( (gattAttribute_t*)NULL );
}

/*********************************************************************
    @fn      GATT_FindHandle

    @brief   Find the attribute record for a given handle

    @param   handle - handle to look for
    @param   pHandle - handle of owner of attribute (to be returned)

    @return  Pointer to attribute record. NULL, otherwise.
*/
gattAttribute_t* GATT_FindHandle( uint16 handle, uint16* pHandle )
{
    gattServiceList_t* pLoop = pServiceList;

    while ( pLoop != NULL )
    {
        uint16 serviceHandle = pLoop->service.attrs[0].handle;

        // See if the handle falls within this service
        if ( ( handle >= serviceHandle ) && ( handle < serviceHandle + pLoop->service.numAttrs ) )
        {
            for ( uint16 i = 0; i < pLoop->service.numAttrs; i++ )
            {
                gattAttribute_t* pAttr = &(pLoop->service.attrs[i]);

                if ( pAttr->handle == handle )
                {
                    // Entry found
                    if ( pHandle != NULL )
                    {
                        // Handle of the service that the attribute belongs to
                        *pHandle = serviceHandle;
                    }

                    return ( pAttr );
                }
            }
        }

        // Try next service
        pLoop = pLoop->next;
    }

    return ( (gattAttribute_t*)NULL );
}

/*********************************************************************
    @fn      GATT_FindNextAttr

    @brief   Find the next attribute of the same type for a given attribute.

    @param   pAttr - pointer to attribute to find a next for
    @param   endHandle - last handle to look for
    @param   service - handle of owner service
    @param   pLastHandle - handle of last attribute (to be returned)

    @return  Pointer to next attribute record. NULL, otherwise.
*/
gattAttribute_t* GATT_FindNextAttr( gattAttribute_t* pAttr, uint16 endHandle,
                                    uint16 service, uint16* pLastHandle )
{
    uint16 lastHandle;
    gattAttribute_t* pNext = NULL;
    uint16 owner = GATT_INVALID_HANDLE;

    // Try to find the next attribute of the same type

    // All attribute types are effectively compared as 128-bit UUIDs,
    // even if a 16-bit UUID is provided in this request or defined
    // for an attribute.
    if ( pAttr->handle != GATT_MAX_HANDLE )
    {
        pNext = GATT_FindHandleUUID( pAttr->handle+1, endHandle, pAttr->type.uuid,
                                     pAttr->type.len, &owner );
    }

    // Try to find the handle of the last attribute
    if ( gattServiceType( pAttr->type ) )
    {
        // Get the handle of the last attribute withis this service
        lastHandle = gattServiceLastHandle( pAttr->handle );
    }
    else if ( gattCharacterType( pAttr->type ) )
    {
        // Check to see if this is the last characteristic within the service
        if ( ( pNext == NULL ) || ( owner != service ) )
        {
            lastHandle = gattServiceLastHandle( service );
        }
        else
        {
            lastHandle = pNext->handle - 1;
        }
    }
    else
    {
        // Not a grouping attribute -- return its handle
        lastHandle = pAttr->handle;
    }

    if ( pLastHandle != NULL )
    {
        *pLastHandle = lastHandle;
    }

    return ( pNext );
}

/*********************************************************************
    @fn      GATT_ServiceNumAttrs

    @brief   Get the number of attributes for a given service

    @param   handle - service handle to look for

    @return  Number of attributes. 0, otherwise.
*/
uint16 GATT_ServiceNumAttrs( uint16 handle )
{
    gattServiceList_t* pLoop = pServiceList;

    while ( pLoop != NULL )
    {
        if ( pLoop->service.attrs[0].handle == handle )
        {
            // Service found
            return ( pLoop->service.numAttrs );
        }

        // Try next service
        pLoop = pLoop->next;
    }

    return ( 0 );
}

/*  -------------------------------------------------------------------
    GATT Server Sub-Procedure APIs
*/

/*********************************************************************
    @fn      GATT_Indication

    @brief   This sub-procedure is used when a server is configured to
            indicate a characteristic value to a client and expects an
            attribute protocol layer acknowledgement that the indication
            was successfully received.

            The ATT Handle Value Indication is used in this sub-procedure.

            If the return status from this function is SUCCESS, the calling
            application task will receive an OSAL GATT_MSG_EVENT message.
            The type of the message will be ATT_HANDLE_VALUE_CFM.

            Note: This sub-procedure is complete when ATT_HANDLE_VALUE_CFM
                  (with SUCCESS or bleTimeout status) is received by the
                  calling application task.

    @param   connHandle - connection to use
    @param   pInd - pointer to indication to be sent
    @param   taskId - task to be notified of confirmation
    @param   authenticated - whether an authenticated link is required

    @return  SUCCESS: Indication was sent successfully.
            INVALIDPARAMETER: Invalid connection handle or request field.
            ATT_ERR_INSUFFICIENT_AUTHEN - link is not encrypted
            ATT_ERR_INSUFFICIENT_KEY_SIZE - key size encrypted is not large enough
            ATT_ERR_INSUFFICIENT_ENCRYPT - link is encrypted, but not authenticated
            MSG_BUFFER_NOT_AVAIL: No HCI buffer is available.
            bleNotConnected: Connection is down.
            blePending: A confirmation is pending with this client.
            bleTimeout: Previous transaction timed out.
*/
bStatus_t GATT_Indication( uint16 connHandle, attHandleValueInd_t* pInd,
                           uint8 authenticated, uint8 taskId )
{
    gattServerInfo_t* pServer;
    uint8 status;
    // Make sure we're allowed to send a new indication
    status = gattGetServerStatus( connHandle, &pServer );

    if ( status == SUCCESS )
    {
        // Make sure the link is authenticated if requested
        if ( authenticated )
        {
            // Indication operation requires authentication
            status = linkDB_Authen( connHandle, GATT_ENCRYPT_KEY_SIZE, TRUE );
        }

        if ( status == SUCCESS )
        {
            status = ATT_HandleValueInd( connHandle, pInd );

            if ( status == SUCCESS )
            {
                // Store server info
                gattStoreServerInfo( pServer, taskId );
            }
        }
    }

    return ( status );
}

/*********************************************************************
    @fn      GATT_Notification

    @brief   This sub-procedure is used when a server is configured to
            notify a characteristic value to a client without expecting
            any attribute protocol layer acknowledgement that the
            notification was successfully received.

            The ATT Handle Value Notification is used in this sub-procedure.

            Note: A notification may be sent at any time and does not
            invoke a confirmation.

            No confirmation will be sent to the calling application task for
            this sub-procedure.

    @param   connHandle - connection to use
    @param   pNoti - pointer to notification to be sent
    @param   authenticated - whether an authenticated link is required

    @return  SUCCESS: Notification was sent successfully.
            INVALIDPARAMETER: Invalid connection handle or request field.
            ATT_ERR_INSUFFICIENT_AUTHEN - link is not encrypted
            ATT_ERR_INSUFFICIENT_KEY_SIZE - key size encrypted is not large enough
            ATT_ERR_INSUFFICIENT_ENCRYPT - link is encrypted, but not authenticated
            MSG_BUFFER_NOT_AVAIL: No HCI buffer is available.
            bleNotConnected: Connection is down.
            bleTimeout: Previous transaction timed out.
*/
bStatus_t GATT_Notification( uint16 connHandle, attHandleValueNoti_t* pNoti,
                             uint8 authenticated )
{
    gattServerInfo_t* pServer;
    uint8 status;
    // Make sure we're allowed to send a new notification
    status = gattGetServerStatus( connHandle, &pServer );

    if ( status != bleTimeout )
    {
        // Make sure the link is authenticated if requested
        if ( authenticated )
        {
            // Notification operation requires authentication
            status = linkDB_Authen( connHandle, GATT_ENCRYPT_KEY_SIZE, TRUE );

            if ( status != SUCCESS )
            {
                return ( status );
            }
        }

        status = ATT_HandleValueNoti( connHandle, pNoti );
    }

    return ( status );
}

/*  -------------------------------------------------------------------
    GATT Server Internal Functions
*/

/*********************************************************************
    @fn      gattServiceLastHandle

    @brief   Get the handle of the last attribute within a given service.

    @param   handle - service handle

    @return  Handle of last attribute for service. Handle, otherwise.
*/
static uint16 gattServiceLastHandle( uint16 handle )
{
    uint16 lastHandle;
    // Find out the handle of the last attribute withis this service
    lastHandle = GATT_ServiceNumAttrs( handle );

    if ( lastHandle != 0 )
    {
        lastHandle += (handle - 1);
    }
    else
    {
        lastHandle = handle;
    }

    return ( lastHandle );
}

/*********************************************************************
    @fn      gattStoreServerInfo

    @brief   Store server info.

    @param   connHandle ? connection to use
    @param   taskId ? task to be notified of response

    @return  void
*/
static void gattStoreServerInfo( gattServerInfo_t* pServer, uint8 taskId )
{
    if ( taskId != INVALID_TASK_ID )
    {
        // Start a timeout timer for the confirmation
        gattServerStartTimer( (uint8*)pServer, ATT_MSG_TIMEOUT, &pServer->timerId );
        // Store task id to forward the confirmation to
        pServer->taskId = taskId;
    }
}

/*********************************************************************
    @fn          gattProcessServerMsgCB

    @brief       GATT Server message processing function.

    @param       connHandle - connection packet was received on
    @param       pPkt - pointer to received packet

    @return      SUCCESS: Message processed successfully
                ATT_ERR_UNSUPPORTED_REQ: Unsupported request or command
                ATT_ERR_INVALID_PDU: Invalid PDU
                bleMemAllocError: Memory allocation error occurred
*/
static bStatus_t gattServerProcessMsgCB( uint16 connHandle, attPacket_t* pPkt )
{
    gattMsg_t msg;
    uint8 status = SUCCESS;

    // See if this is a confirmation to an indication
    if ( pPkt->method == ATT_HANDLE_VALUE_CFM )
    {
        gattServerInfo_t* pServer;
        // Make sure we have the info about the Server that sent the indication
        pServer = gattFindServerInfo( connHandle );

        if ( pServer != NULL )
        {
            status = gattNotifyEvent( pServer->taskId, connHandle, SUCCESS, pPkt->method, NULL );
            // Reset server info
            gattResetServerInfo( pServer );
        }

        // We're done here
        return ( status );
    }

    // Make sure the incoming request or command is supported
    if ( ( reqTaskId == INVALID_TASK_ID )           ||
            ( pPkt->method > ATT_EXECUTE_WRITE_REQ )   ||
            ( gattParseReq( pPkt->method ) == NULL )   ||
            ( gattProcessReq( pPkt->method ) == NULL ) ||
            ( ( ( pPkt->sig != ATT_SIG_NOT_INCLUDED )  ||
                ( pPkt->cmd == TRUE ) )                &&
              ( pPkt->method != ATT_WRITE_REQ ) ) )
    {
        // Unsupported request or command
        return ( ATT_ERR_UNSUPPORTED_REQ );
    }

    // Parse the incoming request or command
    status = gattParseReq( pPkt->method )( pPkt->sig, pPkt->cmd, pPkt->pParams,
                                           pPkt->len, (attMsg_t*)&msg );

    if ( status == SUCCESS )
    {
        // Try to process the request or command
        status = gattProcessReq( pPkt->method )( connHandle, (attMsg_t*)&msg );

        if ( status == SUCCESS )
        {
            // Foward the request up to the application for further processing
            if ( pPkt->method != ATT_FIND_INFO_REQ )
            {
                status = gattNotifyEvent( reqTaskId, connHandle, SUCCESS, pPkt->method, &msg );
            }
        }
        // Do not send an error response back for any command
        else if ( pPkt->cmd == FALSE )
        {
            attErrorRsp_t errorRsp;
            // Send an Error Response back
            errorRsp.reqOpcode = pPkt->method;
            errorRsp.errCode = status;

            // Set the handle
            if ( ( pPkt->method == ATT_FIND_INFO_REQ )          ||
                    ( pPkt->method == ATT_FIND_BY_TYPE_VALUE_REQ ) ||
                    ( pPkt->method == ATT_READ_BY_TYPE_REQ )       ||
                    ( pPkt->method == ATT_READ_BY_GRP_TYPE_REQ ) )
            {
                // Set handle to the starting handle
                errorRsp.handle = msg.findInfoReq.startHandle;
            }
            else
            {
                // All requests share the handle field
                errorRsp.handle = msg.readReq.handle;
            }

            // Send an Error Response back
            VOID ATT_ErrorRsp( connHandle, &errorRsp );
            // We're done with this request
            status = SUCCESS;
        }
    }

    return ( status );
}

/*********************************************************************
    @fn      gattProcessExchangeMTUReq

    @brief   Process Exchange MTU Request.

    @param   connHandle ? connection message was received on
    @param   pMsg - pointer to message structure

    @return  SUCCESS: Forward the request up to the application
*/
static bStatus_t gattProcessExchangeMTUReq( uint16 connHandle, attMsg_t* pMsg )
{
    VOID connHandle; // Not used here
    //VOID pMsg; // Not used here
    attExchangeMTUReq_t* pReq = &pMsg->exchangeMTUReq;
    g_attMtuClientServer.clientMTU=ATT_MTU_SIZE_MIN;

    if(pReq->clientRxMTU < ATT_MTU_SIZE_MIN)
    {
        return (ATT_ERR_INVALID_PDU);
    }

    g_attMtuClientServer.clientMTU=pReq->clientRxMTU;
    return ( SUCCESS );
}

/*********************************************************************
    @fn      gattProcessFindInfoReq

    @brief   Process Find Information Request.

    @param   connHandle ? connection message was received on
    @param   pMsg - pointer to message structure

    @return  SUCCESS: Forward the request up to the application
            ATT_ERR_INVALID_HANDLE: Invalid attribute handle
            ATT_ERR_ATTR_NOT_FOUND: Attribute not found
*/
static bStatus_t gattProcessFindInfoReq( uint16 connHandle, attMsg_t* pMsg )
{
    attFindInfoReq_t* pReq = &pMsg->findInfoReq;
    uint16 startHandle = pReq->startHandle;
    attFindInfoRsp_t rsp;
    uint8 done = FALSE;

    // If the starting handle greater than the ending handle or the starting
    // handle is 0x0000 then return the status code Invalid Handle
    if ( ( startHandle > pReq->endHandle ) || ( startHandle == 0 ) )
    {
        return ( ATT_ERR_INVALID_HANDLE );
    }

    rsp.numInfo = 0;

    while ( !done )
    {
        // All attribute types are effectively compared as 128 bit UUIDs, even if
        // a 16 bit UUID is provided in this request or defined for an attribute.
        gattAttribute_t* pAttr = GATT_FindHandleUUID( startHandle, pReq->endHandle,
                                                      NULL, 0, NULL );

        // If the size of the UUID Filter parameter is 0 octets, then all attribute
        // types will be returned. If the size of the UUID Filter parameter is 2 or
        // 16 octets, then only attribute types with this UUID Filter will be returned.
        if ( pAttr == NULL )
        {
            // No more attributes found
            break;
        }

        // Is this the first UUID found?
        if ( rsp.numInfo == 0 )
        {
            // Set the Format field using the first UUID's length
            if ( pAttr->type.len == ATT_BT_UUID_SIZE )
            {
                // A list of 1 or more handles with their 16 bit Bluetooth UUIDs
                rsp.format = ATT_HANDLE_BT_UUID_TYPE;
            }
            else
            {
                // A list of 1 or more handles with their 128 bit UUIDs
                rsp.format = ATT_HANDLE_UUID_TYPE;
            }
        }

        // Copy handle and UUID into the response
        if ( rsp.format == ATT_HANDLE_BT_UUID_TYPE )
        {
            // Handle with its 16 bit Bluetooth UUID
            if ( pAttr->type.len != ATT_BT_UUID_SIZE )
            {
                // It's not possible to include attributes with differing UUID sizes
                // into a single response
                break;
            }

            rsp.info.btPair[rsp.numInfo].handle = pAttr->handle;
            VOID osal_memcpy( rsp.info.btPair[rsp.numInfo].uuid, pAttr->type.uuid, ATT_BT_UUID_SIZE );

            if ( ( ++rsp.numInfo >= ATT_MAX_NUM_HANDLE_BT_UUID ) ||
                    ( pAttr->handle == GATT_MAX_HANDLE ) )
            {
                done = TRUE; // We're done successfully
            }
        }
        else // ATT_HANDLE_UUID_TYPE
        {
            // Handle with its 128 bit Bluetooth UUID
            rsp.info.pair[rsp.numInfo].handle = pAttr->handle;
            VOID osal_memcpy( rsp.info.pair[rsp.numInfo].uuid, pAttr->type.uuid, ATT_UUID_SIZE );

            if ( ( ++rsp.numInfo >= ATT_MAX_NUM_HANDLE_UUID ) ||
                    ( pAttr->handle == GATT_MAX_HANDLE ) )
            {
                done = TRUE; // We're done successfully
            }
        }

        // Update start handle and search again
        startHandle = pAttr->handle + 1;
    }

    // If no attribute is found then return the status code Attribute Not Found
    if ( rsp.numInfo == 0 )
    {
        return ( ATT_ERR_ATTR_NOT_FOUND );
    }

    // Send a Find Info Response back
    VOID ATT_FindInfoRsp( connHandle, &rsp );
    return ( SUCCESS );
}

/*********************************************************************
    @fn      gattProcessFindByTypeVaueReq

    @brief   Process Find By Type Value Request.

    @param   connHandle ? connection message was received on
    @param   pMsg - pointer to message structure

    @return  SUCCESS: Forward the request up to the application
            ATT_ERR_INVALID_HANDLE: Invalid attribute handle
            ATT_ERR_ATTR_NOT_FOUND: Attribute not found
*/
static bStatus_t gattProcessFindByTypeValueReq( uint16 connHandle, attMsg_t* pMsg )
{
    attFindByTypeValueReq_t* pReq = &pMsg->findByTypeValueReq;
    VOID connHandle; // Not used here

    // If the starting handle greater than the ending handle or the starting
    // handle is 0x0000 then return the status code Invalid Handle
    if ( ( pReq->startHandle > pReq->endHandle ) || ( pReq->startHandle == 0 ) )
    {
        return ( ATT_ERR_INVALID_HANDLE );
    }

    // Only attributes with attribute handles between and including the Starting
    // Handle parameter and the Ending Handle parameter that match the requested
    // attribute type and the attribute value will be returned.

    // All attribute types are effectively compared as 128 bit UUIDs, even if
    // a 16 bit UUID is provided in this request or defined for an attribute.
    if ( GATT_FindHandleUUID( pReq->startHandle, pReq->endHandle,
                              pReq->type.uuid, pReq->type.len, NULL ) == NULL )
    {
        // If no attribute with the given type exists within the handle range
        // then return the status code Attribute Not Found
        return ( ATT_ERR_ATTR_NOT_FOUND );
    }

    // Forward the request up to the application
    return ( SUCCESS );
}

/*********************************************************************
    @fn      gattProcessReadByTypeReq

    @brief   Process Read By Type Request.

    @param   connHandle ? connection message was received on
    @param   pMsg - pointer to message structure

    @return  SUCCESS: Forward the request up to the application
            ATT_ERR_INVALID_HANDLE: Invalid attribute handle
            ATT_ERR_ATTR_NOT_FOUND: Attribute not found
*/
static bStatus_t gattProcessReadByTypeReq( uint16 connHandle, attMsg_t* pMsg )
{
    attReadByTypeReq_t* pReq = &pMsg->readByTypeReq;
    VOID connHandle; // Not used here

    // If the starting handle greater than the ending handle or the starting
    // handle is 0x0000 then return the status code Invalid Handle
    if ( ( pReq->startHandle > pReq->endHandle ) || ( pReq->startHandle == 0 ) )
    {
        return ( ATT_ERR_INVALID_HANDLE );
    }

    // Only an attribute with attribute type that is the same as the Type
    // given will be returned. The attribute returned must be the attribute
    // with the lowest handle within the handle range.

    // All attribute types are effectively compared as 128 bit UUIDs, even if
    // a 16 bit UUID is provided in this request or defined for an attribute.
    if ( GATT_FindHandleUUID( pReq->startHandle, pReq->endHandle,
                              pReq->type.uuid, pReq->type.len, NULL ) == NULL )
    {
        // If no attribute with the given type exists within the handle range
        // then return the status code Attribute Not Found
        return ( ATT_ERR_ATTR_NOT_FOUND );
    }

    // Forward the request up to the application
    return ( SUCCESS );
}

/*********************************************************************
    @fn      gattProcessReadReq

    @brief   Process Read Request.

    @param   connHandle ? connection message was received on
    @param   pMsg - pointer to message structure

    @return  SUCCESS: Forward the request up to the application
            ATT_ERR_INVALID_HANDLE: Invalid attribute handle
            ATT_ERR_READ_NOT_PERMITTED: Attribute cannot be read
            ATT_ERR_INSUFFICIENT_AUTHEN: Attribute requires authentication
            ATT_ERR_INSUFFICIENT_KEY_SIZE: Key Size used for encrypting is insufficient
            ATT_ERR_INSUFFICIENT_ENCRYPT: Attribute requires encryption
*/
static bStatus_t gattProcessReadReq( uint16 connHandle, attMsg_t* pMsg )
{
    gattAttribute_t* pAttr;
    uint16 handle = pMsg->readReq.handle;
    // Make sure the handle is valid
    pAttr = GATT_FindHandle( handle, NULL );

    if ( pAttr == NULL )
    {
        return ( ATT_ERR_INVALID_HANDLE );
    }

    // Forward the request up to the application (if reading allowed)
    return ( GATT_VerifyReadPermissions( connHandle, pAttr->permissions ) );
}

/*********************************************************************
    @fn      gattProcessReadMultiReq

    @brief   Process Read Multiple Request.

    @param   connHandle ? connection message was received on
    @param   pMsg - pointer to message structure

    @return  SUCCESS: Forward the request up to the application
            ATT_ERR_INVALID_HANDLE: Invalid attribute handle
            ATT_ERR_READ_NOT_PERMITTED: Attribute cannot be read
            ATT_ERR_INSUFFICIENT_AUTHEN: Attribute requires authentication
            ATT_ERR_INSUFFICIENT_KEY_SIZE: Key Size used for encrypting is insufficient
            ATT_ERR_INSUFFICIENT_ENCRYPT: Attribute requires encryption
*/
static bStatus_t gattProcessReadMultiReq( uint16 connHandle, attMsg_t* pMsg )
{
    attReadMultiReq_t* pReq = &pMsg->readMultiReq;

    // Make sure all the handles are valid and all attributes have sufficient
    // permissions to allow reading.
    for ( uint8 i = 0; i < pReq->numHandles; i++ )
    {
        gattAttribute_t* pAttr;
        uint8 status;
        // Make sure the handle is valid
        pAttr = GATT_FindHandle( pReq->handle[i], NULL );

        if ( pAttr == NULL )
        {
            // The handle of the first attribute causing the error
            pReq->handle[0] = pReq->handle[i];
            return ( ATT_ERR_INVALID_HANDLE );
        }

        // Make sure the attribute has sufficient permissions to allow reading
        status = GATT_VerifyReadPermissions( connHandle, pAttr->permissions );

        if ( status != SUCCESS )
        {
            // The handle of the first attribute causing the error
            pReq->handle[0] = pReq->handle[i];
            return ( status );
        }
    }

    // Forward the request up to the application
    return ( SUCCESS );
}

/*********************************************************************
    @fn      gattProcessReadByGrpTypeReq

    @brief   Process Read By Group Type Request.

    @param   connHandle ? connection message was received on
    @param   pMsg - pointer to message structure

    @return  SUCCESS: Forward the request up to the application
            ATT_ERR_INVALID_HANDLE: Invalid attribute handle
            ATT_ERR_UNSUPPORTED_GRP_TYPE: Group attribute type not supported
            ATT_ERR_ATTR_NOT_FOUND: Attribute not found
*/
static bStatus_t gattProcessReadByGrpTypeReq( uint16 connHandle, attMsg_t* pMsg )
{
    attReadByGrpTypeReq_t* pReq = &pMsg->readByGrpTypeReq;
    VOID connHandle; // Not used here

    // If the starting handle greater than the ending handle or the starting
    // handle is 0x0000 then return the status code Invalid Handle
    if ( ( pReq->startHandle > pReq->endHandle ) || ( pReq->startHandle == 0 ) )
    {
        return ( ATT_ERR_INVALID_HANDLE );
    }

    // If the Attribute Group Type is not a supported grouping attribute
    // the return the status code Unsupported Group Type
    if ( !gattPrimaryServiceType( pReq->type ) )
    {
        return ( ATT_ERR_UNSUPPORTED_GRP_TYPE );
    }

    // Only the attributes with attribute handles between and including the
    // Starting Handle and the Ending Handle with the attribute type that
    // is the same as the Attribute Group Type given will be returned.

    // All attribute types are effectively compared as 128 bit UUIDs, even if
    // a 16 bit UUID is provided in this request or defined for an attribute.
    if ( GATT_FindHandleUUID( pReq->startHandle, pReq->endHandle,
                              pReq->type.uuid, pReq->type.len, NULL ) == NULL )
    {
        // If no attribute with the given type exists within the handle range
        // then return the status code Attribute Not Found
        return ( ATT_ERR_ATTR_NOT_FOUND );
    }

    // Forward the request up to the application
    return ( SUCCESS );
}

/*********************************************************************
    @fn      gattProcessWriteReq

    @brief   Process Write Request or Command.

    @param   connHandle ? connection message was received on
    @param   pMsg - pointer to message structure

    @return  SUCCESS: Forward the request up to the application
            ATT_ERR_READ_NOT_PERMITTED: Attribute cannot be written
            ATT_ERR_INSUFFICIENT_AUTHEN: Attribute requires authentication
            ATT_ERR_INSUFFICIENT_KEY_SIZE: Key Size used for encrypting is insufficient
            ATT_ERR_INSUFFICIENT_ENCRYPT: Attribute requires encryption
*/
static bStatus_t gattProcessWriteReq( uint16 connHandle, attMsg_t* pMsg )
{
    gattAttribute_t* pAttr;
    attWriteReq_t* pReq = &(pMsg->writeReq);
    // Make sure the handle is valid
    pAttr = GATT_FindHandle( pReq->handle, NULL );

    if ( pAttr == NULL )
    {
        return ( ATT_ERR_INVALID_HANDLE );
    }

    // Forward the request up to the application (if writting allowed)
    return ( GATT_VerifyWritePermissions( connHandle, pAttr->permissions, pReq ) );
}

/*********************************************************************
    @fn      gattProcessExecuteWriteReq

    @brief   Process Execute Write Request.

    @param   connHandle ? connection message was received on
    @param   pMsg - pointer to message structure

    @return  SUCCESS: Forward the request up to the application
*/
static bStatus_t gattProcessExecuteWriteReq( uint16 connHandle, attMsg_t* pMsg )
{
    VOID connHandle; // Not used here
    VOID pMsg; // Not used here
    // Forward the request up to the application
    return ( SUCCESS );
}

/*********************************************************************
    @fn          gattGetServerStatus

    @brief       Get the status for a given server.

    @param       connHandle - client connection to server
    @param       p2pServer - pointer to server info (to be returned)

    @return      SUCCESS: No confirmation pending
                INVALIDPARAMETER: Invalid connection handle
                blePending: Confirmation pending
                bleTimeout: Previous transaction timed out
*/
static bStatus_t gattGetServerStatus( uint16 connHandle, gattServerInfo_t** p2pServer )
{
    gattServerInfo_t* pServer;
    pServer = gattFindServerInfo( connHandle );

    if ( pServer != NULL )
    {
        if ( p2pServer != NULL )
        {
            *p2pServer = pServer;
        }

        // Make sure there's no confirmation pending or timed out with this client
        return ( TIMER_STATUS( pServer->timerId ) );
    }

    // Connection handle not found
    return ( INVALIDPARAMETER );
}

/*********************************************************************
    @fn      attFindServerInfo

    @brief   Find the server info.  Uses the connection handle to search
            the server info table.

    @param   connHandle - connection handle.

    @return  a pointer to the found item. NULL, otherwise.
*/
static gattServerInfo_t* gattFindServerInfo( uint16 connHandle )
{
    uint8 i;

    for ( i = 0; i < GATT_MAX_NUM_CONN; i++ )
    {
        if ( serverInfoTbl[i].connHandle == connHandle )
        {
            // Entry found
            return ( &serverInfoTbl[i] );
        }
    }

    return ( (gattServerInfo_t*)NULL );
}

/*********************************************************************
    @fn      gattResetServerInfo

    @brief   Reset the server info.

    @param   pServer - pointer to server info.

    @return  void
*/
static void gattResetServerInfo( gattServerInfo_t* pServer )
{
    // Cancel the confirmation timer
    gattStopTimer( &pServer->timerId );
    // Reset confirmation info
    pServer->taskId = INVALID_TASK_ID;
}

/*********************************************************************
    @fn      attServertStartTimer

    @brief   Start a server timer to expire in n seconds.

    @param   pData - data to be passed in to callback function
    @param   timeout - in milliseconds.
    @param   pTimerId - will point to new timer Id (if not null)

    @return  void
*/
static void gattServerStartTimer( uint8* pData, uint16 timeout, uint8* pTimerId )
{
    gattStartTimer( gattServerHandleTimerCB, pData, timeout, pTimerId );
}

/*********************************************************************
    @fn      gattServerHandleTimerCB

    @brief   Handle a callback for a timer that has just expired.

    @param   pData - pointer to timer data

    @return  void
*/
static void gattServerHandleTimerCB( uint8* pData )
{
    gattServerInfo_t* pServer = (gattServerInfo_t*)pData;

    // Response timer has expired
    if ( ( pServer != NULL ) && TIMER_VALID( pServer->timerId ) )
    {
        // Notify the application about the timeout
        VOID gattNotifyEvent( pServer->taskId, pServer->connHandle, bleTimeout,
                              ATT_HANDLE_VALUE_CFM, NULL );
        // Timer has expired. If a transaction has not completed before it times
        // out, then this transaction shall be considered to have failed. No more
        // attribute protocol requests, commands, indications or notifications
        // shall be sent to the target device on this ATT Bearer.
        pServer->timerId = TIMEOUT_TIMER_ID;
        // Reset confirmation info
        pServer->taskId = INVALID_TASK_ID;
    }
}

/*********************************************************************
    @fn          gattServerHandleConnStatusCB

    @brief       GATT link status change handler function.

    @param       connHandle - connection handle
    @param       changeType - type of change

    @return      void
*/
static void gattServerHandleConnStatusCB( uint16 connHandle, uint8 changeType )
{
    gattServerInfo_t* pServer = NULL;

    // Check to see if this is loopback connection
    if ( connHandle == LOOPBACK_CONNHANDLE )
    {
        return;
    }

    if ( changeType == LINKDB_STATUS_UPDATE_NEW )
    {
        // A new connection has been made
        pServer = gattFindServerInfo( connHandle );

        if ( pServer == NULL )
        {
            // Entry not found; add it to the server table
            pServer = gattFindServerInfo( INVALID_CONNHANDLE );

            if ( pServer != NULL )
            {
                // Empty entry found
                pServer->connHandle = connHandle;
            }
        }

        // We're done here!
        return;
    }

    if ( changeType == LINKDB_STATUS_UPDATE_REMOVED )
    {
        pServer = gattFindServerInfo( connHandle );

        if ( pServer != NULL )
        {
            // Entry found; remove it from the server table
            pServer->connHandle = INVALID_CONNHANDLE;
        }
    }
    else if ( changeType == LINKDB_STATUS_UPDATE_STATEFLAGS )
    {
        // Check to see if the connection has dropped
        if ( !linkDB_Up( connHandle ) )
        {
            pServer = gattFindServerInfo( connHandle );
        }
    }

    // Connection has dropped; notify the application
    if ( pServer != NULL )
    {
        if ( pServer->timerId != INVALID_TIMER_ID )
        {
            if ( pServer->timerId != TIMEOUT_TIMER_ID )
            {
                // Notify the application about the link disconnect
                VOID gattNotifyEvent( pServer->taskId, connHandle, bleNotConnected,
                                      ATT_HANDLE_VALUE_CFM, NULL );
            }

            // Reset server info
            gattResetServerInfo( pServer );
            // Just in case if we've timed out waiting for a confirmation
            pServer->timerId = INVALID_TIMER_ID;
        }
    }
}

/*********************************************************************
    @fn          GATT_SetNextHandle

    @brief       Set the next available attribute handle.

    @param       handle - next attribute handle

    @return      void
*/
void GATT_SetNextHandle( uint16 handle )
{
    if ( handle >= nextHandle )
    {
        #if defined ( GATT_QUAL )
        // Set next available attribute handle
        nextHandle = handle;
        #endif // GATT_QUAL
    }
}

/****************************************************************************
****************************************************************************/
