/******************************************************************************
 *
 *  Copyright (C) 2010-2014 Broadcom Corporation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at:
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 ******************************************************************************/


/******************************************************************************
 *
 *  This file contains the LLCP utilities
 *
 ******************************************************************************/

#include <string.h>
#include "gki.h"
#include "nfc_target.h"
#include "bt_types.h"
#include "trace_api.h"
#include "llcp_int.h"
#include "llcp_defs.h"
#include "nfc_int.h"

/*******************************************************************************
**
** Function         llcp_util_parse_link_params
**
** Description      Parse LLCP Link parameters
**
** Returns          TRUE if success
**
*******************************************************************************/
BOOLEAN llcp_util_parse_link_params (UINT16 length, UINT8 *p_bytes)
{
    UINT8 param_type, param_len, *p = p_bytes;

    while (length)
    {
        BE_STREAM_TO_UINT8 (param_type, p);
        length--;

        switch (param_type)
        {
        case LLCP_VERSION_TYPE:
            BE_STREAM_TO_UINT8 (param_len, p);
            BE_STREAM_TO_UINT8 (llcp_cb.lcb.peer_version, p);
            LLCP_TRACE_DEBUG1 ("Peer Version - 0x%02X", llcp_cb.lcb.peer_version);
            break;

        case LLCP_MIUX_TYPE:
            BE_STREAM_TO_UINT8 (param_len, p);
            BE_STREAM_TO_UINT16 (llcp_cb.lcb.peer_miu, p);
            llcp_cb.lcb.peer_miu &= LLCP_MIUX_MASK;
            llcp_cb.lcb.peer_miu += LLCP_DEFAULT_MIU;
            LLCP_TRACE_DEBUG1 ("Peer MIU - %d bytes", llcp_cb.lcb.peer_miu);
            break;

        case LLCP_WKS_TYPE:
            BE_STREAM_TO_UINT8 (param_len, p);
            BE_STREAM_TO_UINT16 (llcp_cb.lcb.peer_wks, p);
            LLCP_TRACE_DEBUG1 ("Peer WKS - 0x%04X", llcp_cb.lcb.peer_wks);
            break;

        case LLCP_LTO_TYPE:
            BE_STREAM_TO_UINT8 (param_len, p);
            BE_STREAM_TO_UINT8 (llcp_cb.lcb.peer_lto, p);
            llcp_cb.lcb.peer_lto *= LLCP_LTO_UNIT;  /* 10ms unit */
            LLCP_TRACE_DEBUG1 ("Peer LTO - %d ms", llcp_cb.lcb.peer_lto);
            break;

        case LLCP_OPT_TYPE:
            BE_STREAM_TO_UINT8 (param_len, p);
            BE_STREAM_TO_UINT8 (llcp_cb.lcb.peer_opt, p);
            LLCP_TRACE_DEBUG1 ("Peer OPT - 0x%02X", llcp_cb.lcb.peer_opt);
            break;

        default:
            LLCP_TRACE_ERROR1 ("llcp_util_parse_link_params (): Unexpected type 0x%x", param_type);
            BE_STREAM_TO_UINT8 (param_len, p);
            p += param_len;
            break;
        }

        if (length >= param_len + 1)
            length -= param_len + 1;
        else
        {
            LLCP_TRACE_ERROR0 ("llcp_util_parse_link_params (): Bad LTV's");
            return (FALSE);
        }
    }
    return (TRUE);
}

