/* 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 ATT client optional signed PDU processing functions.
 */
/*************************************************************************************************/

#include <string.h>
#include "wsf_types.h"
#include "wsf_buf.h"
#include "wsf_msg.h"
#include "sec_api.h"
#include "util/bstream.h"
#include "util/calc128.h"
#include "util/wstr.h"
#include "att_api.h"
#include "att_main.h"
#include "attc_main.h"
#include "att_sign.h"

/**************************************************************************************************
  Data Types
**************************************************************************************************/

/* ATTC signed PDU control block */
typedef struct
{
  attcApiMsg_t  msg;
} attcSignCb_t;

/* Message parameters */
typedef union
{
  wsfMsgHdr_t     hdr;
  attcApiMsg_t    api;
} attcSignMsg_t;

/**************************************************************************************************
  Function Prototypes
**************************************************************************************************/

static void attcSignCloseCback(attcCcb_t *pCcb, uint8_t status);
static void attcSignMsgCback(attcCcb_t *pCcb, attcSignMsg_t *pMsg);

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

/* Interface to ATT */
static const attcSignFcnIf_t attcSignFcnIf =
{
  (attcSignMsgCback_t) attcSignMsgCback,
  attcSignCloseCback
};

/* Control block */
static attcSignCb_t *pAttcSignCb[DM_CONN_MAX];

/*************************************************************************************************/
/*!
 *  \brief  Allocate a signing control block.
 *
 *  \param  connId    Connection ID.
 *
 *  \return Pointer to control block.
 */
/*************************************************************************************************/
static attcSignCb_t *attcSignCbAlloc(dmConnId_t connId)
{
  WSF_ASSERT((connId > 0) && (connId <= DM_CONN_MAX));
  WSF_ASSERT(pAttcSignCb[connId - 1] == NULL);

  return (pAttcSignCb[connId - 1] = WsfBufAlloc(sizeof(attcSignCb_t)));
}
/*************************************************************************************************/
/*!
 *  \brief  Free a signing control block.
 *
 *  \param  connId    Connection ID.
 *
 *  \return None.
 */
/*************************************************************************************************/
static void attcSignCbFree(dmConnId_t connId)
{
  WsfBufFree(pAttcSignCb[connId - 1]);
  pAttcSignCb[connId - 1] = NULL;
}

/*************************************************************************************************/
/*!
 *  \brief  Return the signing control block for the connection ID.
 *
 *  \param  connId    Connection ID.
 *
 *  \return Pointer to control block.
 */
/*************************************************************************************************/
static attcSignCb_t *attcSignCbByConnId(dmConnId_t connId)
{
  WSF_ASSERT((connId > 0) && (connId <= DM_CONN_MAX));

  return pAttcSignCb[connId - 1];
}

/*************************************************************************************************/
/*!
 *  \brief  Connection close callback for ATTC signed PDU processing.
 *
 *  \param  pCcb    ATT control block.
 *  \param  status  Connection close status.
 *
 *  \return None.
 */
/*************************************************************************************************/
static void attcSignCloseCback(attcCcb_t *pCcb, uint8_t status)
{
  attcSignCb_t  *pCb;

  /* if connection closed while processing in progress */
  if ((pCb = attcSignCbByConnId(pCcb->pMainCcb->connId)) != NULL)
  {

    /* message free buffer */
    if (pCb->msg.hdr.event != ATTC_MSG_API_NONE)
    {
      attcReqClear(pCcb, &pCb->msg, status);
    }

    attcSignCbFree(pCcb->pMainCcb->connId);
  }
}

/*************************************************************************************************/
/*!
 *  \brief  Message handler callback for ATTC signed PDU processing.
 *
 *  \param  pCcb    ATT control block.
 *  \param  pMsg  ATTC message.
 *
 *  \return None.
 */
