普通文本  |  856行  |  29.46 KB

/******************************************************************************
 *
 *  Copyright (C) 2009-2012 Broadcom Corporation
 *
 *  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.
 *
 ******************************************************************************/

/******************************************************************************
 *
 *  This is the API implementation file for the Multi-Channel Adaptation
 *  Protocol (MCAP).
 *
 ******************************************************************************/
#include <base/logging.h>
#include <string.h>

#include "bt_target.h"
#include "btm_api.h"
#include "btm_int.h"
#include "mca_api.h"
#include "mca_defs.h"
#include "mca_int.h"

#include "btu.h"

/*******************************************************************************
 *
 * Function         mca_process_timeout
 *
 * Description      This function is called by BTU when an MCA timer
 *                  expires.
 *
 *                  This function is for use internal to the stack only.
 *
 * Returns          void
 *
 ******************************************************************************/
void mca_ccb_timer_timeout(void* data) {
  tMCA_CCB* p_ccb = (tMCA_CCB*)data;

  mca_ccb_event(p_ccb, MCA_CCB_RSP_TOUT_EVT, NULL);
}

/*******************************************************************************
 *
 * Function         MCA_Init
 *
 * Description      Initialize MCAP main control block.
 *                  This function is called at stack start up.
 *
 * Returns          void
 *
 ******************************************************************************/
void MCA_Init(void) {
  memset(&mca_cb, 0, sizeof(tMCA_CB));

#if defined(MCA_INITIAL_TRACE_LEVEL)
  mca_cb.trace_level = MCA_INITIAL_TRACE_LEVEL;
#else
  mca_cb.trace_level = BT_TRACE_LEVEL_NONE;
#endif
}

/*******************************************************************************
 *
 * Function         MCA_SetTraceLevel
 *
 * Description      This function sets the debug trace level for MCA.
 *                  If 0xff is passed, the current trace level is returned.
 *
 *                  Input Parameters:
 *                      level:  The level to set the MCA tracing to:
 *                      0xff-returns the current setting.
 *                      0-turns off tracing.
 *                      >= 1-Errors.
 *                      >= 2-Warnings.
 *                      >= 3-APIs.
 *                      >= 4-Events.
 *                      >= 5-Debug.
 *
 * Returns          The new trace level or current trace level if
 *                  the input parameter is 0xff.
 *
 ******************************************************************************/
uint8_t MCA_SetTraceLevel(uint8_t level) {
  if (level != 0xFF) mca_cb.trace_level = level;

  return (mca_cb.trace_level);
}

/*******************************************************************************
 *
 * Function         MCA_Register
 *
 * Description      This function registers an MCAP implementation.
 *                  It is assumed that the control channel PSM and data channel
 *                  PSM are not used by any other instances of the stack.
 *                  If the given p_reg->ctrl_psm is 0, this handle is INT only.
 *
 * Returns          0, if failed. Otherwise, the MCA handle.
 *
 ******************************************************************************/
tMCA_HANDLE MCA_Register(tMCA_REG* p_reg, tMCA_CTRL_CBACK* p_cback) {
  tMCA_RCB* p_rcb;
  tMCA_HANDLE handle = 0;
  tL2CAP_APPL_INFO l2c_cacp_appl;
  tL2CAP_APPL_INFO l2c_dacp_appl;

  CHECK(p_reg != NULL);
  CHECK(p_cback != NULL);

  MCA_TRACE_API("MCA_Register: ctrl_psm:0x%x, data_psm:0x%x", p_reg->ctrl_psm,
                p_reg->data_psm);

  p_rcb = mca_rcb_alloc(p_reg);
  if (p_rcb != NULL) {
    if (p_reg->ctrl_psm) {
      if (L2C_INVALID_PSM(p_reg->ctrl_psm) ||
          L2C_INVALID_PSM(p_reg->data_psm)) {
        MCA_TRACE_ERROR("INVALID_PSM");
        return 0;
      }

      l2c_cacp_appl = *(tL2CAP_APPL_INFO*)&mca_l2c_int_appl;
      l2c_cacp_appl.pL2CA_ConnectCfm_Cb = NULL;
      l2c_dacp_appl = *(tL2CAP_APPL_INFO*)&l2c_cacp_appl;
      l2c_cacp_appl.pL2CA_ConnectInd_Cb = mca_l2c_cconn_ind_cback;
      l2c_dacp_appl.pL2CA_ConnectInd_Cb = mca_l2c_dconn_ind_cback;
      if (L2CA_Register(p_reg->ctrl_psm, (tL2CAP_APPL_INFO*)&l2c_cacp_appl) &&
          L2CA_Register(p_reg->data_psm, (tL2CAP_APPL_INFO*)&l2c_dacp_appl)) {
        /* set security level */
        BTM_SetSecurityLevel(false, "", BTM_SEC_SERVICE_MCAP_CTRL,
                             p_reg->sec_mask, p_reg->ctrl_psm,
                             BTM_SEC_PROTO_MCA, MCA_CTRL_TCID);

        /* in theory, we do not need this one for data_psm
         * If we don't, L2CAP rejects with security block (3),
         * which is different reject code from what MCAP spec suggests.
         * we set this one, so mca_l2c_dconn_ind_cback can reject /w no
         * resources (4) */
        BTM_SetSecurityLevel(false, "", BTM_SEC_SERVICE_MCAP_DATA,
                             p_reg->sec_mask, p_reg->data_psm,
                             BTM_SEC_PROTO_MCA, MCA_CTRL_TCID);
      } else {
        MCA_TRACE_ERROR("Failed to register to L2CAP");
        return 0;
      }
    } else
      p_rcb->reg.data_psm = 0;
    handle = mca_rcb_to_handle(p_rcb);
    p_rcb->p_cback = p_cback;
    p_rcb->reg.rsp_tout = p_reg->rsp_tout;
  }
  return handle;
}

