/******************************************************************************
 *
 *  Copyright (C) 2002-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 module contains the AVDTP adaption layer.
 *
 ******************************************************************************/

#include <base/logging.h>
#include <string.h>

#include "avdt_api.h"
#include "avdt_int.h"
#include "avdtc_api.h"
#include "bt_target.h"
#include "bt_types.h"
#include "bt_utils.h"
#include "l2c_api.h"
#include "l2cdefs.h"
#include "osi/include/osi.h"

/*******************************************************************************
 *
 * Function         avdt_ad_type_to_tcid
 *
 * Description      Derives the TCID from the channel type and SCB.
 *
 *
 * Returns          TCID value.
 *
 ******************************************************************************/
uint8_t avdt_ad_type_to_tcid(uint8_t type, tAVDT_SCB* p_scb) {
  uint8_t scb_idx;

  if (type == AVDT_CHAN_SIG) {
    return 0;
  } else {
    scb_idx = avdt_scb_to_hdl(p_scb) - 1;
    /*
    AVDT_TRACE_DEBUG("type: %d, tcid: %d", type, ((scb_idx *
    (AVDT_CHAN_NUM_TYPES - 1)) + type));
    */
    return ((scb_idx * (AVDT_CHAN_NUM_TYPES - 1)) + type);
  }
}

/*******************************************************************************
 *
 * Function         avdt_ad_tcid_to_type
 *
 * Description      Derives the channel type from the TCID.
 *
 *
 * Returns          Channel type value.
 *
 ******************************************************************************/
static uint8_t avdt_ad_tcid_to_type(uint8_t tcid) {
  uint8_t type;

  if (tcid == 0) {
    type = AVDT_CHAN_SIG;
  } else {
    /* tcid translates to type based on number of channels, as follows:
    ** only media channel   :  tcid=1,2,3,4,5,6...  type=1,1,1,1,1,1...
    ** media and report     :  tcid=1,2,3,4,5,6...  type=1,2,1,2,1,2...
    ** media, report, recov :  tcid=1,2,3,4,5,6...  type=1,2,3,1,2,3...
    */
    type = ((tcid + AVDT_CHAN_NUM_TYPES - 2) % (AVDT_CHAN_NUM_TYPES - 1)) + 1;
  }
  AVDT_TRACE_DEBUG("tcid: %d, type: %d", tcid, type);
  return type;
}

/*******************************************************************************
 *
 * Function         avdt_ad_init
 *
 * Description      Initialize adaption layer.
 *
 *
 * Returns          Nothing.
 *
 ******************************************************************************/
void avdt_ad_init(void) {
  int i;
  tAVDT_TC_TBL* p_tbl = avdt_cb.ad.tc_tbl;
  memset(&avdt_cb.ad, 0, sizeof(tAVDT_AD));

  /* make sure the peer_mtu is a valid value */
  for (i = 0; i < AVDT_NUM_TC_TBL; i++, p_tbl++) {
    p_tbl->peer_mtu = L2CAP_DEFAULT_MTU;
  }
}

/*******************************************************************************
 *
 * Function         avdt_ad_tc_tbl_by_st
 *
 * Description      Find adaption layer transport channel table entry matching
 *                  the given state.
 *
 *
 * Returns          Pointer to matching entry.  For control channel it returns
 *                  the matching entry.  For media or other it returns the
 *                  first matching entry (there could be more than one).
 *
 ******************************************************************************/
tAVDT_TC_TBL* avdt_ad_tc_tbl_by_st(uint8_t type, tAVDT_CCB* p_ccb,
                                   uint8_t state) {
  int i;
  tAVDT_TC_TBL* p_tbl = avdt_cb.ad.tc_tbl;
  uint8_t ccb_idx;

  if (p_ccb == NULL) {
    /* resending security req */
    for (i = 0; i < AVDT_NUM_TC_TBL; i++, p_tbl++) {
      /* must be AVDT_CHAN_SIG - tcid always zero */
      if ((p_tbl->tcid == 0) && (p_tbl->state == state)) {
        break;
      }
    }
  } else {
    ccb_idx = avdt_ccb_to_idx(p_ccb);

    for (i = 0; i < AVDT_NUM_TC_TBL; i++, p_tbl++) {
      if (type == AVDT_CHAN_SIG) {
        /* if control channel, tcid always zero */
        if ((p_tbl->tcid == 0) && (p_tbl->ccb_idx == ccb_idx) &&
            (p_tbl->state == state)) {
          break;
        }
      } else {
        /* if other channel, tcid is always > zero */
        if ((p_tbl->tcid > 0) && (p_tbl->ccb_idx == ccb_idx) &&
            (p_tbl->state == state)) {
          break;
        }
      }
    }
  }

  /* if nothing found return null */
  if (i == AVDT_NUM_TC_TBL) {
    p_tbl = NULL;
  }

  return p_tbl;
}

