/* Copyright (c) 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.
 */

/*************************************************************************************************/
/*!
 * \file
 * \brief Link layer controller slave data channel packet implementation file.
 */
/*************************************************************************************************/

#include "ll_math.h"
#include "lctr_int_conn.h"
#include "util/bstream.h"
#include "hci_defs.h"
#include "wsf_math.h"
#include <string.h>
#include <stdint.h>

/*************************************************************************************************/
/*!
 *  \brief  Check whether the channel map mask is valid.
 *
 *  \param  chanMask    Channel map mask.
 *
 *  \return TRUE if channel map is valid, FALSE otherwise.
 */
/*************************************************************************************************/
static inline bool_t lctrIsChanMaskValid(uint64_t chanMask)
{
  /* Valid channels are between bit 0 and bit 36. */
  if (chanMask & (~LL_CHAN_DATA_ALL))
  {
    return FALSE;
  }

  /* The minimum number of used channels shall be 2. */
  if (LlMathGetNumBitsSet(chanMask) < LL_MIN_NUM_CHAN_DATA)
  {
    return FALSE;
  }

  return TRUE;
}

/*************************************************************************************************/
/*!
 *  \brief  Check whether the connection update indication parameters are valid.
 *
 *  \param  pParam    Connection update parameter.
 *
 *  \return TRUE if parameters are valid, FALSE otherwise.
 */
/*************************************************************************************************/
static inline bool_t lctrIsConnUpdateParamValid(lctrConnUpdInd_t *pParam)
{
  /* In the range of 7.5ms to 4s. */
  if ((pParam->interval < LL_MIN_CONN_INTERVAL) || (pParam->interval > LL_MAX_CONN_INTERVAL))
  {
    return FALSE;
  }

  /* In the range of 1.25ms to the lesser of 10ms and (connInterval - 1.25ms). */
  if ((pParam->txWinSize < LL_MIN_TX_WIN_SIZE) ||
      (pParam->txWinSize > WSF_MIN(LL_MAX_TX_WIN_SIZE, (pParam->interval - 1))))
  {
    return FALSE;
  }

  /* In the range of 0ms to the connInterval. */
  if (pParam->txWinOffset > (pParam->interval))
  {
    return FALSE;
  }

  /* In the range of 100ms to 32000ms. And it shall be larger than (1 + connSlaveLatency) * connInterval * 2. */
  if ((pParam->timeout < LL_MIN_SUP_TIMEOUT) || (pParam->timeout > LL_MAX_SUP_TIMEOUT) ||
      ((pParam->timeout << 2) <= ((1 + pParam->latency) * pParam->interval)))
  {
    return FALSE;
  }

  /* In the range of 0 to the ((connSupervisionTimeout / (connInterval*2)) - 1), checked above already.
   * And it shall be less than 500. */
  if (pParam->latency > LL_MAX_CONN_LATENCY)
  {
    return FALSE;
  }

  return TRUE;
}

/*************************************************************************************************/
/*!
 *  \brief  Check whether the set minimum number of used channels parameters are valid.
 *
 *  \param  pParam    Set minimum number of used channels parameter.
 *
 *  \return TRUE if parameters are valid, FALSE otherwise.
 */
/*************************************************************************************************/
static inline bool_t lctrIsSetMinUsedChanParamValid(lctrMinUsedChanInd_t *pParam)
{
  /* At least one bit shall be set. */
  if (pParam->phys == 0)
  {
    return FALSE;
  }

  /* The minimum number of used channels shall be between 2 - 37. */
  if ((pParam->minUsedChan < LL_MIN_NUM_CHAN_DATA) ||
      (pParam->minUsedChan > LL_MAX_NUM_CHAN_DATA))
  {
    return FALSE;
  }

  return TRUE;
}

/*************************************************************************************************/
/*!
 *  \brief  Pack an ACL header.
 *
 *  \param  pBuf        Packed packet buffer.
 *  \param  pHdr        Unpacked packet header.
 *
 *  \return Header length.
 */
/*************************************************************************************************/
uint8_t lctrPackAclHdr(uint8_t *pBuf, const lctrAclHdr_t *pHdr)
{
  const uint8_t len = sizeof(uint32_t);

  uint16_t flags = 0;

  flags |= (pHdr->connHandle) <<  0;
  flags |= (pHdr->pktBound  ) << 12;
  UINT16_TO_BSTREAM(pBuf, flags);

  UINT16_TO_BSTREAM(pBuf, pHdr->len);

  return len;
}

