/******************************************************************************
 *
 *  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 implementation file for the MCAP Main Control Block and
 *  Utility functions.
 *
 ******************************************************************************/
#include <base/logging.h>
#include <string.h>

#include "bt_common.h"
#include "bt_target.h"
#include "l2c_api.h"
#include "mca_api.h"
#include "mca_defs.h"
#include "mca_int.h"

/* Main Control block for MCA */
tMCA_CB mca_cb;

/*****************************************************************************
 * constants
 ****************************************************************************/

/* table of standard opcode message size */
const uint8_t mca_std_msg_len[MCA_NUM_STANDARD_OPCODE] = {
    4, /* MCA_OP_ERROR_RSP         */
    5, /* MCA_OP_MDL_CREATE_REQ    */
    5, /* MCA_OP_MDL_CREATE_RSP    */
    3, /* MCA_OP_MDL_RECONNECT_REQ */
    4, /* MCA_OP_MDL_RECONNECT_RSP */
    3, /* MCA_OP_MDL_ABORT_REQ     */
    4, /* MCA_OP_MDL_ABORT_RSP     */
    3, /* MCA_OP_MDL_DELETE_REQ    */
    4  /* MCA_OP_MDL_DELETE_RSP    */
};

/*******************************************************************************
 *
 * Function         mca_handle_by_cpsm
 *
 * Description      This function returns the handle for the given control
 *                  channel PSM. 0, if not found.
 *
 * Returns          the MCA handle.
 *
 ******************************************************************************/
tMCA_HANDLE mca_handle_by_cpsm(uint16_t psm) {
  int i;
  tMCA_HANDLE handle = 0;
  tMCA_RCB* p_rcb = &mca_cb.rcb[0];

  for (i = 0; i < MCA_NUM_REGS; i++, p_rcb++) {
    if (p_rcb->p_cback && p_rcb->reg.ctrl_psm == psm) {
      handle = i + 1;
      break;
    }
  }
  return handle;
}

/*******************************************************************************
 *
 * Function         mca_handle_by_dpsm
 *
 * Description      This function returns the handle for the given data
 *                  channel PSM. 0, if not found.
 *
 * Returns          the MCA handle.
 *
 ******************************************************************************/
tMCA_HANDLE mca_handle_by_dpsm(uint16_t psm) {
  int i;
  tMCA_HANDLE handle = 0;
  tMCA_RCB* p_rcb = &mca_cb.rcb[0];

  for (i = 0; i < MCA_NUM_REGS; i++, p_rcb++) {
    if (p_rcb->p_cback && p_rcb->reg.data_psm == psm) {
      handle = i + 1;
      break;
    }
  }
  return handle;
}

/*******************************************************************************
 *
 * Function         mca_tc_tbl_calloc
 *
 * Description      This function allocates a transport table for the given
 *                  control channel.
 *
 * Returns          The tranport table.
 *
 ******************************************************************************/
tMCA_TC_TBL* mca_tc_tbl_calloc(tMCA_CCB* p_ccb) {
  tMCA_TC_TBL* p_tbl = mca_cb.tc.tc_tbl;
  int i;

  /* find next free entry in tc table */
  for (i = 0; i < MCA_NUM_TC_TBL; i++, p_tbl++) {
    if (p_tbl->state == MCA_TC_ST_UNUSED) {
      break;
    }
  }

  /* sanity check */
  CHECK(i != MCA_NUM_TC_TBL);

  /* initialize entry */
  p_tbl->peer_mtu = L2CAP_DEFAULT_MTU;
  p_tbl->cfg_flags = 0;
  p_tbl->cb_idx = mca_ccb_to_hdl(p_ccb);
  p_tbl->tcid = MCA_CTRL_TCID;
  p_tbl->my_mtu = MCA_CTRL_MTU;
  p_tbl->state = MCA_TC_ST_IDLE;
  p_tbl->lcid = p_ccb->lcid;
  mca_cb.tc.lcid_tbl[p_ccb->lcid - L2CAP_BASE_APPL_CID] = i;

  MCA_TRACE_DEBUG("%s() - cb_idx: %d", __func__, p_tbl->cb_idx);
  return p_tbl;
}