/*******************************************************************************
 *
 * Function         avdt_ad_tc_tbl_by_lcid
 *
 * Description      Find adaption layer transport channel table entry by LCID.
 *
 *
 * Returns          Pointer to entry.
 *
 ******************************************************************************/
tAVDT_TC_TBL* avdt_ad_tc_tbl_by_lcid(uint16_t lcid) {
  uint8_t idx;

  idx = avdt_cb.ad.lcid_tbl[lcid - L2CAP_BASE_APPL_CID];

  if (idx < AVDT_NUM_TC_TBL) {
    return &avdt_cb.ad.tc_tbl[idx];
  } else {
    return NULL;
  }
}

/*******************************************************************************
 *
 * Function         avdt_ad_tc_tbl_by_type
 *
 * Description      This function retrieves the transport channel table entry
 *                  for a particular channel.
 *
 *
 * Returns          Pointer to transport channel table entry.
 *
 ******************************************************************************/
tAVDT_TC_TBL* avdt_ad_tc_tbl_by_type(uint8_t type, tAVDT_CCB* p_ccb,
                                     tAVDT_SCB* p_scb) {
  uint8_t tcid;
  int i;
  tAVDT_TC_TBL* p_tbl = avdt_cb.ad.tc_tbl;
  uint8_t ccb_idx = avdt_ccb_to_idx(p_ccb);

  /* get tcid from type, scb */
  tcid = avdt_ad_type_to_tcid(type, p_scb);

  for (i = 0; i < AVDT_NUM_TC_TBL; i++, p_tbl++) {
    if ((p_tbl->tcid == tcid) && (p_tbl->ccb_idx == ccb_idx)) {
      break;
    }
  }

  CHECK(i != AVDT_NUM_TC_TBL);

  return p_tbl;
}

/*******************************************************************************
 *
 * Function         avdt_ad_tc_tbl_alloc
 *
 * Description      Allocate an entry in the traffic channel table.
 *
 *
 * Returns          Pointer to entry.
 *
 ******************************************************************************/
tAVDT_TC_TBL* avdt_ad_tc_tbl_alloc(tAVDT_CCB* p_ccb) {
  int i;
  tAVDT_TC_TBL* p_tbl = avdt_cb.ad.tc_tbl;

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

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

  /* initialize entry */
  p_tbl->peer_mtu = L2CAP_DEFAULT_MTU;
  p_tbl->cfg_flags = 0;
  p_tbl->ccb_idx = avdt_ccb_to_idx(p_ccb);
  p_tbl->state = AVDT_AD_ST_IDLE;
  return p_tbl;
}

/*******************************************************************************
 *
 * Function         avdt_ad_tc_tbl_to_idx
 *
 * Description      Convert a transport channel table entry to an index.
 *
 *
 * Returns          Index value.
 *
 ******************************************************************************/
uint8_t avdt_ad_tc_tbl_to_idx(tAVDT_TC_TBL* p_tbl) {
  AVDT_TRACE_DEBUG("avdt_ad_tc_tbl_to_idx: %d", (p_tbl - avdt_cb.ad.tc_tbl));
  /* use array arithmetic to determine index */
  return (uint8_t)(p_tbl - avdt_cb.ad.tc_tbl);
}