/*************************************************************************************************/
/*!
 *  \brief  Pack an data channel PDU header.
 *
 *  \param  pBuf        Packed packet buffer.
 *  \param  pHdr        Unpacked PDU header.
 *
 *  \return Header length.
 */
/*************************************************************************************************/
uint8_t lctrPackDataPduHdr(uint8_t *pBuf, const lctrDataPduHdr_t *pHdr)
{
  const uint8_t len = LL_DATA_HDR_LEN;

  uint16_t hdr = 0;

  hdr |= (pHdr->llid    ) << 0;
  hdr |= (pHdr->nesn & 1) << 2;
  hdr |= (pHdr->sn   & 1) << 3;
  hdr |= (pHdr->md   & 1) << 4;
  hdr |= (pHdr->len     ) << 8;
  UINT16_TO_BSTREAM(pBuf, hdr);

  return len;
}

/*************************************************************************************************/
/*!
 *  \brief  Pack a connection update request PDU.
 *
 *  \param  pBuf        Packed packet buffer.
 *  \param  pPdu        Unpacked control data PDU.
 *
 *  \return PDU length.
 */
/*************************************************************************************************/
uint8_t lctrPackConnUpdInd(uint8_t *pBuf, const lctrConnUpdInd_t *pPdu)
{
  const uint8_t len = LL_CONN_UPD_IND_PDU_LEN;

  UINT8_TO_BSTREAM (pBuf, LL_PDU_CONN_UPDATE_IND);
  UINT8_TO_BSTREAM (pBuf, pPdu->txWinSize);
  UINT16_TO_BSTREAM(pBuf, pPdu->txWinOffset);
  UINT16_TO_BSTREAM(pBuf, pPdu->interval);
  UINT16_TO_BSTREAM(pBuf, pPdu->latency);
  UINT16_TO_BSTREAM(pBuf, pPdu->timeout);
  UINT16_TO_BSTREAM(pBuf, pPdu->instant);

  return len;
}

/*************************************************************************************************/
/*!
 *  \brief  Unpack an ACL header.
 *
 *  \param  pHdr        Unpacked packet header.
 *  \param  pBuf        Packed packet buffer.
 *
 *  \return Header length.
 */
/*************************************************************************************************/
uint8_t lctrUnpackAclHdr(lctrAclHdr_t *pHdr, const uint8_t *pBuf)
{
  const uint8_t len = HCI_ACL_HDR_LEN;

  uint16_t flags;

  BSTREAM_TO_UINT16(flags, pBuf);
  pHdr->connHandle = (flags >>  0) & 0x0FFF;
  pHdr->pktBound   = (flags >> 12) & 0x0003;

  BSTREAM_TO_UINT16(pHdr->len, pBuf);

  return len;
}

/*************************************************************************************************/
/*!
 *  \brief  Unpack a data channel PDU header.
 *
 *  \param  pHdr        Unpacked header.
 *  \param  pBuf        Packed packet buffer.
 *
 *  \return Header length.
 */
/*************************************************************************************************/
uint8_t lctrUnpackDataPduHdr(lctrDataPduHdr_t *pHdr, const uint8_t *pBuf)
{
  const uint8_t len = LL_DATA_HDR_LEN;

  uint16_t hdr;

  BSTREAM_TO_UINT16(hdr, pBuf);

  pHdr->llid = (hdr >> 0) & LL_DATA_HDR_LLID_MSK;
  pHdr->nesn = (hdr >> 2) & 0x0001;
  pHdr->sn   = (hdr >> 3) & 0x0001;
  pHdr->md   = (hdr >> 4) & 0x0001;
  pHdr->len  = (hdr >> 8) & LL_DATA_HDR_LEN_MSK;

  return len;
}

/*************************************************************************************************/
/*!
 *  \brief  Unpack a connection update request PDU.
 *
 *  \param  pPdu        Unpacked control data PDU.
 *  \param  pBuf        Packed packet buffer.
 *
 *  \return PDU length.
 */