/*******************************************************************************
 *
 * Function         mca_tc_tbl_dalloc
 *
 * Description      This function allocates a transport table for the given
 *                  data channel.
 *
 * Returns          The tranport table.
 *
 ******************************************************************************/
tMCA_TC_TBL* mca_tc_tbl_dalloc(tMCA_DCB* p_dcb) {
  tMCA_TC_TBL* p_tbl = mca_cb.tc.tc_tbl;
  int i;

  /* find next free entry in tc table */
  for (i = 0; i < MCA_NUM_TC_TBL; i++, p_tbl++) {
    if (p_tbl->state == MCA_TC_ST_UNUSED) {
      break;
    }
  }

  /* sanity check */
  CHECK(i != MCA_NUM_TC_TBL);

  /* initialize entry */
  p_tbl->peer_mtu = L2CAP_DEFAULT_MTU;
  p_tbl->cfg_flags = 0;
  p_tbl->cb_idx = mca_dcb_to_hdl(p_dcb);
  p_tbl->tcid = p_dcb->p_cs->type + 1;
  p_tbl->my_mtu = p_dcb->p_chnl_cfg->data_mtu;
  p_tbl->state = MCA_TC_ST_IDLE;
  p_tbl->lcid = p_dcb->lcid;
  mca_cb.tc.lcid_tbl[p_dcb->lcid - L2CAP_BASE_APPL_CID] = i;

  MCA_TRACE_DEBUG("%s() - tcid: %d, cb_idx: %d", __func__, p_tbl->tcid,
                  p_tbl->cb_idx);
  return p_tbl;
}

/*******************************************************************************
 *
 * Function         mca_tc_tbl_by_lcid
 *
 * Description      Find the transport channel table entry by LCID.
 *
 *
 * Returns          The tranport table.
 *
 ******************************************************************************/
tMCA_TC_TBL* mca_tc_tbl_by_lcid(uint16_t lcid) {
  uint8_t idx;

  if (lcid >= L2CAP_BASE_APPL_CID) {
    idx = mca_cb.tc.lcid_tbl[lcid - L2CAP_BASE_APPL_CID];

    if (idx < MCA_NUM_TC_TBL) {
      return &mca_cb.tc.tc_tbl[idx];
    }
  }
  return NULL;
}

/*******************************************************************************
 *
 * Function         mca_free_tc_tbl_by_lcid
 *
 * Description      Find the  transport table entry by LCID
 *                  and free the tc_tbl
 *
 * Returns          void.
 *
 ******************************************************************************/
void mca_free_tc_tbl_by_lcid(uint16_t lcid) {
  uint8_t idx;

  if (lcid >= L2CAP_BASE_APPL_CID) {
    idx = mca_cb.tc.lcid_tbl[lcid - L2CAP_BASE_APPL_CID];

    if (idx < MCA_NUM_TC_TBL) {
      mca_cb.tc.tc_tbl[idx].state = MCA_TC_ST_UNUSED;
    }
  }
}

/*******************************************************************************
 *
 * Function         mca_set_cfg_by_tbl
 *
 * Description      Set the L2CAP configuration information
 *
 * Returns          none.
 *
 ******************************************************************************/
void mca_set_cfg_by_tbl(tL2CAP_CFG_INFO* p_cfg, tMCA_TC_TBL* p_tbl) {
  tMCA_DCB* p_dcb;
  const tL2CAP_FCR_OPTS* p_opt;
  tMCA_FCS_OPT fcs = MCA_FCS_NONE;

  if (p_tbl->tcid == MCA_CTRL_TCID) {
    p_opt = &mca_l2c_fcr_opts_def;
  } else {
    p_dcb = mca_dcb_by_hdl(p_tbl->cb_idx);
    if (p_dcb) {
      p_opt = &p_dcb->p_chnl_cfg->fcr_opt;
      fcs = p_dcb->p_chnl_cfg->fcs;
    }
  }
  memset(p_cfg, 0, sizeof(tL2CAP_CFG_INFO));
  p_cfg->mtu_present = true;
  p_cfg->mtu = p_tbl->my_mtu;
  p_cfg->fcr_present = true;
  memcpy(&p_cfg->fcr, p_opt, sizeof(tL2CAP_FCR_OPTS));
  if (fcs & MCA_FCS_PRESNT_MASK) {
    p_cfg->fcs_present = true;
    p_cfg->fcs = (fcs & MCA_FCS_USE_MASK);
  }
}

