/* Copyright (c) 2009-2019 Arm Limited
 * SPDX-License-Identifier: Apache-2.0
 *
 * 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.
 */

/*************************************************************************************************/
/*!
 *  \brief DM security module.
 */
/*************************************************************************************************/

#include <string.h>
#include "wsf_types.h"
#include "wsf_assert.h"
#include "wsf_msg.h"
#include "sec_api.h"
#include "wsf_buf.h"
#include "wsf_trace.h"
#include "util/calc128.h"
#include "smp_api.h"
#include "dm_api.h"
#include "dm_main.h"
#include "dm_conn.h"
#include "dm_sec.h"

/**************************************************************************************************
  Global Variables
**************************************************************************************************/

/* Control block */
dmSecCb_t dmSecCb;

/**************************************************************************************************
  Local Variables
**************************************************************************************************/

/* Component function interface */
static const dmFcnIf_t dmSecFcnIf =
{
  dmSecReset,
  dmSecHciHandler,
  (dmMsgHandler_t) dmSecMsgHandler
};

/*************************************************************************************************/
/*!
 *  \brief  Utility function for API encrypt request or ltk response.
 *
 *  \param  connId    DM connection ID.
 *  \param  status    HCI_SUCCESS if LTK available.
 *  \param  *pLTK     Pointer to LTK parameter structure.
 *  \param  event     DM handler event.
 *
 *  \return None.
 */
/*************************************************************************************************/
void dmSecApiLtkMsg(dmConnId_t connId, uint8_t status, dmSecLtk_t *pLtk, uint8_t event)
{
  dmSecApiEncryptReq_t  *pMsg;

  if ((pMsg = WsfMsgAlloc(sizeof(dmSecApiEncryptReq_t))) != NULL)
  {
    pMsg->hdr.event = event;
    pMsg->hdr.param = connId;
    pMsg->hdr.status = status;
    memcpy(&pMsg->ltk, pLtk, sizeof(dmSecLtk_t));

    WsfMsgSend(dmCb.handlerId, pMsg);
  }
}

/*************************************************************************************************/
/*!
 *  \brief  DM dev HCI event handler.
 *
 *  \param  pMsg    WSF message.
 *
 *  \return None.
 */
/*************************************************************************************************/
void dmSecHciHandler(hciEvt_t *pEvent)
{
  dmConnCcb_t           *pCcb;
  uint8_t               *pKey;
  dmSecEncryptIndEvt_t  encryptInd;
  uint8_t               secLevel;

  if ((pCcb = dmConnCcbByHandle(pEvent->hdr.param)) != NULL)
  {
    if (pEvent->hdr.event == HCI_LE_LTK_REQ_CBACK_EVT)
    {
      /* if ediv and rand are zero then check if STK is available from SMP */
      if ((pEvent->leLtkReq.encDiversifier == 0) &&
          (memcmp(pEvent->leLtkReq.randNum, calc128Zeros, HCI_RAND_LEN) == 0))
      {
        if ((pKey = SmpDmGetStk(pCcb->connId, &secLevel)) != NULL)
        {
          /* store security level */
          pCcb->tmpSecLevel = secLevel;

          /* not using LTK */
          pCcb->usingLtk = FALSE;

          /* provide key to HCI */
          HciLeLtkReqReplCmd(pEvent->hdr.param, pKey);
          return;
        }
      }

      /* call callback to get key from app */

      /* set connection busy */
      DmConnSetIdle(pCcb->connId, DM_IDLE_DM_ENC, DM_CONN_BUSY);

      /* using LTK */
      pCcb->usingLtk = TRUE;

      /* use the header from the encryptInd struct for efficiency */
      pEvent->hdr.param = pCcb->connId;
      pEvent->hdr.event = DM_SEC_LTK_REQ_IND;
      (*dmCb.cback)((dmEvt_t *) pEvent);
    }
    else if (pEvent->hdr.event == HCI_ENC_KEY_REFRESH_CMPL_CBACK_EVT ||
             pEvent->hdr.event == HCI_ENC_CHANGE_CBACK_EVT)
    {
      /* set connection idle */
      DmConnSetIdle(pCcb->connId, DM_IDLE_DM_ENC, DM_CONN_IDLE);

      encryptInd.hdr.param = pCcb->connId;
      encryptInd.hdr.status = pEvent->hdr.status;
      if (encryptInd.hdr.status == HCI_SUCCESS)
      {
        encryptInd.hdr.event = DM_SEC_ENCRYPT_IND;

        /* update security level of connection */
        pCcb->secLevel = pCcb->tmpSecLevel;

        /* set LTK flag */
        encryptInd.usingLtk = pCcb->usingLtk;
      }
      else
      {
        encryptInd.hdr.event = DM_SEC_ENCRYPT_FAIL_IND;
      }

      /* call callback before passing to SMP */
      DmSmpCbackExec((dmEvt_t *) &encryptInd);

      /* pass to SMP */
      encryptInd.hdr.param = pCcb->connId;
      encryptInd.hdr.status = pEvent->hdr.status;
      SmpDmEncryptInd((wsfMsgHdr_t *) &encryptInd);
    }
  }
}