/*************************************************************************************************/
uint8_t lctrUnpackConnUpdateIndPdu(lctrConnUpdInd_t *pPdu, const uint8_t *pBuf)
{
  const uint8_t len = LL_CONN_UPD_IND_PDU_LEN;

  pBuf += 1;        /* skip opcode */
  BSTREAM_TO_UINT8 (pPdu->txWinSize, pBuf);
  BSTREAM_TO_UINT16(pPdu->txWinOffset, pBuf);
  BSTREAM_TO_UINT16(pPdu->interval, pBuf);
  BSTREAM_TO_UINT16(pPdu->latency, pBuf);
  BSTREAM_TO_UINT16(pPdu->timeout, pBuf);
  BSTREAM_TO_UINT16(pPdu->instant, pBuf);

  return len;
}

/*************************************************************************************************/
/*!
 *  \brief  Unpack a connection update request PDU.
 *
 *  \param  pPdu        Unpacked control data PDU.
 *  \param  pBuf        Packed packet buffer.
 *
 *  \return PDU length.
 */
/*************************************************************************************************/
uint8_t lctrUnpackChanMapIndPdu(lctrChanMapInd_t *pPdu, const uint8_t *pBuf)
{
  const uint8_t len = LL_CHAN_MAP_IND_PDU_LEN;

  pBuf += 1;        /* skip opcode */
  BSTREAM_TO_UINT40(pPdu->chanMask, pBuf);
  BSTREAM_TO_UINT16(pPdu->instant, pBuf);

  return len;
}

/*************************************************************************************************/
/*!
 *  \brief  Unpack a terminate indication PDU.
 *
 *  \param  pPdu        Unpacked control data PDU.
 *  \param  pBuf        Packed packet buffer.
 *
 *  \return PDU length.
 */
/*************************************************************************************************/
uint8_t lctrUnpackTerminateIndPdu(lctrTermInd_t *pPdu, const uint8_t *pBuf)
{
  const uint8_t len = LL_TERMINATE_IND_PDU_LEN;

  pBuf += 1;        /* skip opcode */
  BSTREAM_TO_UINT8(pPdu->errorCode, pBuf);

  return len;
}

/*************************************************************************************************/
/*!
 *  \brief  Unpack a unknown response PDU.
 *
 *  \param  pPdu        Unpacked control data PDU.
 *  \param  pBuf        Packed packet buffer.
 *
 *  \return PDU length.
 */
/*************************************************************************************************/
uint8_t lctrUnpackUnknownRspPdu(lctrUnknownRsp_t *pPdu, const uint8_t *pBuf)
{
  const uint8_t len = LL_UNKNOWN_RSP_LEN;

  pBuf += 1;        /* skip opcode */
  BSTREAM_TO_UINT8(pPdu->unknownType, pBuf);

  return len;
}

/*************************************************************************************************/
/*!
 *  \brief  Unpack a feature request or response PDU.
 *
 *  \param  pPdu        Unpacked control data PDU.
 *  \param  pBuf        Packed packet buffer.
 *
 *  \return PDU length.
 */
/*************************************************************************************************/
uint8_t lctrUnpackFeaturePdu(lctrFeat_t *pPdu, const uint8_t *pBuf)
{
  const uint8_t len = LL_FEATURE_PDU_LEN;

  pBuf += 1;        /* skip opcode */
  BSTREAM_TO_UINT64(pPdu->featSet, pBuf);

  return len;
}

/*************************************************************************************************/
/*!
 *  \brief  Unpack a reject indication PDU.
 *
 *  \param  pReason     Reason code.
 *  \param  pBuf        Packed packet buffer.
 *
 *  \return PDU length.
 */
/*************************************************************************************************/
uint8_t lctrUnpackRejectIndPdu(uint8_t *pReason, const uint8_t *pBuf)
{
  const uint8_t len = LL_REJECT_IND_PDU_LEN;

  pBuf += 1;        /* skip opcode */
  BSTREAM_TO_UINT8(*pReason, pBuf);

  return len;
}

/*************************************************************************************************/
/*!
 *  \brief  Unpack a version indication PDU.
 *
 *  \param  pPdu        Unpacked control data PDU.
 *  \param  pBuf        Packed packet buffer.
 *
 *  \return PDU length.
 */