/*******************************************************************************
 *
 * Function         mca_tc_close_ind
 *
 * Description      This function is called by the L2CAP interface when the
 *                  L2CAP channel is closed.  It looks up the CCB or DCB for
 *                  the channel and sends it a close event.  The reason
 *                  parameter is the same value passed by the L2CAP
 *                  callback function.
 *
 * Returns          Nothing.
 *
 ******************************************************************************/
void mca_tc_close_ind(tMCA_TC_TBL* p_tbl, uint16_t reason) {
  tMCA_CCB* p_ccb;
  tMCA_DCB* p_dcb;

  MCA_TRACE_DEBUG("%s() - tcid: %d, cb_idx:%d, old: %d", __func__, p_tbl->tcid,
                  p_tbl->cb_idx, p_tbl->state);

  /* Check if the transport channel is in use */
  if (p_tbl->state == MCA_TC_ST_UNUSED) return;

  tMCA_CLOSE close;

  close.param = MCA_ACP;
  close.reason = reason;
  close.lcid = p_tbl->lcid;

  /* clear mca_tc_tbl entry */
  if (p_tbl->cfg_flags & MCA_L2C_CFG_DISCN_INT) close.param = MCA_INT;
  p_tbl->cfg_flags = 0;
  p_tbl->peer_mtu = L2CAP_DEFAULT_MTU;

  /* if control channel, notify ccb of the channel close */
  if (p_tbl->tcid == MCA_CTRL_TCID) {
    p_ccb = mca_ccb_by_hdl((tMCA_CL)p_tbl->cb_idx);
    tMCA_CCB_EVT mca_ccb_evt;
    mca_ccb_evt.close = close;
    mca_ccb_event(p_ccb, MCA_CCB_LL_CLOSE_EVT, &mca_ccb_evt);
  } else {
    /* notify dcb of the channel close */
    p_dcb = mca_dcb_by_hdl(p_tbl->cb_idx);
    if (p_dcb != NULL) {
      tMCA_DCB_EVT mca_dcb_evt;
      mca_dcb_evt.close = close;
      mca_dcb_event(p_dcb, MCA_DCB_TC_CLOSE_EVT, &mca_dcb_evt);
    }
  }
  p_tbl->state = MCA_TC_ST_UNUSED;
}

/*******************************************************************************
 *
 * Function         mca_tc_open_ind
 *
 * Description      This function is called by the L2CAP interface when
 *                  the L2CAP channel is opened.  It looks up the CCB or DCB
 *                  for the channel and sends it an open event.
 *
 * Returns          Nothing.
 *
 ******************************************************************************/
void mca_tc_open_ind(tMCA_TC_TBL* p_tbl) {
  tMCA_CCB* p_ccb;
  tMCA_DCB* p_dcb;
  tMCA_OPEN open;

  MCA_TRACE_DEBUG("mca_tc_open_ind tcid: %d, cb_idx: %d", p_tbl->tcid,
                  p_tbl->cb_idx);
  p_tbl->state = MCA_TC_ST_OPEN;

  open.peer_mtu = p_tbl->peer_mtu;
  open.lcid = p_tbl->lcid;
  /* use param to indicate the role of connection.
   * MCA_ACP, if ACP */
  open.param = MCA_INT;
  if (p_tbl->cfg_flags & MCA_L2C_CFG_CONN_ACP) {
    open.param = MCA_ACP;
  }

  /* if control channel, notify ccb that the channel is open */
  if (p_tbl->tcid == MCA_CTRL_TCID) {
    p_ccb = mca_ccb_by_hdl((tMCA_CL)p_tbl->cb_idx);
    tMCA_CCB_EVT mca_ccb_evt;
    mca_ccb_evt.open = open;
    mca_ccb_event(p_ccb, MCA_CCB_LL_OPEN_EVT, &mca_ccb_evt);
  } else {
    /* must be data channel, notify dcb that the channel is open */
    p_dcb = mca_dcb_by_hdl(p_tbl->cb_idx);

    /* put lcid in event data */
    if (p_dcb != NULL) {
      tMCA_DCB_EVT mca_dcb_evt;
      mca_dcb_evt.open = open;
      mca_dcb_event(p_dcb, MCA_DCB_TC_OPEN_EVT, &mca_dcb_evt);
    }
  }
}

