/* 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 Device manager periodic advertising synchronization management and state machine
 */
/*************************************************************************************************/

#include <string.h>
#include "wsf_types.h"
#include "wsf_msg.h"
#include "wsf_assert.h"
#include "wsf_trace.h"
#include "dm_api.h"
#include "dm_main.h"
#include "dm_scan.h"

/**************************************************************************************************
  Macros
**************************************************************************************************/

/*! DM sync event handler messages for state machine */
enum
{
  /* messages from API */
  DM_SYNC_MSG_API_START = DM_MSG_START(DM_ID_SYNC), /*!< Start Sync */
  DM_SYNC_MSG_API_STOP,                             /*!< Stop Sync */

  /* messages from HCI */
  DM_SYNC_MSG_HCI_LE_SYNC_EST_FAIL,                 /*!< HCI LE Sync Establishment Failed */
  DM_SYNC_MSG_HCI_LE_SYNC_EST,                      /*!< HCI LE Sync Established */
  DM_SYNC_MSG_HCI_LE_SYNC_LOST,                     /*!< HCI LE Sync Lost */
  DM_SYNC_MSG_HCI_LE_SYNC_TRSF_EST_FAIL,            /*!< HCI LE Sync Transfer Establishment Failed */
  DM_SYNC_MSG_HCI_LE_SYNC_TRSF_EST                  /*!< HCI LE Sync Transfer Established */
};

/*! State machine states */
enum
{
  DM_SYNC_SM_ST_IDLE,                               /*!< Idle State */
  DM_SYNC_SM_ST_SYNCING,                            /*!< Synchronizing State */
  DM_SYNC_SM_ST_SYNCED,                             /*!< Synced State */
  DM_SYNC_SM_ST_DESYNCING,                          /*!< Desynchronizing State */

  DM_SYNC_SM_NUM_STATES
};

/*! State machine actions */
enum
{
  DM_SYNC_SM_ACT_NONE,                              /*!< No Action */
  DM_SYNC_SM_ACT_START,                             /*!< Process Start Sync */
  DM_SYNC_SM_ACT_STOP,                              /*!< Process Stop Sync */
  DM_SYNC_SM_ACT_CANCEL_START,                      /*!< Process Cancel Start Sync */
  DM_SYNC_SM_ACT_SYNC_EST,                          /*!< Process Sync Established */
  DM_SYNC_SM_ACT_SYNC_EST_FAILED,                   /*!< Process Sync Establishment Failed */
  DM_SYNC_SM_ACT_SYNC_LOST,                         /*!< Process Sync Lost */
  DM_SYNC_SM_ACT_SYNC_TRSF_EST,                     /*!< Process Sync Transfer Established */
  DM_SYNC_SM_ACT_SYNC_TRSF_EST_FAILED               /*!< Process Sync Transfer Establishment Failed */
};

/*! Column position of next state */
#define DM_SYNC_NEXT_STATE          0

/*! Column position of action */
#define DM_SYNC_ACTION              1

/*! Number of columns in the state machine state tables */
#define DM_SYNC_NUM_COLS            2

/*! Number of messages */
#define DM_SYNC_NUM_MSGS            (DM_SYNC_MSG_HCI_LE_SYNC_TRSF_EST - DM_SYNC_MSG_API_START + 1)

/*! Translate HCI event to state machine message */
#define DM_SYNC_HCI_EVT_2_MSG(evt)  (DM_SYNC_MSG_HCI_LE_SYNC_LOST - HCI_LE_PER_ADV_SYNC_LOST_CBACK_EVT + (evt))

/*! Uninitialized HCI sync handle */
#define DM_SYNC_HCI_HANDLE_NONE     0xFFFF

/*! Action function */
typedef void (*dmSyncAct_t)(dmSyncCb_t *pCcb, dmSyncMsg_t *pMsg);

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