/*******************************************************************************
 *
 * Function         MCA_Deregister
 *
 * Description      Deregister an MCAP implementation.  Before this function can
 *                  be called, all control and data channels must be removed
 *                  with MCA_DisconnectReq and MCA_CloseReq.
 *
 * Returns          void
 *
 ******************************************************************************/
void MCA_Deregister(tMCA_HANDLE handle) {
  tMCA_RCB* p_rcb = mca_rcb_by_handle(handle);

  MCA_TRACE_API("MCA_Deregister: %d", handle);
  if (p_rcb && p_rcb->reg.ctrl_psm) {
    L2CA_Deregister(p_rcb->reg.ctrl_psm);
    L2CA_Deregister(p_rcb->reg.data_psm);
    btm_sec_clr_service_by_psm(p_rcb->reg.ctrl_psm);
    btm_sec_clr_service_by_psm(p_rcb->reg.data_psm);
  }
  mca_rcb_dealloc(handle);
}

/*******************************************************************************
 *
 * Function         MCA_CreateDep
 *
 * Description      Create a data endpoint. If the MDEP is created successfully,
 *                  the MDEP ID is returned in *p_dep. After a data endpoint is
 *                  created, an application can initiate a connection between
 *                  this endpoint and an endpoint on a peer device.
 *
 * Returns          MCA_SUCCESS if successful, otherwise error.
 *
 ******************************************************************************/
tMCA_RESULT MCA_CreateDep(tMCA_HANDLE handle, tMCA_DEP* p_dep, tMCA_CS* p_cs) {
  tMCA_RESULT result = MCA_BAD_HANDLE;
  int i;
  tMCA_RCB* p_rcb = mca_rcb_by_handle(handle);
  tMCA_CS* p_depcs;

  CHECK(p_dep != NULL);
  CHECK(p_cs != NULL);
  CHECK(p_cs->p_data_cback != NULL);

  MCA_TRACE_API("MCA_CreateDep: %d", handle);
  if (p_rcb) {
    if (p_cs->max_mdl > MCA_NUM_MDLS) {
      MCA_TRACE_ERROR("max_mdl: %d is too big", p_cs->max_mdl);
      result = MCA_BAD_PARAMS;
    } else {
      p_depcs = p_rcb->dep;
      if (p_cs->type == MCA_TDEP_ECHO) {
        if (p_depcs->p_data_cback) {
          MCA_TRACE_ERROR("Already has ECHO MDEP");
          return MCA_NO_RESOURCES;
        }
        memcpy(p_depcs, p_cs, sizeof(tMCA_CS));
        *p_dep = 0;
        result = MCA_SUCCESS;
      } else {
        result = MCA_NO_RESOURCES;
        /* non-echo MDEP starts from 1 */
        p_depcs++;
        for (i = 1; i < MCA_NUM_DEPS; i++, p_depcs++) {
          if (p_depcs->p_data_cback == NULL) {
            memcpy(p_depcs, p_cs, sizeof(tMCA_CS));
            /* internally use type as the mdep id */
            p_depcs->type = i;
            *p_dep = i;
            result = MCA_SUCCESS;
            break;
          }
        }
      }
    }
  }
  return result;
}