/*************************************************************************************************/
uint8_t lctrUnpackVersionIndPdu(lctrVerInd_t *pPdu, const uint8_t *pBuf)
{
  const uint8_t len = LL_VERSION_IND_PDU_LEN;

  pBuf += 1;        /* skip opcode */
  BSTREAM_TO_UINT8(pPdu->versNr, pBuf);
  BSTREAM_TO_UINT16(pPdu->compId, pBuf);
  BSTREAM_TO_UINT16(pPdu->subVersNr, pBuf);

  return len;
}

/*************************************************************************************************/
/*!
 *  \brief  Unpack an extended reject indication PDU.
 *
 *  \param  pPdu        Unpacked control data PDU.
 *  \param  pBuf        Packed packet buffer.
 *
 *  \return PDU length.
 */
/*************************************************************************************************/
uint8_t lctrUnpackRejectExtIndPdu(lctrRejInd_t *pPdu, const uint8_t *pBuf)
{
  const uint8_t len = LL_REJECT_EXT_IND_PDU_LEN;

  pBuf += 1;        /* skip opcode */
  BSTREAM_TO_UINT8(pPdu->opcode, pBuf);
  BSTREAM_TO_UINT8(pPdu->reason, pBuf);

  return len;
}

/*************************************************************************************************/
/*!
 *  \brief  Unpack a data length request or response PDU.
 *
 *  \param  pPdu        Unpacked data length PDU.
 *  \param  pBuf        Packed packet buffer.
 *
 *  \return PDU length.
 */
/*************************************************************************************************/
uint8_t lctrUnpackDataLengthPdu(lctrDataLen_t *pPdu, const uint8_t *pBuf)
{
  const uint8_t len = LL_DATA_LEN_PDU_LEN;

  pBuf += 1;        /* skip opcode */
  BSTREAM_TO_UINT16(pPdu->maxRxLen, pBuf);
  BSTREAM_TO_UINT16(pPdu->maxRxTime, pBuf);
  BSTREAM_TO_UINT16(pPdu->maxTxLen, pBuf);
  BSTREAM_TO_UINT16(pPdu->maxTxTime, pBuf);

  return len;
}

/*************************************************************************************************/
/*!
 *  \brief  Unpack a connection parameter request or response PDU.
 *
 *  \param  pConnParam  Connection parameter request/response values.
 *  \param  pBuf        Packed packet buffer.
 *
 *  \return PDU length.
 */
/*************************************************************************************************/
uint8_t lctrUnpackConnParamPdu(lctrConnParam_t *pConnParam, const uint8_t *pBuf)
{
  const uint8_t len = LL_CONN_PARAM_PDU_LEN;

  unsigned int i;

  pBuf += 1;        /* skip opcode */
  BSTREAM_TO_UINT16(pConnParam->connIntervalMin, pBuf);
  BSTREAM_TO_UINT16(pConnParam->connIntervalMax, pBuf);
  BSTREAM_TO_UINT16(pConnParam->connLatency, pBuf);
  BSTREAM_TO_UINT16(pConnParam->supTimeout, pBuf);

  BSTREAM_TO_UINT8 (pConnParam->prefPeriod, pBuf);
  BSTREAM_TO_UINT16(pConnParam->refConnEvtCnt, pBuf);

  for (i = 0; i < LCTR_OFFSET_COUNT; i++)
  {
    BSTREAM_TO_UINT16(pConnParam->offset[i], pBuf);
  }

  return len;
}

/*************************************************************************************************/
/*!
 *  \brief  Unpack a PHY request or response PDU.
 *
 *  \param  pPdu        Unpacked PHY PDU.
 *  \param  pBuf        Packed packet buffer.
 *
 *  \return PDU length.
 */
/*************************************************************************************************/
uint8_t lctrUnpackPhyPdu(lctrPhy_t *pPdu, const uint8_t *pBuf)
{
  const uint8_t len = LL_PHY_PDU_LEN;

  pBuf += 1;        /* skip opcode */
  BSTREAM_TO_UINT8(pPdu->txPhys, pBuf);
  BSTREAM_TO_UINT8(pPdu->rxPhys, pBuf);

  /* Mask out the RFU bits for PHYs. */
  pPdu->txPhys &= LL_ALL_PHYS_MSK;
  pPdu->rxPhys &= LL_ALL_PHYS_MSK;

  return len;
}

/*************************************************************************************************/
/*!
 *  \brief  Unpack a PHY update indication PDU.
 *
 *  \param  pPdu        Unpacked PHY update indication PDU.
 *  \param  pBuf        Packed packet buffer.
 *
 *  \return PDU length.
 */