/*! DM Sync state machine state tables */
static const uint8_t dmSyncStateTbl[DM_SYNC_SM_NUM_STATES][DM_SYNC_NUM_MSGS][DM_SYNC_NUM_COLS] =
{
  /* Idle state */
  {
  /* Event                        Next state                    Action */
  /* API_START */                 {DM_SYNC_SM_ST_SYNCING,       DM_SYNC_SM_ACT_START},
  /* API_STOP */                  {DM_SYNC_SM_ST_IDLE,          DM_SYNC_SM_ACT_NONE},
  /* HCI_LE_SYNC_EST_FAIL */      {DM_SYNC_SM_ST_IDLE,          DM_SYNC_SM_ACT_NONE},
  /* HCI_LE_SYNC_EST */           {DM_SYNC_SM_ST_IDLE,          DM_SYNC_SM_ACT_NONE},
  /* HCI_LE_SYNC_LOST */          {DM_SYNC_SM_ST_IDLE,          DM_SYNC_SM_ACT_NONE},
  /* HCI_LE_SYNC_TRSF_EST_FAIL */ {DM_SYNC_SM_ST_IDLE,          DM_SYNC_SM_ACT_SYNC_TRSF_EST_FAILED},
  /* HCI_LE_SYNC_TRSF_EST */      {DM_SYNC_SM_ST_SYNCED,        DM_SYNC_SM_ACT_SYNC_TRSF_EST}
  },
  /* Syncing state */
  {
  /* Event                        Next state                    Action */
  /* API_START */                 {DM_SYNC_SM_ST_SYNCING,       DM_SYNC_SM_ACT_NONE},
  /* API_STOP */                  {DM_SYNC_SM_ST_DESYNCING,     DM_SYNC_SM_ACT_CANCEL_START},
  /* HCI_LE_SYNC_EST_FAIL */      {DM_SYNC_SM_ST_IDLE,          DM_SYNC_SM_ACT_SYNC_EST_FAILED},
  /* HCI_LE_SYNC_EST */           {DM_SYNC_SM_ST_SYNCED,        DM_SYNC_SM_ACT_SYNC_EST},
  /* HCI_LE_SYNC_LOST */          {DM_SYNC_SM_ST_IDLE,          DM_SYNC_SM_ACT_SYNC_EST_FAILED},
  /* HCI_LE_SYNC_TRSF_EST_FAIL */ {DM_SYNC_SM_ST_IDLE,          DM_SYNC_SM_ACT_SYNC_TRSF_EST_FAILED},
  /* HCI_LE_SYNC_TRSF_EST */      {DM_SYNC_SM_ST_SYNCED,        DM_SYNC_SM_ACT_SYNC_TRSF_EST},
  },
  /* Synced state */
  {
  /* Event                        Next state                    Action */
  /* API_START */                 {DM_SYNC_SM_ST_SYNCED,        DM_SYNC_SM_ACT_NONE},
  /* API_STOP */                  {DM_SYNC_SM_ST_IDLE,          DM_SYNC_SM_ACT_STOP},
  /* HCI_LE_SYNC_EST_FAIL */      {DM_SYNC_SM_ST_SYNCED,        DM_SYNC_SM_ACT_NONE},
  /* HCI_LE_SYNC_EST */           {DM_SYNC_SM_ST_SYNCED,        DM_SYNC_SM_ACT_NONE},
  /* HCI_LE_SYNC_LOST */          {DM_SYNC_SM_ST_IDLE,          DM_SYNC_SM_ACT_SYNC_LOST},
  /* HCI_LE_SYNC_TRSF_EST_FAIL */ {DM_SYNC_SM_ST_SYNCED,        DM_SYNC_SM_ACT_NONE},
  /* HCI_LE_SYNC_TRSF_EST */      {DM_SYNC_SM_ST_SYNCED,        DM_SYNC_SM_ACT_NONE},
  },
  /* Desyncing state */
  {
  /* Event                        Next state                    Action */
  /* API_START */                 {DM_SYNC_SM_ST_DESYNCING,     DM_SYNC_SM_ACT_NONE},
  /* API_STOP */                  {DM_SYNC_SM_ST_DESYNCING,     DM_SYNC_SM_ACT_NONE},
  /* HCI_LE_SYNC_EST_FAIL */      {DM_SYNC_SM_ST_IDLE,          DM_SYNC_SM_ACT_SYNC_EST_FAILED},
  /* HCI_LE_SYNC_EST */           {DM_SYNC_SM_ST_DESYNCING,     DM_SYNC_SM_ACT_STOP},
  /* HCI_LE_SYNC_LOST */          {DM_SYNC_SM_ST_IDLE,          DM_SYNC_SM_ACT_SYNC_LOST},
  /* HCI_LE_SYNC_TRSF_EST_FAIL */ {DM_SYNC_SM_ST_IDLE,          DM_SYNC_SM_ACT_SYNC_TRSF_EST_FAILED},
  /* HCI_LE_SYNC_TRSF_EST */      {DM_SYNC_SM_ST_DESYNCING,     DM_SYNC_SM_ACT_STOP},
  }
};

