/******************************************************************************
 *
 *  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 Service Discovery
 *
 ******************************************************************************/

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

/*******************************************************************************
**
** Function         llcp_sdp_proc_data
**
** Description      Do nothing
**
**
** Returns          void
**
*******************************************************************************/
void llcp_sdp_proc_data (tLLCP_SAP_CBACK_DATA *p_data)
{
    /*
    ** Do nothing
    ** llcp_sdp_proc_SNL () is called by link layer
    */
}

/*******************************************************************************
**
** Function         llcp_sdp_check_send_snl
**
** Description      Enqueue Service Name Lookup PDU into sig_xmit_q for transmitting
**
**
** Returns          void
**
*******************************************************************************/
void llcp_sdp_check_send_snl (void)
{
    UINT8 *p;

    if (llcp_cb.sdp_cb.p_snl)
    {
        LLCP_TRACE_DEBUG0 ("SDP: llcp_sdp_check_send_snl ()");

        llcp_cb.sdp_cb.p_snl->len     += LLCP_PDU_HEADER_SIZE;
        llcp_cb.sdp_cb.p_snl->offset  -= LLCP_PDU_HEADER_SIZE;

        p = (UINT8 *) (llcp_cb.sdp_cb.p_snl + 1) + llcp_cb.sdp_cb.p_snl->offset;
        UINT16_TO_BE_STREAM (p, LLCP_GET_PDU_HEADER (LLCP_SAP_SDP, LLCP_PDU_SNL_TYPE, LLCP_SAP_SDP ));

        GKI_enqueue (&llcp_cb.lcb.sig_xmit_q, llcp_cb.sdp_cb.p_snl);
        llcp_cb.sdp_cb.p_snl = NULL;
    }
}

/*******************************************************************************
**
** Function         llcp_sdp_add_sdreq
**
** Description      Add Service Discovery Request into SNL PDU
**
**
** Returns          void
**
*******************************************************************************/
static void llcp_sdp_add_sdreq (UINT8 tid, char *p_name)
{
    UINT8  *p;
    UINT16 name_len = (UINT16) strlen (p_name);

    p = (UINT8 *) (llcp_cb.sdp_cb.p_snl + 1) + llcp_cb.sdp_cb.p_snl->offset + llcp_cb.sdp_cb.p_snl->len;

    UINT8_TO_BE_STREAM (p, LLCP_SDREQ_TYPE);
    UINT8_TO_BE_STREAM (p, (1 + name_len));
    UINT8_TO_BE_STREAM (p, tid);
    ARRAY_TO_BE_STREAM (p, p_name, name_len);

    llcp_cb.sdp_cb.p_snl->len += LLCP_SDREQ_MIN_LEN + name_len;
}