/*************************************************************************************************/
static void attcSignMsgCback(attcCcb_t *pCcb, attcSignMsg_t *pMsg)
{
  attcSignCb_t *pCb;

  if (pMsg->hdr.event == ATTC_MSG_API_SIGNED_WRITE_CMD)
  {
    /* adjust msg event id for later use with app callback */
    pMsg->hdr.event = ATTC_MSG_API_WRITE_CMD;

    /* verify no API request already waiting on deck or in progress,
     * and no signed write already in progress
     */
    if ((pCcb->onDeck.hdr.event != ATTC_MSG_API_NONE) ||
        (pCcb->outReq.hdr.event > ATTC_MSG_API_MTU) ||
        (attcSignCbByConnId((dmConnId_t) pMsg->hdr.param) != NULL))
    {
      /* free request and call callback with failure status */
      attcReqClear(pCcb, &pMsg->api, ATT_ERR_OVERFLOW);
      return;
    }

    /* allocate signing control block */
    if ((pCb = attcSignCbAlloc((dmConnId_t) pMsg->hdr.param)) != NULL)
    {
      /* store message */
      pCb->msg = pMsg->api;

      uint16_t cmacTxtLen = pCb->msg.pPkt->len - ATT_AUTH_SIG_LEN + sizeof(uint32_t);
      uint8_t *pCmacText = WsfBufAlloc(cmacTxtLen);

      if (pCmacText)
      {
        uint8_t revLocalCsrk[SEC_CMAC_KEY_LEN] = {0};

        WStrReverseCpy(revLocalCsrk, DmSecGetLocalCsrk(), SEC_CMAC_KEY_LEN);
        WStrReverseCpy(pCmacText, (uint8_t *)pCb->msg.pPkt + L2C_PAYLOAD_START, cmacTxtLen);

        if (SecCmac(revLocalCsrk, pCmacText, cmacTxtLen, attCb.handlerId,
                    (dmConnId_t) pMsg->hdr.param, ATTC_MSG_CMAC_CMPL))
        {
          return;
        }
        else
        {
          WsfBufFree(pCmacText);
        }
      }
    }
    else
    {
      /* allocation failure */
      attcReqClear(pCcb, &pMsg->api, ATT_ERR_UNDEFINED);
      return;
    }
  }
  else if (pMsg->hdr.event == ATTC_MSG_CMAC_CMPL)
  {
    secCmacMsg_t *pCmacMsg = (secCmacMsg_t*) pMsg;
    WsfBufFree(pCmacMsg->pPlainText);

    if ((pCb = attcSignCbByConnId((dmConnId_t) pMsg->hdr.param)) != NULL)
    {
      /* append signature to packet */
      WStrReverseCpy((uint8_t *) pCb->msg.pPkt +
                    (L2C_PAYLOAD_START + pCb->msg.pPkt->len - ATT_CMAC_RESULT_LEN),
                    pCmacMsg->pCiphertext, ATT_CMAC_RESULT_LEN);

      /* if MTU request in progress or flow controlled */
      if (pCcb->outReq.hdr.event == ATTC_MSG_API_MTU || pCcb->flowDisabled)
      {
        /* put request "on deck" for processing later */
        pCcb->onDeck = pCb->msg;
      }
      /* otherwise ready to send */
      else
      {
        attcSetupReq(pCcb, &pCb->msg);
      }

      /* we're done-- free control block */
      attcSignCbFree((dmConnId_t) pMsg->hdr.param);
    }
  }
}

/*************************************************************************************************/
/*!
 *  \brief  Initiate an attribute protocol signed Write Command.
 *
 *  \param  connId      DM connection ID.
 *  \param  handle      Attribute handle.
 *  \param  signCounter Value of the sign counter.
 *  \param  valueLen    Length of value data.
 *  \param  pValue      Pointer to value data.
 *
 *  \return None.
 */
/*************************************************************************************************/
void AttcSignedWriteCmd(dmConnId_t connId, uint16_t handle, uint32_t signCounter,
                        uint16_t valueLen, uint8_t *pValue)
{
  attcPktParam_t  *pPkt;
  uint8_t         *p;

  /* if connection already encrypted with security mode 1 level 2 or higher */
  if (DmConnSecLevel(connId) > DM_SEC_LEVEL_NONE)
  {
    /* use write command instead */
    AttcWriteCmd(connId, handle, valueLen, pValue);
    return;
  }

  /* allocate packet and parameter buffer */
  if ((pPkt = attMsgAlloc(ATT_SIGNED_WRITE_CMD_BUF_LEN + valueLen)) != NULL)
  {
    /* set length */
    pPkt->len = ATT_SIGNED_WRITE_CMD_LEN + valueLen;

    /* build packet */
    p = (uint8_t *) pPkt + L2C_PAYLOAD_START;
    UINT8_TO_BSTREAM(p, ATT_PDU_SIGNED_WRITE_CMD);
    UINT16_TO_BSTREAM(p, handle);
    memcpy(p, pValue, valueLen);
    p += valueLen;
    UINT32_TO_BSTREAM(p, signCounter);

    /* send message */
    attcSendMsg(connId, handle, ATTC_MSG_API_SIGNED_WRITE_CMD, pPkt, FALSE);
  }
}

/*************************************************************************************************/
/*!
 *  \brief  Initialize ATT client for data signing.
 *
 *  \return None.
 */
/*************************************************************************************************/
void AttcSignInit(void)
{
  /* set up callback interface */
  attcCb.pSign = &attcSignFcnIf;
}