/*! DM Sync action function table */
static const dmSyncAct_t dmSyncAct[] =
{
  dmSyncSmActNone,
  dmSyncSmActStart,
  dmSyncSmActStop,
  dmSyncSmActCancelStart,
  dmSyncSmActSyncEst,
  dmSyncSmActSyncEstFailed,
  dmSyncSmActSyncLost,
  dmSyncSmActSyncTrsfEst,
  dmSyncSmActSyncTrsfEstFailed
};

/*! DM Sync component function interface */
static const dmFcnIf_t dmSyncFcnIf =
{
  dmSyncReset,
  dmSyncHciHandler,
  dmSyncMsgHandler
};

/*! DM Periodic advertising sync control block */
static dmSyncCb_t dmSyncCb[DM_SYNC_MAX];

/*************************************************************************************************/
/*!
 *  \brief  Return the SCB with particular state conditions.
 *
 *  \return Pointer to SCB or NULL if failure.
 */
/*************************************************************************************************/
static dmSyncCb_t *dmSyncCmplState(void)
{
  dmSyncCb_t   *pScb = dmSyncCb;
  uint8_t       i;

  /* if there's a scb in accepting state */
  for (i = DM_SYNC_MAX; i > 0; i--, pScb++)
  {
    /* look for sync in desyncing state, cancelled sync */
    if (pScb->inUse                              &&
        (pScb->state == DM_SYNC_SM_ST_DESYNCING) &&
        (pScb->handle == DM_SYNC_HCI_HANDLE_NONE))
    {
      DM_TRACE_INFO1("dmSyncCmplState %d", pScb->syncId);
      return pScb;
    }
  }

  return NULL;
}

/*************************************************************************************************/
/*!
*  \brief  Allocate a DM sync control block.
*
*  \param  sid    Advertising Sid.
*  \param  pAddr  Advertiser BD address.
*
*  \return Pointer to sync CB or NULL if failure.
*/
/*************************************************************************************************/
static dmSyncCb_t *dmSyncCbAlloc(uint8_t sid, const uint8_t *pAddr)
{
  dmSyncCb_t   *pScb = dmSyncCb;
  uint8_t      i;

  for (i = 0; i < DM_SYNC_MAX; i++, pScb++)
  {
    if (pScb->inUse == FALSE)
    {
      memset(pScb, 0, sizeof(dmSyncCb_t));

      pScb->advSid = sid;
      BdaCpy(pScb->advAddr, pAddr);
      pScb->handle = DM_SYNC_HCI_HANDLE_NONE;
      pScb->syncId = i + 1;
      pScb->inUse = TRUE;

      DM_TRACE_ALLOC1("dmSyncCbAlloc %d", pScb->syncId);

      return pScb;
    }
  }

  DM_TRACE_ERR0("dmSyncCbAlloc failed");

  return NULL;
}