/*******************************************************************************
**
** Function         llcp_util_adjust_ll_congestion
**
** Description      adjust tx/rx congestion thresholds on logical link
**
** Returns          void
**
*******************************************************************************/
void llcp_util_adjust_ll_congestion (void)
{
    /* buffer quota is allocated equally for each logical data link */
    if (llcp_cb.num_logical_data_link)
    {
        llcp_cb.ll_tx_congest_start = llcp_cb.max_num_ll_tx_buff / llcp_cb.num_logical_data_link;
        llcp_cb.ll_rx_congest_start = llcp_cb.max_num_ll_rx_buff / llcp_cb.num_logical_data_link;
    }
    else
    {
        llcp_cb.ll_tx_congest_start = llcp_cb.max_num_ll_tx_buff;
        llcp_cb.ll_rx_congest_start = llcp_cb.max_num_ll_rx_buff;
    }

    /* at least one for each logical data link */
    if (llcp_cb.ll_tx_congest_start == 0)
    {
        llcp_cb.ll_tx_congest_start = 1;
    }
    if (llcp_cb.ll_rx_congest_start == 0)
    {
        llcp_cb.ll_rx_congest_start = 1;
    }

    if (llcp_cb.ll_tx_congest_start > 1)
    {
        llcp_cb.ll_tx_congest_end = 1;
    }
    else
    {
        llcp_cb.ll_tx_congest_end = 0;
    }

    LLCP_TRACE_DEBUG4 ("num_logical_data_link=%d, ll_tx_congest_start=%d, ll_tx_congest_end=%d, ll_rx_congest_start=%d",
                       llcp_cb.num_logical_data_link,
                       llcp_cb.ll_tx_congest_start,
                       llcp_cb.ll_tx_congest_end,
                       llcp_cb.ll_rx_congest_start);
}

/*******************************************************************************
**
** Function         llcp_util_adjust_dl_rx_congestion
**
** Description      adjust rx congestion thresholds on data link
**
** Returns          void
**
*******************************************************************************/
void llcp_util_adjust_dl_rx_congestion (void)
{
    UINT8 idx, rx_congest_start;

    if (llcp_cb.num_data_link_connection)
    {
        rx_congest_start = llcp_cb.num_rx_buff / llcp_cb.num_data_link_connection;

        for (idx = 0; idx < LLCP_MAX_DATA_LINK; idx++)
        {
            if (llcp_cb.dlcb[idx].state == LLCP_DLC_STATE_CONNECTED)
            {
                if (rx_congest_start > llcp_cb.dlcb[idx].local_rw)
                {
                    /*
                    ** set rx congestion threshold LLCP_DL_MIN_RX_CONGEST at least
                    ** so, we don't need to flow off too often.
                    */
                    if (llcp_cb.dlcb[idx].local_rw + 1 > LLCP_DL_MIN_RX_CONGEST)
                        llcp_cb.dlcb[idx].rx_congest_threshold = llcp_cb.dlcb[idx].local_rw + 1;
                    else
                        llcp_cb.dlcb[idx].rx_congest_threshold = LLCP_DL_MIN_RX_CONGEST;
                }
                else
                {
                    llcp_cb.dlcb[idx].rx_congest_threshold = LLCP_DL_MIN_RX_CONGEST;
                }

                LLCP_TRACE_DEBUG3 ("DLC[%d], local_rw=%d, rx_congest_threshold=%d",
                                   idx,
                                   llcp_cb.dlcb[idx].local_rw,
                                   llcp_cb.dlcb[idx].rx_congest_threshold);
            }
        }
    }

}

/*******************************************************************************
**
** Function         llcp_util_check_rx_congested_status
**
** Description      Update rx congested status
**
** Returns          void
**
*******************************************************************************/
void llcp_util_check_rx_congested_status (void)
{
    UINT8 idx;

    if (llcp_cb.overall_rx_congested)
    {
        /* check if rx congestion clear */
        if (llcp_cb.total_rx_ui_pdu + llcp_cb.total_rx_i_pdu <= llcp_cb.overall_rx_congest_end)
        {
            LLCP_TRACE_DEBUG3 ("llcp_util_check_rx_congested_status (): rx link is uncongested, %d+%d <= %d",
                                llcp_cb.total_rx_ui_pdu, llcp_cb.total_rx_i_pdu,
                                llcp_cb.overall_rx_congest_end);

            llcp_cb.overall_rx_congested = FALSE;

            for (idx = 0; idx < LLCP_MAX_DATA_LINK; idx++)
            {
                /* set flag to clear local busy status on data link connections */
                if (  (llcp_cb.dlcb[idx].state == LLCP_DLC_STATE_CONNECTED)
                    &&(llcp_cb.dlcb[idx].is_rx_congested == FALSE)  )
                {
                    llcp_cb.dlcb[idx].flags |= LLCP_DATA_LINK_FLAG_PENDING_RR_RNR;
                }
            }
        }
    }
    else
    {
        /* check if rx link is congested */
        if (llcp_cb.total_rx_ui_pdu + llcp_cb.total_rx_i_pdu >= llcp_cb.overall_rx_congest_start)
        {
            LLCP_TRACE_WARNING3 ("llcp_util_check_rx_congested_status (): rx link is congested, %d+%d >= %d",
                                  llcp_cb.total_rx_ui_pdu, llcp_cb.total_rx_i_pdu,
                                  llcp_cb.overall_rx_congest_start);

            llcp_cb.overall_rx_congested = TRUE;

            /* rx link congestion is started, send RNR to remote end point */
            for (idx = 0; idx < LLCP_MAX_DATA_LINK; idx++)
            {
                if (  (llcp_cb.dlcb[idx].state == LLCP_DLC_STATE_CONNECTED)
                    &&(llcp_cb.dlcb[idx].is_rx_congested == FALSE)  )
                {
                    llcp_cb.dlcb[idx].flags |= LLCP_DATA_LINK_FLAG_PENDING_RR_RNR;
                }
            }
        }
    }
}