/*******************************************************************************
 *
 * Function         avdt_ad_tc_close_ind
 *
 * Description      This function is called by the L2CAP interface when the
 *                  L2CAP channel is closed.  It looks up the CCB or SCB 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 avdt_ad_tc_close_ind(tAVDT_TC_TBL* p_tbl, UNUSED_ATTR uint16_t reason) {
  tAVDT_CCB* p_ccb;
  tAVDT_SCB* p_scb;
  tAVDT_SCB_TC_CLOSE close;

  close.old_tc_state = p_tbl->state;
  /* clear avdt_ad_tc_tbl entry */
  p_tbl->state = AVDT_AD_ST_UNUSED;
  p_tbl->cfg_flags = 0;
  p_tbl->peer_mtu = L2CAP_DEFAULT_MTU;

  AVDT_TRACE_DEBUG("avdt_ad_tc_close_ind tcid: %d, old: %d", p_tbl->tcid,
                   close.old_tc_state);
  /* if signaling channel, notify ccb that channel open */
  if (p_tbl->tcid == 0) {
    p_ccb = avdt_ccb_by_idx(p_tbl->ccb_idx);
    avdt_ccb_event(p_ccb, AVDT_CCB_LL_CLOSE_EVT, NULL);
  }
  /* if media or other channel, notify scb that channel close */
  else {
    /* look up scb in stream routing table by ccb, tcid */
    p_scb =
        avdt_scb_by_hdl(avdt_cb.ad.rt_tbl[p_tbl->ccb_idx][p_tbl->tcid].scb_hdl);
    if (p_scb != NULL) {
      close.tcid = p_tbl->tcid;
      close.type = avdt_ad_tcid_to_type(p_tbl->tcid);
      avdt_scb_event(p_scb, AVDT_SCB_TC_CLOSE_EVT, (tAVDT_SCB_EVT*)&close);
    }
  }
}

/*******************************************************************************
 *
 * Function         avdt_ad_tc_open_ind
 *
 * Description      This function is called by the L2CAP interface when
 *                  the L2CAP channel is opened.  It looks up the CCB or SCB
 *                  for the channel and sends it an open event.
 *
 *
 * Returns          Nothing.
 *
 ******************************************************************************/
void avdt_ad_tc_open_ind(tAVDT_TC_TBL* p_tbl) {
  tAVDT_CCB* p_ccb;
  tAVDT_SCB* p_scb;
  tAVDT_OPEN open;
  tAVDT_EVT_HDR evt;

  p_tbl->state = AVDT_AD_ST_OPEN;

  /* if signaling channel, notify ccb that channel open */
  if (p_tbl->tcid == 0) {
    /* set the signal channel to use high priority within the ACL link */
    L2CA_SetTxPriority(avdt_cb.ad.rt_tbl[p_tbl->ccb_idx][AVDT_CHAN_SIG].lcid,
                       L2CAP_CHNL_PRIORITY_HIGH);

    p_ccb = avdt_ccb_by_idx(p_tbl->ccb_idx);
    /* use err_param to indicate the role of connection.
     * AVDT_ACP, if ACP */
    evt.err_param = AVDT_INT;
    if (p_tbl->cfg_flags & AVDT_L2C_CFG_CONN_ACP) {
      evt.err_param = AVDT_ACP;
    }
    avdt_ccb_event(p_ccb, AVDT_CCB_LL_OPEN_EVT, (tAVDT_CCB_EVT*)&evt);
  }
  /* if media or other channel, notify scb that channel open */
  else {
    /* look up scb in stream routing table by ccb, tcid */
    p_scb =
        avdt_scb_by_hdl(avdt_cb.ad.rt_tbl[p_tbl->ccb_idx][p_tbl->tcid].scb_hdl);

    /* put lcid in event data */
    if (p_scb != NULL) {
      open.peer_mtu = p_tbl->peer_mtu;
      open.lcid = avdt_cb.ad.rt_tbl[p_tbl->ccb_idx][p_tbl->tcid].lcid;
      open.hdr.err_code = avdt_ad_tcid_to_type(p_tbl->tcid);
      avdt_scb_event(p_scb, AVDT_SCB_TC_OPEN_EVT, (tAVDT_SCB_EVT*)&open);
    }
  }
}

