blob: a2264b8f9afb00d72219f55d997de3ee2053993c [file] [log] [blame]
/******************************************************************************
*
* Copyright 2003-2016 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.
*
******************************************************************************/
/*****************************************************************************
*
* Name: avct_bcb_act.cc
*
* Description: This module contains action functions of the browsing
* control state machine.
*
*****************************************************************************/
#define LOG_TAG "bluetooth"
#include <android_bluetooth_sysprop.h>
#include <bluetooth/log.h>
#include <string.h>
#include "avct_api.h"
#include "avct_int.h"
#include "bta/include/bta_sec_api.h"
#include "internal_include/bt_target.h"
#include "osi/include/allocator.h"
#include "osi/include/osi.h"
#include "stack/avct/avct_defs.h"
#include "stack/include/bt_hdr.h"
#include "stack/include/bt_types.h"
using namespace bluetooth;
/* action function list */
const tAVCT_BCB_ACTION avct_bcb_action[] = {
avct_bcb_chnl_open, /* AVCT_LCB_CHNL_OPEN */
avct_bcb_chnl_disc, /* AVCT_LCB_CHNL_DISC */
avct_bcb_send_msg, /* AVCT_LCB_SEND_MSG */
avct_bcb_open_ind, /* AVCT_LCB_OPEN_IND */
avct_bcb_open_fail, /* AVCT_LCB_OPEN_FAIL */
avct_bcb_close_ind, /* AVCT_LCB_CLOSE_IND */
avct_bcb_close_cfm, /* AVCT_LCB_CLOSE_CFM */
avct_bcb_msg_ind, /* AVCT_LCB_MSG_IND */
avct_bcb_cong_ind, /* AVCT_LCB_CONG_IND */
avct_bcb_bind_conn, /* AVCT_LCB_BIND_CONN */
avct_bcb_bind_fail, /* AVCT_LCB_BIND_FAIL */
avct_bcb_unbind_disc, /* AVCT_LCB_UNBIND_DISC */
avct_bcb_chk_disc, /* AVCT_LCB_CHK_DISC */
avct_bcb_discard_msg, /* AVCT_LCB_DISCARD_MSG */
avct_bcb_dealloc, /* AVCT_LCB_DEALLOC */
avct_bcb_free_msg_ind /* AVCT_LCB_FREE_MSG_IND */
};
/*******************************************************************************
*
* Function avct_bcb_msg_asmbl
*
* Description Reassemble incoming message.
*
*
* Returns Pointer to reassembled message; NULL if no message
* available.
*
******************************************************************************/
static BT_HDR* avct_bcb_msg_asmbl(UNUSED_ATTR tAVCT_BCB* p_bcb, BT_HDR* p_buf) {
uint8_t* p;
uint8_t pkt_type;
if (p_buf->len == 0) {
osi_free_and_reset((void**)&p_buf);
return nullptr;
}
/* parse the message header */
p = (uint8_t*)(p_buf + 1) + p_buf->offset;
pkt_type = AVCT_PKT_TYPE(p);
/* must be single packet - can not fragment */
if (pkt_type != AVCT_PKT_TYPE_SINGLE) {
osi_free_and_reset((void**)&p_buf);
log::warn("Pkt type={} - fragmentation not allowed. drop it", pkt_type);
}
return p_buf;
}
/*******************************************************************************
*
* Function avct_bcb_chnl_open
*
* Description Open L2CAP channel to peer
*
*
* Returns Nothing.
*
******************************************************************************/
void avct_bcb_chnl_open(tAVCT_BCB* p_bcb, UNUSED_ATTR tAVCT_LCB_EVT* p_data) {
uint16_t result = AVCT_RESULT_FAIL;
tAVCT_LCB* p_lcb = avct_lcb_by_bcb(p_bcb);
tL2CAP_ERTM_INFO ertm_info;
/* Set the FCR options: Browsing channel mandates ERTM */
ertm_info.preferred_mode = L2CAP_FCR_ERTM_MODE;
/* call l2cap connect req */
p_bcb->ch_state = AVCT_CH_CONN;
p_bcb->ch_lcid =
L2CA_ConnectReq2(AVCT_BR_PSM, p_lcb->peer_addr, BTA_SEC_AUTHENTICATE);
if (p_bcb->ch_lcid == 0) {
/* if connect req failed, send ourselves close event */
tAVCT_LCB_EVT avct_lcb_evt;
avct_lcb_evt.result = result;
avct_bcb_event(p_bcb, AVCT_LCB_LL_CLOSE_EVT, &avct_lcb_evt);
}
}
/*******************************************************************************
*
* Function avct_bcb_unbind_disc
*
* Description call callback with disconnect event.
*
*
* Returns Nothing.
*
******************************************************************************/
void avct_bcb_unbind_disc(UNUSED_ATTR tAVCT_BCB* p_bcb, tAVCT_LCB_EVT* p_data) {
p_data->p_ccb->p_bcb = NULL;
(*p_data->p_ccb->cc.p_ctrl_cback)(avct_ccb_to_idx(p_data->p_ccb),
AVCT_BROWSE_DISCONN_CFM_EVT, 0, NULL);
}
/*******************************************************************************
*
* Function avct_bcb_open_ind
*
* Description Handle an LL_OPEN event.
* For the allocated ccb already bound to the bcb, send a
* connect event. For the unbound ccb with a new PID, bind that
* ccb to the bcb with the same bd_addr and send a connect
* event.
*
*
* Returns Nothing.
*
******************************************************************************/
void avct_bcb_open_ind(tAVCT_BCB* p_bcb, tAVCT_LCB_EVT* p_data) {
tAVCT_CCB* p_ccb = &avct_cb.ccb[0];
tAVCT_CCB* p_ccb_bind = NULL;
bool bind = false;
tAVCT_UL_MSG ul_msg;
for (int idx = 0; idx < AVCT_NUM_CONN; idx++, p_ccb++) {
/* if ccb allocated and */
if (p_ccb->allocated) {
/* if bound to this bcb send connect confirm event */
if (p_ccb->p_bcb == p_bcb) {
bind = true;
p_ccb_bind = p_ccb;
p_ccb->cc.p_ctrl_cback(avct_ccb_to_idx(p_ccb), AVCT_BROWSE_CONN_CFM_EVT,
0, &p_ccb->p_lcb->peer_addr);
}
/* if unbound acceptor and lcb allocated and bd_addr are the same for bcb
and lcb */
else if ((p_ccb->p_bcb == NULL) && (p_ccb->cc.role == AVCT_ACP) &&
(p_ccb->p_lcb != NULL) &&
p_bcb->peer_addr == p_ccb->p_lcb->peer_addr) {
/* bind bcb to ccb and send connect ind event */
bind = true;
p_ccb_bind = p_ccb;
p_ccb->p_bcb = p_bcb;
p_ccb->cc.p_ctrl_cback(avct_ccb_to_idx(p_ccb), AVCT_BROWSE_CONN_IND_EVT,
0, &p_ccb->p_lcb->peer_addr);
}
}
}
/* if no ccbs bound to this lcb, disconnect */
if (!bind) {
avct_bcb_event(p_bcb, AVCT_LCB_INT_CLOSE_EVT, p_data);
return;
}
if (!p_bcb->p_tx_msg || !p_ccb_bind) {
return;
}
ul_msg.p_buf = p_bcb->p_tx_msg;
ul_msg.p_ccb = p_ccb_bind;
ul_msg.label = (uint8_t)(p_bcb->p_tx_msg->layer_specific & 0xFF);
ul_msg.cr = (uint8_t)((p_bcb->p_tx_msg->layer_specific & 0xFF00) >> 8);
p_bcb->p_tx_msg->layer_specific = AVCT_DATA_BROWSE;
p_bcb->p_tx_msg = NULL;
/* send msg event to bcb */
tAVCT_LCB_EVT avct_lcb_evt;
avct_lcb_evt.ul_msg = ul_msg;
avct_bcb_event(p_bcb, AVCT_LCB_UL_MSG_EVT, &avct_lcb_evt);
}
/*******************************************************************************
*
* Function avct_bcb_open_fail
*
* Description L2CAP channel open attempt failed. Mark the ccbs
* as NULL bcb.
*
*
* Returns Nothing.
*
******************************************************************************/
void avct_bcb_open_fail(tAVCT_BCB* p_bcb, UNUSED_ATTR tAVCT_LCB_EVT* p_data) {
tAVCT_CCB* p_ccb = &avct_cb.ccb[0];
for (int idx = 0; idx < AVCT_NUM_CONN; idx++, p_ccb++) {
if (p_ccb->allocated && (p_ccb->p_bcb == p_bcb)) {
p_ccb->p_bcb = NULL;
}
}
}
/*******************************************************************************
*
* Function avct_bcb_close_ind
*
* Description L2CAP channel closed by peer. Deallocate any initiator
* ccbs on this lcb and send disconnect ind event.
*
*
* Returns Nothing.
*
******************************************************************************/
void avct_bcb_close_ind(tAVCT_BCB* p_bcb, UNUSED_ATTR tAVCT_LCB_EVT* p_data) {
tAVCT_CCB* p_ccb = &avct_cb.ccb[0];
tAVCT_LCB* p_lcb = avct_lcb_by_bcb(p_bcb);
for (int idx = 0; idx < AVCT_NUM_CONN; idx++, p_ccb++) {
if (p_ccb->allocated && (p_ccb->p_bcb == p_bcb)) {
if (p_ccb->cc.role == AVCT_INT) {
(*p_ccb->cc.p_ctrl_cback)(avct_ccb_to_idx(p_ccb),
AVCT_BROWSE_DISCONN_CFM_EVT, 0,
&p_lcb->peer_addr);
} else {
(*p_ccb->cc.p_ctrl_cback)(avct_ccb_to_idx(p_ccb),
AVCT_BROWSE_DISCONN_IND_EVT, 0,
&p_lcb->peer_addr);
}
p_ccb->p_bcb = NULL;
}
}
}
/*******************************************************************************
*
* Function avct_bcb_close_cfm
*
* Description L2CAP channel closed by us. Deallocate any initiator
* ccbs on this lcb and send disconnect ind or cfm event.
*
*
* Returns Nothing.
*
******************************************************************************/
void avct_bcb_close_cfm(tAVCT_BCB* p_bcb, tAVCT_LCB_EVT* p_data) {
tAVCT_CCB* p_ccb = &avct_cb.ccb[0];
uint8_t event = 0;
/* Whether BCB initiated channel close */
bool ch_close = p_bcb->ch_close;
tAVCT_CTRL_CBACK* p_cback;
p_bcb->ch_close = false;
p_bcb->allocated = 0;
for (int idx = 0; idx < AVCT_NUM_CONN; idx++, p_ccb++) {
if (p_ccb->allocated && (p_ccb->p_bcb == p_bcb)) {
/* if this ccb initiated close send disconnect cfm otherwise ind */
if (ch_close) {
event = AVCT_BROWSE_DISCONN_CFM_EVT;
} else {
event = AVCT_BROWSE_DISCONN_IND_EVT;
}
p_cback = p_ccb->cc.p_ctrl_cback;
p_ccb->p_bcb = NULL;
if (p_ccb->p_lcb == NULL) avct_ccb_dealloc(p_ccb, AVCT_NO_EVT, 0, NULL);
(*p_cback)(avct_ccb_to_idx(p_ccb), event, p_data->result,
&p_bcb->peer_addr);
}
}
}
/*******************************************************************************
*
* Function avct_bcb_bind_conn
*
* Description Bind ccb to lcb and send connect cfm event.
*
*
* Returns Nothing.
*
******************************************************************************/
void avct_bcb_bind_conn(tAVCT_BCB* p_bcb, tAVCT_LCB_EVT* p_data) {
tAVCT_LCB* p_lcb = avct_lcb_by_bcb(p_bcb);
p_data->p_ccb->p_bcb = p_bcb;
(*p_data->p_ccb->cc.p_ctrl_cback)(avct_ccb_to_idx(p_data->p_ccb),
AVCT_BROWSE_CONN_CFM_EVT, 0,
&p_lcb->peer_addr);
}
/*******************************************************************************
*
* Function avct_bcb_chk_disc
*
* Description A ccb wants to close; if it is the last ccb on this lcb,
* close channel. Otherwise just deallocate and call
* callback.
*
*
* Returns Nothing.
*
******************************************************************************/
void avct_bcb_chk_disc(tAVCT_BCB* p_bcb, tAVCT_LCB_EVT* p_data) {
p_bcb->ch_close = avct_bcb_get_last_ccb_index(p_bcb, p_data->p_ccb);
if (p_bcb->ch_close) {
avct_bcb_event(p_bcb, AVCT_LCB_INT_CLOSE_EVT, p_data);
return;
}
avct_bcb_unbind_disc(p_bcb, p_data);
}
/*******************************************************************************
*
* Function avct_bcb_chnl_disc
*
* Description Disconnect L2CAP channel.
*
*
* Returns Nothing.
*
******************************************************************************/
void avct_bcb_chnl_disc(tAVCT_BCB* p_bcb, UNUSED_ATTR tAVCT_LCB_EVT* p_data) {
avct_l2c_br_disconnect(p_bcb->ch_lcid, 0);
}
/*******************************************************************************
*
* Function avct_bcb_bind_fail
*
* Description Deallocate ccb and call callback with connect event
* with failure result.
*
*
* Returns Nothing.
*
******************************************************************************/
void avct_bcb_bind_fail(UNUSED_ATTR tAVCT_BCB* p_bcb, tAVCT_LCB_EVT* p_data) {
p_data->p_ccb->p_bcb = NULL;
(*p_data->p_ccb->cc.p_ctrl_cback)(avct_ccb_to_idx(p_data->p_ccb),
AVCT_BROWSE_CONN_CFM_EVT, AVCT_RESULT_FAIL,
NULL);
}
/*******************************************************************************
*
* Function avct_bcb_cong_ind
*
* Description Handle congestion indication from L2CAP.
*
*
* Returns Nothing.
*
******************************************************************************/
void avct_bcb_cong_ind(tAVCT_BCB* p_bcb, tAVCT_LCB_EVT* p_data) {
tAVCT_CCB* p_ccb = &avct_cb.ccb[0];
uint8_t event;
tAVCT_LCB* p_lcb = avct_lcb_by_bcb(p_bcb);
/* set event */
event =
(p_data->cong) ? AVCT_BROWSE_CONG_IND_EVT : AVCT_BROWSE_UNCONG_IND_EVT;
/* send event to all ccbs on this lcb */
for (int idx = 0; idx < AVCT_NUM_CONN; idx++, p_ccb++) {
if (p_ccb->allocated && (p_ccb->p_bcb == p_bcb)) {
(*p_ccb->cc.p_ctrl_cback)(avct_ccb_to_idx(p_ccb), event, 0,
&p_lcb->peer_addr);
}
}
}
/*******************************************************************************
*
* Function avct_bcb_discard_msg
*
* Description Discard a message sent in from the API.
*
*
* Returns Nothing.
*
******************************************************************************/
void avct_bcb_discard_msg(tAVCT_BCB* p_bcb, tAVCT_LCB_EVT* p_data) {
osi_free_and_reset((void**)&p_bcb->p_tx_msg);
/* if control channel is up, save the message and open the browsing channel */
if (p_data->ul_msg.p_ccb->p_lcb == NULL) {
osi_free_and_reset((void**)&p_data->ul_msg.p_buf);
return;
}
p_bcb->p_tx_msg = p_data->ul_msg.p_buf;
if (p_bcb->p_tx_msg) {
p_bcb->p_tx_msg->layer_specific =
(p_data->ul_msg.cr << 8) + p_data->ul_msg.label;
/* the channel is closed, opening or closing - open it again */
log::verbose("ch_state: {}, allocated:{}->{}", p_bcb->ch_state,
p_bcb->allocated, p_data->ul_msg.p_ccb->p_lcb->allocated);
p_bcb->allocated = p_data->ul_msg.p_ccb->p_lcb->allocated;
avct_bcb_event(p_bcb, AVCT_LCB_UL_BIND_EVT,
(tAVCT_LCB_EVT*)p_data->ul_msg.p_ccb);
}
}
/*******************************************************************************
*
* Function avct_bcb_send_msg
*
* Description Build and send an AVCTP message.
*
*
* Returns Nothing.
*
******************************************************************************/
void avct_bcb_send_msg(tAVCT_BCB* p_bcb, tAVCT_LCB_EVT* p_data) {
uint16_t curr_msg_len;
uint8_t pkt_type = AVCT_PKT_TYPE_SINGLE;
uint8_t hdr_len;
BT_HDR* p_buf;
uint8_t* p;
/* store msg len */
curr_msg_len = p_data->ul_msg.p_buf->len;
/* initialize packet type and other stuff */
if (curr_msg_len > (p_bcb->peer_mtu - AVCT_HDR_LEN_SINGLE)) {
log::error("msg len ({}) exceeds peer mtu({}-{})!!", curr_msg_len,
p_bcb->peer_mtu, AVCT_HDR_LEN_SINGLE);
osi_free_and_reset((void**)&p_data->ul_msg.p_buf);
return;
}
/* set header len */
hdr_len = avct_lcb_pkt_type_len[pkt_type];
p_buf = p_data->ul_msg.p_buf;
/* set up to build header */
p_buf->len += hdr_len;
p_buf->offset -= hdr_len;
p = (uint8_t*)(p_buf + 1) + p_buf->offset;
/* build header */
AVCT_BUILD_HDR(p, p_data->ul_msg.label, pkt_type, p_data->ul_msg.cr);
UINT16_TO_BE_STREAM(p, p_data->ul_msg.p_ccb->cc.pid);
p_buf->layer_specific = AVCT_DATA_BROWSE;
/* send message to L2CAP */
L2CA_DataWrite(p_bcb->ch_lcid, p_buf);
}
/*******************************************************************************
*
* Function avct_bcb_free_msg_ind
*
* Description Discard an incoming AVCTP message.
*
*
* Returns Nothing.
*
******************************************************************************/
void avct_bcb_free_msg_ind(UNUSED_ATTR tAVCT_BCB* p_bcb,
tAVCT_LCB_EVT* p_data) {
if (p_data) osi_free_and_reset((void**)&p_data->p_buf);
}
/*******************************************************************************
*
* Function avct_bcb_msg_ind
*
* Description Handle an incoming AVCTP message.
*
*
* Returns Nothing.
*
******************************************************************************/
void avct_bcb_msg_ind(tAVCT_BCB* p_bcb, tAVCT_LCB_EVT* p_data) {
uint8_t* p;
uint8_t label, type, cr_ipid;
uint16_t pid;
tAVCT_CCB* p_ccb;
tAVCT_LCB* p_lcb = avct_lcb_by_bcb(p_bcb);
if ((p_data == NULL) || (p_data->p_buf == NULL)) {
log::warn("p_data is NULL, returning!");
return;
}
/* this p_buf is to be reported through p_msg_cback. The layer_specific
* needs to be set properly to indicate that it is received through
* browsing channel */
p_data->p_buf->layer_specific = AVCT_DATA_BROWSE;
/* reassemble message; if no message available (we received a fragment) return
*/
p_data->p_buf = avct_bcb_msg_asmbl(p_bcb, p_data->p_buf);
if (p_data->p_buf == NULL) {
return;
}
if (p_data->p_buf->len < AVCT_HDR_LEN_SINGLE) {
log::warn("Invalid AVCTP packet length {}: must be at least {}",
p_data->p_buf->len, AVCT_HDR_LEN_SINGLE);
osi_free_and_reset((void**)&p_data->p_buf);
return;
}
p = (uint8_t*)(p_data->p_buf + 1) + p_data->p_buf->offset;
/* parse header byte */
AVCT_PARSE_HDR(p, label, type, cr_ipid);
/* parse PID */
BE_STREAM_TO_UINT16(pid, p);
/* check for invalid cr_ipid */
if (cr_ipid == AVCT_CR_IPID_INVALID) {
log::warn("Invalid cr_ipid {}", cr_ipid);
osi_free_and_reset((void**)&p_data->p_buf);
return;
}
bool bind = false;
if (GET_SYSPROP(A2dp, src_sink_coexist, false)) {
bind = avct_msg_ind_for_src_sink_coexist(p_lcb, p_data, label, cr_ipid);
osi_free_and_reset((void**)&p_data->p_buf);
if (bind) return;
} else {
/* lookup PID */
p_ccb = avct_lcb_has_pid(p_lcb, pid);
if (p_ccb) {
/* PID found; send msg up, adjust bt hdr and call msg callback */
p_data->p_buf->offset += AVCT_HDR_LEN_SINGLE;
p_data->p_buf->len -= AVCT_HDR_LEN_SINGLE;
(*p_ccb->cc.p_msg_cback)(avct_ccb_to_idx(p_ccb), label, cr_ipid,
p_data->p_buf);
return;
}
}
/* PID not found; drop message */
log::warn("No ccb for PID={:x}", pid);
osi_free_and_reset((void**)&p_data->p_buf);
/* if command send reject */
if (cr_ipid == AVCT_CMD) {
BT_HDR* p_buf = (BT_HDR*)osi_malloc(AVRC_CMD_BUF_SIZE);
p_buf->len = AVCT_HDR_LEN_SINGLE;
p_buf->offset = AVCT_MSG_OFFSET - AVCT_HDR_LEN_SINGLE;
p = (uint8_t*)(p_buf + 1) + p_buf->offset;
AVCT_BUILD_HDR(p, label, AVCT_PKT_TYPE_SINGLE, AVCT_REJ);
UINT16_TO_BE_STREAM(p, pid);
p_buf->layer_specific = AVCT_DATA_BROWSE;
L2CA_DataWrite(p_bcb->ch_lcid, p_buf);
}
}
/*******************************************************************************
*
* Function avct_bcb_dealloc
*
* Description Deallocate a browse control block.
*
*
* Returns void.
*
******************************************************************************/
void avct_bcb_dealloc(tAVCT_BCB* p_bcb, UNUSED_ATTR tAVCT_LCB_EVT* p_data) {
tAVCT_CCB* p_ccb = &avct_cb.ccb[0];
log::verbose("{}", p_bcb->allocated);
for (int idx = 0; idx < AVCT_NUM_CONN; idx++, p_ccb++) {
/* if ccb allocated and */
if ((p_ccb->allocated) && (p_ccb->p_bcb == p_bcb)) {
p_ccb->p_bcb = NULL;
log::verbose("used by ccb: {}", idx);
break;
}
}
/* the browsing channel is down. Check if we have pending messages */
osi_free_and_reset((void**)&p_bcb->p_tx_msg);
memset(p_bcb, 0, sizeof(tAVCT_BCB));
}
/*******************************************************************************
*
* Function avct_close_bcb
*
* Description this function is called right before LCB disconnects.
*
*
* Returns Nothing.
*
******************************************************************************/
void avct_close_bcb(tAVCT_LCB* p_lcb, tAVCT_LCB_EVT* p_data) {
tAVCT_BCB* p_bcb = avct_bcb_by_lcb(p_lcb);
if (p_bcb->allocated) {
avct_bcb_event(p_bcb, AVCT_LCB_UL_UNBIND_EVT, p_data);
}
}
/*******************************************************************************
*
* Function avct_lcb_by_bcb
*
* Description This lookup function finds the lcb for a bcb.
*
* Returns pointer to the lcb.
*
******************************************************************************/
tAVCT_LCB* avct_lcb_by_bcb(tAVCT_BCB* p_bcb) {
return &avct_cb.lcb[p_bcb->allocated - 1];
}
/*******************************************************************************
*
* Function avct_bcb_by_lcb
*
* Description This lookup function finds the bcb for a lcb.
*
* Returns pointer to the lcb.
*
******************************************************************************/
tAVCT_BCB* avct_bcb_by_lcb(tAVCT_LCB* p_lcb) {
return &avct_cb.bcb[p_lcb->allocated - 1];
}
/*******************************************************************************
*
* Function avct_bcb_get_last_ccb_index
*
* Description See if given ccb is only one on the bcb.
*
*
* Returns 0, if ccb is last, (ccb index + 1) otherwise.
*
******************************************************************************/
uint8_t avct_bcb_get_last_ccb_index(tAVCT_BCB* p_bcb, tAVCT_CCB* p_ccb_last) {
tAVCT_CCB* p_ccb = &avct_cb.ccb[0];
uint8_t idx = 0;
for (int i = 0; i < AVCT_NUM_CONN; i++, p_ccb++) {
if (p_ccb->allocated && (p_ccb->p_bcb == p_bcb)) {
if (p_ccb != p_ccb_last) return 0;
idx = (uint8_t)(i + 1);
}
}
return idx;
}
/*******************************************************************************
*
* Function avct_bcb_by_lcid
*
* Description Find the BCB associated with the L2CAP LCID
*
*
* Returns pointer to the lcb, or NULL if none found.
*
******************************************************************************/
tAVCT_BCB* avct_bcb_by_lcid(uint16_t lcid) {
tAVCT_BCB* p_bcb = &avct_cb.bcb[0];
int idx;
for (idx = 0; idx < AVCT_NUM_LINKS; idx++, p_bcb++) {
if (p_bcb->allocated &&
((p_bcb->ch_lcid == lcid) || (p_bcb->conflict_lcid == lcid))) {
return p_bcb;
}
}
/* out of lcbs */
log::warn("No bcb for lcid {:x}", lcid);
return NULL;
}