/*******************************************************************************
**
** Function         llcp_util_send_ui
**
** Description      Send UI PDU
**
** Returns          tLLCP_STATUS
**
*******************************************************************************/
tLLCP_STATUS llcp_util_send_ui (UINT8 ssap, UINT8 dsap, tLLCP_APP_CB *p_app_cb, BT_HDR *p_msg)
{
    UINT8        *p;
    tLLCP_STATUS status = LLCP_STATUS_SUCCESS;

    p_msg->offset -= LLCP_PDU_HEADER_SIZE;
    p_msg->len    += LLCP_PDU_HEADER_SIZE;

    p = (UINT8 *) (p_msg + 1) + p_msg->offset;
    UINT16_TO_BE_STREAM (p, LLCP_GET_PDU_HEADER (dsap, LLCP_PDU_UI_TYPE, ssap));

    GKI_enqueue (&p_app_cb->ui_xmit_q, p_msg);
    llcp_cb.total_tx_ui_pdu++;

    llcp_link_check_send_data ();

    if (  (p_app_cb->is_ui_tx_congested)
        ||(p_app_cb->ui_xmit_q.count >= llcp_cb.ll_tx_congest_start)
        ||(llcp_cb.overall_tx_congested)
        ||(llcp_cb.total_tx_ui_pdu >= llcp_cb.max_num_ll_tx_buff)  )
    {
        /* set congested here so overall congestion check routine will not report event again, */
        /* or notify uncongestion later                                                        */
        p_app_cb->is_ui_tx_congested = TRUE;

        LLCP_TRACE_WARNING2 ("Logical link (SAP=0x%X) congested: ui_xmit_q.count=%d",
                              ssap, p_app_cb->ui_xmit_q.count);

        status = LLCP_STATUS_CONGESTED;
    }

    return status;
}

/*******************************************************************************
**
** Function         llcp_util_send_disc
**
** Description      Send DISC PDU
**
** Returns          void
**
*******************************************************************************/
void llcp_util_send_disc (UINT8 dsap, UINT8 ssap)
{
    BT_HDR *p_msg;
    UINT8  *p;

    p_msg = (BT_HDR*) GKI_getpoolbuf (LLCP_POOL_ID);

    if (p_msg)
    {
        p_msg->len      = LLCP_PDU_DISC_SIZE;
        p_msg->offset   = NCI_MSG_OFFSET_SIZE + NCI_DATA_HDR_SIZE;

        p = (UINT8 *) (p_msg + 1) + p_msg->offset;
        UINT16_TO_BE_STREAM (p, LLCP_GET_PDU_HEADER (dsap, LLCP_PDU_DISC_TYPE, ssap));

        GKI_enqueue (&llcp_cb.lcb.sig_xmit_q, p_msg);
        llcp_link_check_send_data ();
    }
}