/*******************************************************************************
 *
 * Function         MCA_DeleteDep
 *
 * Description      Delete a data endpoint.  This function is called when
 *                  the implementation is no longer using a data endpoint.
 *                  If this function is called when the endpoint is connected
 *                  the connection is closed and the data endpoint
 *                  is removed.
 *
 * Returns          MCA_SUCCESS if successful, otherwise error.
 *
 ******************************************************************************/
tMCA_RESULT MCA_DeleteDep(tMCA_HANDLE handle, tMCA_DEP dep) {
  tMCA_RESULT result = MCA_BAD_HANDLE;
  tMCA_RCB* p_rcb = mca_rcb_by_handle(handle);
  tMCA_DCB* p_dcb;
  int i, max;
  tMCA_CS* p_depcs;

  MCA_TRACE_API("MCA_DeleteDep: %d dep:%d", handle, dep);
  if (p_rcb) {
    if (dep < MCA_NUM_DEPS && p_rcb->dep[dep].p_data_cback) {
      result = MCA_SUCCESS;
      p_rcb->dep[dep].p_data_cback = NULL;
      p_depcs = &(p_rcb->dep[dep]);
      i = handle - 1;
      max = MCA_NUM_MDLS * MCA_NUM_LINKS;
      p_dcb = &mca_cb.dcb[i * max];
      /* make sure no MDL exists for this MDEP */
      for (i = 0; i < max; i++, p_dcb++) {
        if (p_dcb->state && p_dcb->p_cs == p_depcs) {
          mca_dcb_event(p_dcb, MCA_DCB_API_CLOSE_EVT, NULL);
        }
      }
    }
  }
  return result;
}

/*******************************************************************************
 *
 * Function         MCA_ConnectReq
 *
 * Description      This function initiates an MCAP control channel connection
 *                  to the peer device.  When the connection is completed, an
 *                  MCA_CONNECT_IND_EVT is reported to the application via its
 *                  control callback function.
 *                  This control channel is identified by the tMCA_CL.
 *                  If the connection attempt fails, an MCA_DISCONNECT_IND_EVT
 *                  is reported. The security mask parameter overrides the
 *                  outgoing security mask set in MCA_Register().
 *
 * Returns          MCA_SUCCESS if successful, otherwise error.
 *
 ******************************************************************************/
tMCA_RESULT MCA_ConnectReq(tMCA_HANDLE handle, BD_ADDR bd_addr,
                           uint16_t ctrl_psm, uint16_t sec_mask) {
  tMCA_RESULT result = MCA_BAD_HANDLE;
  tMCA_CCB* p_ccb;
  tMCA_TC_TBL* p_tbl;

  MCA_TRACE_API("MCA_ConnectReq: %d psm:0x%x", handle, ctrl_psm);
  p_ccb = mca_ccb_by_bd(handle, bd_addr);
  if (p_ccb == NULL)
    p_ccb = mca_ccb_alloc(handle, bd_addr);
  else {
    MCA_TRACE_ERROR("control channel already exists");
    return MCA_BUSY;
  }

  if (p_ccb) {
    p_ccb->ctrl_vpsm =
        L2CA_Register(ctrl_psm, (tL2CAP_APPL_INFO*)&mca_l2c_int_appl);
    result = MCA_NO_RESOURCES;
    if (p_ccb->ctrl_vpsm) {
      BTM_SetSecurityLevel(true, "", BTM_SEC_SERVICE_MCAP_CTRL, sec_mask,
                           p_ccb->ctrl_vpsm, BTM_SEC_PROTO_MCA, MCA_CTRL_TCID);
      p_ccb->lcid = mca_l2c_open_req(bd_addr, p_ccb->ctrl_vpsm, NULL);
      if (p_ccb->lcid) {
        p_tbl = mca_tc_tbl_calloc(p_ccb);
        if (p_tbl) {
          p_tbl->state = MCA_TC_ST_CONN;
          p_ccb->sec_mask = sec_mask;
          result = MCA_SUCCESS;
        }
      }
    }
    if (result != MCA_SUCCESS) mca_ccb_dealloc(p_ccb, NULL);
  }
  return result;
}

/*******************************************************************************
 *
 * Function         MCA_DisconnectReq
 *
 * Description      This function disconnect an MCAP control channel
 *                  to the peer device.
 *                  If associated data channel exists, they are disconnected.
 *                  When the MCL is disconnected an MCA_DISCONNECT_IND_EVT is
 *                  reported to the application via its control callback
 *                  function.
 *
 * Returns          MCA_SUCCESS if successful, otherwise error.
 *
 ******************************************************************************/
