/******************************************************************************
 *
 *  Copyright 1999-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 file contains functions callable by an application
 * running on top of RFCOMM
 *
 *****************************************************************************/

#include <cstring>
#include "bt_common.h"
#include "bt_target.h"
#include "bt_utils.h"
#include "l2c_api.h"
#include "osi/include/osi.h"
#include "port_api.h"
#include "port_int.h"
#include "rfc_int.h"
#include "rfcdefs.h"

tRFC_CB rfc_cb;

/*******************************************************************************
 *
 * Function         RFCOMM_StartReq
 *
 * Description      This function handles Start Request from the upper layer.
 *                  If RFCOMM multiplexer channel can not be allocated
 *                  send start not accepted confirmation.  Otherwise dispatch
 *                  start event to the state machine.
 *
 ******************************************************************************/
void RFCOMM_StartReq(tRFC_MCB* p_mcb) {
  rfc_mx_sm_execute(p_mcb, RFC_MX_EVENT_START_REQ, nullptr);
}

/*******************************************************************************
 *
 * Function         RFCOMM_StartRsp
 *
 * Description      This function handles Start Response from the upper layer.
 *                  Save upper layer handle and result of the Start Indication
 *                  in the control block and dispatch event to the FSM.
 *
 ******************************************************************************/
void RFCOMM_StartRsp(tRFC_MCB* p_mcb, uint16_t result) {
  rfc_mx_sm_execute(p_mcb, RFC_MX_EVENT_START_RSP, &result);
}

/*******************************************************************************
 *
 * Function         RFCOMM_DlcEstablishReq
 *
 * Description      This function is called by the user app to establish
 *                  connection with the specific dlci on a specific bd device.
 *                  It will allocate RFCOMM connection control block if not
 *                  allocated before and dispatch open event to the state
 *                  machine.
 *
 ******************************************************************************/
void RFCOMM_DlcEstablishReq(tRFC_MCB* p_mcb, uint8_t dlci,
                            UNUSED_ATTR uint16_t mtu) {
  if (p_mcb->state != RFC_MX_STATE_CONNECTED) {
    PORT_DlcEstablishCnf(p_mcb, dlci, 0, RFCOMM_ERROR);
    return;
  }

  tPORT* p_port = port_find_mcb_dlci_port(p_mcb, dlci);
  if (p_port == nullptr) {
    RFCOMM_TRACE_WARNING("%s Unable to find DLCI port dlci:%d", __func__, dlci);
    return;
  }

  rfc_port_sm_execute(p_port, RFC_EVENT_OPEN, nullptr);
}

/*******************************************************************************
 *
 * Function         RFCOMM_DlcEstablishRsp
 *
 * Description      This function is called by the port emulation entity
 *                  acks Establish Indication.
 *
 ******************************************************************************/
void RFCOMM_DlcEstablishRsp(tRFC_MCB* p_mcb, uint8_t dlci,
                            UNUSED_ATTR uint16_t mtu, uint16_t result) {
  if ((p_mcb->state != RFC_MX_STATE_CONNECTED) && (result == RFCOMM_SUCCESS)) {
    PORT_DlcReleaseInd(p_mcb, dlci);
    return;
  }

  tPORT* p_port = port_find_mcb_dlci_port(p_mcb, dlci);
  if (p_port == nullptr) {
    RFCOMM_TRACE_WARNING("%s Unable to find DLCI port dlci:%d", __func__, dlci);
    return;
  }
  rfc_port_sm_execute(p_port, RFC_EVENT_ESTABLISH_RSP, &result);
}

/*******************************************************************************
 *
 * Function         RFCOMM_ParameterNegotiationRequest
 *
 * Description      This function is called by the user app to start
 *                  DLC parameter negotiation.  Port emulation can send this
 *                  request before actually establishing the DLC.  In this
 *                  case the function will allocate RFCOMM connection control
 *                  block.
 *
 ******************************************************************************/