/*******************************************************************************
**
** Function         llcp_util_allocate_data_link
**
** Description      Allocate tLLCP_DLCB for data link connection
**
** Returns          tLLCP_DLCB *
**
******************************************************************************/
tLLCP_DLCB *llcp_util_allocate_data_link (UINT8 reg_sap, UINT8 remote_sap)
{
    tLLCP_DLCB *p_dlcb = NULL;
    int         idx;

    LLCP_TRACE_DEBUG2 ("llcp_util_allocate_data_link (): reg_sap = 0x%x, remote_sap = 0x%x",
                        reg_sap, remote_sap);

    for (idx = 0; idx < LLCP_MAX_DATA_LINK; idx++)
    {
        if (llcp_cb.dlcb[idx].state == LLCP_DLC_STATE_IDLE)
        {
            p_dlcb = &(llcp_cb.dlcb[idx]);

            memset (p_dlcb, 0, sizeof (tLLCP_DLCB));
            break;
        }
    }

    if (!p_dlcb)
    {
        LLCP_TRACE_ERROR0 ("llcp_util_allocate_data_link (): Out of DLCB");
    }
    else
    {
        p_dlcb->p_app_cb    = llcp_util_get_app_cb (reg_sap);
        p_dlcb->local_sap   = reg_sap;
        p_dlcb->remote_sap  = remote_sap;
        p_dlcb->timer.param = (TIMER_PARAM_TYPE) p_dlcb;

        /* this is for inactivity timer and congestion control. */
        llcp_cb.num_data_link_connection++;

        LLCP_TRACE_DEBUG3 ("llcp_util_allocate_data_link (): local_sap = 0x%x, remote_sap = 0x%x, num_data_link_connection = %d",
                            p_dlcb->local_sap, p_dlcb->remote_sap, llcp_cb.num_data_link_connection);
    }
    return p_dlcb;
}

/*******************************************************************************
**
** Function         llcp_util_deallocate_data_link
**
** Description      Deallocate tLLCP_DLCB
**
** Returns          void
**
******************************************************************************/
void llcp_util_deallocate_data_link (tLLCP_DLCB *p_dlcb)
{
    if (p_dlcb)
    {
        LLCP_TRACE_DEBUG1 ("llcp_util_deallocate_data_link (): local_sap = 0x%x", p_dlcb->local_sap);

        if (p_dlcb->state != LLCP_DLC_STATE_IDLE)
        {
            nfc_stop_quick_timer (&p_dlcb->timer);
            llcp_dlc_flush_q (p_dlcb);

            p_dlcb->state = LLCP_DLC_STATE_IDLE;

            if (llcp_cb.num_data_link_connection > 0)
            {
                llcp_cb.num_data_link_connection--;
            }

            LLCP_TRACE_DEBUG1 ("llcp_util_deallocate_data_link (): num_data_link_connection = %d", llcp_cb.num_data_link_connection);
        }
    }
}

/*******************************************************************************
**
** Function         llcp_util_send_connect
**
** Description      Send CONNECT PDU
**
** Returns          tLLCP_STATUS
**
******************************************************************************/
tLLCP_STATUS llcp_util_send_connect (tLLCP_DLCB *p_dlcb, tLLCP_CONNECTION_PARAMS *p_params)
{
    BT_HDR *p_msg;
    UINT8  *p;
    UINT16  miu_len = 0, rw_len = 0, sn_len = 0;

    if (p_params->miu != LLCP_DEFAULT_MIU)
    {
        miu_len = 4;    /* TYPE, LEN, 2 bytes MIU */
    }
    if (p_params->rw != LLCP_DEFAULT_RW)
    {
        rw_len = 3;     /* TYPE, LEN, 1 byte RW */
        p_params->rw &= 0x0F;   /* only 4 bits  */
    }
    if ((strlen (p_params->sn)) && (p_dlcb->remote_sap == LLCP_SAP_SDP))
    {
        sn_len = (UINT16) (2 + strlen (p_params->sn));    /* TYPE, LEN, SN */
    }

    p_msg = (BT_HDR*) GKI_getpoolbuf (LLCP_POOL_ID);

    if (p_msg)
    {
        p_msg->len    = LLCP_PDU_HEADER_SIZE + miu_len + rw_len + sn_len;
        p_msg->offset = NCI_MSG_OFFSET_SIZE + NCI_DATA_HDR_SIZE;

        p = (UINT8 *) (p_msg + 1) + p_msg->offset;

        UINT16_TO_BE_STREAM (p, LLCP_GET_PDU_HEADER (p_dlcb->remote_sap, LLCP_PDU_CONNECT_TYPE, p_dlcb->local_sap));

        if (miu_len)
        {
            UINT8_TO_BE_STREAM (p, LLCP_MIUX_TYPE);
            UINT8_TO_BE_STREAM (p, LLCP_MIUX_LEN);
            UINT16_TO_BE_STREAM (p, p_params->miu - LLCP_DEFAULT_MIU);
        }

        if (rw_len)
        {
            UINT8_TO_BE_STREAM (p, LLCP_RW_TYPE);
            UINT8_TO_BE_STREAM (p, LLCP_RW_LEN);
            UINT8_TO_BE_STREAM (p, p_params->rw);
        }

        if (sn_len)
        {
            UINT8_TO_BE_STREAM (p, LLCP_SN_TYPE);
            UINT8_TO_BE_STREAM (p, sn_len - 2);
            memcpy (p, p_params->sn, sn_len - 2);
        }

        GKI_enqueue (&llcp_cb.lcb.sig_xmit_q, p_msg);
        llcp_link_check_send_data ();

        return LLCP_STATUS_SUCCESS;
    }

    return LLCP_STATUS_FAIL;
}