/*******************************************************************************
 *
 * Function         avdt_ad_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 SCB 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 avdt_ad_tc_cong_ind(tAVDT_TC_TBL* p_tbl, bool is_congested) {
  tAVDT_CCB* p_ccb;
  tAVDT_SCB* p_scb;

  /* if signaling channel, notify ccb of congestion */
  if (p_tbl->tcid == 0) {
    p_ccb = avdt_ccb_by_idx(p_tbl->ccb_idx);
    avdt_ccb_event(p_ccb, AVDT_CCB_LL_CONG_EVT, (tAVDT_CCB_EVT*)&is_congested);
  }
  /* if media or other channel, notify scb that channel open */
  else {
    /* look up scb in stream routing table by ccb, tcid */
    p_scb =
        avdt_scb_by_hdl(avdt_cb.ad.rt_tbl[p_tbl->ccb_idx][p_tbl->tcid].scb_hdl);
    if (p_scb != NULL) {
      avdt_scb_event(p_scb, AVDT_SCB_TC_CONG_EVT,
                     (tAVDT_SCB_EVT*)&is_congested);
    }
  }
}

/*******************************************************************************
 *
 * Function         avdt_ad_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 SCB for the channel and routes the data accordingly.
 *
 *
 * Returns          Nothing.
 *
 ******************************************************************************/
void avdt_ad_tc_data_ind(tAVDT_TC_TBL* p_tbl, BT_HDR* p_buf) {
  tAVDT_CCB* p_ccb;
  tAVDT_SCB* p_scb;

  /* store type (media, recovery, reporting) */
  p_buf->layer_specific = avdt_ad_tcid_to_type(p_tbl->tcid);

  /* if signaling channel, handle control message */
  if (p_tbl->tcid == 0) {
    p_ccb = avdt_ccb_by_idx(p_tbl->ccb_idx);
    avdt_msg_ind(p_ccb, p_buf);
  }
  /* if media or other channel, send event to scb */
  else {
    p_scb =
        avdt_scb_by_hdl(avdt_cb.ad.rt_tbl[p_tbl->ccb_idx][p_tbl->tcid].scb_hdl);
    if (p_scb != NULL) {
      avdt_scb_event(p_scb, AVDT_SCB_TC_DATA_EVT, (tAVDT_SCB_EVT*)&p_buf);
    } else {
      osi_free(p_buf);
      AVDT_TRACE_ERROR(" avdt_ad_tc_data_ind buffer freed");
    }
  }
}

/*******************************************************************************
 *
 * Function         avdt_ad_write_req
 *
 * Description      This function is called by a CCB or SCB to send data to a
 *                  transport channel.  It looks up the LCID of the channel
 *                  based on the type, CCB, and SCB (if present).  Then it
 *                  passes the data to L2CA_DataWrite().
 *
 *
 * Returns          AVDT_AD_SUCCESS, if data accepted
 *                  AVDT_AD_CONGESTED, if data accepted and the channel is
 *                                     congested
 *                  AVDT_AD_FAILED, if error
 *
 ******************************************************************************/
uint8_t avdt_ad_write_req(uint8_t type, tAVDT_CCB* p_ccb, tAVDT_SCB* p_scb,
                          BT_HDR* p_buf) {
  uint8_t tcid;

  /* get tcid from type, scb */
  tcid = avdt_ad_type_to_tcid(type, p_scb);

  return L2CA_DataWrite(avdt_cb.ad.rt_tbl[avdt_ccb_to_idx(p_ccb)][tcid].lcid,
                        p_buf);
}

/*******************************************************************************
 *
 * Function         avdt_ad_open_req
 *
 * Description      This function is called by a CCB or SCB to open a transport
 *                  channel.  This function allocates and initializes a
 *                  transport channel table entry.  The channel can be opened
 *                  in two roles:  as an initiator or acceptor.  When opened
 *                  as an initiator the function will start an L2CAP connection.
 *                  When opened as an acceptor the function simply configures
 *                  the table entry to listen for an incoming channel.
 *
 *
 * Returns          Nothing.
 *
 ******************************************************************************/