void RFCOMM_ParameterNegotiationRequest(tRFC_MCB* p_mcb, uint8_t dlci,
                                        uint16_t mtu) {
  uint8_t flow;
  uint8_t cl;
  uint8_t k;

  tPORT* p_port = port_find_mcb_dlci_port(p_mcb, dlci);
  if (p_port == nullptr) {
    RFCOMM_TRACE_WARNING("%s Unable to find DLCI port dlci:%d", __func__, dlci);
    return;
  }

  if (p_mcb->state != RFC_MX_STATE_CONNECTED) {
    p_port->error = PORT_PAR_NEG_FAILED;
    return;
  }

  /* Negotiate the flow control mechanism.  If flow control mechanism for */
  /* mux has not been set yet, use our default value.  If it has been set, */
  /* use that value. */
  flow = (p_mcb->flow == PORT_FC_UNDEFINED) ? PORT_FC_DEFAULT : p_mcb->flow;

  /* Set convergence layer and number of credits (k) */
  if (flow == PORT_FC_CREDIT) {
    cl = RFCOMM_PN_CONV_LAYER_CBFC_I;
    k = (p_port->credit_rx_max < RFCOMM_K_MAX) ? p_port->credit_rx_max
                                               : RFCOMM_K_MAX;
    p_port->credit_rx = k;
  } else {
    cl = RFCOMM_PN_CONV_LAYER_TYPE_1;
    k = 0;
  }

  /* Send Parameter Negotiation Command UIH frame */
  p_port->rfc.expected_rsp |= RFC_RSP_PN;

  rfc_send_pn(p_mcb, dlci, true, mtu, cl, k);

  rfc_port_timer_start(p_port, RFC_T2_TIMEOUT);
}

/*******************************************************************************
 *
 * Function         RFCOMM_ParameterNegotiationResponse
 *
 * Description      This function is called by the user app to acknowledge
 *                  DLC parameter negotiation.
 *
 ******************************************************************************/
void RFCOMM_ParameterNegotiationResponse(tRFC_MCB* p_mcb, uint8_t dlci,
                                         uint16_t mtu, uint8_t cl, uint8_t k) {
  if (p_mcb->state != RFC_MX_STATE_CONNECTED) return;

  /* Send Parameter Negotiation Response UIH frame */
  rfc_send_pn(p_mcb, dlci, false, mtu, cl, k);
}

/*******************************************************************************
 *
 * Function         RFCOMM_PortParameterNegotiationRequest
 *
 * Description      This function is called by the user app to start
 *                  Remote Port parameter negotiation.  Port emulation can
 *                  send this request before actually establishing the DLC.
 *                  In this case the function will allocate RFCOMM connection
 *                  control block.
 *
 ******************************************************************************/
void RFCOMM_PortParameterNegotiationRequest(tRFC_MCB* p_mcb, uint8_t dlci,
                                            tPORT_STATE* p_pars) {
  if (p_mcb->state != RFC_MX_STATE_CONNECTED) {
    PORT_PortNegCnf(p_mcb, dlci, nullptr, RFCOMM_ERROR);
    return;
  }

  tPORT* p_port = port_find_mcb_dlci_port(p_mcb, dlci);
  if (p_port == nullptr) {
    RFCOMM_TRACE_WARNING("%s Unable to find DLCI port dlci:%d", __func__, dlci);
    return;
  }

  /* Send Parameter Negotiation Command UIH frame */
  if (!p_pars)
    p_port->rfc.expected_rsp |= RFC_RSP_RPN_REPLY;
  else
    p_port->rfc.expected_rsp |= RFC_RSP_RPN;

  rfc_send_rpn(p_mcb, dlci, true, p_pars, RFCOMM_RPN_PM_MASK);
  rfc_port_timer_start(p_port, RFC_T2_TIMEOUT);
}

/*******************************************************************************
 *
 * Function         RFCOMM_PortParameterNegotiationResponse
 *
 * Description      This function is called by the user app to acknowledge
 *                  Port parameters negotiation.
 *
 ******************************************************************************/
void RFCOMM_PortParameterNegotiationResponse(tRFC_MCB* p_mcb, uint8_t dlci,
                                             tPORT_STATE* p_pars,
                                             uint16_t param_mask) {
  if (p_mcb->state != RFC_MX_STATE_CONNECTED) return;

  rfc_send_rpn(p_mcb, dlci, false, p_pars, param_mask);
}