/*******************************************************************************
**
** Function         llcp_util_parse_connect
**
** Description      Parse CONNECT PDU
**
** Returns          tLLCP_STATUS
**
*******************************************************************************/
tLLCP_STATUS llcp_util_parse_connect (UINT8  *p_bytes, UINT16 length, tLLCP_CONNECTION_PARAMS *p_params)
{
    UINT8 param_type, param_len, *p = p_bytes;

    p_params->miu = LLCP_DEFAULT_MIU;
    p_params->rw  = LLCP_DEFAULT_RW;
    p_params->sn[0] = 0;
    p_params->sn[1] = 0;

    while (length)
    {
        BE_STREAM_TO_UINT8 (param_type, p);
        length--;

        switch (param_type)
        {
        case LLCP_MIUX_TYPE:
            BE_STREAM_TO_UINT8 (param_len, p);
            BE_STREAM_TO_UINT16 (p_params->miu, p);
            p_params->miu &= LLCP_MIUX_MASK;
            p_params->miu += LLCP_DEFAULT_MIU;

            LLCP_TRACE_DEBUG1 ("llcp_util_parse_connect (): LLCP_MIUX_TYPE:%d", p_params->miu);
            break;

        case LLCP_RW_TYPE:
            BE_STREAM_TO_UINT8 (param_len, p);
            BE_STREAM_TO_UINT8 (p_params->rw, p);
            p_params->rw &= 0x0F;

            LLCP_TRACE_DEBUG1 ("llcp_util_parse_connect (): LLCP_RW_TYPE:%d", p_params->rw);
            break;

        case LLCP_SN_TYPE:
            BE_STREAM_TO_UINT8 (param_len, p);

            if (param_len == 0)
            {
                /* indicate that SN type is included without SN */
                p_params->sn[1] = LLCP_SN_TYPE;
            }
            else if (param_len <= LLCP_MAX_SN_LEN)
            {
                memcpy (p_params->sn, p, param_len);
                p_params->sn[param_len] = 0;
            }
            else
            {
                memcpy (p_params->sn, p, LLCP_MAX_SN_LEN);
                p_params->sn[LLCP_MAX_SN_LEN] = 0;
            }
            p += param_len;

            LLCP_TRACE_DEBUG1 ("llcp_util_parse_connect (): LLCP_SN_TYPE:<%s>", p_params->sn);
            break;

        default:
            LLCP_TRACE_ERROR1 ("llcp_util_parse_connect (): Unexpected type 0x%x", param_type);
            BE_STREAM_TO_UINT8 (param_len, p);
            p += param_len;
            break;
        }

        /* check remaining lengh */
        if (length >= param_len + 1)
        {
            length -= param_len + 1;
        }
        else
        {
            LLCP_TRACE_ERROR0 ("llcp_util_parse_connect (): Bad LTV's");
            return LLCP_STATUS_FAIL;
        }
    }
    return LLCP_STATUS_SUCCESS;
}