tMCA_RESULT MCA_DisconnectReq(tMCA_CL mcl) {
  tMCA_RESULT result = MCA_BAD_HANDLE;
  tMCA_CCB* p_ccb = mca_ccb_by_hdl(mcl);

  MCA_TRACE_API("MCA_DisconnectReq: %d ", mcl);
  if (p_ccb) {
    result = MCA_SUCCESS;
    mca_ccb_event(p_ccb, MCA_CCB_API_DISCONNECT_EVT, NULL);
  }
  return result;
}

/*******************************************************************************
 *
 * Function         MCA_CreateMdl
 *
 * Description      This function sends a CREATE_MDL request to the peer device.
 *                  When the response is received, a MCA_CREATE_CFM_EVT is
 *                  reported with the given MDL ID.
 *                  If the response is successful, a data channel is open
 *                  with the given p_chnl_cfg
 *                  If p_chnl_cfg is NULL, the data channel is not initiated
 *                  until MCA_DataChnlCfg is called to provide the p_chnl_cfg.
 *                  When the data channel is open successfully, a
 *                  MCA_OPEN_CFM_EVT is reported. This data channel is
 *                  identified as tMCA_DL.
 *
 * Returns          MCA_SUCCESS if successful, otherwise error.
 *
 ******************************************************************************/
tMCA_RESULT MCA_CreateMdl(tMCA_CL mcl, tMCA_DEP dep, uint16_t data_psm,
                          uint16_t mdl_id, uint8_t peer_dep_id, uint8_t cfg,
                          const tMCA_CHNL_CFG* p_chnl_cfg) {
  tMCA_RESULT result = MCA_BAD_HANDLE;
  tMCA_CCB* p_ccb = mca_ccb_by_hdl(mcl);
  tMCA_DCB* p_dcb;

  MCA_TRACE_API("MCA_CreateMdl: %d dep=%d mdl_id=%d peer_dep_id=%d", mcl, dep,
                mdl_id, peer_dep_id);
  if (p_ccb) {
    if (p_ccb->p_tx_req || p_ccb->p_rx_msg || p_ccb->cong) {
      MCA_TRACE_ERROR("pending req");
      return MCA_BUSY;
    }

    if ((peer_dep_id > MCA_MAX_MDEP_ID) || (!MCA_IS_VALID_MDL_ID(mdl_id))) {
      MCA_TRACE_ERROR("bad peer dep id:%d or bad mdl id: %d ", peer_dep_id,
                      mdl_id);
      return MCA_BAD_PARAMS;
    }

    if (mca_ccb_uses_mdl_id(p_ccb, mdl_id)) {
      MCA_TRACE_ERROR("mdl id: %d is used in the control link", mdl_id);
      return MCA_BAD_MDL_ID;
    }

    p_dcb = mca_dcb_alloc(p_ccb, dep);
    result = MCA_NO_RESOURCES;
    if (p_dcb) {
      /* save the info required by dcb connection */
      p_dcb->p_chnl_cfg = p_chnl_cfg;
      p_dcb->mdl_id = mdl_id;
      tMCA_CCB_MSG* p_evt_data =
          (tMCA_CCB_MSG*)osi_malloc(sizeof(tMCA_CCB_MSG));
      if (!p_ccb->data_vpsm)
        p_ccb->data_vpsm =
            L2CA_Register(data_psm, (tL2CAP_APPL_INFO*)&mca_l2c_int_appl);
      if (p_ccb->data_vpsm) {
        p_evt_data->dcb_idx = mca_dcb_to_hdl(p_dcb);
        p_evt_data->mdep_id = peer_dep_id;
        p_evt_data->mdl_id = mdl_id;
        p_evt_data->param = cfg;
        p_evt_data->op_code = MCA_OP_MDL_CREATE_REQ;
        p_evt_data->hdr.event = MCA_CCB_API_REQ_EVT;
        p_evt_data->hdr.layer_specific = false;
        mca_ccb_event(p_ccb, MCA_CCB_API_REQ_EVT, (tMCA_CCB_EVT*)p_evt_data);
        return MCA_SUCCESS;
      } else {
        osi_free(p_evt_data);
      }

      mca_dcb_dealloc(p_dcb, NULL);
    }
  }
  return result;
}