/*************************************************************************************************/
uint8_t lctrUnpackPhyUpdateIndPdu(lctrPhyUpdInd_t *pPdu, const uint8_t *pBuf)
{
  const uint8_t len = LL_PHY_UPD_IND_PDU_LEN;

  pBuf += 1;        /* skip opcode */
  BSTREAM_TO_UINT8 (pPdu->masterToSlavePhy, pBuf);
  BSTREAM_TO_UINT8 (pPdu->slaveToMasterPhy, pBuf);
  BSTREAM_TO_UINT16(pPdu->instant, pBuf);

  /* Mask out the RFU bits for PHYs. */
  pPdu->masterToSlavePhy &= LL_ALL_PHYS_MSK;
  pPdu->slaveToMasterPhy &= LL_ALL_PHYS_MSK;

  return len;
}

/*************************************************************************************************/
/*!
 *  \brief  Unpack a set minimum number of used channels indication PDU.
 *
 *  \param  pPdu        Unpack a set minimum number of used channels indication PDU.
 *  \param  pBuf        Packed packet buffer.
 *
 *  \return PDU length.
 */
/*************************************************************************************************/
uint8_t lctrUnpackMinUsedChanIndPdu(lctrMinUsedChanInd_t *pPdu, const uint8_t *pBuf)
{
  const uint8_t len = LL_MIN_USED_CHAN_PDU_LEN;

  pBuf += 1;        /* skip opcode */
  BSTREAM_TO_UINT8 (pPdu->phys, pBuf);
  BSTREAM_TO_UINT8 (pPdu->minUsedChan, pBuf);

  /* Mask out the RFU bits for PHYs. */
  pPdu->phys &= LL_ALL_PHYS_MSK;

  return len;
}

/*************************************************************************************************/
/*!
 *  \brief  Unpack a periodic sync indication PDU.
 *
 *  \param  pPdu        Unpacked periodic sync indication PDU.
 *  \param  pBuf        Packed packet buffer.
 *
 *  \return PDU length.
 */
/*************************************************************************************************/
uint8_t lctrUnpackPerSyncIndPdu(lctrPerSyncInd_t *pPdu, const uint8_t *pBuf)
{
  const uint8_t len = LL_PERIODIC_SYNC_PDU_LEN;
  uint8_t i;

  pBuf += 1;        /* skip opcode */
  BSTREAM_TO_UINT16 (pPdu->id, pBuf);

  for (i = 0; i < LL_SYNC_INFO_LEN; i++)
  {
    BSTREAM_TO_UINT8(pPdu->syncInfo[i], pBuf);
  }

  BSTREAM_TO_UINT16 (pPdu->ceCounter, pBuf);
  BSTREAM_TO_UINT16 (pPdu->lastPECounter, pBuf);

  uint8_t field8;
  BSTREAM_TO_UINT8 (field8, pBuf);
  pPdu->sid     = (field8 >>  0) & 0x0F;
  pPdu->aType   = (field8 >>  4) & 0x01;
  pPdu->sca     = (field8 >>  5) & 0x07;

  BSTREAM_TO_UINT8(pPdu->phy, pBuf);
  BSTREAM_TO_BDA64(pPdu->advA, pBuf);
  BSTREAM_TO_UINT16(pPdu->syncConnEvtCounter, pBuf);

  return len;
}

/*************************************************************************************************/
/*!
 *  \brief  Unpack a peer SCA response PDU.
 *
 *  \param  pPdu        SCA PDU.
 *  \param  pBuf        Packed packet buffer.
 *
 *  \return PDU length.
 */
/*************************************************************************************************/
uint8_t lctrUnpackPeerScaPdu(lctrPeerSca_t *pPdu, const uint8_t *pBuf)
{
  const uint8_t len = LL_PEER_SCA_REQ_LEN;  /* LL_PEER_SCA_REQ_LEN = LL_PEER_SCA_RSP_LEN*/

  pBuf += 1;        /* skip opcode */
  BSTREAM_TO_UINT8 (pPdu->sca, pBuf);

  return len;
}

/*************************************************************************************************/
/*!
 *  \brief  Unpack a CIS request PDU.
 *
 *  \param  pPdu        CIS request PDU.
 *  \param  pBuf        Packed packet buffer.
 *
 *  \return PDU length.
 */