/*******************************************************************************
**
** Function         llcp_util_send_cc
**
** Description      Send CC PDU
**
** Returns          tLLCP_STATUS
**
******************************************************************************/
tLLCP_STATUS llcp_util_send_cc (tLLCP_DLCB *p_dlcb, tLLCP_CONNECTION_PARAMS *p_params)
{
    BT_HDR *p_msg;
    UINT8  *p;
    UINT16  miu_len = 0, rw_len = 0;

    if (p_params->miu != LLCP_DEFAULT_MIU)
    {
        miu_len = 4;
    }
    if (p_params->rw != LLCP_DEFAULT_RW)
    {
        rw_len = 3;
        p_params->rw &= 0x0F;
    }

    p_msg = (BT_HDR*) GKI_getpoolbuf (LLCP_POOL_ID);

    if (p_msg)
    {
        p_msg->len      = LLCP_PDU_HEADER_SIZE + miu_len + rw_len;
        p_msg->offset   = NCI_MSG_OFFSET_SIZE + NCI_DATA_HDR_SIZE;

        p = (UINT8 *) (p_msg + 1) + p_msg->offset;

        UINT16_TO_BE_STREAM (p, LLCP_GET_PDU_HEADER (p_dlcb->remote_sap, LLCP_PDU_CC_TYPE, p_dlcb->local_sap));

        if (miu_len)
        {
            UINT8_TO_BE_STREAM (p, LLCP_MIUX_TYPE);
            UINT8_TO_BE_STREAM (p, LLCP_MIUX_LEN);
            UINT16_TO_BE_STREAM (p, p_params->miu - LLCP_DEFAULT_MIU);
        }

        if (rw_len)
        {
            UINT8_TO_BE_STREAM (p, LLCP_RW_TYPE);
            UINT8_TO_BE_STREAM (p, LLCP_RW_LEN);
            UINT8_TO_BE_STREAM (p, p_params->rw);
        }

        GKI_enqueue (&llcp_cb.lcb.sig_xmit_q, p_msg);
        llcp_link_check_send_data ();

        return LLCP_STATUS_SUCCESS;
    }

    return LLCP_STATUS_FAIL;
}

/*******************************************************************************
**
** Function         llcp_util_parse_cc
**
** Description      Parse CC PDU
**
** Returns          tLLCP_STATUS
**
*******************************************************************************/
tLLCP_STATUS llcp_util_parse_cc (UINT8 *p_bytes, UINT16 length, UINT16 *p_miu, UINT8 *p_rw)
{
    UINT8 param_type, param_len, *p = p_bytes;

    *p_miu = LLCP_DEFAULT_MIU;
    *p_rw  = LLCP_DEFAULT_RW;

    while (length)
    {
        BE_STREAM_TO_UINT8 (param_type, p);
        length--;

        switch (param_type)
        {
        case LLCP_MIUX_TYPE:
            BE_STREAM_TO_UINT8 (param_len, p);
            BE_STREAM_TO_UINT16 ((*p_miu), p);
            (*p_miu) &= LLCP_MIUX_MASK;
            (*p_miu) += LLCP_DEFAULT_MIU;

            LLCP_TRACE_DEBUG1 ("llcp_util_parse_cc (): LLCP_MIUX_TYPE:%d", *p_miu);
            break;

        case LLCP_RW_TYPE:
            BE_STREAM_TO_UINT8 (param_len, p);
            BE_STREAM_TO_UINT8 ((*p_rw), p);
            (*p_rw) &= 0x0F;

            LLCP_TRACE_DEBUG1 ("llcp_util_parse_cc (): LLCP_RW_TYPE:%d", *p_rw);
            break;

        default:
            LLCP_TRACE_ERROR1 ("llcp_util_parse_cc (): Unexpected type 0x%x", param_type);
            BE_STREAM_TO_UINT8 (param_len, p);
            p += param_len;
            break;
        }

        if (length >= param_len + 1)
            length -= param_len + 1;
        else
        {
            LLCP_TRACE_ERROR0 ("llcp_util_parse_cc (): Bad LTV's");
            return LLCP_STATUS_FAIL;
        }
    }
    return LLCP_STATUS_SUCCESS;
}

/*******************************************************************************
**
** Function         llcp_util_send_dm
**
** Description      Send DM PDU
**
** Returns          void
**
*******************************************************************************/
void llcp_util_send_dm (UINT8 dsap, UINT8 ssap, UINT8 reason)
{
    BT_HDR *p_msg;
    UINT8  *p;

    p_msg = (BT_HDR*) GKI_getpoolbuf (LLCP_POOL_ID);

    if (p_msg)
    {
        p_msg->len    = LLCP_PDU_DM_SIZE;
        p_msg->offset = NCI_MSG_OFFSET_SIZE + NCI_DATA_HDR_SIZE;

        p = (UINT8 *) (p_msg + 1) + p_msg->offset;
        UINT16_TO_BE_STREAM (p, LLCP_GET_PDU_HEADER (dsap, LLCP_PDU_DM_TYPE, ssap));
        UINT8_TO_BE_STREAM  (p, reason);

        GKI_enqueue (&llcp_cb.lcb.sig_xmit_q, p_msg);
        llcp_link_check_send_data ();
    }
}