/*******************************************************************************
**
** Function         llcp_sdp_send_sdreq
**
** Description      Send Service Discovery Request
**
**
** Returns          LLCP_STATUS
**
*******************************************************************************/
tLLCP_STATUS llcp_sdp_send_sdreq (UINT8 tid, char *p_name)
{
    tLLCP_STATUS status;
    UINT16       name_len;
    UINT16       available_bytes;

    LLCP_TRACE_DEBUG2 ("llcp_sdp_send_sdreq (): tid=0x%x, ServiceName=%s", tid, p_name);

    /* if there is no pending SNL */
    if (!llcp_cb.sdp_cb.p_snl)
    {
        llcp_cb.sdp_cb.p_snl = (BT_HDR*) GKI_getpoolbuf (LLCP_POOL_ID);

        if (llcp_cb.sdp_cb.p_snl)
        {
            llcp_cb.sdp_cb.p_snl->offset = NCI_MSG_OFFSET_SIZE + NCI_DATA_HDR_SIZE + LLCP_PDU_HEADER_SIZE;
            llcp_cb.sdp_cb.p_snl->len    = 0;
        }
    }

    if (llcp_cb.sdp_cb.p_snl)
    {
        available_bytes = GKI_get_buf_size (llcp_cb.sdp_cb.p_snl)
                          - BT_HDR_SIZE - llcp_cb.sdp_cb.p_snl->offset
                          - llcp_cb.sdp_cb.p_snl->len;

        name_len = (UINT16) strlen (p_name);

        /* if SDREQ parameter can be added in SNL */
        if (  (available_bytes >= LLCP_SDREQ_MIN_LEN + name_len)
            &&(llcp_cb.sdp_cb.p_snl->len + LLCP_SDREQ_MIN_LEN + name_len <= llcp_cb.lcb.effective_miu)  )
        {
            llcp_sdp_add_sdreq (tid, p_name);
            status = LLCP_STATUS_SUCCESS;
        }
        else
        {
            /* send pending SNL PDU to LM */
            llcp_sdp_check_send_snl ();

            llcp_cb.sdp_cb.p_snl = (BT_HDR*) GKI_getpoolbuf (LLCP_POOL_ID);

            if (llcp_cb.sdp_cb.p_snl)
            {
                llcp_cb.sdp_cb.p_snl->offset = NCI_MSG_OFFSET_SIZE + NCI_DATA_HDR_SIZE + LLCP_PDU_HEADER_SIZE;
                llcp_cb.sdp_cb.p_snl->len    = 0;

                llcp_sdp_add_sdreq (tid, p_name);

                status = LLCP_STATUS_SUCCESS;
            }
            else
            {
                status = LLCP_STATUS_FAIL;
            }
        }
    }
    else
    {
        status = LLCP_STATUS_FAIL;
    }

    /* if LM is waiting for PDUs from upper layer */
    if (  (status == LLCP_STATUS_SUCCESS)
        &&(llcp_cb.lcb.symm_state == LLCP_LINK_SYMM_LOCAL_XMIT_NEXT)  )
    {
        llcp_link_check_send_data ();
    }

    return status;
}

/*******************************************************************************
**
** Function         llcp_sdp_add_sdres
**
** Description      Add Service Discovery Response into SNL PDU
**
**
** Returns          void
**
*******************************************************************************/
static void llcp_sdp_add_sdres (UINT8 tid, UINT8 sap)
{
    UINT8  *p;

    p = (UINT8 *) (llcp_cb.sdp_cb.p_snl + 1) + llcp_cb.sdp_cb.p_snl->offset + llcp_cb.sdp_cb.p_snl->len;

    UINT8_TO_BE_STREAM (p, LLCP_SDRES_TYPE);
    UINT8_TO_BE_STREAM (p, LLCP_SDRES_LEN);
    UINT8_TO_BE_STREAM (p, tid);
    UINT8_TO_BE_STREAM (p, sap);

    llcp_cb.sdp_cb.p_snl->len += 2 + LLCP_SDRES_LEN;   /* type and length */
}