/*************************************************************************************************/
/*!
 *  \brief  DM dev event handler.
 *
 *  \param  pMsg    WSF message.
 *
 *  \return None.
 */
/*************************************************************************************************/
void dmSecMsgHandler(dmSecMsg_t *pMsg)
{
  dmConnCcb_t *pCcb;

  /* look up ccb */
  if ((pCcb = dmConnCcbById((dmConnId_t) pMsg->hdr.param)) != NULL)
  {
    /* process API encrypt req */
    switch (pMsg->hdr.event)
    {
    case DM_SEC_MSG_API_ENCRYPT_REQ:
      /* set connection busy */
      DmConnSetIdle(pCcb->connId, DM_IDLE_DM_ENC, DM_CONN_BUSY);

      /* store security level */
      pCcb->tmpSecLevel = pMsg->encryptReq.secLevel;

      /* using LTK */
      pCcb->usingLtk = TRUE;

      /* start encryption */
      HciLeStartEncryptionCmd(pCcb->handle, pMsg->encryptReq.ltk.rand,
                              pMsg->encryptReq.ltk.ediv, pMsg->encryptReq.ltk.key);

      break;

    case DM_SEC_MSG_API_LTK_RSP:
      /* if key found */
      if (pMsg->ltkRsp.keyFound)
      {
        /* store security level */
        pCcb->tmpSecLevel = pMsg->ltkRsp.secLevel;

        /* provide key to HCI */
        HciLeLtkReqReplCmd(pCcb->handle, pMsg->ltkRsp.key);
      }
      else
      {
        /* key not found; set connection idle */
        DmConnSetIdle(pCcb->connId, DM_IDLE_DM_ENC, DM_CONN_IDLE);

        HciLeLtkReqNegReplCmd(pCcb->handle);
      }
      break;

    default:
      break;
    }
  }
}

/*************************************************************************************************/
/*!
 *  \brief  For internal use only.  Execute DM callback from SMP procedures.
 *
 *  \param  pDmEvt    Pointer to callback event data.
 *
 *  \return None.
 */
/*************************************************************************************************/
void DmSmpCbackExec(dmEvt_t *pDmEvt)
{
  /* certain messages need to get to ATT */
  if (pDmEvt->hdr.event == DM_SEC_PAIR_CMPL_IND ||
      pDmEvt->hdr.event == DM_SEC_ENCRYPT_IND)
  {
    if (dmConnCb.connCback[DM_CLIENT_ID_ATT] != NULL)
    {
      (*dmConnCb.connCback[DM_CLIENT_ID_ATT])(pDmEvt);
    }
  }

  /* execute DM client callback */
  (*dmCb.cback)(pDmEvt);
}