/*******************************************************************************
 *
 * Function         MCA_CreateMdlRsp
 *
 * Description      This function sends a CREATE_MDL response to the peer device
 *                  in response to a received MCA_CREATE_IND_EVT.
 *                  If the rsp_code is successful, a data channel is open
 *                  with the given p_chnl_cfg
 *                  When the data channel is open successfully, a
 *                  MCA_OPEN_IND_EVT
 *                  is reported. This data channel is identified as tMCA_DL.
 *
 * Returns          MCA_SUCCESS if successful, otherwise error.
 *
 ******************************************************************************/
tMCA_RESULT MCA_CreateMdlRsp(tMCA_CL mcl, tMCA_DEP dep, uint16_t mdl_id,
                             uint8_t cfg, uint8_t rsp_code,
                             const tMCA_CHNL_CFG* p_chnl_cfg) {
  tMCA_RESULT result = MCA_BAD_HANDLE;
  tMCA_CCB* p_ccb = mca_ccb_by_hdl(mcl);
  tMCA_CCB_MSG evt_data;
  tMCA_DCB* p_dcb;

  MCA_TRACE_API("MCA_CreateMdlRsp: %d dep=%d mdl_id=%d cfg=%d rsp_code=%d", mcl,
                dep, mdl_id, cfg, rsp_code);
  CHECK(p_chnl_cfg != NULL);
  if (p_ccb) {
    if (p_ccb->cong) {
      MCA_TRACE_ERROR("congested");
      return MCA_BUSY;
    }
    if (p_ccb->p_rx_msg && (p_ccb->p_rx_msg->mdep_id == dep) &&
        (p_ccb->p_rx_msg->mdl_id == mdl_id) &&
        (p_ccb->p_rx_msg->op_code == MCA_OP_MDL_CREATE_REQ)) {
      result = MCA_SUCCESS;
      evt_data.dcb_idx = 0;
      if (rsp_code == MCA_RSP_SUCCESS) {
        p_dcb = mca_dcb_alloc(p_ccb, dep);
        if (p_dcb) {
          evt_data.dcb_idx = mca_dcb_to_hdl(p_dcb);
          p_dcb->p_chnl_cfg = p_chnl_cfg;
          p_dcb->mdl_id = mdl_id;
        } else {
          rsp_code = MCA_RSP_MDEP_BUSY;
          result = MCA_NO_RESOURCES;
        }
      }

      if (result == MCA_SUCCESS) {
        evt_data.mdl_id = mdl_id;
        evt_data.param = cfg;
        evt_data.rsp_code = rsp_code;
        evt_data.op_code = MCA_OP_MDL_CREATE_RSP;
        mca_ccb_event(p_ccb, MCA_CCB_API_RSP_EVT, (tMCA_CCB_EVT*)&evt_data);
      }
    } else {
      MCA_TRACE_ERROR(
          "The given MCL is not expecting a MCA_CreateMdlRsp with the given "
          "parameters");
      result = MCA_BAD_PARAMS;
    }
  }
  return result;
}

/*******************************************************************************
 *
 * Function         MCA_CloseReq
 *
 * Description      Close a data channel.  When the channel is closed, an
 *                  MCA_CLOSE_CFM_EVT is sent to the application via the
 *                  control callback function for this handle.
 *
 * Returns          MCA_SUCCESS if successful, otherwise error.
 *
 ******************************************************************************/
tMCA_RESULT MCA_CloseReq(tMCA_DL mdl) {
  tMCA_RESULT result = MCA_BAD_HANDLE;
  tMCA_DCB* p_dcb = mca_dcb_by_hdl(mdl);

  MCA_TRACE_API("MCA_CloseReq: %d ", mdl);
  if (p_dcb) {
    result = MCA_SUCCESS;
    mca_dcb_event(p_dcb, MCA_DCB_API_CLOSE_EVT, NULL);
  }
  return result;
}

/*******************************************************************************
 *
 * Function         MCA_ReconnectMdl
 *
 * Description      This function sends a RECONNECT_MDL request to the peer
 *                  device. When the response is received, a
 *                  MCA_RECONNECT_CFM_EVT is reported. If p_chnl_cfg is NULL,
 *                  the data channel is not initiated until MCA_DataChnlCfg is
 *                  called to provide the p_chnl_cfg. If the response is
 *                  successful, a data channel is open. When the data channel is
 *                  open successfully, a MCA_OPEN_CFM_EVT is reported.
 *
 * Returns          MCA_SUCCESS if successful, otherwise error.
 *
 ******************************************************************************/