/*******************************************************************************
 *
 * Function         mca_tc_cong_ind
 *
 * Description      This function is called by the L2CAP interface layer when
 *                  L2CAP calls the congestion callback.  It looks up the CCB
 *                  or DCB for the channel and sends it a congestion event.
 *                  The is_congested parameter is the same value passed by
 *                  the L2CAP callback function.
 *
 *
 * Returns          Nothing.
 *
 ******************************************************************************/
void mca_tc_cong_ind(tMCA_TC_TBL* p_tbl, bool is_congested) {
  tMCA_CCB* p_ccb;
  tMCA_DCB* p_dcb;

  MCA_TRACE_DEBUG("%s() - tcid: %d, cb_idx: %d", __func__, p_tbl->tcid,
                  p_tbl->cb_idx);

  /* if control channel, notify ccb of congestion */
  if (p_tbl->tcid == MCA_CTRL_TCID) {
    p_ccb = mca_ccb_by_hdl((tMCA_CL)p_tbl->cb_idx);
    tMCA_CCB_EVT mca_ccb_evt;
    mca_ccb_evt.llcong = is_congested;
    mca_ccb_event(p_ccb, MCA_CCB_LL_CONG_EVT, &mca_ccb_evt);
  } else {
    /* notify dcb that channel open */
    p_dcb = mca_dcb_by_hdl(p_tbl->cb_idx);
    if (p_dcb != NULL) {
      tMCA_DCB_EVT mca_dcb_evt;
      mca_dcb_evt.llcong = is_congested;
      mca_dcb_event(p_dcb, MCA_DCB_TC_CONG_EVT, &mca_dcb_evt);
    }
  }
}

/*******************************************************************************
 *
 * Function         mca_tc_data_ind
 *
 * Description      This function is called by the L2CAP interface layer when
 *                  incoming data is received from L2CAP.  It looks up the CCB
 *                  or DCB for the channel and routes the data accordingly.
 *
 * Returns          Nothing.
 *
 ******************************************************************************/
void mca_tc_data_ind(tMCA_TC_TBL* p_tbl, BT_HDR* p_buf) {
  tMCA_CCB* p_ccb;
  tMCA_DCB* p_dcb;
  uint8_t event = MCA_CCB_MSG_RSP_EVT;
  uint8_t* p;
  uint8_t rej_rsp_code = MCA_RSP_SUCCESS;

  MCA_TRACE_DEBUG("%s: tcid: %d, cb_idx: %d", __func__, p_tbl->tcid,
                  p_tbl->cb_idx);

  /* if control channel, handle control message */
  if (p_tbl->tcid == MCA_CTRL_TCID) {
    p_ccb = mca_ccb_by_hdl((tMCA_CL)p_tbl->cb_idx);
    if (p_ccb) {
      p = (uint8_t*)(p_buf + 1) + p_buf->offset;
      /* all the request opcode has bit 0 set. response code has bit 0 clear */
      if ((*p) & 0x01) event = MCA_CCB_MSG_REQ_EVT;

      if (*p < MCA_NUM_STANDARD_OPCODE) {
        if (p_buf->len != mca_std_msg_len[*p]) {
          MCA_TRACE_ERROR("%s: opcode 0x%02x required len: %d, got len: %d",
                          __func__, *p, mca_std_msg_len[*p], p_buf->len);
          rej_rsp_code = MCA_RSP_BAD_PARAM;
        }
      } else if ((*p >= MCA_FIRST_SYNC_OP) && (*p <= MCA_LAST_SYNC_OP)) {
        MCA_TRACE_ERROR("%s: unsupported SYNC opcode: 0x%02x len:%d", __func__,
                        *p, p_buf->len);
        /* reject unsupported request */
        rej_rsp_code = MCA_RSP_NO_SUPPORT;
      } else {
        MCA_TRACE_ERROR("%s: bad opcode: 0x%02x len:%d", __func__, *p,
                        p_buf->len);
        /* reject unsupported request */
        rej_rsp_code = MCA_RSP_BAD_OPCODE;
      }

      p_buf->layer_specific = rej_rsp_code;
      /* forward the request/response to state machine */
      mca_ccb_event(p_ccb, event, (tMCA_CCB_EVT*)p_buf);
    } /* got a valid ccb */
    else
      osi_free(p_buf);
  }
  /* else send event to dcb */
  else {
    p_dcb = mca_dcb_by_hdl(p_tbl->cb_idx);
    if (p_dcb != NULL) {
      mca_dcb_event(p_dcb, MCA_DCB_TC_DATA_EVT, (tMCA_DCB_EVT*)p_buf);
    } else
      osi_free(p_buf);
  }
}