/*************************************************************************************************/
/*!
 *  \brief  This function is called to cancel the pairing process.
 *
 *  \param  connId    DM connection ID.
 *  \param  reason    Failure reason.
 *
 *  \return None.
 */
/*************************************************************************************************/
void DmSecCancelReq(dmConnId_t connId, uint8_t reason)
{
  wsfMsgHdr_t  *pMsg;

  if ((pMsg = WsfMsgAlloc(sizeof(wsfMsgHdr_t))) != NULL)
  {
    pMsg->event = SMP_MSG_API_CANCEL_REQ;
    pMsg->param = connId;
    pMsg->status = reason;

    /* note we're sending this to SMP */
    SmpDmMsgSend((smpDmMsg_t *) pMsg);
  }
}

/*************************************************************************************************/
/*!
 *  \brief  This function is called in response to a DM_SEC_AUTH_REQ_IND event to provide
 *          PIN or OOB data during pairing.
 *
 *  \param  connId      DM connection ID.
 *  \param  authDataLen Length of PIN or OOB data.
 *  \param  pAuthData   pointer to PIN or OOB data.
 *
 *  \return None.
 */
/*************************************************************************************************/
void DmSecAuthRsp(dmConnId_t connId, uint8_t authDataLen, uint8_t *pAuthData)
{
  smpDmAuthRsp_t   *pMsg;

  WSF_ASSERT(authDataLen <= SMP_OOB_LEN);

  if ((pMsg = WsfMsgAlloc(sizeof(smpDmAuthRsp_t))) != NULL)
  {
    pMsg->hdr.event = SMP_MSG_API_AUTH_RSP;
    pMsg->hdr.param = connId;
    pMsg->authDataLen = authDataLen;

    if (pAuthData != NULL)
    {
      memcpy(pMsg->authData, pAuthData, authDataLen);
    }

    /* note we're sending this to SMP */
    SmpDmMsgSend((smpDmMsg_t *) pMsg);
  }
}

/*************************************************************************************************/
/*!
 *  \brief  Initialize DM security.
 *
 *  \return None.
 */
/*************************************************************************************************/
void DmSecInit(void)
{
  dmFcnIfTbl[DM_ID_SEC] = (dmFcnIf_t *) &dmSecFcnIf;

  dmSecCb.pCsrk = dmSecCb.pIrk = (uint8_t *) calc128Zeros;
}

/*************************************************************************************************/
/*!
 *  \brief  This function sets the local CSRK used by the device.
 *
 *  \param  pCsrk     Pointer to CSRK.
 *
 *  \return None.
 */
/*************************************************************************************************/
void DmSecSetLocalCsrk(uint8_t *pCsrk)
{
  WsfTaskLock();
  dmSecCb.pCsrk = pCsrk;
  WsfTaskUnlock();
}

/*************************************************************************************************/
/*!
 *  \brief  This function sets the local IRK used by the device.
 *
 *  \param  pCsrk     Pointer to IRK.
 *
 *  \return None.
 */
/*************************************************************************************************/
void DmSecSetLocalIrk(uint8_t *pIrk)
{
  WsfTaskLock();
  dmSecCb.pIrk = pIrk;
  WsfTaskUnlock();
}

/*************************************************************************************************/
/*!
 *  \brief  This function gets the local CSRK used by the device.
 *
 *  \return Pointer to CSRK.
 */
/*************************************************************************************************/
uint8_t *DmSecGetLocalCsrk(void)
{
  return dmSecCb.pCsrk;
}

/*************************************************************************************************/
/*!
 *  \brief  This function gets the local IRK used by the device.
 *
 *  \return Pointer to IRK.
 */
/*************************************************************************************************/
uint8_t *DmSecGetLocalIrk(void)
{
  return dmSecCb.pIrk;
}

/*************************************************************************************************/
/*!
 *  \brief  Reset the sec module.
 *
 *  \return None.
 */
/*************************************************************************************************/
void dmSecReset(void)
{
  /* initialize smp database */
  SmpDbInit();
}