tMCA_RESULT MCA_ReconnectMdl(tMCA_CL mcl, tMCA_DEP dep, uint16_t data_psm,
                             uint16_t mdl_id, const tMCA_CHNL_CFG* p_chnl_cfg) {
  tMCA_RESULT result = MCA_BAD_HANDLE;
  tMCA_CCB* p_ccb = mca_ccb_by_hdl(mcl);
  tMCA_DCB* p_dcb;

  MCA_TRACE_API("MCA_ReconnectMdl: %d ", mcl);
  CHECK(p_chnl_cfg != NULL);
  if (p_ccb) {
    if (p_ccb->p_tx_req || p_ccb->p_rx_msg || p_ccb->cong) {
      MCA_TRACE_ERROR("pending req");
      return MCA_BUSY;
    }

    if (!MCA_IS_VALID_MDL_ID(mdl_id)) {
      MCA_TRACE_ERROR("bad mdl id: %d ", mdl_id);
      return MCA_BAD_PARAMS;
    }

    if (mca_ccb_uses_mdl_id(p_ccb, mdl_id)) {
      MCA_TRACE_ERROR("mdl id: %d is used in the control link", mdl_id);
      return MCA_BAD_MDL_ID;
    }

    p_dcb = mca_dcb_alloc(p_ccb, dep);
    result = MCA_NO_RESOURCES;
    if (p_dcb) {
      tMCA_CCB_MSG* p_evt_data =
          (tMCA_CCB_MSG*)osi_malloc(sizeof(tMCA_CCB_MSG));

      p_dcb->p_chnl_cfg = p_chnl_cfg;
      p_dcb->mdl_id = mdl_id;
      if (!p_ccb->data_vpsm)
        p_ccb->data_vpsm =
            L2CA_Register(data_psm, (tL2CAP_APPL_INFO*)&mca_l2c_int_appl);
      p_evt_data->dcb_idx = mca_dcb_to_hdl(p_dcb);
      p_evt_data->mdl_id = mdl_id;
      p_evt_data->op_code = MCA_OP_MDL_RECONNECT_REQ;
      p_evt_data->hdr.event = MCA_CCB_API_REQ_EVT;
      mca_ccb_event(p_ccb, MCA_CCB_API_REQ_EVT, (tMCA_CCB_EVT*)p_evt_data);
      return MCA_SUCCESS;
    }
  }
  return result;
}

/*******************************************************************************
 *
 * Function         MCA_ReconnectMdlRsp
 *
 * Description      This function sends a RECONNECT_MDL response to the peer
 *                  device in response to a MCA_RECONNECT_IND_EVT event.
 *                  If the response is successful, a data channel is open.
 *                  When the data channel is open successfully, a
 *                  MCA_OPEN_IND_EVT is reported.
 *
 * Returns          MCA_SUCCESS if successful, otherwise error.
 *
 ******************************************************************************/
tMCA_RESULT MCA_ReconnectMdlRsp(tMCA_CL mcl, tMCA_DEP dep, uint16_t mdl_id,
                                uint8_t rsp_code,
                                const tMCA_CHNL_CFG* p_chnl_cfg) {
  tMCA_RESULT result = MCA_BAD_HANDLE;
  tMCA_CCB* p_ccb = mca_ccb_by_hdl(mcl);
  tMCA_CCB_MSG evt_data;
  tMCA_DCB* p_dcb;

  MCA_TRACE_API("MCA_ReconnectMdlRsp: %d ", mcl);
  CHECK(p_chnl_cfg != NULL);
  if (p_ccb) {
    if (p_ccb->cong) {
      MCA_TRACE_ERROR("congested");
      return MCA_BUSY;
    }
    if (p_ccb->p_rx_msg && (p_ccb->p_rx_msg->mdl_id == mdl_id) &&
        (p_ccb->p_rx_msg->op_code == MCA_OP_MDL_RECONNECT_REQ)) {
      result = MCA_SUCCESS;
      evt_data.dcb_idx = 0;
      if (rsp_code == MCA_RSP_SUCCESS) {
        p_dcb = mca_dcb_alloc(p_ccb, dep);
        if (p_dcb) {
          evt_data.dcb_idx = mca_dcb_to_hdl(p_dcb);
          p_dcb->p_chnl_cfg = p_chnl_cfg;
          p_dcb->mdl_id = mdl_id;
        } else {
          MCA_TRACE_ERROR("Out of MDL for this MDEP");
          rsp_code = MCA_RSP_MDEP_BUSY;
          result = MCA_NO_RESOURCES;
        }
      }

      evt_data.mdl_id = mdl_id;
      evt_data.rsp_code = rsp_code;
      evt_data.op_code = MCA_OP_MDL_RECONNECT_RSP;
      mca_ccb_event(p_ccb, MCA_CCB_API_RSP_EVT, (tMCA_CCB_EVT*)&evt_data);
    } else {
      MCA_TRACE_ERROR(
          "The given MCL is not expecting a MCA_ReconnectMdlRsp with the given "
          "parameters");
      result = MCA_BAD_PARAMS;
    }
  }
  return result;
}