/*******************************************************************************
**
** Function         llcp_sdp_send_sdres
**
** Description      Send Service Discovery Response
**
**
** Returns          LLCP_STATUS
**
*******************************************************************************/
static tLLCP_STATUS llcp_sdp_send_sdres (UINT8 tid, UINT8 sap)
{
    tLLCP_STATUS status;
    UINT16       available_bytes;

    LLCP_TRACE_DEBUG2 ("llcp_sdp_send_sdres (): tid=0x%x, SAP=0x%x", tid, sap);

    /* if there is no pending SNL */
    if (!llcp_cb.sdp_cb.p_snl)
    {
        llcp_cb.sdp_cb.p_snl = (BT_HDR*) GKI_getpoolbuf (LLCP_POOL_ID);

        if (llcp_cb.sdp_cb.p_snl)
        {
            llcp_cb.sdp_cb.p_snl->offset = NCI_MSG_OFFSET_SIZE + NCI_DATA_HDR_SIZE + LLCP_PDU_HEADER_SIZE;
            llcp_cb.sdp_cb.p_snl->len    = 0;
        }
    }

    if (llcp_cb.sdp_cb.p_snl)
    {
        available_bytes = GKI_get_buf_size (llcp_cb.sdp_cb.p_snl)
                          - BT_HDR_SIZE - llcp_cb.sdp_cb.p_snl->offset
                          - llcp_cb.sdp_cb.p_snl->len;

        /* if SDRES parameter can be added in SNL */
        if (  (available_bytes >= 2 + LLCP_SDRES_LEN)
            &&(llcp_cb.sdp_cb.p_snl->len + 2 + LLCP_SDRES_LEN <= llcp_cb.lcb.effective_miu)  )
        {
            llcp_sdp_add_sdres (tid, sap);
            status = LLCP_STATUS_SUCCESS;
        }
        else
        {
            /* send pending SNL PDU to LM */
            llcp_sdp_check_send_snl ();

            llcp_cb.sdp_cb.p_snl = (BT_HDR*) GKI_getpoolbuf (LLCP_POOL_ID);

            if (llcp_cb.sdp_cb.p_snl)
            {
                llcp_cb.sdp_cb.p_snl->offset = NCI_MSG_OFFSET_SIZE + NCI_DATA_HDR_SIZE + LLCP_PDU_HEADER_SIZE;
                llcp_cb.sdp_cb.p_snl->len    = 0;

                llcp_sdp_add_sdres (tid, sap);

                status = LLCP_STATUS_SUCCESS;
            }
            else
            {
                status = LLCP_STATUS_FAIL;
            }
        }
    }
    else
    {
        status = LLCP_STATUS_FAIL;
    }

    /* if LM is waiting for PDUs from upper layer */
    if (  (status == LLCP_STATUS_SUCCESS)
        &&(llcp_cb.lcb.symm_state == LLCP_LINK_SYMM_LOCAL_XMIT_NEXT)  )
    {
        llcp_link_check_send_data ();
    }

    return status;
}

/*******************************************************************************
**
** Function         llcp_sdp_get_sap_by_name
**
** Description      Search SAP by service name
**
**
** Returns          SAP if success
**
*******************************************************************************/
UINT8 llcp_sdp_get_sap_by_name (char *p_name, UINT8 length)
{
    UINT8        sap;
    tLLCP_APP_CB *p_app_cb;

    for (sap = LLCP_SAP_SDP; sap <= LLCP_UPPER_BOUND_SDP_SAP; sap++)
    {
        p_app_cb = llcp_util_get_app_cb (sap);

        if (  (p_app_cb)
            &&(p_app_cb->p_app_cback)
            &&(strlen((char*)p_app_cb->p_service_name) == length)
            &&(!strncmp((char*)p_app_cb->p_service_name, p_name, length))  )
        {
            return (sap);
        }
    }
    return 0;
}

/*******************************************************************************
**
** Function         llcp_sdp_return_sap
**
** Description      Report TID and SAP to requester
**
**
** Returns          void
**
*******************************************************************************/
static void llcp_sdp_return_sap (UINT8 tid, UINT8 sap)
{
    UINT8 i;

    LLCP_TRACE_DEBUG2 ("llcp_sdp_return_sap (): tid=0x%x, SAP=0x%x", tid, sap);

    for (i = 0; i < LLCP_MAX_SDP_TRANSAC; i++)
    {
        if (  (llcp_cb.sdp_cb.transac[i].p_cback)
            &&(llcp_cb.sdp_cb.transac[i].tid == tid)  )
        {
            (*llcp_cb.sdp_cb.transac[i].p_cback) (tid, sap);

            llcp_cb.sdp_cb.transac[i].p_cback = NULL;
        }
    }
}