/*******************************************************************************
**
** Function         llcp_util_build_info_pdu
**
** Description      Add DSAP, PTYPE, SSAP and sequence numbers and update local ack
**                  sequence
**
** Returns          void
**
*******************************************************************************/
void llcp_util_build_info_pdu (tLLCP_DLCB *p_dlcb, BT_HDR *p_msg)
{
    UINT8  *p;
    UINT8  rcv_seq;

    p_msg->offset -= LLCP_PDU_HEADER_SIZE + LLCP_SEQUENCE_SIZE;
    p_msg->len    += LLCP_PDU_HEADER_SIZE + LLCP_SEQUENCE_SIZE;
    p = (UINT8 *) (p_msg + 1) + p_msg->offset;

    UINT16_TO_BE_STREAM (p, LLCP_GET_PDU_HEADER (p_dlcb->remote_sap, LLCP_PDU_I_TYPE, p_dlcb->local_sap));

    /* if local_busy or rx congested then do not update receive sequence number to flow off */
    if (  (p_dlcb->local_busy)
        ||(p_dlcb->is_rx_congested)
        ||(llcp_cb.overall_rx_congested)  )
    {
        rcv_seq = p_dlcb->sent_ack_seq;
    }
    else
    {
        p_dlcb->sent_ack_seq = p_dlcb->next_rx_seq;
        rcv_seq = p_dlcb->sent_ack_seq;
    }
    UINT8_TO_BE_STREAM  (p, LLCP_GET_SEQUENCE (p_dlcb->next_tx_seq, rcv_seq));
}

/*******************************************************************************
**
** Function         llcp_util_send_frmr
**
** Description      Send FRMR PDU
**
** Returns          tLLCP_STATUS
**
*******************************************************************************/
tLLCP_STATUS llcp_util_send_frmr (tLLCP_DLCB *p_dlcb, UINT8 flags, UINT8 ptype, UINT8 sequence)
{
    BT_HDR *p_msg;
    UINT8  *p;

    p_msg = (BT_HDR*) GKI_getpoolbuf (LLCP_POOL_ID);

    if (p_msg)
    {
        p_msg->len      = LLCP_PDU_FRMR_SIZE;
        p_msg->offset   = NCI_MSG_OFFSET_SIZE + NCI_DATA_HDR_SIZE;

        p = (UINT8 *) (p_msg + 1) + p_msg->offset;

        UINT16_TO_BE_STREAM (p, LLCP_GET_PDU_HEADER (p_dlcb->remote_sap, LLCP_PDU_FRMR_TYPE, p_dlcb->local_sap));
        UINT8_TO_BE_STREAM (p, (flags << 4) | ptype);
        UINT8_TO_BE_STREAM (p, sequence);
        UINT8_TO_BE_STREAM (p, (p_dlcb->next_tx_seq << 4) | p_dlcb->next_rx_seq);
        UINT8_TO_BE_STREAM (p, (p_dlcb->rcvd_ack_seq << 4) | p_dlcb->sent_ack_seq);

        GKI_enqueue (&llcp_cb.lcb.sig_xmit_q, p_msg);
        llcp_link_check_send_data ();

        return LLCP_STATUS_SUCCESS;
    }
    else
    {
        LLCP_TRACE_ERROR0 ("llcp_util_send_frmr (): Out of resource");
        return LLCP_STATUS_FAIL;
    }
}