/*************************************************************************************************/
/*!
*  \brief  Deallocate a DM sync control block.
*
*  \param  pCb   Sync control block.
*
*  \return None.
*/
/*************************************************************************************************/
static void dmSyncCbDealloc(dmSyncCb_t *pCb)
{
  DM_TRACE_FREE1("dmSyncCbDealloc %d", pCb->syncId);

  pCb->inUse = FALSE;
}

/*************************************************************************************************/
/*!
*  \brief  Find a sync control block with matching handle.
*
*  \param  handle  Handle to find.
*
*  \return Pointer to sync CB or NULL if failure.
*/
/*************************************************************************************************/
static dmSyncCb_t *dmSyncCbByHandle(uint16_t handle)
{
  dmSyncCb_t   *pScb = dmSyncCb;
  uint8_t      i;

  for (i = DM_SYNC_MAX; i > 0; i--, pScb++)
  {
    if (pScb->inUse && (pScb->handle == handle))
    {
      return pScb;
    }
  }

  DM_TRACE_WARN1("dmSyncCbByHandle not found 0x%04x", handle);

  return NULL;
}

/*************************************************************************************************/
/*!
*  \brief  Find a sync control block with advertising SID and advertiser BD address.
*
*  \param  sid    Sid to find.
*  \param  pAddr  BD address to find.
*
*  \return Pointer to CB or NULL if failure.
*/
/*************************************************************************************************/
dmSyncCb_t *dmSyncCbBySidBdAddr(uint8_t sid, const uint8_t *pAddr)
{
  dmSyncCb_t   *pScb = dmSyncCb;
  uint8_t       i;

  for (i = DM_SYNC_MAX; i > 0; i--, pScb++)
  {
    if (pScb->inUse && (pScb->advSid == sid) && BdaCmp(pScb->advAddr, pAddr))
    {
      return pScb;
    }
  }

  DM_TRACE_INFO0("dmSyncCbBySidBdAddr not found");

  return NULL;
}

/*************************************************************************************************/
/*!
*  \brief  Return the sync control block for the given sync ID.
*
*  \param  syncId  Sync ID.
*
*  \return Pointer to CB or NULL if failure.
*/
/*************************************************************************************************/
dmSyncCb_t *dmSyncCbById(dmSyncId_t syncId)
{
  WSF_ASSERT((syncId > 0) && (syncId <= DM_SYNC_MAX));

  syncId--;
  if (dmSyncCb[syncId].inUse)
  {
    return &dmSyncCb[syncId];
  }

  return NULL;
}

/*************************************************************************************************/
/*!
 *  \brief  Empty action.
 *
 *  \param  pScb    Sync control block.
 *  \param  pMsg    WSF message.
 *
 *  \return None.
 */
/*************************************************************************************************/
void dmSyncSmActNone(dmSyncCb_t *pScb, dmSyncMsg_t *pMsg)
{
  return;
}

/*************************************************************************************************/
/*!
 *  \brief  Synchronize with periodic advertising from the given advertiser.
 *
 *  \param  pScb    Sync control block.
 *  \param  pMsg    WSF message.
 *
 *  \return None.
 */
/*************************************************************************************************/
void dmSyncSmActStart(dmSyncCb_t *pScb, dmSyncMsg_t *pMsg)
{
  HciLePerAdvCreateSyncCmd(dmCb.syncOptions, pMsg->apiSyncStart.advSid,
                           pMsg->apiSyncStart.advAddrType, pMsg->apiSyncStart.advAddr,
                           pMsg->apiSyncStart.skip, pMsg->apiSyncStart.syncTimeout,
                           pMsg->apiSyncStart.unused);
}

/*************************************************************************************************/
/*!
 *  \brief  Stop reception of the periodic advertising identified by the given sync handle.
 *
 *  \param  pScb    Sync control block.
 *  \param  pMsg    WSF message.
 *
 *  \return None.
 */