/*******************************************************************************
 *
 * Function         RFCOMM_ControlReq
 *
 * Description      This function is called by the port entity to send control
 *                  parameters to remote port emulation entity.
 *
 ******************************************************************************/
void RFCOMM_ControlReq(tRFC_MCB* p_mcb, uint8_t dlci, tPORT_CTRL* p_pars) {
  tPORT* p_port = port_find_mcb_dlci_port(p_mcb, dlci);
  if (p_port == nullptr) {
    RFCOMM_TRACE_WARNING("%s Unable to find DLCI port dlci:%d", __func__, dlci);
    return;
  }

  if ((p_port->state != PORT_STATE_OPENED) ||
      (p_port->rfc.state != RFC_STATE_OPENED))
    return;

  p_port->port_ctrl |= PORT_CTRL_REQ_SENT;

  p_port->rfc.expected_rsp |= RFC_RSP_MSC;

  rfc_send_msc(p_mcb, dlci, true, p_pars);
  rfc_port_timer_start(p_port, RFC_T2_TIMEOUT);
}

/*******************************************************************************
 *
 * Function         RFCOMM_FlowReq
 *
 * Description      This function is called by the port entity when flow
 *                  control state has changed.  Enable flag passed shows if
 *                  port can accept more data.
 *
 ******************************************************************************/
void RFCOMM_FlowReq(tRFC_MCB* p_mcb, uint8_t dlci, bool enable) {
  tPORT* p_port = port_find_mcb_dlci_port(p_mcb, dlci);
  if (p_port == nullptr) {
    RFCOMM_TRACE_WARNING("%s Unable to find DLCI port dlci:%d", __func__, dlci);
    return;
  }

  if ((p_port->state != PORT_STATE_OPENED) ||
      (p_port->rfc.state != RFC_STATE_OPENED))
    return;

  p_port->local_ctrl.fc = !enable;

  p_port->rfc.expected_rsp |= RFC_RSP_MSC;

  rfc_send_msc(p_mcb, dlci, true, &p_port->local_ctrl);
  rfc_port_timer_start(p_port, RFC_T2_TIMEOUT);
}

/*******************************************************************************
 *
 * Function         RFCOMM_LineStatusReq
 *
 * Description      This function is called by the port entity when line
 *                  status should be delivered to the peer.
 *
 ******************************************************************************/
void RFCOMM_LineStatusReq(tRFC_MCB* p_mcb, uint8_t dlci, uint8_t status) {
  tPORT* p_port = port_find_mcb_dlci_port(p_mcb, dlci);
  if (p_port == nullptr) {
    RFCOMM_TRACE_WARNING("%s Unable to find DLCI port dlci:%d", __func__, dlci);
    return;
  }

  if ((p_port->state != PORT_STATE_OPENED) ||
      (p_port->rfc.state != RFC_STATE_OPENED))
    return;

  p_port->rfc.expected_rsp |= RFC_RSP_RLS;

  rfc_send_rls(p_mcb, dlci, true, status);
  rfc_port_timer_start(p_port, RFC_T2_TIMEOUT);
}

/*******************************************************************************
 *
 * Function         RFCOMM_DlcReleaseReq
 *
 * Description      This function is called by the PORT unit to close DLC
 *
 ******************************************************************************/
void RFCOMM_DlcReleaseReq(tRFC_MCB* p_mcb, uint8_t dlci) {
  rfc_port_sm_execute(port_find_mcb_dlci_port(p_mcb, dlci), RFC_EVENT_CLOSE,
                      nullptr);
}

/*******************************************************************************
 *
 * Function         RFCOMM_DataReq
 *
 * Description      This function is called by the user app to send data buffer
 *
 ******************************************************************************/
void RFCOMM_DataReq(tRFC_MCB* p_mcb, uint8_t dlci, BT_HDR* p_buf) {
  rfc_port_sm_execute(port_find_mcb_dlci_port(p_mcb, dlci), RFC_EVENT_DATA,
                      p_buf);
}