void avdt_ad_open_req(uint8_t type, tAVDT_CCB* p_ccb, tAVDT_SCB* p_scb,
                      uint8_t role) {
  tAVDT_TC_TBL* p_tbl;
  uint16_t lcid;

  p_tbl = avdt_ad_tc_tbl_alloc(p_ccb);
  if (p_tbl == NULL) {
    AVDT_TRACE_ERROR("avdt_ad_open_req: Cannot allocate p_tbl");
    return;
  }

  p_tbl->tcid = avdt_ad_type_to_tcid(type, p_scb);
  AVDT_TRACE_DEBUG("avdt_ad_open_req: type: %d, role: %d, tcid:%d", type, role,
                   p_tbl->tcid);

  if (type == AVDT_CHAN_SIG) {
    /* if signaling, get mtu from registration control block */
    p_tbl->my_mtu = avdt_cb.rcb.ctrl_mtu;
    p_tbl->my_flush_to = L2CAP_DEFAULT_FLUSH_TO;
  } else {
    /* otherwise get mtu from scb */
    p_tbl->my_mtu = p_scb->cs.mtu;
    p_tbl->my_flush_to = p_scb->cs.flush_to;

    /* also set scb_hdl in rt_tbl */
    avdt_cb.ad.rt_tbl[avdt_ccb_to_idx(p_ccb)][p_tbl->tcid].scb_hdl =
        avdt_scb_to_hdl(p_scb);
    AVDT_TRACE_DEBUG("avdt_cb.ad.rt_tbl[%d][%d].scb_hdl = %d",
                     avdt_ccb_to_idx(p_ccb), p_tbl->tcid,
                     avdt_scb_to_hdl(p_scb));
  }

  /* if we're acceptor, we're done; just sit back and listen */
  if (role == AVDT_ACP) {
    p_tbl->state = AVDT_AD_ST_ACP;
  }
  /* else we're inititator, start the L2CAP connection */
  else {
    p_tbl->state = AVDT_AD_ST_CONN;

    /* call l2cap connect req */
    lcid = L2CA_ConnectReq(AVDT_PSM, p_ccb->peer_addr);
    if (lcid != 0) {
      /* if connect req ok, store tcid in lcid table  */
      avdt_cb.ad.lcid_tbl[lcid - L2CAP_BASE_APPL_CID] =
          avdt_ad_tc_tbl_to_idx(p_tbl);
      AVDT_TRACE_DEBUG("avdt_cb.ad.lcid_tbl[%d] = %d",
                       (lcid - L2CAP_BASE_APPL_CID),
                       avdt_ad_tc_tbl_to_idx(p_tbl));

      avdt_cb.ad.rt_tbl[avdt_ccb_to_idx(p_ccb)][p_tbl->tcid].lcid = lcid;
      AVDT_TRACE_DEBUG("avdt_cb.ad.rt_tbl[%d][%d].lcid = 0x%x",
                       avdt_ccb_to_idx(p_ccb), p_tbl->tcid, lcid);
    } else {
      /* if connect req failed, call avdt_ad_tc_close_ind() */
      avdt_ad_tc_close_ind(p_tbl, 0);
    }
  }
}

/*******************************************************************************
 *
 * Function         avdt_ad_close_req
 *
 * Description      This function is called by a CCB or SCB to close a
 *                  transport channel.  The function looks up the LCID for the
 *                  channel and calls L2CA_DisconnectReq().
 *
 *
 * Returns          Nothing.
 *
 ******************************************************************************/
void avdt_ad_close_req(uint8_t type, tAVDT_CCB* p_ccb, tAVDT_SCB* p_scb) {
  uint8_t tcid;
  tAVDT_TC_TBL* p_tbl;

  p_tbl = avdt_ad_tc_tbl_by_type(type, p_ccb, p_scb);
  AVDT_TRACE_DEBUG("avdt_ad_close_req state: %d", p_tbl->state);

  switch (p_tbl->state) {
    case AVDT_AD_ST_UNUSED:
      /* probably for reporting */
      break;
    case AVDT_AD_ST_ACP:
      /* if we're listening on this channel, send ourselves a close ind */
      avdt_ad_tc_close_ind(p_tbl, 0);
      break;
    default:
      /* get tcid from type, scb */
      tcid = avdt_ad_type_to_tcid(type, p_scb);

      /* call l2cap disconnect req */
      L2CA_DisconnectReq(avdt_cb.ad.rt_tbl[avdt_ccb_to_idx(p_ccb)][tcid].lcid);
  }
}