/*************************************************************************************************/
void dmSyncSmActStop(dmSyncCb_t *pScb, dmSyncMsg_t *pMsg)
{
  HciLePerAdvTerminateSyncCmd(pScb->handle);

  dmSyncSmActSyncLost(pScb, pMsg);
}

/*************************************************************************************************/
/*!
 *  \brief  Cancel creation of a sync while it's pending.
 *
 *  \param  pScb    Sync control block.
 *  \param  pMsg    WSF message.
 *
 *  \return None.
 */
/*************************************************************************************************/
void dmSyncSmActCancelStart(dmSyncCb_t *pScb, dmSyncMsg_t *pMsg)
{
  HciLePerAdvCreateSyncCancelCmd();
}

/*************************************************************************************************/
/*!
 *  \brief  Handle a sync established event from HCI.
 *
 *  \param  pScb    Sync control block.
 *  \param  pMsg    WSF message.
 *
 *  \return None.
 */
/*************************************************************************************************/
void dmSyncSmActSyncEst(dmSyncCb_t *pScb, dmSyncMsg_t *pMsg)
{
  /* store adv sid, adv address and sync handle */
  pScb->advSid = pMsg->perAdvSyncEst.advSid;
  BdaCpy(pScb->advAddr, pMsg->perAdvSyncEst.advAddr);
  pScb->advAddrType = DmHostAddrType(pMsg->perAdvSyncEst.advAddrType);
  pScb->handle = pMsg->perAdvSyncEst.syncHandle;

  pMsg->hdr.event = DM_PER_ADV_SYNC_EST_IND;
  (*dmCb.cback)((dmEvt_t *) pMsg);
}

/*************************************************************************************************/
/*!
 *  \brief  Handle a sync established failure event from HCI.
 *
 *  \param  pScb    Sync control block.
 *  \param  pMsg    WSF message.
 *
 *  \return None.
 */
/*************************************************************************************************/
void dmSyncSmActSyncEstFailed(dmSyncCb_t *pScb, dmSyncMsg_t *pMsg)
{
  /* deallocate scb */
  dmSyncCbDealloc(pScb);

  pMsg->hdr.event = DM_PER_ADV_SYNC_EST_FAIL_IND;
  (*dmCb.cback)((dmEvt_t *) pMsg);
}

/*************************************************************************************************/
/*!
 *  \brief  Handle a sync lost event from HCI.
 *
 *  \param  pScb    Sync control block.
 *  \param  pMsg    WSF message.
 *
 *  \return None.
 */
/*************************************************************************************************/
void dmSyncSmActSyncLost(dmSyncCb_t *pScb, dmSyncMsg_t *pMsg)
{
  /* deallocate scb */
  dmSyncCbDealloc(pScb);

  pMsg->hdr.event = DM_PER_ADV_SYNC_LOST_IND;
  (*dmCb.cback)((dmEvt_t *) pMsg);
}

/*************************************************************************************************/
/*!
 *  \brief  Handle a sync transfer established event from HCI.
 *
 *  \param  pScb    Sync control block.
 *  \param  pMsg    WSF message.
 *
 *  \return None.
 */
/*************************************************************************************************/
void dmSyncSmActSyncTrsfEst(dmSyncCb_t *pScb, dmSyncMsg_t *pMsg)
{
  /* store adv sid, adv address and sync handle */
  pScb->advSid = pMsg->perAdvSyncTrsfEst.advSid;
  BdaCpy(pScb->advAddr, pMsg->perAdvSyncTrsfEst.advAddr);
  pScb->advAddrType = DmHostAddrType(pMsg->perAdvSyncTrsfEst.advAddrType);
  pScb->handle = pMsg->perAdvSyncTrsfEst.syncHandle;

  pMsg->hdr.event = DM_PER_ADV_SYNC_TRSF_EST_IND;
  (*dmCb.cback)((dmEvt_t *) pMsg);
}

