text: /*************************************************************************************************/
  /*!
   *  \file
   *
   *  \brief  Device manager connection management module.
   *
   *  Copyright (c) 2016-2018 Arm Ltd.
   *
   *  Copyright (c) 2019 Packetcraft, Inc.
   *
   *  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.
   */
  /*************************************************************************************************/
  
  #include <string.h>
  #include "wsf_types.h"
  #include "wsf_assert.h"
  #include "wsf_trace.h"
  #include "wsf_msg.h"
  #include "wsf_os.h"
  #include "util/bstream.h"
  #include "dm_api.h"
  #include "dm_dev.h"
  #include "dm_main.h"
  #include "dm_conn.h"
  
  /**************************************************************************************************
    Macros
  **************************************************************************************************/
  
  /* Translate HCI event to state machine message */
  #define DM_CONN_HCI_EVT_2_MSG(evt)  (DM_CONN_MSG_HCI_DISCONNECT_CMPL - 3 + (evt))
  
  /**************************************************************************************************
    Local Variables
  **************************************************************************************************/
  
  /* Conn spec defaults */
  static const hciConnSpec_t dmConnSpecDefaults =
  {
    DM_GAP_INITIAL_CONN_INT_MIN,
    DM_GAP_INITIAL_CONN_INT_MAX,
    DM_GAP_CONN_EST_LATENCY,
    DM_DEFAULT_EST_SUP_TIMEOUT,
    DM_GAP_CONN_EST_MIN_CE_LEN,
    DM_GAP_CONN_EST_MAX_CE_LEN
  };
  
  /* Action set for this module */
  static const dmConnAct_t dmConnActSetMain[] =
  {
    dmConnSmActNone,
    dmConnSmActClose,
    dmConnSmActConnOpened,
    dmConnSmActConnFailed,
    dmConnSmActConnClosed,
    dmConnSmActHciUpdated
  };
  
  /* Component function interface */
  static const dmFcnIf_t dmConnFcnIf =
  {
    dmConnReset,
    dmConnHciHandler,
    dmConnMsgHandler
  };
  
  /* Component 2 function interface */
  static const dmFcnIf_t dmConn2FcnIf =
  {
    dmEmptyReset,
    dmConn2HciHandler,
    dmConn2MsgHandler
  };
  
  /**************************************************************************************************
    Global Variables
  **************************************************************************************************/
  
  /* Control block */
  dmConnCb_t dmConnCb;
  
  /**************************************************************************************************
    Local Functions
  **************************************************************************************************/
  static void dmConn2ActRssiRead(dmConnCcb_t *pCcb, hciEvt_t *pEvent);
  static void dmConn2ActRemoteConnParamReq(dmConnCcb_t *pCcb, hciEvt_t *pEvent);
  static void dmConn2ActDataLenChange(dmConnCcb_t *pCcb, hciEvt_t *pEvent);
  static void dmConn2ActWriteAuthToCmpl(dmConnCcb_t *pCcb, hciEvt_t *pEvent);
  static void dmConn2ActAuthToExpired(dmConnCcb_t *pCcb, hciEvt_t *pEvent);
  static void dmConn2ActReadRemoteFeaturesCmpl(dmConnCcb_t *pCcb, hciEvt_t *pEvent);
  static void dmConn2ActReadRemoteVerInfoCmpl(dmConnCcb_t *pCcb, hciEvt_t *pEvent);
  
  /*************************************************************************************************/
  /*!
   *  \brief  Return the CCB with particular state conditions.
   *
   *  \return Pointer to CCB or NULL if failure.
   */
  /*************************************************************************************************/
  static dmConnCcb_t *dmConnCmplStates(void)
  {
    dmConnCcb_t   *pCcb = dmConnCb.ccb;
    uint8_t       i;
  
    /* if there's a ccb in accepting state */
    for (i = DM_CONN_MAX; i > 0; i--, pCcb++)
    {
      /* look for connection in accepting state or disconnecting state, cancelled connection */
      if (pCcb->inUse &&
          ((pCcb->state == DM_CONN_SM_ST_ACCEPTING) ||
           ((pCcb->state == DM_CONN_SM_ST_DISCONNECTING) && (pCcb->handle == DM_CONN_HCI_HANDLE_NONE))))
      {
        DM_TRACE_INFO1("dmConnCmplStates %d", pCcb->connId);
        return pCcb;
      }
    }
  
    return NULL;
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Allocate a DM connection control block.
   *
   *  \param  pAddr   BD address for connection.
   *
   *  \return Pointer to CCB or NULL if failure.
   */
  /*************************************************************************************************/
  dmConnCcb_t *dmConnCcbAlloc(uint8_t *pAddr)
  {
    dmConnCcb_t   *pCcb = dmConnCb.ccb;
    uint8_t       i;
  
    for (i = 0; i < DM_CONN_MAX; i++, pCcb++)
    {
      if (pCcb->inUse == FALSE)
      {
        memset(pCcb, 0, sizeof(dmConnCcb_t));
  
        BdaCpy(pCcb->peerAddr, pAddr);
        pCcb->handle = DM_CONN_HCI_HANDLE_NONE;
        pCcb->connId = i + 1;
        pCcb->updating = FALSE;
        pCcb->inUse = TRUE;
        pCcb->featuresPresent = FALSE;
  
        DM_TRACE_ALLOC1("dmConnCcbAlloc %d", pCcb->connId);
  
        return pCcb;
      }
    }
  
    DM_TRACE_ERR0("dmConnCcbAlloc failed");
  
    return NULL;
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Deallocate a DM connection control block.
   *
   *  \param  pCcb  Connection control block.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  void dmConnCcbDealloc(dmConnCcb_t *pCcb)
  {
    DM_TRACE_FREE1("dmConnCcbDealloc %d", pCcb->connId);
  
    pCcb->inUse = FALSE;
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Find a connection control block with matching handle.
   *
   *  \param  handle  Handle to find.
   *
   *  \return Pointer to CCB or NULL if failure.
   */
  /*************************************************************************************************/
  dmConnCcb_t *dmConnCcbByHandle(uint16_t handle)
  {
    dmConnCcb_t   *pCcb = dmConnCb.ccb;
    uint8_t       i;
  
    for (i = DM_CONN_MAX; i > 0; i--, pCcb++)
    {
      if (pCcb->inUse && (pCcb->handle == handle))
      {
        return pCcb;
      }
    }
  
    DM_TRACE_WARN1("dmConnCcbByHandle not found 0x%04x", handle);
  
    return NULL;
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Find a connection control block with BD address.
   *
   *  \param  pAddr  BD address to find.
   *
   *  \return Pointer to CCB or NULL if failure.
   */
  /*************************************************************************************************/
  dmConnCcb_t *dmConnCcbByBdAddr(uint8_t *pAddr)
  {
    dmConnCcb_t   *pCcb = dmConnCb.ccb;
    uint8_t       i;
  
    for (i = DM_CONN_MAX; i > 0; i--, pCcb++)
    {
      if (pCcb->inUse && BdaCmp(pCcb->peerAddr, pAddr))
      {
        return pCcb;
      }
    }
  
    DM_TRACE_INFO0("dmConnIdByBdAddr not found");
  
    return NULL;
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Return the connection control block for the given connection ID.
   *
   *  \param  connId  Connection ID.
   *
   *  \return Pointer to CCB or NULL if failure.
   */
  /*************************************************************************************************/
  dmConnCcb_t *dmConnCcbById(dmConnId_t connId)
  {
    WSF_ASSERT((connId > 0) && (connId <= DM_CONN_MAX));
  
    connId--;
    if (dmConnCb.ccb[connId].inUse)
    {
      return &dmConnCb.ccb[connId];
    }
  
    return NULL;
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Return the number of active connections.
   *
   *  \return Number of connections.
   */
  /*************************************************************************************************/
  uint8_t dmConnNum(void)
  {
    dmConnCcb_t   *pCcb = dmConnCb.ccb;
    uint8_t       i, j;
  
    for (i = DM_CONN_MAX, j = 0; i > 0; i--, pCcb++)
    {
      if (pCcb->inUse)
      {
        j++;
      }
    }
  
    return j;
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Execute all registered DM connection callbacks.
   *
   *  \param  pMsg    WSF message.
   *  \param  pCcb    Connection control block.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  void dmConnExecCback(dmConnMsg_t *pMsg)
  {
    uint8_t i;
  
    for (i = 0; i < DM_CLIENT_ID_MAX; i++)
    {
      if (dmConnCb.connCback[i] != NULL)
      {
        (*dmConnCb.connCback[i])((dmEvt_t *) pMsg);
      }
    }
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  This utility function is called to open or accept a connection.
   *
   *  \param  clientId     The client identifier.
   *  \param  initPhys     Initiator PHYs.
   *  \param  advHandle    Advertising handle.
   *  \param  advType      Advertising type.
   *  \param  duration     Advertising duration (in ms).
   *  \param  maxEaEvents  Maximum number of extended advertising events.
   *  \param  addrType     Address type.
   *  \param  pAddr        Peer device address.
   *  \param  role         Device role.
   *
   *  \return Connection identifier.
   */
  /*************************************************************************************************/
  dmConnId_t dmConnOpenAccept(uint8_t clientId, uint8_t initPhys, uint8_t advHandle, uint8_t advType,
                              uint16_t duration, uint8_t maxEaEvents, uint8_t addrType, uint8_t *pAddr,
                              uint8_t role)
  {
    dmConnCcb_t           *pCcb = NULL;
    dmConnApiOpen_t       *pMsg;
  
    /* make sure ccb not already allocated */
    WsfTaskLock();
    if ((pCcb = dmConnCcbByBdAddr(pAddr)) == NULL)
    {
      /* allocate ccb */
      pCcb = dmConnCcbAlloc(pAddr);
    }
    WsfTaskUnlock();
  
    if (pCcb != NULL)
    {
      if ((pMsg = WsfMsgAlloc(sizeof(dmConnApiOpen_t))) != NULL)
      {
        pMsg->hdr.param = pCcb->connId;
        pMsg->hdr.event = (role == DM_ROLE_MASTER) ?
                          DM_CONN_MSG_API_OPEN : DM_CONN_MSG_API_ACCEPT;
        pMsg->initPhys = initPhys;
        pMsg->advHandle = advHandle;
        pMsg->advType = advType;
        pMsg->duration = duration;
        pMsg->maxEaEvents = maxEaEvents;
        BdaCpy(pMsg->peerAddr, pAddr);
        pMsg->addrType = addrType;
        pMsg->clientId = clientId;
        WsfMsgSend(dmCb.handlerId, pMsg);
  
        /* set role */
        WsfTaskLock();
        pCcb->role = role;
        WsfTaskUnlock();
  
        /* return connection id */
        return pCcb->connId;
      }
      else
      {
        WsfTaskLock();
        dmConnCcbDealloc(pCcb);
        WsfTaskUnlock();
      }
    }
  
    /* open failed */
    return DM_CONN_ID_NONE;
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Empty action.
   *
   *  \param  pMsg    WSF message.
   *  \param  pCcb    Connection control block.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  void dmConnSmActNone(dmConnCcb_t *pCcb, dmConnMsg_t *pMsg)
  {
    return;
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Close a connection.
   *
   *  \param  pMsg    WSF message.
   *  \param  pCcb    Connection control block.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  void dmConnSmActClose(dmConnCcb_t *pCcb, dmConnMsg_t *pMsg)
  {
    HciDisconnectCmd(pCcb->handle, pMsg->apiClose.reason);
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Handle a connection complete event from HCI.
   *
   *  \param  pMsg    WSF message.
   *  \param  pCcb    Connection control block.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  void dmConnSmActConnOpened(dmConnCcb_t *pCcb, dmConnMsg_t *pMsg)
  {
    /* store peer address, handle, and role */
    BdaCpy(pCcb->peerAddr, pMsg->hciLeConnCmpl.peerAddr);
    pCcb->handle = pMsg->hciLeConnCmpl.handle;
    pCcb->peerAddrType = DmHostAddrType(pMsg->hciLeConnCmpl.addrType);
    pCcb->role = pMsg->hciLeConnCmpl.role;
  
    /* set local address type of connection */
    if (pCcb->role == DM_ROLE_MASTER)
    {
      pCcb->localAddrType = dmCb.connAddrType;
    }
    else
    {
      pCcb->localAddrType = dmCb.advAddrType;
    }
  
    /* set local address of connection */
    if (pCcb->localAddrType == DM_ADDR_PUBLIC)
    {
      BdaCpy(pCcb->localAddr, HciGetBdAddr());
    }
    else
    {
      BdaCpy(pCcb->localAddr, dmCb.localAddr);
    }
  
    /* store enhanced fields */
    BdaCpy(pCcb->localRpa, pMsg->hciLeConnCmpl.localRpa);
    BdaCpy(pCcb->peerRpa, pMsg->hciLeConnCmpl.peerRpa);
  
    /* initialize idle state */
    pCcb->idleMask = 0;
  
    /* if central */
    if (pCcb->role == DM_ROLE_MASTER)
    {
      /* pass connection initiation completed to dev priv */
      dmDevPassEvtToDevPriv(DM_DEV_PRIV_MSG_CTRL, DM_DEV_PRIV_MSG_CONN_INIT_STOP, 0 , 0);
  
      /* if first connection opened */
      if (dmConnNum() == 1)
      {
        /* pass conn open event to dev priv */
        dmDevPassEvtToDevPriv(DM_DEV_PRIV_MSG_RPA_START, DM_CONN_OPEN_IND, 0, 0);
      }
    }
  
    /* pass conn open event to Connection CTE */
    dmDevPassEvtToConnCte(DM_CONN_OPEN_IND, pCcb->connId);
  
    pMsg->hdr.event = DM_CONN_OPEN_IND;
    dmConnExecCback(pMsg);
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Handle a connection complete failure event from HCI.
   *
   *  \param  pMsg    WSF message.
   *  \param  pCcb    Connection control block.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  void dmConnSmActConnFailed(dmConnCcb_t *pCcb, dmConnMsg_t *pMsg)
  {
    /* deallocate ccb */
    dmConnCcbDealloc(pCcb);
  
    /* if central */
    if (pCcb->role == DM_ROLE_MASTER)
    {
      /* pass connection initiation completed to dev priv */
      dmDevPassEvtToDevPriv(DM_DEV_PRIV_MSG_CTRL, DM_DEV_PRIV_MSG_CONN_INIT_STOP, 0, 0);
  
      /* if last connection closed */
      if (dmConnNum() == 0)
      {
        /* pass conn close event to dev priv */
        dmDevPassEvtToDevPriv(DM_DEV_PRIV_MSG_RPA_STOP, DM_CONN_CLOSE_IND, 0 , 0);
      }
    }
  
    pMsg->hdr.event = DM_CONN_CLOSE_IND;
    pMsg->hciLeConnCmpl.handle = pMsg->hciLeConnCmpl.role = 0;
    dmConnExecCback(pMsg);
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Handle a disconnect complete event from HCI.
   *
   *  \param  pMsg    WSF message.
   *  \param  pCcb    Connection control block.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  void dmConnSmActConnClosed(dmConnCcb_t *pCcb, dmConnMsg_t *pMsg)
  {
    /* pass conn close event to Connection CTE */
    dmDevPassEvtToConnCte(DM_CONN_CLOSE_IND, pCcb->connId);
  
    /* deallocate ccb */
    dmConnCcbDealloc(pCcb);
  
    /* if central and last connection closed */
    if ((pCcb->role == DM_ROLE_MASTER) && (dmConnNum() == 0))
    {
      /* pass conn close event to dev priv */
      dmDevPassEvtToDevPriv(DM_DEV_PRIV_MSG_RPA_STOP, DM_CONN_CLOSE_IND, 0, 0);
    }
  
    pMsg->hdr.event = DM_CONN_CLOSE_IND;
    dmConnExecCback(pMsg);
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Handle a connection update event from HCI.
   *
   *  \param  pMsg    WSF message.
   *  \param  pCcb    Connection control block.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  void dmConnSmActHciUpdated(dmConnCcb_t *pCcb, dmConnMsg_t *pMsg)
  {
    /* call callback */
    pMsg->hdr.event = DM_CONN_UPDATE_IND;
    (*dmConnCb.connCback[DM_CLIENT_ID_APP])((dmEvt_t *) pMsg);
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Reset the scan module.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  void dmConnReset(void)
  {
    dmConnCcb_t             *pCcb = dmConnCb.ccb;
    hciDisconnectCmplEvt_t  disconnectCmpl;
    uint8_t                 i;
  
    /* generate HCI disconnect complete event */
    disconnectCmpl.hdr.event = HCI_DISCONNECT_CMPL_CBACK_EVT;
    disconnectCmpl.hdr.status = disconnectCmpl.status = HCI_SUCCESS;
    disconnectCmpl.reason = HCI_ERR_LOCAL_TERMINATED;
  
    for (i = DM_CONN_MAX; i > 0; i--, pCcb++)
    {
      if (pCcb->inUse)
      {
        /* set connection id */
        disconnectCmpl.hdr.param = disconnectCmpl.handle = pCcb->handle;
  
        /* handle the event */
        dmConnHciHandler((hciEvt_t *) &disconnectCmpl);
      }
    }
  
    /* initialize control block */
    for (i = 0; i < DM_NUM_PHYS; i++)
    {
      dmConnCb.scanInterval[i] = DM_GAP_SCAN_FAST_INT_MIN;
      dmConnCb.scanWindow[i] = DM_GAP_SCAN_FAST_WINDOW;
      dmConnCb.connSpec[i] = dmConnSpecDefaults;
    }
  
    dmCb.initFiltPolicy = HCI_FILT_NONE;
    dmCb.connAddrType = DM_ADDR_PUBLIC;
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  DM Conn WSF message handler.
   *
   *  \param  pMsg    WSF message.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  void dmConnMsgHandler(wsfMsgHdr_t *pMsg)
  {
    dmConnCcb_t *pCcb;
  
    /* look up ccb from conn id */
    if ((pCcb = dmConnCcbById((dmConnId_t) pMsg->param)) != NULL)
    {
      /* execute state machine */
      dmConnSmExecute(pCcb, (dmConnMsg_t *) pMsg);
    }
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  DM Conn HCI callback event handler.
   *
   *  \param  pEvent  Pointer to HCI callback event structure.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  void dmConnHciHandler(hciEvt_t *pEvent)
  {
    dmConnCcb_t *pCcb;
  
    /* handle special cases for connection complete event */
    if ((pEvent->hdr.event == HCI_LE_CONN_CMPL_CBACK_EVT) ||
        (pEvent->hdr.event == HCI_LE_ENHANCED_CONN_CMPL_CBACK_EVT))
    {
      /* first check if ccb exists for this bd addr */
      if ((pCcb = dmConnCcbByBdAddr(pEvent->leConnCmpl.peerAddr)) == NULL)
      {
        /* check for special case states */
        if ((pCcb = dmConnCmplStates()) == NULL)
        {
          /* else default case for slave, allocate new ccb */
          if ((pEvent->hdr.status == HCI_SUCCESS) && (pEvent->leConnCmpl.role == HCI_ROLE_SLAVE))
          {
            pCcb = dmConnCcbAlloc(pEvent->leConnCmpl.peerAddr);
          }
        }
      }
  
      /* translate HCI event to state machine event */
      if (pEvent->hdr.status == HCI_SUCCESS)
      {
        pEvent->hdr.event =  DM_CONN_MSG_HCI_LE_CONN_CMPL;
      }
      else
      {
        pEvent->hdr.event = DM_CONN_MSG_HCI_LE_CONN_CMPL_FAIL;
      }
    }
    else
    {
      pCcb = dmConnCcbByHandle(pEvent->hdr.param);
  
      /* translate HCI event to state machine message */
      pEvent->hdr.event = DM_CONN_HCI_EVT_2_MSG(pEvent->hdr.event);
    }
  
    /* if ccb found */
    if (pCcb != NULL)
    {
      /* set conn id */
      pEvent->hdr.param = pCcb->connId;
  
      /* execute state machine */
      dmConnSmExecute(pCcb, (dmConnMsg_t *) pEvent);
    }
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  DM Conn 2 WSF message handler.
   *
   *  \param  pMsg    WSF message.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  void dmConn2MsgHandler(wsfMsgHdr_t *pMsg)
  {
    dmConnCcb_t *pCcb;
  
    /* look up ccb from conn id */
    if ((pCcb = dmConnCcbById((dmConnId_t) pMsg->param)) != NULL)
    {
      dmConn2Msg_t *pConn2Msg = (dmConn2Msg_t *) pMsg;
  
      /* handle incoming message */
      switch (pMsg->event)
      {
        case DM_CONN_MSG_API_READ_RSSI:
          HciReadRssiCmd(pCcb->handle);
          break;
  
        case DM_CONN_MSG_API_REM_CONN_PARAM_REQ_REPLY:
          {
            hciConnSpec_t *pConnSpec = &pConn2Msg->apiRemConnParamReqReply.connSpec;
  
            HciLeRemoteConnParamReqReply(pCcb->handle, pConnSpec->connIntervalMin,
                                         pConnSpec->connIntervalMax, pConnSpec->connLatency,
                                         pConnSpec->supTimeout, pConnSpec->minCeLen,
                                         pConnSpec->maxCeLen);
          }
          break;
  
        case DM_CONN_MSG_API_REM_CONN_PARAM_REQ_NEG_REPLY:
          HciLeRemoteConnParamReqNegReply(pCcb->handle, pConn2Msg->apiRemConnParamReqNegReply.reason);
          break;
  
        case DM_CONN_MSG_API_SET_DATA_LEN:
          {
            dmConnApiSetDataLen_t *pDataLen = &pConn2Msg->apiSetDataLen;
  
            HciLeSetDataLen(pCcb->handle, pDataLen->txOctets, pDataLen->txTime);
          }
          break;
  
        case DM_CONN_MSG_API_WRITE_AUTH_TO:
          HciWriteAuthPayloadTimeout(pCcb->handle, pConn2Msg->apiWriteAuthPayloadTo.timeout);
          break;
  
        default:
          /* should never get here */
          break;
      }
    }
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  DM Conn 2 HCI callback event handler.
   *
   *  \param  pEvent  Pointer to HCI callback event structure.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  void dmConn2HciHandler(hciEvt_t *pEvent)
  {
    dmConnCcb_t *pCcb;
  
    /* look up ccb from conn handle */
    if ((pCcb = dmConnCcbByHandle(pEvent->hdr.param)) != NULL)
    {
      /* handle incoming event */
      switch (pEvent->hdr.event)
      {
        case HCI_READ_RSSI_CMD_CMPL_CBACK_EVT:
          dmConn2ActRssiRead(pCcb, pEvent);
          break;
  
        case HCI_LE_REM_CONN_PARAM_REQ_CBACK_EVT:
          dmConn2ActRemoteConnParamReq(pCcb, pEvent);
          break;
  
        case HCI_LE_DATA_LEN_CHANGE_CBACK_EVT:
          dmConn2ActDataLenChange(pCcb, pEvent);
          break;
  
        case HCI_WRITE_AUTH_PAYLOAD_TO_CMD_CMPL_CBACK_EVT:
          dmConn2ActWriteAuthToCmpl(pCcb, pEvent);
          break;
  
        case HCI_AUTH_PAYLOAD_TO_EXPIRED_CBACK_EVT:
          dmConn2ActAuthToExpired(pCcb, pEvent);
          break;
  
        case HCI_LE_READ_REMOTE_FEAT_CMPL_CBACK_EVT:
          dmConn2ActReadRemoteFeaturesCmpl(pCcb, pEvent);
          break;
  
        case HCI_READ_REMOTE_VER_INFO_CMPL_CBACK_EVT:
          dmConn2ActReadRemoteVerInfoCmpl(pCcb, pEvent);
          break;
  
        default:
          /* should never get here */
          break;
      }
    }
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Handle a read RSSI event from HCI.
   *
   *  \param  pCcb    Connection control block.
   *  \param  pEvent  Pointer to HCI callback event structure.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  static void dmConn2ActRssiRead(dmConnCcb_t *pCcb, hciEvt_t *pEvent)
  {
    hciReadRssiCmdCmplEvt_t evt;
  
    /* call callback */
    evt.hdr.event = DM_CONN_READ_RSSI_IND;
    evt.hdr.param = pCcb->connId;
    evt.status = evt.hdr.status = (uint8_t) pEvent->readRssiCmdCmpl.status;
    evt.handle = pCcb->handle;
    evt.rssi = pEvent->readRssiCmdCmpl.rssi;
  
    (*dmConnCb.connCback[DM_CLIENT_ID_APP])((dmEvt_t *) &evt);
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Handle a remote connection parameter request event from HCI.
   *
   *  \param  pCcb    Connection control block.
   *  \param  pEvent  Pointer to HCI callback event structure.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  static void dmConn2ActRemoteConnParamReq(dmConnCcb_t *pCcb, hciEvt_t *pEvent)
  {
    hciLeRemConnParamReqEvt_t evt;
  
    /* call callback */
    evt.hdr.event = DM_REM_CONN_PARAM_REQ_IND;
    evt.hdr.param = pCcb->connId;
    evt.hdr.status = HCI_SUCCESS;
    evt.handle = pCcb->handle;
    evt.intervalMin = pEvent->leRemConnParamReq.intervalMin;
    evt.intervalMax = pEvent->leRemConnParamReq.intervalMax;
    evt.latency = pEvent->leRemConnParamReq.latency;
    evt.timeout = pEvent->leRemConnParamReq.timeout;
  
    (*dmConnCb.connCback[DM_CLIENT_ID_APP])((dmEvt_t *) &evt);
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Handle a data length change event from HCI.
   *
   *  \param  pCcb    Connection control block.
   *  \param  pEvent  Pointer to HCI callback event structure.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  static void dmConn2ActDataLenChange(dmConnCcb_t *pCcb, hciEvt_t *pEvent)
  {
    hciLeDataLenChangeEvt_t evt;
  
    /* call callback */
    evt.hdr.event = DM_CONN_DATA_LEN_CHANGE_IND;
    evt.hdr.param = pCcb->connId;
    evt.hdr.status = HCI_SUCCESS;
    evt.handle = pCcb->handle;
    evt.maxTxOctets = pEvent->leDataLenChange.maxTxOctets;
    evt.maxTxTime = pEvent->leDataLenChange.maxTxTime;
    evt.maxRxOctets = pEvent->leDataLenChange.maxRxOctets;
    evt.maxRxTime = pEvent->leDataLenChange.maxRxTime;
  
    (*dmConnCb.connCback[DM_CLIENT_ID_APP])((dmEvt_t *) &evt);
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Handle a Write Authenticated Payload Timeout Command Complete event from HCI.
   *
   *  \param  pCcb    Connection control block.
   *  \param  pEvent  Pointer to HCI callback event structure.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  static void dmConn2ActWriteAuthToCmpl(dmConnCcb_t *pCcb, hciEvt_t *pEvent)
  {
    hciWriteAuthPayloadToCmdCmplEvt_t evt;
  
    /* call callback */
    evt.hdr.event = DM_CONN_WRITE_AUTH_TO_IND;
    evt.hdr.param = pCcb->connId;
    evt.hdr.status = HCI_SUCCESS;
    evt.handle = pEvent->writeAuthPayloadToCmdCmpl.handle;
    evt.status = pEvent->writeAuthPayloadToCmdCmpl.status;
  
    (*dmConnCb.connCback[DM_CLIENT_ID_APP])((dmEvt_t *) &evt);
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Handle an Authenticated Payload Timeout Expired event from HCI.
   *
   *  \param  pCcb    Connection control block.
   *  \param  pEvent  Pointer to HCI callback event structure.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  static void dmConn2ActAuthToExpired(dmConnCcb_t *pCcb, hciEvt_t *pEvent)
  {
    hciAuthPayloadToExpiredEvt_t evt;
  
    /* call callback */
    evt.hdr.event = DM_CONN_AUTH_TO_EXPIRED_IND;
    evt.hdr.param = pCcb->connId;
    evt.hdr.status = HCI_SUCCESS;
    evt.handle = pEvent->authPayloadToExpired.handle;
  
    (*dmConnCb.connCback[DM_CLIENT_ID_APP])((dmEvt_t *) &evt);
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Handle an Authenticated Payload Timeout Expired event from HCI.
   *
   *  \param  pCcb    Connection control block.
   *  \param  pEvent  Pointer to HCI callback event structure.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  static void dmConn2ActReadRemoteFeaturesCmpl(dmConnCcb_t *pCcb, hciEvt_t *pEvent)
  {
    hciLeReadRemoteFeatCmplEvt_t evt;
  
    /* Save the features */
    BYTES_TO_UINT32(pCcb->features, pEvent->leReadRemoteFeatCmpl.features);
    pCcb->featuresPresent = TRUE;
  
    /* call callback */
    evt.hdr.event = DM_REMOTE_FEATURES_IND;
    evt.hdr.param = pCcb->connId;
    evt.hdr.status = HCI_SUCCESS;
  
    evt.status = pEvent->leReadRemoteFeatCmpl.status;
    evt.handle = pEvent->leReadRemoteFeatCmpl.handle;
    memcpy(evt.features, pEvent->leReadRemoteFeatCmpl.features, sizeof(evt.features));
  
    (*dmConnCb.connCback[DM_CLIENT_ID_APP])((dmEvt_t *) &evt);
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Handle an Authenticated Payload Timeout Expired event from HCI.
   *
   *  \param  pCcb    Connection control block.
   *  \param  pEvent  Pointer to HCI callback event structure.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  static void dmConn2ActReadRemoteVerInfoCmpl(dmConnCcb_t *pCcb, hciEvt_t *pEvent)
  {
    hciReadRemoteVerInfoCmplEvt_t evt;
  
    /* call callback */
    evt.hdr.event = DM_READ_REMOTE_VER_INFO_IND;
    evt.hdr.param = pCcb->connId;
    evt.hdr.status = HCI_SUCCESS;
  
    evt.status = pEvent->readRemoteVerInfoCmpl.status;
    evt.handle = pEvent->readRemoteVerInfoCmpl.handle;
    evt.version = pEvent->readRemoteVerInfoCmpl.version;
    evt.mfrName = pEvent->readRemoteVerInfoCmpl.mfrName;
    evt.subversion = pEvent->readRemoteVerInfoCmpl.subversion;
  
    (*dmConnCb.connCback[DM_CLIENT_ID_APP])((dmEvt_t *) &evt);
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Initialize DM connection manager.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  void DmConnInit(void)
  {
    dmFcnIfTbl[DM_ID_CONN] = (dmFcnIf_t *) &dmConnFcnIf;
    dmFcnIfTbl[DM_ID_CONN_2] = (dmFcnIf_t *) &dmConn2FcnIf;
    dmConnActSet[DM_CONN_ACT_SET_MAIN] = (dmConnAct_t *) dmConnActSetMain;
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  register with the DM connection manager.
   *
   *  \param  clientId  The client identifier.
   *  \param  cback     Client callback function.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  void DmConnRegister(uint8_t clientId, dmCback_t cback)
  {
    WSF_ASSERT(clientId < DM_CLIENT_ID_MAX);
  
    /* store callback */
    WsfTaskLock();
    dmConnCb.connCback[clientId] = cback;
    WsfTaskUnlock();
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Close the connection with the give connection identifier.
   *
   *  \param  clientId  The client identifier.
   *  \param  connId    Connection identifier.
   *  \param  reason    Reason connection is being closed.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  void DmConnClose(uint8_t clientId, dmConnId_t connId, uint8_t reason)
  {
    dmConnApiClose_t *pMsg;
  
    if ((pMsg = WsfMsgAlloc(sizeof(dmConnApiClose_t))) != NULL)
    {
      pMsg->hdr.event = DM_CONN_MSG_API_CLOSE;
      pMsg->hdr.param = connId;
      pMsg->hdr.status = pMsg->reason = reason;
      pMsg->clientId = clientId;
  
      WsfMsgSend(dmCb.handlerId, pMsg);
    }
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Read the features of the remote device.
   *
   *  \param  connId      Connection identifier.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  void DmReadRemoteFeatures(dmConnId_t connId)
  {
    dmConnCcb_t *pCcb;
  
    /* look up ccb from conn handle */
    if ((pCcb = dmConnCcbById(connId)) != NULL)
    {
      if (pCcb->featuresPresent)
      {
        hciLeReadRemoteFeatCmplEvt_t evt;
        uint8_t *p = evt.features;
  
        /* call callback */
        evt.hdr.event = DM_REMOTE_FEATURES_IND;
        evt.hdr.param = pCcb->connId;
        evt.hdr.status = HCI_SUCCESS;
  
        evt.status = HCI_SUCCESS;
        evt.handle = pCcb->handle;
        UINT32_TO_BSTREAM(p, pCcb->features);
  
        (*dmConnCb.connCback[DM_CLIENT_ID_APP])((dmEvt_t *) &evt);
      }
      else
      {
        /* Request the remote features from the peer */
        HciLeReadRemoteFeatCmd(pCcb->handle);
      }
    }
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Read the version info of the remote device.
   *
   *  \param  connId      Connection identifier.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  void DmReadRemoteVerInfo(dmConnId_t connId)
  {
    dmConnCcb_t *pCcb;
  
    /* look up ccb from conn handle */
    if ((pCcb = dmConnCcbById(connId)) != NULL)
    {
      /* Request the version info from the peer */
      HciReadRemoteVerInfoCmd(pCcb->handle);
    }
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Update the connection parameters of an open connection
   *
   *  \param  connId      Connection identifier.
   *  \param  pConnSpec   Connection specification.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  void DmConnUpdate(dmConnId_t connId, hciConnSpec_t *pConnSpec)
  {
    dmConnApiUpdate_t *pMsg;
  
    if ((pMsg = WsfMsgAlloc(sizeof(dmConnApiUpdate_t))) != NULL)
    {
      pMsg->hdr.event = (DmConnRole(connId) == DM_ROLE_MASTER) ?
                        DM_CONN_MSG_API_UPDATE_MASTER : DM_CONN_MSG_API_UPDATE_SLAVE;
      pMsg->hdr.param = connId;
      memcpy(&pMsg->connSpec, pConnSpec, sizeof(hciConnSpec_t));
  
      WsfMsgSend(dmCb.handlerId, pMsg);
    }
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Set the connection spec parameters for created connections created with DmConnOpen().
   *
   *  \param  initPhy     The initiator PHY.
   *  \param  pConnSpec   Connection spec parameters.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  static void dmConnSetConnSpec(uint8_t initPhy, hciConnSpec_t *pConnSpec)
  {
    WSF_ASSERT((initPhy == HCI_INIT_PHY_LE_1M_BIT) ||
               (initPhy == HCI_INIT_PHY_LE_2M_BIT) ||
               (initPhy == HCI_INIT_PHY_LE_CODED_BIT));
  
    WsfTaskLock();
    dmConnCb.connSpec[DmInitPhyToIdx(initPhy)] = *pConnSpec;
    WsfTaskUnlock();
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Set the scan interval and window for created connections created with DmConnOpen().
   *
   *  \param  initPhy       The initiator PHY.
   *  \param  scanInterval  The scan interval.
   *  \param  scanWindow    The scan window.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  static void dmConnSetScanInterval(uint8_t initPhy, uint16_t scanInterval, uint16_t scanWindow)
  {
    uint8_t phyIdx;
  
    WSF_ASSERT((initPhy == HCI_INIT_PHY_LE_1M_BIT) || (initPhy == HCI_INIT_PHY_LE_CODED_BIT));
  
    WsfTaskLock();
    phyIdx = DmInitPhyToIdx(initPhy);
    dmConnCb.scanInterval[phyIdx] = scanInterval;
    dmConnCb.scanWindow[phyIdx] = scanWindow;
    WsfTaskUnlock();
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Set the scan interval and window for connections to be created with DmConnOpen().
   *
   *  \param  scanInterval  The scan interval.
   *  \param  scanWindow    The scan window.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  void DmConnSetScanInterval(uint16_t scanInterval, uint16_t scanWindow)
  {
    dmConnSetScanInterval(HCI_INIT_PHY_LE_1M_BIT, scanInterval, scanWindow);
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Set the scan interval and window for extended connections to be created with
   *          DmConnOpen().
   *
   *  \param  initPhy       Initiator PHYs.
   *  \param  pScanInterval Scan interval array.
   *  \param  pScanWindow   Scan window array.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  void DmExtConnSetScanInterval(uint8_t initPhys, uint16_t *pScanInterval, uint16_t *pScanWindow)
  {
    uint8_t i;   /* initPhy bit position */
    uint8_t idx; /* param array index */
  
    for (i = 0, idx = 0; (i < 8) && (idx < DM_NUM_PHYS); i++)
    {
      if (initPhys & (1 << i))
      {
        dmConnSetScanInterval((1 << i), pScanInterval[idx], pScanWindow[idx]);
        idx++;
      }
    }
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Set the connection spec parameters for connections to be created with DmConnOpen().
   *
   *  \param  pConnSpec   Connection spec parameters.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  void DmConnSetConnSpec(hciConnSpec_t *pConnSpec)
  {
    dmConnSetConnSpec(HCI_INIT_PHY_LE_1M_BIT, pConnSpec);
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Set the extended connection spec parameters for extended connections to be created
   *          with DmConnOpen().
   *
   *  \param  initPhys    The initiator PHYs.
   *  \param  pConnSpec   Connection spec parameters array.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  void DmExtConnSetConnSpec(uint8_t initPhys, hciConnSpec_t *pConnSpec)
  {
    uint8_t i;   /* initPhy bit position */
    uint8_t idx; /* param array index */
  
    for (i = 0, idx = 0; (i < 8) && (idx < DM_NUM_PHYS); i++)
    {
      if (initPhys & (1 << i))
      {
        dmConnSetConnSpec((1 << i), &(pConnSpec[idx]));
        idx++;
      }
    }
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Read RSSI of a given connection.
   *
   *  \param  connId      Connection identifier.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  void DmConnReadRssi(dmConnId_t connId)
  {
    dmConnApiReadRssi_t *pMsg;
  
    if ((pMsg = WsfMsgAlloc(sizeof(dmConnApiReadRssi_t))) != NULL)
    {
      pMsg->hdr.event = DM_CONN_MSG_API_READ_RSSI;
      pMsg->hdr.param = connId;
  
      WsfMsgSend(dmCb.handlerId, pMsg);
    }
  }
  
  /*************************************************************************************************/
  /*!
  *  \brief  Reply to the HCI remote connection parameter request event.  This command is used to
  *          indicate that the Host has accepted the remote device's request to change connection
  *          parameters.
  *
  *  \param  connId      Connection identifier.
  *  \param  pConnSpec   Connection specification.
  *
  *  \return None.
  */
  /*************************************************************************************************/
  void DmRemoteConnParamReqReply(dmConnId_t connId, hciConnSpec_t *pConnSpec)
  {
    dmConnApiRemConnParamReqReply_t *pMsg;
  
    if ((pMsg = WsfMsgAlloc(sizeof(dmConnApiRemConnParamReqReply_t))) != NULL)
    {
      pMsg->hdr.event = DM_CONN_MSG_API_REM_CONN_PARAM_REQ_REPLY;
      pMsg->hdr.param = connId;
      memcpy(&pMsg->connSpec, pConnSpec, sizeof(hciConnSpec_t));
  
      WsfMsgSend(dmCb.handlerId, pMsg);
    }
  }
  
  /*************************************************************************************************/
  /*!
  *  \brief  Negative reply to the HCI remote connection parameter request event.  This command
  *          is used to indicate that the Host has rejected the remote device's request to change
  *          connection parameters.
  *
  *  \param  connId      Connection identifier.
  *  \param  reason      Reason for rejection.
  *
  *  \return None.
  */
  /*************************************************************************************************/
  void DmRemoteConnParamReqNegReply(dmConnId_t connId, uint8_t reason)
  {
    dmConnApiRemConnParamReqNegReply_t *pMsg;
  
    if ((pMsg = WsfMsgAlloc(sizeof(dmConnApiRemConnParamReqNegReply_t))) != NULL)
    {
      pMsg->hdr.event = DM_CONN_MSG_API_REM_CONN_PARAM_REQ_NEG_REPLY;
      pMsg->hdr.param = connId;
      pMsg->reason = reason;
  
      WsfMsgSend(dmCb.handlerId, pMsg);
    }
  }
  
  /*************************************************************************************************/
  /*!
  *  \brief  Set data length for a given connection.
  *
  *  \param  connId      Connection identifier.
  *  \param  txOctets    Maximum number of payload octets for a Data PDU.
  *  \param  txTime      Maximum number of microseconds for a Data PDU.
  *
  *  \return None.
  */
  /*************************************************************************************************/
  void DmConnSetDataLen(dmConnId_t connId, uint16_t txOctets, uint16_t txTime)
  {
    dmConnApiSetDataLen_t *pMsg;
  
    if ((pMsg = WsfMsgAlloc(sizeof(dmConnApiSetDataLen_t))) != NULL)
    {
      pMsg->hdr.event = DM_CONN_MSG_API_SET_DATA_LEN;
      pMsg->hdr.param = connId;
      pMsg->txOctets = txOctets;
      pMsg->txTime = txTime;
  
      WsfMsgSend(dmCb.handlerId, pMsg);
    }
  }
  
  /*************************************************************************************************/
  /*!
  *  \brief  Set authenticated payload timeout for a given connection.
  *
  *  \param  connId      Connection identifier.
  *  \param  timeout     Timeout period in units of 10ms
  *
  *  \return None.
  */
  /*************************************************************************************************/
  void DmWriteAuthPayloadTimeout(dmConnId_t connId, uint16_t timeout)
  {
    dmConnApiWriteAuthPayloadTo_t *pMsg;
  
    if ((pMsg = WsfMsgAlloc(sizeof(dmConnApiWriteAuthPayloadTo_t))) != NULL)
    {
      pMsg->hdr.event = DM_CONN_MSG_API_WRITE_AUTH_TO;
      pMsg->hdr.param = connId;
      pMsg->timeout = timeout;
  
      WsfMsgSend(dmCb.handlerId, pMsg);
    }
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief   For internal use only.  Find the connection ID with matching handle.
   *
   *  \param  handle  Handle to find.
   *
   *  \return Connection ID or DM_CONN_ID_NONE if error.
   */
  /*************************************************************************************************/
  dmConnId_t DmConnIdByHandle(uint16_t handle)
  {
    dmConnCcb_t   *pCcb = dmConnCb.ccb;
    uint8_t       i;
  
    for (i = DM_CONN_MAX; i > 0; i--, pCcb++)
    {
      if (pCcb->inUse && (pCcb->handle == handle))
      {
        return pCcb->connId;
      }
    }
  
    DM_TRACE_WARN1("DmConnIdByHandle not found 0x%04x", handle);
  
    return DM_CONN_ID_NONE;
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  For internal use only.  Return TRUE if the connection is in use.
   *
   *  \param  connId  Connection ID.
   *
   *  \return TRUE if the connection is in use, FALSE otherwise.
   */
  /*************************************************************************************************/
  bool_t DmConnInUse(dmConnId_t connId)
  {
    WSF_ASSERT((connId > 0) && (connId <= DM_CONN_MAX));
  
    return dmConnCb.ccb[connId-1].inUse;
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  For internal use only.  Return the peer address type.
   *
   *  \param  connId  Connection ID.
   *
   *  \return Peer address type.
   */
  /*************************************************************************************************/
  uint8_t DmConnPeerAddrType(dmConnId_t connId)
  {
    WSF_ASSERT((connId > 0) && (connId <= DM_CONN_MAX));
  
    return dmConnCb.ccb[connId-1].peerAddrType;
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  For internal use only.  Return the peer device address.
   *
   *  \param  connId  Connection ID.
   *
   *  \return Pointer to peer device address.
   */
  /*************************************************************************************************/
  uint8_t *DmConnPeerAddr(dmConnId_t connId)
  {
    WSF_ASSERT((connId > 0) && (connId <= DM_CONN_MAX));
  
    return dmConnCb.ccb[connId-1].peerAddr;
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  For internal use only.  Return the local address type.
   *
   *  \param  connId  Connection ID.
   *
   *  \return Local address type.
   */
  /*************************************************************************************************/
  uint8_t DmConnLocalAddrType(dmConnId_t connId)
  {
    WSF_ASSERT((connId > 0) && (connId <= DM_CONN_MAX));
  
    return dmConnCb.ccb[connId-1].localAddrType;
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  For internal use only.  Return the local address.
   *
   *  \param  connId  Connection ID.
   *
   *  \return Pointer to local address.
   */
  /*************************************************************************************************/
  uint8_t *DmConnLocalAddr(dmConnId_t connId)
  {
    WSF_ASSERT((connId > 0) && (connId <= DM_CONN_MAX));
  
    return dmConnCb.ccb[connId-1].localAddr;
  }
  
  /*************************************************************************************************/
  /*!
  *  \brief  For internal use only.  Return the peer resolvable private address (RPA).
  *
  *  \param  connId  Connection ID.
  *
  *  \return Pointer to peer RPA.
  */
  /*************************************************************************************************/
  uint8_t *DmConnPeerRpa(dmConnId_t connId)
  {
    WSF_ASSERT((connId > 0) && (connId <= DM_CONN_MAX));
  
    return dmConnCb.ccb[connId - 1].peerRpa;
  }
  
  /*************************************************************************************************/
  /*!
  *  \brief  For internal use only.  Return the local resolvable private address (RPA).
  *
  *  \param  connId  Connection ID.
  *
  *  \return Pointer to local RPA.
  */
  /*************************************************************************************************/
  uint8_t *DmConnLocalRpa(dmConnId_t connId)
  {
    WSF_ASSERT((connId > 0) && (connId <= DM_CONN_MAX));
  
    return dmConnCb.ccb[connId - 1].localRpa;
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Return the security level of the connection.
   *
   *  \param  connId  Connection ID.
   *
   *  \return Security level of the connection.
   */
  /*************************************************************************************************/
  uint8_t DmConnSecLevel(dmConnId_t connId)
  {
    WSF_ASSERT((connId > 0) && (connId <= DM_CONN_MAX));
  
    return dmConnCb.ccb[connId-1].secLevel;
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Configure a bit in the connection idle state mask as busy or idle.
   *
   *  \param  connId      Connection identifier.
   *  \param  idleMask    Bit in the idle state mask to configure.
   *  \param  idle        DM_CONN_BUSY or DM_CONN_IDLE.
   *
   *  \return None.
   */
  /*************************************************************************************************/
  void DmConnSetIdle(dmConnId_t connId, uint16_t idleMask, uint8_t idle)
  {
    WsfTaskLock();
  
    if (DmConnInUse(connId))
    {
      if (idle == DM_CONN_IDLE)
      {
        /* clear bit if idle */
        dmConnCb.ccb[connId-1].idleMask &= ~idleMask;
      }
      else
      {
        /* set bit if busy */
        dmConnCb.ccb[connId-1].idleMask |= idleMask;
      }
    }
  
    WsfTaskUnlock();
  
    DM_TRACE_INFO2("connId=%d idleMask=0x%04x", connId, dmConnCb.ccb[connId-1].idleMask);
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Check if a connection is idle.
   *
   *  \param  connId      Connection identifier.
   *
   *  \return Zero if connection is idle, nonzero if busy.
   */
  /*************************************************************************************************/
  uint16_t DmConnCheckIdle(dmConnId_t connId)
  {
    uint16_t idleMask;
  
    WSF_ASSERT((connId > 0) && (connId <= DM_CONN_MAX));
  
    WsfTaskLock();
    idleMask = dmConnCb.ccb[connId-1].idleMask;
    WsfTaskUnlock();
  
    return idleMask;
  }
  
  /*************************************************************************************************/
  /*!
   *  \brief  Return the connection role indicating master or slave.
   *
   *  \param  connId      Connection identifier.
   *
   *  \return Connection role.
   */
  /*************************************************************************************************/
  uint8_t DmConnRole(dmConnId_t connId)
  {
    WSF_ASSERT((connId > 0) && (connId <= DM_CONN_MAX));
  
    return dmConnCb.ccb[connId-1].role;
  }
  