/*******************************************************************************
**
** Function         llcp_util_send_rr_rnr
**
** Description      Send RR or RNR PDU
**
** Returns          void
**
*******************************************************************************/
void llcp_util_send_rr_rnr (tLLCP_DLCB *p_dlcb)
{
    BT_HDR *p_msg;
    UINT8  *p;
    UINT8   pdu_type;
    UINT8   pdu_size;
    UINT8   rcv_seq;

    /* if no indication of change in local busy or rx congestion */
    if ((p_dlcb->flags & LLCP_DATA_LINK_FLAG_PENDING_RR_RNR) == 0)
    {
        /* if all ack is sent */
        if (p_dlcb->sent_ack_seq == p_dlcb->next_rx_seq)
        {
            /* we don't need to send RR/RNR */
            return;
        }
        else
        {
            /* if rx flow off because of local busy or congestion */
            if (  (p_dlcb->local_busy)
                ||(p_dlcb->is_rx_congested)
                ||(llcp_cb.overall_rx_congested)  )
            {
                /* don't send RR/RNR */
                return;
            }
        }
    }

    if (  (p_dlcb->local_busy)
        ||(p_dlcb->is_rx_congested)
        ||(llcp_cb.overall_rx_congested)  )
    {
        LLCP_TRACE_DEBUG3 ("llcp_util_send_rr_rnr (): local_busy=%d,is_rx_congested=%d,overall_rx_congested=%d",
                            p_dlcb->local_busy, p_dlcb->is_rx_congested, llcp_cb.overall_rx_congested);

        /* if local_busy or rx congested then do not update receive sequence number to flow off */
        pdu_type = LLCP_PDU_RNR_TYPE;
        pdu_size = LLCP_PDU_RNR_SIZE;
        rcv_seq = p_dlcb->sent_ack_seq;
    }
    else
    {
        pdu_type = LLCP_PDU_RR_TYPE;
        pdu_size = LLCP_PDU_RR_SIZE;

        p_dlcb->sent_ack_seq = p_dlcb->next_rx_seq;
        rcv_seq = p_dlcb->sent_ack_seq;
    }

    p_msg = (BT_HDR*) GKI_getpoolbuf (LLCP_POOL_ID);

    if (p_msg)
    {
        p_dlcb->flags &= ~LLCP_DATA_LINK_FLAG_PENDING_RR_RNR;

        p_msg->len    = pdu_size;
        p_msg->offset = NCI_MSG_OFFSET_SIZE + NCI_DATA_HDR_SIZE;

        p = (UINT8 *) (p_msg + 1) + p_msg->offset;

        UINT16_TO_BE_STREAM (p, LLCP_GET_PDU_HEADER (p_dlcb->remote_sap, pdu_type, p_dlcb->local_sap));

        UINT8_TO_BE_STREAM (p, rcv_seq);

#if (BT_TRACE_VERBOSE == TRUE)
        LLCP_TRACE_DEBUG5 ("LLCP TX - N(S,R):(NA,%d) V(S,SA,R,RA):(%d,%d,%d,%d)",
                            p_dlcb->next_rx_seq,
                            p_dlcb->next_tx_seq, p_dlcb->rcvd_ack_seq,
                            p_dlcb->next_rx_seq, p_dlcb->sent_ack_seq);
#endif
        GKI_enqueue (&llcp_cb.lcb.sig_xmit_q, p_msg);
        llcp_link_check_send_data ();
    }
    else
    {
        LLCP_TRACE_ERROR0 ("llcp_util_send_rr_rnr (): Out of resource");
    }
}

/*******************************************************************************
**
** Function         llcp_util_get_app_cb
**
** Description      get pointer of application registered control block by SAP
**
** Returns          tLLCP_APP_CB *
**
*******************************************************************************/
tLLCP_APP_CB *llcp_util_get_app_cb (UINT8 local_sap)
{
    tLLCP_APP_CB *p_app_cb = NULL;

    if (local_sap <= LLCP_UPPER_BOUND_WK_SAP)
    {
        if ((local_sap != LLCP_SAP_LM) && (local_sap < LLCP_MAX_WKS))
        {
            p_app_cb = &llcp_cb.wks_cb[local_sap];
        }
    }
    else if (local_sap <= LLCP_UPPER_BOUND_SDP_SAP)
    {
        if (local_sap - LLCP_LOWER_BOUND_SDP_SAP < LLCP_MAX_SERVER)
        {
            p_app_cb = &llcp_cb.server_cb[local_sap - LLCP_LOWER_BOUND_SDP_SAP];
        }
    }
    else if (local_sap <= LLCP_UPPER_BOUND_LOCAL_SAP)
    {
        if (local_sap - LLCP_LOWER_BOUND_LOCAL_SAP < LLCP_MAX_CLIENT)
        {
            p_app_cb = &llcp_cb.client_cb[local_sap - LLCP_LOWER_BOUND_LOCAL_SAP];
        }
    }

    return (p_app_cb);
}