/*************************************************************************************************/
/*!
 *  \brief  Handle a sync transfer established failure event from HCI.
 *
 *  \param  pScb    Sync control block.
 *  \param  pMsg    WSF message.
 *
 *  \return None.
 */
/*************************************************************************************************/
void dmSyncSmActSyncTrsfEstFailed(dmSyncCb_t *pScb, dmSyncMsg_t *pMsg)
{
  /* deallocate scb */
  dmSyncCbDealloc(pScb);

  pMsg->hdr.event = DM_PER_ADV_SYNC_TRSF_EST_FAIL_IND;
  (*dmCb.cback)((dmEvt_t *) pMsg);
}

/*************************************************************************************************/
/*!
 *  \brief  Execute the DM sync state machine.
 *
 *  \param  pScb    Sync control block.
 *  \param  pMsg    WSF message.
 *
 *  \return None.
 */
/*************************************************************************************************/
void dmSyncSmExecute(dmSyncCb_t *pScb, dmSyncMsg_t *pMsg)
{
  uint8_t action;
  uint8_t event;

  DM_TRACE_INFO2("dmSyncSmExecute event=%d state=%d", pMsg->hdr.event, pScb->state);

  /* get the event */
  event = DM_MSG_MASK(pMsg->hdr.event);

  /* get action */
  action = dmSyncStateTbl[pScb->state][event][DM_SYNC_ACTION];

  /* set next state */
  pScb->state = dmSyncStateTbl[pScb->state][event][DM_SYNC_NEXT_STATE];

  /* execute action function */
  (*dmSyncAct[action])(pScb, pMsg);
}

/*************************************************************************************************/
/*!
 *  \brief  Initialize the sync module.
 *
 *  \return None.
 */
/*************************************************************************************************/
void dmSyncInit(void)
{
  dmFcnIfTbl[DM_ID_SYNC] = (dmFcnIf_t *) &dmSyncFcnIf;
}

/*************************************************************************************************/
/*!
 *  \brief  Reset the sync module.
 *
 *  \return None.
 */
/*************************************************************************************************/
void dmSyncReset(void)
{
  dmSyncCb_t                *pScb = dmSyncCb;
  hciLePerAdvSyncLostEvt_t  syncLost;
  uint8_t                   i;

  /* generate HCI sync lost event */
  syncLost.hdr.event = HCI_LE_PER_ADV_SYNC_LOST_CBACK_EVT;
  syncLost.hdr.status = HCI_SUCCESS;

  for (i = DM_SYNC_MAX; i > 0; i--, pScb++)
  {
    if (pScb->inUse)
    {
      /* set sync handle */
      syncLost.hdr.param = syncLost.syncHandle = pScb->handle;

      /* handle the event */
      dmSyncHciHandler((hciEvt_t *) &syncLost);
    }
  }
}

/*************************************************************************************************/
/*!
 *  \brief  DM sync HCI event handler.
 *
 *  \param  pEvent  Pointer to HCI callback event structure.
 *
 *  \return None.
 */