/*******************************************************************************
 *
 * Function         mca_rcb_alloc
 *
 * Description      This function allocates a registration control block.
 *                  If no free RCB is available, it returns NULL.
 *
 * Returns          tMCA_RCB *
 *
 ******************************************************************************/
tMCA_RCB* mca_rcb_alloc(tMCA_REG* p_reg) {
  int i;
  tMCA_RCB* p_rcb = NULL;

  for (i = 0; i < MCA_NUM_REGS; i++) {
    if (mca_cb.rcb[i].p_cback == NULL) {
      p_rcb = &mca_cb.rcb[i];
      memcpy(&p_rcb->reg, p_reg, sizeof(tMCA_REG));
      break;
    }
  }
  return p_rcb;
}

/*******************************************************************************
 *
 * Function         mca_rcb_dealloc
 *
 * Description      This function deallocates the RCB with the given handle.
 *
 * Returns          void.
 *
 ******************************************************************************/
void mca_rcb_dealloc(tMCA_HANDLE handle) {
  int i;
  bool done = true;
  tMCA_RCB* p_rcb;
  tMCA_CCB* p_ccb;

  if (handle && (handle <= MCA_NUM_REGS)) {
    handle--;
    p_rcb = &mca_cb.rcb[handle];
    if (p_rcb->p_cback) {
      p_ccb = &mca_cb.ccb[handle * MCA_NUM_LINKS];
      /* check if all associated CCB are disconnected */
      for (i = 0; i < MCA_NUM_LINKS; i++, p_ccb++) {
        if (p_ccb->p_rcb) {
          done = false;
          mca_ccb_event(p_ccb, MCA_CCB_API_DISCONNECT_EVT, NULL);
        }
      }

      if (done) {
        memset(p_rcb, 0, sizeof(tMCA_RCB));
        MCA_TRACE_DEBUG("%s() - reset MCA_RCB index=%d", __func__, handle);
      }
    }
  }
}

/*******************************************************************************
 *
 * Function         mca_rcb_to_handle
 *
 * Description      This function converts a pointer to an RCB to
 *                  a handle (tMCA_HANDLE).  It returns the handle.
 *
 * Returns          void.
 *
 ******************************************************************************/
tMCA_HANDLE mca_rcb_to_handle(tMCA_RCB* p_rcb) {
  return (uint8_t)(p_rcb - mca_cb.rcb + 1);
}

/*******************************************************************************
 *
 * Function         mca_rcb_by_handle
 *
 * Description      This function finds the RCB for a handle (tMCA_HANDLE).
 *                  It returns a pointer to the RCB.  If no RCB matches the
 *                  handle it returns NULL.
 *
 * Returns          tMCA_RCB *
 *
 ******************************************************************************/
tMCA_RCB* mca_rcb_by_handle(tMCA_HANDLE handle) {
  tMCA_RCB* p_rcb = NULL;

  if (handle && (handle <= MCA_NUM_REGS) && mca_cb.rcb[handle - 1].p_cback) {
    p_rcb = &mca_cb.rcb[handle - 1];
  }
  return p_rcb;
}

/*******************************************************************************
 *
 * Function         mca_is_valid_dep_id
 *
 * Description      This function checks if the given dep_id is valid.
 *
 * Returns          true, if this is a valid local dep_id
 *
 ******************************************************************************/
bool mca_is_valid_dep_id(tMCA_RCB* p_rcb, tMCA_DEP dep) {
  bool valid = false;
  if (dep < MCA_NUM_DEPS && p_rcb->dep[dep].p_data_cback) {
    valid = true;
  }
  return valid;
}