/*************************************************************************************************/
uint8_t lctrUnpackCisReqPdu(lctrCisReq_t *pPdu, const uint8_t *pBuf)
{
  const uint8_t len = LL_CIS_REQ_LEN;
  uint8_t bn;
  uint16_t sduSizeMToS;

  pBuf += 1;        /* skip opcode */
  BSTREAM_TO_UINT8 (pPdu->cigId, pBuf);
  BSTREAM_TO_UINT8 (pPdu->cisId, pBuf);
  BSTREAM_TO_UINT8 (pPdu->phyMToS, pBuf);
  BSTREAM_TO_UINT8 (pPdu->phySToM, pBuf);

  BSTREAM_TO_UINT16 (sduSizeMToS, pBuf);
  pPdu->isoalPduType = (sduSizeMToS >> 15) & 0x01;
  pPdu->sduSizeMToS = sduSizeMToS & 0x0FFF;
  BSTREAM_TO_UINT16 (pPdu->sduSizeSToM, pBuf);
  pPdu->sduSizeSToM &= 0x0FFF;
  BSTREAM_TO_UINT24 (pPdu->sduIntervalMToS, pBuf);
  pPdu->sduIntervalMToS &= 0xFFFFF;
  BSTREAM_TO_UINT24 (pPdu->sduIntervalSToM, pBuf);
  pPdu->sduIntervalSToM &= 0xFFFFF;

  BSTREAM_TO_UINT8 (pPdu->plMToS, pBuf);
  BSTREAM_TO_UINT8 (pPdu->plSToM, pBuf);
  BSTREAM_TO_UINT8 (pPdu->nse, pBuf);
  BSTREAM_TO_UINT24 (pPdu->subIntervUsec, pBuf);
  BSTREAM_TO_UINT8 (bn, pBuf);
  pPdu->bnSToM = (bn & 0xF0) >> 4;
  pPdu->bnMToS = bn & 0x0F;
  BSTREAM_TO_UINT8 (pPdu->ftMToS, pBuf);
  BSTREAM_TO_UINT8 (pPdu->ftSToM, pBuf);
  BSTREAM_TO_UINT16 (pPdu->isoInterval, pBuf);
  BSTREAM_TO_UINT24 (pPdu->cisOffMinUsec, pBuf);
  BSTREAM_TO_UINT24 (pPdu->cisOffMaxUsec, pBuf);
  BSTREAM_TO_UINT16 (pPdu->ceRef, pBuf);

  return len;
}

/*************************************************************************************************/
/*!
 *  \brief  Unpack a CIS response PDU.
 *
 *  \param  pPdu        CIS response PDU.
 *  \param  pBuf        Packed packet buffer.
 *
 *  \return PDU length.
 */
/*************************************************************************************************/
uint8_t lctrUnpackCisRspPdu(lctrCisRsp_t *pPdu, const uint8_t *pBuf)
{
  const uint8_t len = LL_CIS_RSP_LEN;

  pBuf += 1;        /* skip opcode */
  BSTREAM_TO_UINT24 (pPdu->cisOffMinUsec, pBuf);
  BSTREAM_TO_UINT24 (pPdu->cisOffMaxUsec, pBuf);
  BSTREAM_TO_UINT16 (pPdu->ceRef, pBuf);

  return len;
}

/*************************************************************************************************/
/*!
 *  \brief  Unpack a CIS indication PDU.
 *
 *  \param  pPdu        CIS indication PDU.
 *  \param  pBuf        Packed packet buffer.
 *
 *  \return PDU length.
 */
/*************************************************************************************************/
uint8_t lctrUnpackCisIndPdu(lctrCisInd_t *pPdu, const uint8_t *pBuf)
{
  const uint8_t len = LL_CIS_IND_LEN;

  pBuf += 1;        /* skip opcode */
  BSTREAM_TO_UINT32 (pPdu->accessAddr, pBuf);
  BSTREAM_TO_UINT24 (pPdu->cisOffUsec, pBuf);
  BSTREAM_TO_UINT24 (pPdu->cigSyncDelayUsec, pBuf);
  BSTREAM_TO_UINT24 (pPdu->cisSyncDelayUsec, pBuf);
  BSTREAM_TO_UINT16 (pPdu->ceRef, pBuf);

  return len;
}