/*******************************************************************************
 *
 * Function         MCA_DataChnlCfg
 *
 * Description      This function initiates a data channel connection toward the
 *                  connected peer device.
 *                  When the data channel is open successfully, a
 *                  MCA_OPEN_CFM_EVT is reported. This data channel is
 *                  identified as tMCA_DL.
 *
 * Returns          MCA_SUCCESS if successful, otherwise error.
 *
 ******************************************************************************/
tMCA_RESULT MCA_DataChnlCfg(tMCA_CL mcl, const tMCA_CHNL_CFG* p_chnl_cfg) {
  tMCA_RESULT result = MCA_BAD_HANDLE;
  tMCA_CCB* p_ccb = mca_ccb_by_hdl(mcl);
  tMCA_DCB* p_dcb;
  tMCA_TC_TBL* p_tbl;

  MCA_TRACE_API("MCA_DataChnlCfg: %d ", mcl);
  CHECK(p_chnl_cfg != NULL);
  if (p_ccb) {
    result = MCA_NO_RESOURCES;
    if ((p_ccb->p_tx_req == NULL) || (p_ccb->status != MCA_CCB_STAT_PENDING) ||
        ((p_dcb = mca_dcb_by_hdl(p_ccb->p_tx_req->dcb_idx)) == NULL)) {
      MCA_TRACE_ERROR("The given MCL is not expecting this API:%d",
                      p_ccb->status);
      return result;
    }

    p_dcb->p_chnl_cfg = p_chnl_cfg;
    BTM_SetSecurityLevel(true, "", BTM_SEC_SERVICE_MCAP_DATA, p_ccb->sec_mask,
                         p_ccb->data_vpsm, BTM_SEC_PROTO_MCA,
                         p_ccb->p_tx_req->dcb_idx);
    p_dcb->lcid =
        mca_l2c_open_req(p_ccb->peer_addr, p_ccb->data_vpsm, p_dcb->p_chnl_cfg);
    if (p_dcb->lcid) {
      p_tbl = mca_tc_tbl_dalloc(p_dcb);
      if (p_tbl) {
        p_tbl->state = MCA_TC_ST_CONN;
        result = MCA_SUCCESS;
      }
    }
  }
  return result;
}

/*******************************************************************************
 *
 * Function         MCA_Abort
 *
 * Description      This function sends a ABORT_MDL request to the peer device.
 *                  When the response is received, a MCA_ABORT_CFM_EVT is
 *                  reported.
 *
 * Returns          MCA_SUCCESS if successful, otherwise error.
 *
 ******************************************************************************/
tMCA_RESULT MCA_Abort(tMCA_CL mcl) {
  tMCA_RESULT result = MCA_BAD_HANDLE;
  tMCA_CCB* p_ccb = mca_ccb_by_hdl(mcl);
  tMCA_DCB* p_dcb;

  MCA_TRACE_API("MCA_Abort: %d", mcl);
  if (p_ccb) {
    result = MCA_NO_RESOURCES;
    /* verify that we are waiting for data channel to come up with the given mdl
     */
    if ((p_ccb->p_tx_req == NULL) || (p_ccb->status != MCA_CCB_STAT_PENDING) ||
        ((p_dcb = mca_dcb_by_hdl(p_ccb->p_tx_req->dcb_idx)) == NULL)) {
      MCA_TRACE_ERROR("The given MCL is not expecting this API:%d",
                      p_ccb->status);
      return result;
    }

    if (p_ccb->cong) {
      MCA_TRACE_ERROR("congested");
      return MCA_BUSY;
    }

    tMCA_CCB_MSG* p_evt_data = (tMCA_CCB_MSG*)osi_malloc(sizeof(tMCA_CCB_MSG));
    result = MCA_SUCCESS;
    p_evt_data->op_code = MCA_OP_MDL_ABORT_REQ;
    p_evt_data->hdr.event = MCA_CCB_API_REQ_EVT;
    mca_ccb_event(p_ccb, MCA_CCB_API_REQ_EVT, (tMCA_CCB_EVT*)p_evt_data);
  }
  return result;
}