/*************************************************************************************************/
void dmSyncHciHandler(hciEvt_t *pEvent)
{
  dmSyncCb_t *pScb;

  /* handle special case for periodic advertising report event */
  if (pEvent->hdr.event == HCI_LE_PER_ADV_REPORT_CBACK_EVT)
  {
    pEvent->hdr.event = DM_PER_ADV_REPORT_IND;
    (*dmCb.cback)((dmEvt_t *) pEvent);
    return;
  }

  /* handle special cases for sync established event */
  if (pEvent->hdr.event == HCI_LE_PER_ADV_SYNC_EST_CBACK_EVT)
  {
    /* first check if scb exists for this sid and bd addr */
    pScb = dmSyncCbBySidBdAddr(pEvent->lePerAdvSyncEst.advSid, pEvent->lePerAdvSyncEst.advAddr);

    /* if scb not found */
    if (pScb == NULL)
    {
      /* check for special case state */
      pScb = dmSyncCmplState();
    }

    /* translate HCI event to state machine event */
    if (pEvent->hdr.status == HCI_SUCCESS)
    {
      pEvent->hdr.event = DM_SYNC_MSG_HCI_LE_SYNC_EST;
    }
    else
    {
      pEvent->hdr.event = DM_SYNC_MSG_HCI_LE_SYNC_EST_FAIL;
    }
  }
  /* handle special cases for past received event */
  else if (pEvent->hdr.event == HCI_LE_PER_SYNC_TRSF_RCVD_CBACK_EVT)
  {
    /* first check if scb exists for this sid and bd addr */
    pScb = dmSyncCbBySidBdAddr(pEvent->lePerAdvSyncTrsfRcvd.advSid, pEvent->lePerAdvSyncTrsfRcvd.advAddr);

    /* if scb not found */
    if (pScb == NULL)
    {
      /* allocate scb */
      pScb = dmSyncCbAlloc(pEvent->lePerAdvSyncTrsfRcvd.advSid, pEvent->lePerAdvSyncTrsfRcvd.advAddr);
    }

    /* translate HCI event to state machine event */
    if (pEvent->hdr.status == HCI_SUCCESS)
    {
      pEvent->hdr.event = DM_SYNC_MSG_HCI_LE_SYNC_TRSF_EST;
    }
    else
    {
      pEvent->hdr.event = DM_SYNC_MSG_HCI_LE_SYNC_TRSF_EST_FAIL;
    }
  }
  else
  {
    pScb = dmSyncCbByHandle(pEvent->hdr.param);

    /* translate HCI event to state machine message */
    pEvent->hdr.event = DM_SYNC_HCI_EVT_2_MSG(pEvent->hdr.event);
  }

  /* if scb found */
  if (pScb != NULL)
  {
    /* set sync id */
    pEvent->hdr.param = pScb->syncId;

    /* execute state machine */
    dmSyncSmExecute(pScb, (dmSyncMsg_t *) pEvent);
  }
}

/*************************************************************************************************/
/*!
 *  \brief  DM sync event handler.
 *
 *  \param  pMsg    WSF message.
 *
 *  \return None.
 */
/*************************************************************************************************/
void dmSyncMsgHandler(wsfMsgHdr_t *pMsg)
{
  dmSyncCb_t *pScb;

  /* look up scb from sync id */
  if ((pScb = dmSyncCbById((dmSyncId_t) pMsg->param)) != NULL)
  {
    /* execute state machine */
    dmSyncSmExecute(pScb, (dmSyncMsg_t *) pMsg);
  }
}

/*************************************************************************************************/
/*!
 *  \brief  Synchronize with periodic advertising from the given advertiser, and start receiving
 *          periodic advertising packets.
 *
 *          Note: The synchronization filter policy is used to determine whether the periodic
 *                advertiser list is used. If the periodic advertiser list is not used, the
 *                advertising SID, advertiser address type, and advertiser address parameters
 *                specify the periodic advertising device to listen to; otherwise these parameters
 *                are ignored.
 *
 *  \param  advSid        Advertising SID.
 *  \param  advAddrType   Advertiser address type.
 *  \param  pAdvAddr      Advertiser address.
 *  \param  skip          Number of periodic advertising packets that can be skipped after
 *                        successful receive.
 *  \param  syncTimeout   Synchronization timeout.
 *
 *  \return Sync identifier.
 */