/*************************************************************************************************/
/*!
 *  \brief  Unpack a CIS terminate PDU.
 *
 *  \param  pPdu        CIS terminate PDU.
 *  \param  pBuf        Packed packet buffer.
 *
 *  \return PDU length.
 */
/*************************************************************************************************/
uint8_t lctrUnpackCisTermPdu(lctrCisTermInd_t *pPdu, const uint8_t *pBuf)
{
  const uint8_t len = LL_CIS_TERM_LEN;

  pBuf += 1;        /* skip opcode */
  BSTREAM_TO_UINT8 (pPdu->cigId, pBuf);
  BSTREAM_TO_UINT8 (pPdu->cisId, pBuf);
  BSTREAM_TO_UINT8 (pPdu->reason, pBuf);

  return len;
}

/*************************************************************************************************/
/*!
 *  \brief  Decode an LE-C channel buffer.
 *
 *  \param  pPdu    Destination unpacked PDU.
 *  \param  pBuf    Source packed packet buffer.
 *  \param  role    Role.
 *
 *  \return LL error code or success.
 */
/*************************************************************************************************/
uint8_t lctrDecodeCtrlPdu(lctrDataPdu_t *pPdu, const uint8_t *pBuf, uint8_t role)
{
  uint8_t result = LL_SUCCESS;

  pBuf += lctrUnpackDataPduHdr(&pPdu->hdr, pBuf);
  pPdu->opcode = *pBuf;

  switch (pPdu->opcode)
  {
    case LL_PDU_CONN_UPDATE_IND:
      if (role == LL_ROLE_MASTER)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      if (lctrUnpackConnUpdateIndPdu(&pPdu->pld.connUpdInd, pBuf) != pPdu->hdr.len)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      if (lctrIsConnUpdateParamValid(&pPdu->pld.connUpdInd) == FALSE)
      {
        return LL_ERROR_CODE_INVALID_LMP_PARAMS;
      }
      break;
    case LL_PDU_CONN_PARAM_RSP:
      if (role == LL_ROLE_SLAVE)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      /* Fallthrough */
    case LL_PDU_CONN_PARAM_REQ:
      if ((lmgrCb.features & LL_FEAT_CONN_PARAM_REQ_PROC) == 0)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      if (lctrUnpackConnParamPdu(&pPdu->pld.connParamReqRsp, pBuf) != pPdu->hdr.len)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      break;
    case LL_PDU_CHANNEL_MAP_IND:
      if (role == LL_ROLE_MASTER)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      if (lctrUnpackChanMapIndPdu(&pPdu->pld.chanMapInd, pBuf) != pPdu->hdr.len)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      if (lctrIsChanMaskValid(pPdu->pld.chanMapInd.chanMask) == FALSE)
      {
        return LL_ERROR_CODE_INVALID_LMP_PARAMS;
      }
      break;
    case LL_PDU_LENGTH_REQ:
    case LL_PDU_LENGTH_RSP:
      if (lctrUnpackDataLengthPdu(&pPdu->pld.lenRsp, pBuf) != pPdu->hdr.len)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      break;
    case LL_PDU_TERMINATE_IND:
      if (lctrUnpackTerminateIndPdu(&pPdu->pld.termInd, pBuf) != pPdu->hdr.len)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      break;
    case LL_PDU_UNKNOWN_RSP:
      if (lctrUnpackUnknownRspPdu(&pPdu->pld.unknownRsp, pBuf) != pPdu->hdr.len)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      break;
    case LL_PDU_FEATURE_REQ:
      if (role == LL_ROLE_MASTER)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      /* Fallthrough */
    case LL_PDU_FEATURE_RSP:
      if (lctrUnpackFeaturePdu(&pPdu->pld.featReqRsp, pBuf) != pPdu->hdr.len)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      break;
    case LL_PDU_SLV_FEATURE_REQ:
      if (role == LL_ROLE_SLAVE)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      if ((lmgrCb.features & LL_FEAT_SLV_INIT_FEAT_EXCH) == 0)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      if (lctrUnpackFeaturePdu(&pPdu->pld.featReqRsp, pBuf) != pPdu->hdr.len)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      break;
    case LL_PDU_VERSION_IND:
      if (lctrUnpackVersionIndPdu(&pPdu->pld.verInd, pBuf) != pPdu->hdr.len)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      break;
    case LL_PDU_REJECT_IND:
      pPdu->pld.rejInd.opcode = LL_PDU_UNSPECIFIED;
      if (lctrUnpackRejectIndPdu(&pPdu->pld.rejInd.reason, pBuf) != pPdu->hdr.len)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      break;
    case LL_PDU_REJECT_EXT_IND:
      if ((lmgrCb.features & LL_FEAT_EXT_REJECT_IND) == 0)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      if (lctrUnpackRejectExtIndPdu(&pPdu->pld.rejInd, pBuf) != pPdu->hdr.len)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      break;
    case LL_PDU_PHY_RSP:
      if (role == LL_ROLE_SLAVE)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      /* Fallthrough */
    case LL_PDU_PHY_REQ:
      if ((lmgrCb.features & (LL_FEAT_LE_2M_PHY | LL_FEAT_LE_CODED_PHY)) == 0)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      if (lctrUnpackPhyPdu(&pPdu->pld.phyReq, pBuf) != pPdu->hdr.len)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      break;
    case LL_PDU_PHY_UPDATE_IND:
      if (role == LL_ROLE_MASTER)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      if ((lmgrCb.features & (LL_FEAT_LE_2M_PHY | LL_FEAT_LE_CODED_PHY)) == 0)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      if (lctrUnpackPhyUpdateIndPdu(&pPdu->pld.phyUpdInd, pBuf) != pPdu->hdr.len)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      break;
    case LL_PDU_MIN_USED_CHAN_IND:
      if (role == LL_ROLE_SLAVE)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      if ((lmgrCb.features & LL_FEAT_MIN_NUM_USED_CHAN) == 0)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      if (lctrUnpackMinUsedChanIndPdu(&pPdu->pld.minUsedChanInd, pBuf) != pPdu->hdr.len)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      if (lctrIsSetMinUsedChanParamValid(&pPdu->pld.minUsedChanInd) == FALSE)
      {
        return LL_ERROR_CODE_INVALID_LMP_PARAMS;
      }
      break;
    case LL_PDU_PERIODIC_SYNC_IND:
      if ((lmgrCb.features & LL_FEAT_PAST_RECIPIENT) == 0)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      if (lctrUnpackPerSyncIndPdu(&pPdu->pld.perSyncInd, pBuf) != pPdu->hdr.len)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      break;
    case LL_PDU_PEER_SCA_REQ:
      if ((lmgrCb.features & (LL_FEAT_SCA_UPDATE)) == 0)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      if (lctrUnpackPeerScaPdu(&pPdu->pld.peerSca, pBuf) != pPdu->hdr.len)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      break;
    case LL_PDU_PEER_SCA_RSP:
      if ((lmgrCb.features & (LL_FEAT_SCA_UPDATE)) == 0)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      if (lctrUnpackPeerScaPdu(&pPdu->pld.peerSca, pBuf) != pPdu->hdr.len)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      break;
    case LL_PDU_CIS_REQ:
      if ((lmgrCb.features & (LL_FEAT_CIS_SLAVE_ROLE)) == 0)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      if (lctrUnpackCisReqPdu(&pPdu->pld.cisReq, pBuf) != pPdu->hdr.len)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      break;
    case LL_PDU_CIS_RSP:
      if ((lmgrCb.features & (LL_FEAT_CIS_MASTER_ROLE)) == 0)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      if (lctrUnpackCisRspPdu(&pPdu->pld.cisRsp, pBuf) != pPdu->hdr.len)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      break;
    case LL_PDU_CIS_IND:
      if ((lmgrCb.features & (LL_FEAT_CIS_SLAVE_ROLE)) == 0)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      if (lctrUnpackCisIndPdu(&pPdu->pld.cisInd, pBuf) != pPdu->hdr.len)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      break;
    case LL_PDU_CIS_TERM_IND:
      if (((lmgrCb.features & (LL_FEAT_CIS_SLAVE_ROLE)) == 0) &&
          ((lmgrCb.features & (LL_FEAT_CIS_MASTER_ROLE)) == 0))
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      if (lctrUnpackCisTermPdu(&pPdu->pld.cisTerm, pBuf) != pPdu->hdr.len)
      {
        return LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      }
      break;

    default:
      result = LL_ERROR_CODE_UNKNOWN_LMP_PDU;
      break;
  }

  return result;
}