/*******************************************************************************
**
** Function         llcp_sdp_proc_deactivation
**
** Description      Report SDP failure for any pending request because of deactivation
**
**
** Returns          void
**
*******************************************************************************/
void llcp_sdp_proc_deactivation (void)
{
    UINT8 i;

    LLCP_TRACE_DEBUG0 ("llcp_sdp_proc_deactivation ()");

    for (i = 0; i < LLCP_MAX_SDP_TRANSAC; i++)
    {
        if (llcp_cb.sdp_cb.transac[i].p_cback)
        {
            (*llcp_cb.sdp_cb.transac[i].p_cback) (llcp_cb.sdp_cb.transac[i].tid, 0x00);

            llcp_cb.sdp_cb.transac[i].p_cback = NULL;
        }
    }

    /* free any pending SNL PDU */
    if (llcp_cb.sdp_cb.p_snl)
    {
        GKI_freebuf (llcp_cb.sdp_cb.p_snl);
        llcp_cb.sdp_cb.p_snl = NULL;
    }

    llcp_cb.sdp_cb.next_tid = 0;
}

/*******************************************************************************
**
** Function         llcp_sdp_proc_snl
**
** Description      Process SDREQ and SDRES in SNL
**
**
** Returns          LLCP_STATUS
**
*******************************************************************************/
tLLCP_STATUS llcp_sdp_proc_snl (UINT16 sdu_length, UINT8 *p)
{
    UINT8  type, length, tid, sap, *p_value;

    LLCP_TRACE_DEBUG0 ("llcp_sdp_proc_snl ()");

    if ((llcp_cb.lcb.agreed_major_version < LLCP_MIN_SNL_MAJOR_VERSION)||
       ((llcp_cb.lcb.agreed_major_version == LLCP_MIN_SNL_MAJOR_VERSION)&&(llcp_cb.lcb.agreed_minor_version < LLCP_MIN_SNL_MINOR_VERSION)))
    {
        LLCP_TRACE_DEBUG0 ("llcp_sdp_proc_snl(): version number less than 1.1, SNL not supported.");
        return LLCP_STATUS_FAIL;
    }
    while (sdu_length >= 2) /* at least type and length */
    {
        BE_STREAM_TO_UINT8 (type, p);
        BE_STREAM_TO_UINT8 (length, p);

        switch (type)
        {
        case LLCP_SDREQ_TYPE:
            if (  (length > 1)                /* TID and sevice name */
                &&(sdu_length >= 2 + length)  ) /* type, length, TID and service name */
            {
                p_value = p;
                BE_STREAM_TO_UINT8 (tid, p_value);
                sap = llcp_sdp_get_sap_by_name ((char*) p_value, (UINT8) (length - 1));
                llcp_sdp_send_sdres (tid, sap);
            }
            else
            {
                LLCP_TRACE_ERROR1 ("llcp_sdp_proc_snl (): bad length (%d) in LLCP_SDREQ_TYPE", length);
            }
            break;

        case LLCP_SDRES_TYPE:
            if (  (length == LLCP_SDRES_LEN)  /* TID and SAP */
                &&(sdu_length >= 2 + length)  ) /* type, length, TID and SAP */
            {
                p_value = p;
                BE_STREAM_TO_UINT8 (tid, p_value);
                BE_STREAM_TO_UINT8 (sap, p_value);
                llcp_sdp_return_sap (tid, sap);
            }
            else
            {
                LLCP_TRACE_ERROR1 ("llcp_sdp_proc_snl (): bad length (%d) in LLCP_SDRES_TYPE", length);
            }
            break;

        default:
            LLCP_TRACE_WARNING1 ("llcp_sdp_proc_snl (): Unknown type (0x%x) is ignored", type);
            break;
        }

        if (sdu_length >= 2 + length)   /* type, length, value */
        {
            sdu_length -= 2 + length;
            p += length;
        }
        else
        {
            break;
        }
    }

    if (sdu_length)
    {
        LLCP_TRACE_ERROR0 ("llcp_sdp_proc_snl (): Bad format of SNL");
        return LLCP_STATUS_FAIL;
    }
    else
    {
        return LLCP_STATUS_SUCCESS;
    }
}