/*************************************************************************************************/
dmSyncId_t DmSyncStart(uint8_t advSid, uint8_t advAddrType, const uint8_t *pAdvAddr, uint16_t skip,
                       uint16_t syncTimeout)
{
  dmSyncCb_t       *pScb = NULL;
  dmSyncApiStart_t *pMsg;

  /* make sure scb not already allocated */
  WsfTaskLock();
  if ((pScb = dmSyncCbBySidBdAddr(advSid, pAdvAddr)) == NULL)
  {
    /* allocate scb */
    pScb = dmSyncCbAlloc(advSid, pAdvAddr);
  }
  WsfTaskUnlock();

  if (pScb != NULL)
  {
    if ((pMsg = WsfMsgAlloc(sizeof(dmSyncApiStart_t))) != NULL)
    {
      pMsg->hdr.param = pScb->syncId;
      pMsg->hdr.event = DM_SYNC_MSG_API_START;
      pMsg->advSid = advSid;
      pMsg->advAddrType = advAddrType;
      BdaCpy(pMsg->advAddr, pAdvAddr);
      pMsg->skip = skip;
      pMsg->syncTimeout = syncTimeout;
      pMsg->unused = 0;
      WsfMsgSend(dmCb.handlerId, pMsg);

      /* return sync id */
      return pScb->syncId;
    }
    else
    {
      WsfTaskLock();
      dmSyncCbDealloc(pScb);
      WsfTaskUnlock();
    }
  }

  /* open failed */
  return DM_SYNC_ID_NONE;
}

/*************************************************************************************************/
/*!
 *  \brief  Stop reception of the periodic advertising identified by the given sync identifier.
 *
 *  \param  syncId        Sync identifier.
 *
 *  \return None.
 */
/*************************************************************************************************/
void DmSyncStop(dmSyncId_t syncId)
{
  wsfMsgHdr_t *pMsg;

  if ((pMsg = WsfMsgAlloc(sizeof(wsfMsgHdr_t))) != NULL)
  {
    pMsg->param = syncId;
    pMsg->event = DM_SYNC_MSG_API_STOP;
    WsfMsgSend(dmCb.handlerId, pMsg);
  }
}

/*************************************************************************************************/
/*!
 *  \brief  Add device to periodic advertiser list.
 *
 *  \param  advAddrType   Advertiser address type.
 *  \param  pAdvAddr      Advertiser address.
 *  \param  advSid        Advertising SID.
 *
 *  \return None.
 */
/*************************************************************************************************/
void DmAddDeviceToPerAdvList(uint8_t advAddrType, uint8_t *pAdvAddr, uint8_t advSid)
{
  HciLeAddDeviceToPerAdvListCmd(advAddrType, pAdvAddr, advSid);
}

/*************************************************************************************************/
/*!
 *  \brief  DM remove device from periodic advertiser list.
 *
 *  \param  advAddrType   Advertiser address type.
 *  \param  pAdvAddr      Advertiser address.
 *  \param  advSid        Advertising SID.
 *
 *  \return None.
 */
/*************************************************************************************************/
void DmRemoveDeviceFromPerAdvList(uint8_t advAddrType, uint8_t *pAdvAddr, uint8_t advSid)
{
  HciLeRemoveDeviceFromPerAdvListCmd(advAddrType, pAdvAddr, advSid);
}

/*************************************************************************************************/
/*!
 *  \brief  DM clear periodic advertiser list.
 *
 *  \return None.
 */
/*************************************************************************************************/
void DmClearPerAdvList(void)
{
  HciLeClearPerAdvListCmd();
}

/*************************************************************************************************/
/*!
 *  \brief  DM enable or disable initial periodic advertising reports once synchronized.
 *
 *  \param  enable        TRUE to enable initial reporting, FALSE to disable initial reporting.
 *
 *  \return None.
 */
/*************************************************************************************************/
void DmSyncInitialRptEnable(bool_t enable)
{
  WsfTaskLock();
  if (enable)
  {
    /* Enable initial periodic advertisement reporting */
    dmCb.syncOptions &= ~HCI_OPTIONS_INIT_RPT_ENABLE_BIT;
  }
  else
  {
    /* Disable initial periodic advertisement reporting */
    dmCb.syncOptions |= HCI_OPTIONS_INIT_RPT_ENABLE_BIT;
  }
  WsfTaskUnlock();
}