/*******************************************************************************
 *
 * Function         MCA_Delete
 *
 * Description      This function sends a DELETE_MDL request to the peer device.
 *                  When the response is received, a MCA_DELETE_CFM_EVT is
 *                  reported.
 *
 * Returns          MCA_SUCCESS if successful, otherwise error.
 *
 ******************************************************************************/
tMCA_RESULT MCA_Delete(tMCA_CL mcl, uint16_t mdl_id) {
  tMCA_RESULT result = MCA_BAD_HANDLE;
  tMCA_CCB* p_ccb = mca_ccb_by_hdl(mcl);

  MCA_TRACE_API("MCA_Delete: %d ", mcl);
  if (p_ccb) {
    if (p_ccb->cong) {
      MCA_TRACE_ERROR("congested");
      return MCA_BUSY;
    }
    if (!MCA_IS_VALID_MDL_ID(mdl_id) && (mdl_id != MCA_ALL_MDL_ID)) {
      MCA_TRACE_ERROR("bad mdl id: %d ", mdl_id);
      return MCA_BAD_PARAMS;
    }

    tMCA_CCB_MSG* p_evt_data = (tMCA_CCB_MSG*)osi_malloc(sizeof(tMCA_CCB_MSG));
    result = MCA_SUCCESS;
    p_evt_data->mdl_id = mdl_id;
    p_evt_data->op_code = MCA_OP_MDL_DELETE_REQ;
    p_evt_data->hdr.event = MCA_CCB_API_REQ_EVT;
    mca_ccb_event(p_ccb, MCA_CCB_API_REQ_EVT, (tMCA_CCB_EVT*)p_evt_data);
  }
  return result;
}

/*******************************************************************************
 *
 * Function         MCA_WriteReq
 *
 * Description      Send a data packet to the peer device.
 *
 *                  The application passes the packet using the BT_HDR
 *                  structure. The offset field must be equal to or greater than
 *                  L2CAP_MIN_OFFSET. This allows enough space in the buffer for
 *                  the L2CAP header.
 *
 *                  The memory pointed to by p_pkt must be a GKI buffer
 *                  allocated by the application.  This buffer will be freed
 *                  by the protocol stack; the application must not free
 *                  this buffer.
 *
 * Returns          MCA_SUCCESS if successful, otherwise error.
 *
 ******************************************************************************/
tMCA_RESULT MCA_WriteReq(tMCA_DL mdl, BT_HDR* p_pkt) {
  tMCA_RESULT result = MCA_BAD_HANDLE;
  tMCA_DCB* p_dcb = mca_dcb_by_hdl(mdl);
  tMCA_DCB_EVT evt_data;

  MCA_TRACE_API("MCA_WriteReq: %d ", mdl);
  if (p_dcb) {
    if (p_dcb->cong) {
      result = MCA_BUSY;
    } else {
      evt_data.p_pkt = p_pkt;
      result = MCA_SUCCESS;
      mca_dcb_event(p_dcb, MCA_DCB_API_WRITE_EVT, &evt_data);
    }
  }
  return result;
}

/*******************************************************************************
 *
 * Function         MCA_GetL2CapChannel
 *
 * Description      Get the L2CAP CID used by the given data channel handle.
 *
 * Returns          L2CAP channel ID if successful, otherwise 0.
 *
 ******************************************************************************/
uint16_t MCA_GetL2CapChannel(tMCA_DL mdl) {
  uint16_t lcid = 0;
  tMCA_DCB* p_dcb = mca_dcb_by_hdl(mdl);

  MCA_TRACE_API("MCA_GetL2CapChannel: %d ", mdl);
  if (p_dcb) lcid = p_dcb->lcid;
  return lcid;
}

static const btmcap_test_interface_t mcap_test_interface = {
    sizeof(btmcap_test_interface_t),
    MCA_Init,
    MCA_Register,
    MCA_Deregister,
    MCA_CreateDep,
    MCA_DeleteDep,
    MCA_ConnectReq,
    MCA_DisconnectReq,
    MCA_CreateMdl,
    MCA_CreateMdlRsp,
    MCA_CloseReq,
    MCA_ReconnectMdl,
    MCA_ReconnectMdlRsp,
    MCA_DataChnlCfg,
    MCA_Abort,
    MCA_Delete,
    MCA_WriteReq,
    MCA_GetL2CapChannel,
};

const btmcap_test_interface_t* stack_mcap_get_interface(void) {
  BTIF_TRACE_EVENT("%s", __func__);
  return &mcap_test_interface;
}