| /****************************************************************************** |
| * |
| * 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 the L2CAP channel state machine |
| * |
| ******************************************************************************/ |
| #define LOG_TAG "l2c_csm" |
| |
| #include <base/functional/callback.h> |
| #include <bluetooth/log.h> |
| #include <bluetooth/metrics/os_metrics.h> |
| #include <com_android_bluetooth_flags.h> |
| #include <frameworks/proto_logging/stats/enums/bluetooth/enums.pb.h> |
| |
| #include <string> |
| |
| #include "hal/snoop_logger.h" |
| #include "internal_include/bt_target.h" |
| #include "main/shim/entry.h" |
| #include "osi/include/allocator.h" |
| #include "osi/include/stack_power_telemetry.h" |
| #include "stack/btm/btm_sec.h" |
| #include "stack/include/acl_api.h" |
| #include "stack/include/bt_hdr.h" |
| #include "stack/include/bt_psm_types.h" |
| #include "stack/include/bt_types.h" |
| #include "stack/include/l2cdefs.h" |
| #include "stack/l2cap/l2c_int.h" |
| |
| using namespace bluetooth; |
| |
| /******************************************************************************/ |
| /* L O C A L F U N C T I O N P R O T O T Y P E S */ |
| /******************************************************************************/ |
| static void l2c_csm_closed(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data); |
| static void l2c_csm_orig_w4_sec_comp(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data); |
| static void l2c_csm_term_w4_sec_comp(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data); |
| static void l2c_csm_w4_l2cap_connect_rsp(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data); |
| static void l2c_csm_w4_l2ca_connect_rsp(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data); |
| static void l2c_csm_config(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data); |
| static void l2c_csm_open(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data); |
| static void l2c_csm_w4_l2cap_disconnect_rsp(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data); |
| static void l2c_csm_w4_l2ca_disconnect_rsp(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data); |
| |
| static std::string l2c_csm_get_event_name(const tL2CEVT& event); |
| |
| // Send a connect response with result OK and adjust the state machine |
| static void l2c_csm_send_connect_rsp(tL2C_CCB* p_ccb) { |
| l2c_csm_execute(p_ccb, L2CEVT_L2CA_CONNECT_RSP, NULL); |
| } |
| |
| #if (L2CAP_CONFORMANCE_TESTING == TRUE) |
| #include "osi/include/properties.h" |
| |
| /* FCS Flag configuration for L2CAP/FOC/BV-04 and L2CAP/FOC/BV-05 |
| * Upper tester implementation for above two testcases where |
| * different FCS options need to be used in different steps. |
| */ |
| |
| /* L2CAP.TSp38, table 4.13 */ |
| static uint8_t pts_fcs_option_bv_04_c[] = {0x01, 0x01, 0x01, 0x01, 0x01, 0x01}; |
| |
| /* L2CAP.TSp38, table 4.68 */ |
| static uint8_t pts_fcs_option_bv_05_c[] = {0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}; |
| |
| static uint8_t pts_foc_bv_test_id_property; |
| |
| static uint8_t pts_get_fcs_option(void) { |
| static size_t fcs_opt_iter = 0; |
| uint8_t* fcs_test_options = nullptr; |
| uint8_t iter_cnt = 0; |
| |
| uint8_t test_id = osi_property_get_int32("bluetooth.pts.l2cap.foc.bv.test", 0); |
| if (pts_foc_bv_test_id_property != test_id) { |
| pts_foc_bv_test_id_property = test_id; |
| fcs_opt_iter = 0; |
| } |
| |
| switch (test_id) { |
| case 4: |
| log::info("Proceed test L2CAP/FOC/BV-04-C"); |
| fcs_test_options = &pts_fcs_option_bv_04_c[0]; |
| iter_cnt = sizeof(pts_fcs_option_bv_04_c); |
| break; |
| case 5: |
| log::info("Proceed test L2CAP/FOC/BV-05-C"); |
| fcs_test_options = &pts_fcs_option_bv_05_c[0]; |
| iter_cnt = sizeof(pts_fcs_option_bv_05_c); |
| break; |
| default: |
| log::info("Proceed unknown test"); |
| return 1; |
| } |
| |
| log::info("fcs_opt_iter: {}, fcs option: {}", fcs_opt_iter, |
| fcs_opt_iter < iter_cnt ? fcs_test_options[fcs_opt_iter] : -1); |
| |
| if (fcs_opt_iter < iter_cnt) { |
| return fcs_test_options[fcs_opt_iter++]; |
| } |
| |
| log::info("Too many iterations: {}, return fcs = 0x01", fcs_opt_iter); |
| return 1; |
| } |
| |
| static void l2c_csm_send_config_req(tL2C_CCB* p_ccb); |
| static void l2c_ccb_pts_delay_config_timeout(void* data) { |
| tL2C_CCB* p_ccb = (tL2C_CCB*)data; |
| l2c_csm_send_config_req(p_ccb); |
| } |
| #endif |
| |
| static uint8_t get_fcs_option(void) { |
| #if (L2CAP_CONFORMANCE_TESTING == TRUE) |
| return pts_get_fcs_option(); |
| #else |
| return 0x01; |
| #endif |
| } |
| |
| // Send a config request and adjust the state machine |
| static void l2c_csm_send_config_req(tL2C_CCB* p_ccb) { |
| tL2CAP_CFG_INFO config{}; |
| config.mtu_present = true; |
| config.mtu = p_ccb->p_rcb->my_mtu; |
| p_ccb->max_rx_mtu = config.mtu; |
| if (p_ccb->p_rcb->ertm_info.preferred_mode != L2CAP_FCR_BASIC_MODE) { |
| config.fcr_present = true; |
| config.fcr = kDefaultErtmOptions; |
| |
| if (com::android::bluetooth::flags::l2cap_fcs_option_fix()) { |
| /* Later l2cu_process_our_cfg_req() will check if remote supports it, and if not, it will be |
| * cleared as per spec. */ |
| config.fcs_present = true; |
| config.fcs = get_fcs_option(); |
| } |
| } |
| p_ccb->our_cfg = config; |
| l2c_csm_execute(p_ccb, L2CEVT_L2CA_CONFIG_REQ, &config); |
| } |
| |
| // Send a config response with result OK and adjust the state machine |
| static void l2c_csm_send_config_rsp_ok(tL2C_CCB* p_ccb, bool cbit) { |
| tL2CAP_CFG_INFO config{}; |
| config.result = tL2CAP_CFG_RESULT::L2CAP_CFG_OK; |
| if (cbit) { |
| config.flags = L2CAP_CFG_FLAGS_MASK_CONT; |
| } |
| l2c_csm_execute(p_ccb, L2CEVT_L2CA_CONFIG_RSP, &config); |
| } |
| |
| static void l2c_csm_send_disconnect_rsp(tL2C_CCB* p_ccb) { |
| l2c_csm_execute(p_ccb, L2CEVT_L2CA_DISCONNECT_RSP, NULL); |
| } |
| |
| static void l2c_csm_indicate_connection_open(tL2C_CCB* p_ccb) { |
| if (p_ccb->connection_initiator == L2CAP_INITIATOR_LOCAL) { |
| (*p_ccb->p_rcb->api.pL2CA_ConnectCfm_Cb)(p_ccb->local_cid, tL2CAP_CONN::L2CAP_CONN_OK); |
| } else { |
| if (*p_ccb->p_rcb->api.pL2CA_ConnectInd_Cb) { |
| (*p_ccb->p_rcb->api.pL2CA_ConnectInd_Cb)(p_ccb->p_lcb->remote_bd_addr, p_ccb->local_cid, |
| p_ccb->p_rcb->psm, p_ccb->remote_id); |
| } else { |
| log::warn("pL2CA_ConnectInd_Cb is null"); |
| } |
| } |
| if (p_ccb->chnl_state == CST_OPEN && !p_ccb->p_lcb->is_transport_ble()) { |
| (*p_ccb->p_rcb->api.pL2CA_ConfigCfm_Cb)(p_ccb->local_cid, p_ccb->connection_initiator, |
| &p_ccb->peer_cfg); |
| } |
| power_telemetry::GetInstance().LogChannelConnected( |
| p_ccb->p_rcb->psm, p_ccb->local_cid, p_ccb->remote_id, p_ccb->p_lcb->remote_bd_addr); |
| } |
| |
| /******************************************************************************* |
| * |
| * Function l2c_csm_execute |
| * |
| * Description This function executes the state machine. |
| * |
| * Returns void |
| * |
| ******************************************************************************/ |
| void l2c_csm_execute(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data) { |
| if (p_ccb == nullptr) { |
| log::warn("CCB is null for event ({})", event); |
| return; |
| } |
| |
| if (!l2cu_is_ccb_active(p_ccb)) { |
| log::warn("CCB not in use, event ({}) cannot be processed", event); |
| return; |
| } |
| |
| // Log all but data events |
| if (event != L2CEVT_L2CAP_DATA && event != L2CEVT_L2CA_DATA_READ && |
| event != L2CEVT_L2CA_DATA_WRITE) { |
| log::info("Enter CSM, chnl_state:{} [{}] event:{} lcid:0x{:04x} rcid:0x{:04x}", |
| channel_state_text(p_ccb->chnl_state), p_ccb->chnl_state, |
| l2c_csm_get_event_name(event), p_ccb->local_cid, p_ccb->remote_cid); |
| } |
| |
| switch (p_ccb->chnl_state) { |
| case CST_CLOSED: |
| l2c_csm_closed(p_ccb, event, p_data); |
| break; |
| |
| case CST_ORIG_W4_SEC_COMP: |
| l2c_csm_orig_w4_sec_comp(p_ccb, event, p_data); |
| break; |
| |
| case CST_TERM_W4_SEC_COMP: |
| l2c_csm_term_w4_sec_comp(p_ccb, event, p_data); |
| break; |
| |
| case CST_W4_L2CAP_CONNECT_RSP: |
| l2c_csm_w4_l2cap_connect_rsp(p_ccb, event, p_data); |
| break; |
| |
| case CST_W4_L2CA_CONNECT_RSP: |
| l2c_csm_w4_l2ca_connect_rsp(p_ccb, event, p_data); |
| break; |
| |
| case CST_CONFIG: |
| l2c_csm_config(p_ccb, event, p_data); |
| break; |
| |
| case CST_OPEN: |
| l2c_csm_open(p_ccb, event, p_data); |
| break; |
| |
| case CST_W4_L2CAP_DISCONNECT_RSP: |
| l2c_csm_w4_l2cap_disconnect_rsp(p_ccb, event, p_data); |
| break; |
| |
| case CST_W4_L2CA_DISCONNECT_RSP: |
| l2c_csm_w4_l2ca_disconnect_rsp(p_ccb, event, p_data); |
| break; |
| |
| default: |
| log::error("Unhandled state {}, event {}", p_ccb->chnl_state, event); |
| break; |
| } |
| } |
| |
| /******************************************************************************* |
| * |
| * Function l2c_csm_closed |
| * |
| * Description This function handles events when the channel is in |
| * CLOSED state. This state exists only when the link is |
| * being initially established. |
| * |
| * Returns void |
| * |
| ******************************************************************************/ |
| static void l2c_csm_closed(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data) { |
| tL2C_CONN_INFO* p_ci = (tL2C_CONN_INFO*)p_data; |
| uint16_t local_cid = p_ccb->local_cid; |
| tL2CA_DISCONNECT_IND_CB* disconnect_ind; |
| |
| if (p_ccb->p_rcb == NULL) { |
| log::error("LCID: 0x{:04x} st: CLOSED evt: {} p_rcb == NULL", p_ccb->local_cid, |
| l2c_csm_get_event_name(event)); |
| return; |
| } |
| |
| disconnect_ind = p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb; |
| |
| log::debug("LCID: 0x{:04x} st: CLOSED evt: {} psm: {}", p_ccb->local_cid, |
| l2c_csm_get_event_name(event), psm_to_text(p_ccb->p_rcb->psm)); |
| |
| switch (event) { |
| case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */ |
| log::debug("Calling Disconnect_Ind_Cb(), CID: 0x{:04x} No Conf Needed", p_ccb->local_cid); |
| l2cu_release_ccb(p_ccb); |
| (*disconnect_ind)(local_cid, false); |
| break; |
| |
| case L2CEVT_LP_CONNECT_CFM: /* Link came up */ |
| if (p_ccb->p_lcb->transport == BT_TRANSPORT_LE) { |
| p_ccb->chnl_state = CST_ORIG_W4_SEC_COMP; |
| l2ble_sec_access_req(p_ccb->p_lcb->remote_bd_addr, p_ccb->p_rcb->psm, true, |
| &l2c_link_sec_comp, p_ccb); |
| } else { |
| p_ccb->chnl_state = CST_ORIG_W4_SEC_COMP; |
| btm_sec_l2cap_access_req(p_ccb->p_lcb->remote_bd_addr, p_ccb->p_rcb->psm, true, |
| &l2c_link_sec_comp, p_ccb); |
| } |
| break; |
| |
| case L2CEVT_LP_CONNECT_CFM_NEG: /* Link failed */ |
| if (p_ci->hci_status == HCI_ERR_CONNECTION_EXISTS) { |
| btm_acl_notif_conn_collision(p_ccb->p_lcb->remote_bd_addr); |
| } else { |
| l2cu_release_ccb(p_ccb); |
| (*p_ccb->p_rcb->api.pL2CA_Error_Cb)( |
| local_cid, static_cast<uint16_t>(tL2CAP_CONN::L2CAP_CONN_ACL_CONNECTION_FAILED)); |
| bluetooth::os::CountCounterMetrics( |
| android::bluetooth::CodePathCounterKeyEnum::L2CAP_CONNECT_CONFIRM_NEG, 1); |
| } |
| break; |
| |
| case L2CEVT_L2CA_CREDIT_BASED_CONNECT_REQ: /* API connect request */ |
| case L2CEVT_L2CA_CONNECT_REQ: |
| if (p_ccb->p_lcb->transport == BT_TRANSPORT_LE) { |
| p_ccb->chnl_state = CST_ORIG_W4_SEC_COMP; |
| l2ble_sec_access_req(p_ccb->p_lcb->remote_bd_addr, p_ccb->p_rcb->psm, true, |
| &l2c_link_sec_comp, p_ccb); |
| } else { |
| if (!BTM_SetLinkPolicyActiveMode(p_ccb->p_lcb->remote_bd_addr)) { |
| log::warn("Unable to set link policy active"); |
| } |
| /* If sec access does not result in started SEC_COM or COMP_NEG are |
| * already processed */ |
| if (btm_sec_l2cap_access_req(p_ccb->p_lcb->remote_bd_addr, p_ccb->p_rcb->psm, true, |
| &l2c_link_sec_comp, p_ccb) == tBTM_STATUS::BTM_CMD_STARTED) { |
| p_ccb->chnl_state = CST_ORIG_W4_SEC_COMP; |
| } |
| } |
| break; |
| |
| case L2CEVT_SEC_COMP: |
| p_ccb->chnl_state = CST_W4_L2CAP_CONNECT_RSP; |
| |
| /* Wait for the info resp in this state before sending connect req (if |
| * needed) */ |
| if (!p_ccb->p_lcb->w4_info_rsp) { |
| /* Need to have at least one compatible channel to continue */ |
| if (!l2c_fcr_chk_chan_modes(p_ccb)) { |
| l2cu_release_ccb(p_ccb); |
| (*p_ccb->p_rcb->api.pL2CA_Error_Cb)( |
| local_cid, static_cast<uint16_t>(tL2CAP_CONN::L2CAP_CONN_OTHER_ERROR)); |
| bluetooth::os::CountCounterMetrics(android::bluetooth::CodePathCounterKeyEnum:: |
| L2CAP_NO_COMPATIBLE_CHANNEL_AT_CSM_CLOSED, |
| 1); |
| } else { |
| l2cu_send_peer_connect_req(p_ccb); |
| alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_CONNECT_TIMEOUT_MS, |
| l2c_ccb_timer_timeout, p_ccb); |
| } |
| } |
| break; |
| |
| case L2CEVT_SEC_COMP_NEG: /* something is really bad with security */ |
| l2cu_release_ccb(p_ccb); |
| (*p_ccb->p_rcb->api.pL2CA_Error_Cb)( |
| local_cid, |
| static_cast<uint16_t>(tL2CAP_CONN::L2CAP_CONN_CLIENT_SECURITY_CLEARANCE_FAILED)); |
| bluetooth::os::CountCounterMetrics( |
| android::bluetooth::CodePathCounterKeyEnum::L2CAP_SECURITY_NEG_AT_CSM_CLOSED, 1); |
| break; |
| |
| case L2CEVT_L2CAP_CREDIT_BASED_CONNECT_REQ: /* Peer connect request */ |
| case L2CEVT_L2CAP_CONNECT_REQ: |
| /* stop link timer to avoid race condition between A2MP, Security, and |
| * L2CAP */ |
| alarm_cancel(p_ccb->p_lcb->l2c_lcb_timer); |
| |
| if (p_ccb->p_lcb->transport == BT_TRANSPORT_LE) { |
| p_ccb->chnl_state = CST_TERM_W4_SEC_COMP; |
| tL2CAP_LE_RESULT_CODE result = l2ble_sec_access_req( |
| p_ccb->p_lcb->remote_bd_addr, p_ccb->p_rcb->psm, false, &l2c_link_sec_comp, p_ccb); |
| |
| switch (result) { |
| case tL2CAP_LE_RESULT_CODE::L2CAP_LE_RESULT_INSUFFICIENT_AUTHORIZATION: |
| case tL2CAP_LE_RESULT_CODE::L2CAP_LE_RESULT_UNACCEPTABLE_PARAMETERS: |
| case tL2CAP_LE_RESULT_CODE::L2CAP_LE_RESULT_INVALID_PARAMETERS: |
| case tL2CAP_LE_RESULT_CODE::L2CAP_LE_RESULT_INSUFFICIENT_AUTHENTICATION: |
| case tL2CAP_LE_RESULT_CODE::L2CAP_LE_RESULT_INSUFFICIENT_ENCRYP_KEY_SIZE: |
| case tL2CAP_LE_RESULT_CODE::L2CAP_LE_RESULT_INSUFFICIENT_ENCRYP: |
| l2cu_reject_ble_connection(p_ccb, p_ccb->remote_id, result); |
| l2cu_release_ccb(p_ccb); |
| break; |
| case tL2CAP_LE_RESULT_CODE::L2CAP_LE_RESULT_CONN_OK: |
| case tL2CAP_LE_RESULT_CODE::L2CAP_LE_RESULT_NO_PSM: |
| case tL2CAP_LE_RESULT_CODE::L2CAP_LE_RESULT_NO_RESOURCES: |
| case tL2CAP_LE_RESULT_CODE::L2CAP_LE_RESULT_INVALID_SOURCE_CID: |
| case tL2CAP_LE_RESULT_CODE::L2CAP_LE_RESULT_SOURCE_CID_ALREADY_ALLOCATED: |
| break; |
| case tL2CAP_LE_RESULT_CODE::L2CAP_LE_RESULT_CONN_PENDING: |
| case tL2CAP_LE_RESULT_CODE::L2CAP_LE_RESULT_CONN_PENDING_AUTHENTICATION: |
| case tL2CAP_LE_RESULT_CODE::L2CAP_LE_RESULT_CONN_PENDING_AUTHORIZATION: |
| log::warn("Received unexpected connection request return code:{}", |
| l2cap_le_result_code_text(result)); |
| break; |
| } |
| } else { |
| if (!BTM_SetLinkPolicyActiveMode(p_ccb->p_lcb->remote_bd_addr)) { |
| log::warn("Unable to set link policy active"); |
| } |
| p_ccb->chnl_state = CST_TERM_W4_SEC_COMP; |
| auto status = btm_sec_l2cap_access_req(p_ccb->p_lcb->remote_bd_addr, p_ccb->p_rcb->psm, |
| false, &l2c_link_sec_comp, p_ccb); |
| if (status == tBTM_STATUS::BTM_CMD_STARTED) { |
| // started the security process, tell the peer to set a longer timer |
| l2cu_send_peer_connect_rsp(p_ccb, tL2CAP_CONN::L2CAP_CONN_PENDING, 0); |
| } else { |
| log::info("Check security for psm 0x{:04x}, status {}", p_ccb->p_rcb->psm, status); |
| } |
| } |
| break; |
| |
| case L2CEVT_TIMEOUT: |
| l2cu_release_ccb(p_ccb); |
| (*p_ccb->p_rcb->api.pL2CA_Error_Cb)( |
| local_cid, static_cast<uint16_t>(tL2CAP_CONN::L2CAP_CONN_OTHER_ERROR)); |
| bluetooth::os::CountCounterMetrics( |
| android::bluetooth::CodePathCounterKeyEnum::L2CAP_TIMEOUT_AT_CSM_CLOSED, 1); |
| break; |
| |
| case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ |
| case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ |
| osi_free(p_data); |
| break; |
| |
| case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper wants to disconnect */ |
| l2cu_release_ccb(p_ccb); |
| break; |
| |
| default: |
| log::error("Handling unexpected event:{}", l2c_csm_get_event_name(event)); |
| } |
| log::verbose("Exit chnl_state={} [{}], event={} [{}]", channel_state_text(p_ccb->chnl_state), |
| p_ccb->chnl_state, l2c_csm_get_event_name(event), event); |
| } |
| |
| /******************************************************************************* |
| * |
| * Function l2c_csm_orig_w4_sec_comp |
| * |
| * Description This function handles events when the channel is in |
| * CST_ORIG_W4_SEC_COMP state. |
| * |
| * Returns void |
| * |
| ******************************************************************************/ |
| static void l2c_csm_orig_w4_sec_comp(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data) { |
| tL2CA_DISCONNECT_IND_CB* disconnect_ind = p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb; |
| uint16_t local_cid = p_ccb->local_cid; |
| |
| log::debug("{} - LCID: 0x{:04x} st: ORIG_W4_SEC_COMP evt: {} psm: {}", |
| ((p_ccb->p_lcb) && (p_ccb->p_lcb->transport == BT_TRANSPORT_LE)) ? "LE " : "", |
| p_ccb->local_cid, l2c_csm_get_event_name(event), psm_to_text(p_ccb->p_rcb->psm)); |
| |
| switch (event) { |
| case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */ |
| log::debug("Calling Disconnect_Ind_Cb(), CID: 0x{:04x} No Conf Needed", p_ccb->local_cid); |
| l2cu_release_ccb(p_ccb); |
| (*disconnect_ind)(local_cid, false); |
| break; |
| |
| case L2CEVT_SEC_RE_SEND_CMD: /* BTM has enough info to proceed */ |
| case L2CEVT_LP_CONNECT_CFM: /* Link came up */ |
| if (p_ccb->p_lcb->transport == BT_TRANSPORT_LE) { |
| l2ble_sec_access_req(p_ccb->p_lcb->remote_bd_addr, p_ccb->p_rcb->psm, false, |
| &l2c_link_sec_comp, p_ccb); |
| } else { |
| btm_sec_l2cap_access_req(p_ccb->p_lcb->remote_bd_addr, p_ccb->p_rcb->psm, true, |
| &l2c_link_sec_comp, p_ccb); |
| } |
| break; |
| |
| case L2CEVT_SEC_COMP: /* Security completed success */ |
| /* Wait for the info resp in this state before sending connect req (if |
| * needed) */ |
| p_ccb->chnl_state = CST_W4_L2CAP_CONNECT_RSP; |
| if (p_ccb->p_lcb->transport == BT_TRANSPORT_LE) { |
| alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_CONNECT_TIMEOUT_MS, |
| l2c_ccb_timer_timeout, p_ccb); |
| l2cble_credit_based_conn_req(p_ccb); /* Start Connection */ |
| } else { |
| if (!p_ccb->p_lcb->w4_info_rsp) { |
| /* Need to have at least one compatible channel to continue */ |
| if (!l2c_fcr_chk_chan_modes(p_ccb)) { |
| l2cu_release_ccb(p_ccb); |
| (*p_ccb->p_rcb->api.pL2CA_Error_Cb)( |
| local_cid, static_cast<uint16_t>(tL2CAP_CONN::L2CAP_CONN_OTHER_ERROR)); |
| bluetooth::os::CountCounterMetrics(android::bluetooth::CodePathCounterKeyEnum:: |
| L2CAP_NO_COMPATIBLE_CHANNEL_AT_W4_SEC, |
| 1); |
| } else { |
| alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_CONNECT_TIMEOUT_MS, |
| l2c_ccb_timer_timeout, p_ccb); |
| l2cu_send_peer_connect_req(p_ccb); /* Start Connection */ |
| } |
| } |
| } |
| break; |
| |
| case L2CEVT_SEC_COMP_NEG: |
| /* If last channel immediately disconnect the ACL for better security. |
| Also prevents a race condition between BTM and L2CAP */ |
| if ((p_ccb == p_ccb->p_lcb->ccb_queue.p_first_ccb) && |
| (p_ccb == p_ccb->p_lcb->ccb_queue.p_last_ccb)) { |
| p_ccb->p_lcb->idle_timeout = 0; |
| } |
| |
| l2cu_release_ccb(p_ccb); |
| (*p_ccb->p_rcb->api.pL2CA_Error_Cb)( |
| local_cid, |
| static_cast<uint16_t>(tL2CAP_CONN::L2CAP_CONN_CLIENT_SECURITY_CLEARANCE_FAILED)); |
| bluetooth::os::CountCounterMetrics( |
| android::bluetooth::CodePathCounterKeyEnum::L2CAP_SECURITY_NEG_AT_W4_SEC, 1); |
| break; |
| |
| case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ |
| case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ |
| osi_free(p_data); |
| break; |
| |
| case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper wants to disconnect */ |
| /* Tell security manager to abort */ |
| btm_sec_abort_access_req(p_ccb->p_lcb->remote_bd_addr); |
| |
| l2cu_release_ccb(p_ccb); |
| break; |
| |
| default: |
| log::error("Handling unexpected event:{}", l2c_csm_get_event_name(event)); |
| } |
| log::verbose("Exit chnl_state={} [{}], event={} [{}]", channel_state_text(p_ccb->chnl_state), |
| p_ccb->chnl_state, l2c_csm_get_event_name(event), event); |
| } |
| |
| /******************************************************************************* |
| * |
| * Function l2c_csm_term_w4_sec_comp |
| * |
| * Description This function handles events when the channel is in |
| * CST_TERM_W4_SEC_COMP state. |
| * |
| * Returns void |
| * |
| ******************************************************************************/ |
| static void l2c_csm_term_w4_sec_comp(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data) { |
| log::debug("LCID: 0x{:04x} st: TERM_W4_SEC_COMP evt: {} psm: {}", p_ccb->local_cid, |
| l2c_csm_get_event_name(event), psm_to_text(p_ccb->p_rcb->psm)); |
| |
| switch (event) { |
| case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */ |
| /* Tell security manager to abort */ |
| btm_sec_abort_access_req(p_ccb->p_lcb->remote_bd_addr); |
| |
| l2cu_release_ccb(p_ccb); |
| break; |
| |
| case L2CEVT_SEC_COMP: |
| p_ccb->chnl_state = CST_W4_L2CA_CONNECT_RSP; |
| |
| /* Wait for the info resp in next state before sending connect ind (if |
| * needed) */ |
| if (!p_ccb->p_lcb->w4_info_rsp) { |
| log::debug("Not waiting for info response, sending connect response"); |
| /* Don't need to get info from peer or already retrieved so continue */ |
| alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_CONNECT_TIMEOUT_MS, |
| l2c_ccb_timer_timeout, p_ccb); |
| |
| if (p_ccb->p_lcb->transport != BT_TRANSPORT_LE) { |
| log::debug("Not LE connection, sending configure request"); |
| l2c_csm_send_connect_rsp(p_ccb); |
| #if (L2CAP_CONFORMANCE_TESTING == TRUE) |
| // TODO: when b/374014194 is solved on PTS side, revert change adding this delay. |
| alarm_set_on_mloop(p_ccb->pts_config_delay_timer, 5000, l2c_ccb_pts_delay_config_timeout, |
| p_ccb); |
| #else |
| l2c_csm_send_config_req(p_ccb); |
| #endif |
| } else { |
| if (p_ccb->ecoc) { |
| /* Handle Credit Based Connection */ |
| log::debug("Calling CreditBasedConnect_Ind_Cb(), num of cids: {}", |
| p_ccb->p_lcb->pending_ecoc_conn_cnt); |
| |
| std::vector<uint16_t> pending_cids; |
| for (int i = 0; i < p_ccb->p_lcb->pending_ecoc_conn_cnt; i++) { |
| uint16_t cid = p_ccb->p_lcb->pending_ecoc_connection_cids[i]; |
| if (cid != 0) { |
| pending_cids.push_back(cid); |
| } |
| } |
| |
| (*p_ccb->p_rcb->api.pL2CA_CreditBasedConnectInd_Cb)( |
| p_ccb->p_lcb->remote_bd_addr, pending_cids, p_ccb->p_rcb->psm, |
| p_ccb->peer_conn_cfg.mtu, p_ccb->remote_id); |
| } else { |
| /* Handle BLE CoC */ |
| log::debug("Calling Connect_Ind_Cb(), CID: 0x{:04x}", p_ccb->local_cid); |
| l2c_csm_send_connect_rsp(p_ccb); |
| l2c_csm_indicate_connection_open(p_ccb); |
| } |
| } |
| } else { |
| /* |
| ** L2CAP Connect Response will be sent out by 3 sec timer expiration |
| ** because Bluesoleil doesn't respond to L2CAP Information Request. |
| ** Bluesoleil seems to disconnect ACL link as failure case, because |
| ** it takes too long (4~7secs) to get response. |
| ** product version : Bluesoleil 2.1.1.0 EDR Release 060123 |
| ** stack version : 05.04.11.20060119 |
| */ |
| |
| /* Cancel ccb timer as security complete. waiting for w4_info_rsp |
| ** once info rsp received, connection rsp timer will be started |
| ** while sending connection ind to profiles |
| */ |
| alarm_cancel(p_ccb->l2c_ccb_timer); |
| |
| /* Waiting for the info resp, tell the peer to set a longer timer */ |
| log::debug("Waiting for info response, sending connect pending"); |
| l2cu_send_peer_connect_rsp(p_ccb, tL2CAP_CONN::L2CAP_CONN_PENDING, 0); |
| } |
| break; |
| |
| case L2CEVT_SEC_COMP_NEG: |
| if (((tL2C_CONN_INFO*)p_data)->hci_status == |
| static_cast<tHCI_STATUS>(tBTM_STATUS::BTM_DELAY_CHECK)) { |
| /* start a timer - encryption change not received before L2CAP connect |
| * req */ |
| alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_DELAY_CHECK_SM4_TIMEOUT_MS, |
| l2c_ccb_timer_timeout, p_ccb); |
| } else { |
| if (p_ccb->p_lcb->transport == BT_TRANSPORT_LE) { |
| l2cu_reject_ble_connection( |
| p_ccb, p_ccb->remote_id, |
| tL2CAP_LE_RESULT_CODE::L2CAP_LE_RESULT_INSUFFICIENT_AUTHENTICATION); |
| } else { |
| l2cu_send_peer_connect_rsp(p_ccb, tL2CAP_CONN::L2CAP_CONN_SECURITY_BLOCK, 0); |
| } |
| l2cu_release_ccb(p_ccb); |
| } |
| break; |
| |
| case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ |
| case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ |
| osi_free(p_data); |
| break; |
| |
| case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper wants to disconnect */ |
| l2cu_release_ccb(p_ccb); |
| break; |
| |
| case L2CEVT_L2CAP_DISCONNECT_REQ: /* Peer disconnected request */ |
| l2cu_send_peer_disc_rsp(p_ccb->p_lcb, p_ccb->remote_id, p_ccb->local_cid, p_ccb->remote_cid); |
| |
| /* Tell security manager to abort */ |
| btm_sec_abort_access_req(p_ccb->p_lcb->remote_bd_addr); |
| |
| l2cu_release_ccb(p_ccb); |
| break; |
| |
| case L2CEVT_TIMEOUT: |
| /* SM4 related. */ |
| acl_disconnect_from_handle(p_ccb->p_lcb->Handle(), HCI_ERR_AUTH_FAILURE, |
| "stack::l2cap::l2c_csm::l2c_csm_term_w4_sec_comp Event timeout"); |
| break; |
| |
| case L2CEVT_SEC_RE_SEND_CMD: /* BTM has enough info to proceed */ |
| btm_sec_l2cap_access_req(p_ccb->p_lcb->remote_bd_addr, p_ccb->p_rcb->psm, false, |
| &l2c_link_sec_comp, p_ccb); |
| break; |
| |
| default: |
| log::error("Handling unexpected event:{}", l2c_csm_get_event_name(event)); |
| } |
| log::verbose("Exit chnl_state={} [{}], event={} [{}]", channel_state_text(p_ccb->chnl_state), |
| p_ccb->chnl_state, l2c_csm_get_event_name(event), event); |
| } |
| |
| /******************************************************************************* |
| * |
| * Function l2c_csm_w4_l2cap_connect_rsp |
| * |
| * Description This function handles events when the channel is in |
| * CST_W4_L2CAP_CONNECT_RSP state. |
| * |
| * Returns void |
| * |
| ******************************************************************************/ |
| static void l2c_csm_w4_l2cap_connect_rsp(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data) { |
| tL2C_CONN_INFO* p_ci = (tL2C_CONN_INFO*)p_data; |
| tL2CA_DISCONNECT_IND_CB* disconnect_ind = p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb; |
| tL2CA_CREDIT_BASED_CONNECT_CFM_CB* credit_based_connect_cfm = |
| p_ccb->p_rcb->api.pL2CA_CreditBasedConnectCfm_Cb; |
| uint16_t local_cid = p_ccb->local_cid; |
| tL2C_LCB* p_lcb = p_ccb->p_lcb; |
| |
| log::debug("LCID: 0x{:04x} st: W4_L2CAP_CON_RSP evt: {} psm: {}", p_ccb->local_cid, |
| l2c_csm_get_event_name(event), psm_to_text(p_ccb->p_rcb->psm)); |
| |
| switch (event) { |
| case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */ |
| /* Send disc indication unless peer to peer race condition AND normal |
| * disconnect */ |
| /* *((uint8_t *)p_data) != HCI_ERR_PEER_USER happens when peer device try |
| * to disconnect for normal reason */ |
| p_ccb->chnl_state = CST_CLOSED; |
| if ((p_ccb->flags & CCB_FLAG_NO_RETRY) || !p_data || |
| (*((uint8_t*)p_data) != HCI_ERR_PEER_USER)) { |
| log::debug("Calling Disconnect_Ind_Cb(), CID: 0x{:04x} No Conf Needed", p_ccb->local_cid); |
| l2cu_release_ccb(p_ccb); |
| (*disconnect_ind)(local_cid, false); |
| } |
| p_ccb->flags |= CCB_FLAG_NO_RETRY; |
| break; |
| |
| case L2CEVT_L2CAP_CONNECT_RSP: /* Got peer connect confirm */ |
| p_ccb->remote_cid = p_ci->remote_cid; |
| if (p_ccb->p_lcb->transport == BT_TRANSPORT_LE) { |
| /* Connection is completed */ |
| alarm_cancel(p_ccb->l2c_ccb_timer); |
| p_ccb->chnl_state = CST_OPEN; |
| l2c_csm_indicate_connection_open(p_ccb); |
| p_ccb->local_conn_cfg = p_ccb->p_rcb->coc_cfg; |
| p_ccb->remote_credit_count = p_ccb->p_rcb->coc_cfg.credits; |
| l2c_csm_execute(p_ccb, L2CEVT_L2CA_CONNECT_RSP, NULL); |
| } else { |
| p_ccb->chnl_state = CST_CONFIG; |
| alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_CFG_TIMEOUT_MS, l2c_ccb_timer_timeout, |
| p_ccb); |
| } |
| log::debug("Calling Connect_Cfm_Cb(), CID: 0x{:04x}, Success", p_ccb->local_cid); |
| |
| l2c_csm_send_config_req(p_ccb); |
| break; |
| |
| case L2CEVT_L2CAP_CONNECT_RSP_PND: /* Got peer connect pending */ |
| p_ccb->remote_cid = p_ci->remote_cid; |
| alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_CONNECT_EXT_TIMEOUT_MS, |
| l2c_ccb_timer_timeout, p_ccb); |
| break; |
| |
| case L2CEVT_L2CAP_CREDIT_BASED_CONNECT_RSP: |
| alarm_cancel(p_ccb->l2c_ccb_timer); |
| p_ccb->chnl_state = CST_OPEN; |
| log::debug("Calling credit_based_connect_cfm(),cid {}, result:{}", p_ccb->local_cid, |
| l2cap_result_code_text(tL2CAP_CONN::L2CAP_CONN_OK)); |
| |
| (*credit_based_connect_cfm)(p_lcb->remote_bd_addr, p_ccb->local_cid, p_ci->peer_mtu, |
| tL2CAP_LE_RESULT_CODE::L2CAP_LE_RESULT_CONN_OK); |
| break; |
| |
| case L2CEVT_L2CAP_CREDIT_BASED_CONNECT_RSP_NEG: |
| log::debug("Calling pL2CA_Error_Cb(),cid {}, result 0x{:04x}", local_cid, p_ci->l2cap_result); |
| (*p_ccb->p_rcb->api.pL2CA_Error_Cb)(local_cid, static_cast<uint16_t>(p_ci->l2cap_result)); |
| bluetooth::os::CountCounterMetrics( |
| android::bluetooth::CodePathCounterKeyEnum::L2CAP_CREDIT_BASED_CONNECT_RSP_NEG, 1); |
| |
| l2cu_release_ccb(p_ccb); |
| break; |
| |
| case L2CEVT_L2CAP_CONNECT_RSP_NEG: /* Peer rejected connection */ |
| log::warn("L2CAP connection rejected, lcid=0x{:x}, reason=0x{:x}", p_ccb->local_cid, |
| p_ci->l2cap_result); |
| l2cu_release_ccb(p_ccb); |
| if (p_lcb->transport == BT_TRANSPORT_LE) { |
| (*p_ccb->p_rcb->api.pL2CA_Error_Cb)(local_cid, static_cast<uint16_t>(p_ci->l2cap_result)); |
| } else { |
| (*p_ccb->p_rcb->api.pL2CA_Error_Cb)( |
| local_cid, static_cast<uint16_t>(tL2CAP_CONN::L2CAP_CONN_OTHER_ERROR)); |
| } |
| bluetooth::os::CountCounterMetrics( |
| android::bluetooth::CodePathCounterKeyEnum::L2CAP_CONNECT_RSP_NEG, 1); |
| break; |
| |
| case L2CEVT_TIMEOUT: |
| log::warn("L2CAP connection timeout"); |
| |
| if (p_ccb->ecoc) { |
| for (int i = 0; i < p_lcb->pending_ecoc_conn_cnt; i++) { |
| uint16_t cid = p_lcb->pending_ecoc_connection_cids[i]; |
| tL2C_CCB* temp_p_ccb = l2cu_find_ccb_by_cid(p_lcb, cid); |
| log::warn("lcid= 0x{:x}", cid); |
| (*p_ccb->p_rcb->api.pL2CA_Error_Cb)( |
| p_ccb->local_cid, static_cast<uint16_t>(tL2CAP_CONN::L2CAP_CONN_TIMEOUT)); |
| bluetooth::os::CountCounterMetrics( |
| android::bluetooth::CodePathCounterKeyEnum::L2CAP_TIMEOUT_AT_CONNECT_RSP, 1); |
| l2cu_release_ccb(temp_p_ccb); |
| } |
| p_lcb->pending_ecoc_conn_cnt = 0; |
| memset(p_lcb->pending_ecoc_connection_cids, 0, L2CAP_CREDIT_BASED_MAX_CIDS); |
| |
| } else { |
| log::warn("lcid= 0x{:x}", p_ccb->local_cid); |
| l2cu_release_ccb(p_ccb); |
| (*p_ccb->p_rcb->api.pL2CA_Error_Cb)( |
| local_cid, static_cast<uint16_t>(tL2CAP_CONN::L2CAP_CONN_OTHER_ERROR)); |
| bluetooth::os::CountCounterMetrics( |
| android::bluetooth::CodePathCounterKeyEnum::L2CAP_CONN_OTHER_ERROR_AT_CONNECT_RSP, |
| 1); |
| } |
| break; |
| |
| case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper wants to disconnect */ |
| /* If we know peer CID from connect pending, we can send disconnect */ |
| if (p_ccb->remote_cid != 0) { |
| l2cu_send_peer_disc_req(p_ccb); |
| p_ccb->chnl_state = CST_W4_L2CAP_DISCONNECT_RSP; |
| alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_DISCONNECT_TIMEOUT_MS, |
| l2c_ccb_timer_timeout, p_ccb); |
| } else { |
| tL2CA_DISCONNECT_CFM_CB* disconnect_cfm = p_ccb->p_rcb->api.pL2CA_DisconnectCfm_Cb; |
| l2cu_release_ccb(p_ccb); |
| if (disconnect_cfm != nullptr) { |
| (*disconnect_cfm)(local_cid, static_cast<uint16_t>(tL2CAP_CONN::L2CAP_CONN_NO_LINK)); |
| } |
| } |
| break; |
| |
| case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ |
| case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ |
| osi_free(p_data); |
| break; |
| |
| case L2CEVT_L2CAP_INFO_RSP: |
| /* Need to have at least one compatible channel to continue */ |
| if (!l2c_fcr_chk_chan_modes(p_ccb)) { |
| l2cu_release_ccb(p_ccb); |
| (*p_ccb->p_rcb->api.pL2CA_Error_Cb)( |
| local_cid, static_cast<uint16_t>(tL2CAP_CONN::L2CAP_CONN_OTHER_ERROR)); |
| bluetooth::os::CountCounterMetrics( |
| android::bluetooth::CodePathCounterKeyEnum::L2CAP_INFO_NO_COMPATIBLE_CHANNEL_AT_RSP, |
| 1); |
| } else { |
| /* We have feature info, so now send peer connect request */ |
| alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_CONNECT_TIMEOUT_MS, |
| l2c_ccb_timer_timeout, p_ccb); |
| l2cu_send_peer_connect_req(p_ccb); /* Start Connection */ |
| } |
| break; |
| |
| default: |
| log::error("Handling unexpected event:{}", l2c_csm_get_event_name(event)); |
| } |
| log::verbose("Exit chnl_state={} [{}], event={} [{}]", channel_state_text(p_ccb->chnl_state), |
| p_ccb->chnl_state, l2c_csm_get_event_name(event), event); |
| } |
| |
| /******************************************************************************* |
| * |
| * Function l2c_csm_w4_l2ca_connect_rsp |
| * |
| * Description This function handles events when the channel is in |
| * CST_W4_L2CA_CONNECT_RSP state. |
| * |
| * Returns void |
| * |
| ******************************************************************************/ |
| static void l2c_csm_w4_l2ca_connect_rsp(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data) { |
| tL2C_CONN_INFO* p_ci; |
| tL2C_LCB* p_lcb = p_ccb->p_lcb; |
| tL2CA_DISCONNECT_IND_CB* disconnect_ind = p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb; |
| uint16_t local_cid = p_ccb->local_cid; |
| |
| log::debug("LCID: 0x{:04x} st: W4_L2CA_CON_RSP evt: {} psm: {}", p_ccb->local_cid, |
| l2c_csm_get_event_name(event), psm_to_text(p_ccb->p_rcb->psm)); |
| |
| switch (event) { |
| case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */ |
| log::debug("Calling Disconnect_Ind_Cb(), CID: 0x{:04x} No Conf Needed", p_ccb->local_cid); |
| l2cu_release_ccb(p_ccb); |
| (*disconnect_ind)(local_cid, false); |
| break; |
| |
| case L2CEVT_L2CA_CREDIT_BASED_CONNECT_RSP: |
| p_ci = (tL2C_CONN_INFO*)p_data; |
| if ((p_lcb == nullptr) || (p_lcb && p_lcb->transport != BT_TRANSPORT_LE)) { |
| log::warn("LE link doesn't exist"); |
| return; |
| } |
| l2cu_send_peer_credit_based_conn_res(p_ccb, p_ci->lcids, |
| static_cast<tL2CAP_LE_RESULT_CODE>(p_ci->l2cap_result)); |
| alarm_cancel(p_ccb->l2c_ccb_timer); |
| |
| for (int i = 0; i < p_lcb->pending_ecoc_conn_cnt; i++) { |
| uint16_t cid = p_lcb->pending_ecoc_connection_cids[i]; |
| if (cid == 0) { |
| log::warn("pending_ecoc_connection_cids[{}] is {}", i, cid); |
| continue; |
| } |
| |
| tL2C_CCB* temp_p_ccb = l2cu_find_ccb_by_cid(p_lcb, cid); |
| if (temp_p_ccb) { |
| auto it = std::find(p_ci->lcids.begin(), p_ci->lcids.end(), cid); |
| if (it != p_ci->lcids.end()) { |
| temp_p_ccb->chnl_state = CST_OPEN; |
| } else { |
| l2cu_release_ccb(temp_p_ccb); |
| } |
| } else { |
| log::warn("temp_p_ccb is NULL, pending_ecoc_connection_cids[{}] is {}", i, cid); |
| } |
| } |
| p_lcb->pending_ecoc_conn_cnt = 0; |
| memset(p_lcb->pending_ecoc_connection_cids, 0, L2CAP_CREDIT_BASED_MAX_CIDS); |
| |
| break; |
| case L2CEVT_L2CA_CONNECT_RSP: |
| p_ci = (tL2C_CONN_INFO*)p_data; |
| if (p_ccb->p_lcb->transport == BT_TRANSPORT_LE) { |
| /* Result should be OK or Reject */ |
| if ((!p_ci) || (p_ci->l2cap_result == tL2CAP_CONN::L2CAP_CONN_OK)) { |
| l2cble_credit_based_conn_res(p_ccb, tL2CAP_LE_RESULT_CODE::L2CAP_LE_RESULT_CONN_OK); |
| p_ccb->chnl_state = CST_OPEN; |
| alarm_cancel(p_ccb->l2c_ccb_timer); |
| } else { |
| l2cble_credit_based_conn_res(p_ccb, |
| static_cast<tL2CAP_LE_RESULT_CODE>(p_ci->l2cap_result)); |
| l2cu_release_ccb(p_ccb); |
| } |
| } else { |
| /* Result should be OK or PENDING */ |
| if ((!p_ci) || (p_ci->l2cap_result == tL2CAP_CONN::L2CAP_CONN_OK)) { |
| log::debug("Sending connection ok for BR_EDR"); |
| l2cu_send_peer_connect_rsp(p_ccb, tL2CAP_CONN::L2CAP_CONN_OK, 0); |
| p_ccb->chnl_state = CST_CONFIG; |
| alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_CFG_TIMEOUT_MS, l2c_ccb_timer_timeout, |
| p_ccb); |
| } else { |
| /* If pending, stay in same state and start extended timer */ |
| log::debug("Sending connection result {} and status {}", p_ci->l2cap_result, |
| p_ci->l2cap_status); |
| l2cu_send_peer_connect_rsp(p_ccb, p_ci->l2cap_result, p_ci->l2cap_status); |
| alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_CONNECT_EXT_TIMEOUT_MS, |
| l2c_ccb_timer_timeout, p_ccb); |
| } |
| } |
| break; |
| |
| case L2CEVT_L2CA_CREDIT_BASED_CONNECT_RSP_NEG: |
| p_ci = (tL2C_CONN_INFO*)p_data; |
| alarm_cancel(p_ccb->l2c_ccb_timer); |
| if (p_lcb != nullptr) { |
| if (p_lcb->transport == BT_TRANSPORT_LE) { |
| l2cu_send_peer_credit_based_conn_res( |
| p_ccb, p_ci->lcids, static_cast<tL2CAP_LE_RESULT_CODE>(p_ci->l2cap_result)); |
| } |
| for (int i = 0; i < p_lcb->pending_ecoc_conn_cnt; i++) { |
| uint16_t cid = p_lcb->pending_ecoc_connection_cids[i]; |
| tL2C_CCB* temp_p_ccb = l2cu_find_ccb_by_cid(p_lcb, cid); |
| l2cu_release_ccb(temp_p_ccb); |
| } |
| |
| p_lcb->pending_ecoc_conn_cnt = 0; |
| memset(p_lcb->pending_ecoc_connection_cids, 0, L2CAP_CREDIT_BASED_MAX_CIDS); |
| } |
| break; |
| case L2CEVT_L2CA_CONNECT_RSP_NEG: |
| p_ci = (tL2C_CONN_INFO*)p_data; |
| if (p_ccb->p_lcb->transport == BT_TRANSPORT_LE) { |
| l2cble_credit_based_conn_res(p_ccb, static_cast<tL2CAP_LE_RESULT_CODE>(p_ci->l2cap_result)); |
| } else { |
| l2cu_send_peer_connect_rsp(p_ccb, p_ci->l2cap_result, p_ci->l2cap_status); |
| } |
| l2cu_release_ccb(p_ccb); |
| break; |
| |
| case L2CEVT_TIMEOUT: |
| l2cu_send_peer_connect_rsp(p_ccb, tL2CAP_CONN::L2CAP_CONN_NO_PSM, 0); |
| log::debug("Calling Disconnect_Ind_Cb(), CID: 0x{:04x} No Conf Needed", p_ccb->local_cid); |
| l2cu_release_ccb(p_ccb); |
| (*disconnect_ind)(local_cid, false); |
| break; |
| |
| case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ |
| case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ |
| osi_free(p_data); |
| break; |
| |
| case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper wants to disconnect */ |
| l2cu_send_peer_disc_req(p_ccb); |
| p_ccb->chnl_state = CST_W4_L2CAP_DISCONNECT_RSP; |
| alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_DISCONNECT_TIMEOUT_MS, |
| l2c_ccb_timer_timeout, p_ccb); |
| break; |
| |
| case L2CEVT_L2CAP_INFO_RSP: |
| /* We have feature info, so now give the upper layer connect IND */ |
| alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_CONNECT_TIMEOUT_MS, l2c_ccb_timer_timeout, |
| p_ccb); |
| log::debug("Calling Connect_Ind_Cb(), CID: 0x{:04x}", p_ccb->local_cid); |
| |
| l2c_csm_send_connect_rsp(p_ccb); |
| l2c_csm_send_config_req(p_ccb); |
| break; |
| default: |
| log::error("Handling unexpected event:{}", l2c_csm_get_event_name(event)); |
| } |
| log::verbose("Exit chnl_state={} [{}], event={} [{}]", channel_state_text(p_ccb->chnl_state), |
| p_ccb->chnl_state, l2c_csm_get_event_name(event), event); |
| } |
| |
| /******************************************************************************* |
| * |
| * Function l2c_csm_config |
| * |
| * Description This function handles events when the channel is in |
| * CONFIG state. |
| * |
| * Returns void |
| * |
| ******************************************************************************/ |
| static void l2c_csm_config(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data) { |
| tL2CAP_CFG_INFO* p_cfg = (tL2CAP_CFG_INFO*)p_data; |
| tL2CA_DISCONNECT_IND_CB* disconnect_ind = p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb; |
| uint16_t local_cid = p_ccb->local_cid; |
| uint8_t cfg_result; |
| tL2C_LCB* p_lcb = p_ccb->p_lcb; |
| tL2C_CCB* temp_p_ccb; |
| tL2CAP_LE_CFG_INFO* p_le_cfg = (tL2CAP_LE_CFG_INFO*)p_data; |
| |
| log::debug("LCID: 0x{:04x} st: CONFIG evt: {} psm: {}", p_ccb->local_cid, |
| l2c_csm_get_event_name(event), psm_to_text(p_ccb->p_rcb->psm)); |
| |
| switch (event) { |
| case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */ |
| log::debug("Calling Disconnect_Ind_Cb(), CID: 0x{:04x} No Conf Needed", p_ccb->local_cid); |
| l2cu_release_ccb(p_ccb); |
| (*disconnect_ind)(local_cid, false); |
| break; |
| |
| case L2CEVT_L2CAP_CREDIT_BASED_RECONFIG_REQ: |
| /* For ecoc reconfig is handled below in l2c_ble. In case of success |
| * let us notify upper layer about the reconfig |
| */ |
| log::debug("Calling LeReconfigCompleted_Cb(), CID: 0x{:04x}", p_ccb->local_cid); |
| |
| (*p_ccb->p_rcb->api.pL2CA_CreditBasedReconfigCompleted_Cb)(p_lcb->remote_bd_addr, |
| p_ccb->local_cid, false, p_le_cfg); |
| break; |
| case L2CEVT_L2CAP_CONFIG_REQ: /* Peer config request */ |
| cfg_result = l2cu_process_peer_cfg_req(p_ccb, p_cfg); |
| if (cfg_result == L2CAP_PEER_CFG_OK) { |
| log::debug("Calling Config_Req_Cb(), CID: 0x{:04x}, C-bit {}", p_ccb->local_cid, |
| p_cfg->flags & L2CAP_CFG_FLAGS_MASK_CONT); |
| l2c_csm_send_config_rsp_ok(p_ccb, p_cfg->flags & L2CAP_CFG_FLAGS_MASK_CONT); |
| if (p_ccb->config_done & OB_CFG_DONE) { |
| if (p_ccb->remote_config_rsp_result == tL2CAP_CFG_RESULT::L2CAP_CFG_OK) { |
| l2c_csm_indicate_connection_open(p_ccb); |
| } else { |
| if (p_ccb->connection_initiator == L2CAP_INITIATOR_LOCAL) { |
| (*p_ccb->p_rcb->api.pL2CA_Error_Cb)( |
| p_ccb->local_cid, |
| static_cast<uint16_t>(tL2CAP_CFG_RESULT::L2CAP_CFG_FAILED_NO_REASON)); |
| bluetooth::os::CountCounterMetrics( |
| android::bluetooth::CodePathCounterKeyEnum::L2CAP_CONFIG_REQ_FAILURE, 1); |
| } |
| } |
| } |
| } else if (cfg_result == L2CAP_PEER_CFG_DISCONNECT) { |
| /* Disconnect if channels are incompatible */ |
| log::debug("incompatible configurations disconnect"); |
| l2cu_disconnect_chnl(p_ccb); |
| } else /* Return error to peer so it can renegotiate if possible */ |
| { |
| log::debug("incompatible configurations trying reconfig"); |
| l2cu_send_peer_config_rsp(p_ccb, p_cfg); |
| } |
| break; |
| |
| case L2CEVT_L2CAP_CREDIT_BASED_RECONFIG_RSP: |
| p_ccb->config_done |= OB_CFG_DONE; |
| p_ccb->config_done |= RECONFIG_FLAG; |
| p_ccb->chnl_state = CST_OPEN; |
| alarm_cancel(p_ccb->l2c_ccb_timer); |
| |
| log::debug("Calling Config_Rsp_Cb(), CID: 0x{:04x}", p_ccb->local_cid); |
| |
| p_ccb->p_rcb->api.pL2CA_CreditBasedReconfigCompleted_Cb(p_lcb->remote_bd_addr, |
| p_ccb->local_cid, true, p_le_cfg); |
| |
| break; |
| case L2CEVT_L2CAP_CONFIG_RSP: /* Peer config response */ |
| l2cu_process_peer_cfg_rsp(p_ccb, p_cfg); |
| |
| /* TBD: When config options grow beyond minimum MTU (48 bytes) |
| * logic needs to be added to handle responses with |
| * continuation bit set in flags field. |
| * 1. Send additional config request out until C-bit is cleared in |
| * response |
| */ |
| p_ccb->config_done |= OB_CFG_DONE; |
| |
| if (p_ccb->config_done & IB_CFG_DONE) { |
| /* Verify two sides are in compatible modes before continuing */ |
| if (p_ccb->our_cfg.fcr.mode != p_ccb->peer_cfg.fcr.mode) { |
| l2cu_send_peer_disc_req(p_ccb); |
| log::warn( |
| "Calling Disconnect_Ind_Cb(Incompatible CFG), CID: 0x{:04x} No " |
| "Conf Needed", |
| p_ccb->local_cid); |
| l2cu_release_ccb(p_ccb); |
| (*disconnect_ind)(local_cid, false); |
| break; |
| } |
| |
| p_ccb->config_done |= RECONFIG_FLAG; |
| p_ccb->chnl_state = CST_OPEN; |
| l2c_link_adjust_chnl_allocation(); |
| alarm_cancel(p_ccb->l2c_ccb_timer); |
| |
| /* If using eRTM and waiting for an ACK, restart the ACK timer */ |
| if (p_ccb->fcrb.wait_ack) { |
| l2c_fcr_start_timer(p_ccb); |
| } |
| |
| /* |
| ** check p_ccb->our_cfg.fcr.mon_tout and |
| *p_ccb->our_cfg.fcr.rtrans_tout |
| ** we may set them to zero when sending config request during |
| *renegotiation |
| */ |
| if ((p_ccb->our_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) && |
| ((p_ccb->our_cfg.fcr.mon_tout == 0) || (p_ccb->our_cfg.fcr.rtrans_tout))) { |
| l2c_fcr_adj_monitor_retran_timeout(p_ccb); |
| } |
| |
| /* See if we can forward anything on the hold queue */ |
| if (!fixed_queue_is_empty(p_ccb->xmit_hold_q)) { |
| l2c_link_check_send_pkts(p_ccb->p_lcb, 0, NULL); |
| } |
| } |
| |
| if (p_ccb->config_done & RECONFIG_FLAG) { |
| // Notify only once |
| bluetooth::shim::GetSnoopLogger()->SetL2capChannelOpen( |
| p_ccb->p_lcb->Handle(), p_ccb->local_cid, p_ccb->remote_cid, p_ccb->p_rcb->psm, |
| p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_BASIC_MODE); |
| } |
| |
| log::debug("Calling Config_Rsp_Cb(), CID: 0x{:04x}", p_ccb->local_cid); |
| p_ccb->remote_config_rsp_result = p_cfg->result; |
| if (p_ccb->config_done & IB_CFG_DONE) { |
| l2c_csm_indicate_connection_open(p_ccb); |
| } |
| break; |
| |
| case L2CEVT_L2CAP_CONFIG_RSP_NEG: /* Peer config error rsp */ |
| /* Disable the Timer */ |
| alarm_cancel(p_ccb->l2c_ccb_timer); |
| |
| /* If failure was channel mode try to renegotiate */ |
| if (!l2c_fcr_renegotiate_chan(p_ccb, p_cfg)) { |
| log::debug("Calling Config_Rsp_Cb(), CID: 0x{:04x}, cfg_result:{}", p_ccb->local_cid, |
| l2cap_cfg_result_text(p_cfg->result)); |
| if (p_ccb->connection_initiator == L2CAP_INITIATOR_LOCAL) { |
| (*p_ccb->p_rcb->api.pL2CA_Error_Cb)( |
| p_ccb->local_cid, |
| static_cast<uint16_t>(tL2CAP_CFG_RESULT::L2CAP_CFG_FAILED_NO_REASON)); |
| bluetooth::os::CountCounterMetrics( |
| android::bluetooth::CodePathCounterKeyEnum::L2CAP_CONFIG_RSP_NEG, 1); |
| } |
| } |
| break; |
| |
| case L2CEVT_L2CAP_DISCONNECT_REQ: /* Peer disconnected request */ |
| alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_DISCONNECT_TIMEOUT_MS, |
| l2c_ccb_timer_timeout, p_ccb); |
| p_ccb->chnl_state = CST_W4_L2CA_DISCONNECT_RSP; |
| log::debug("Calling Disconnect_Ind_Cb(), CID: 0x{:04x} Conf Needed", p_ccb->local_cid); |
| (*p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb)(p_ccb->local_cid, true); |
| l2c_csm_send_disconnect_rsp(p_ccb); |
| break; |
| |
| case L2CEVT_L2CA_CREDIT_BASED_RECONFIG_REQ: |
| l2cu_send_credit_based_reconfig_req(p_ccb, (tL2CAP_LE_CFG_INFO*)p_data); |
| alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_CFG_TIMEOUT_MS, l2c_ccb_timer_timeout, |
| p_ccb); |
| break; |
| case L2CEVT_L2CA_CONFIG_REQ: /* Upper layer config req */ |
| l2cu_process_our_cfg_req(p_ccb, p_cfg); |
| l2cu_send_peer_config_req(p_ccb, p_cfg); |
| alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_CFG_TIMEOUT_MS, l2c_ccb_timer_timeout, |
| p_ccb); |
| break; |
| |
| case L2CEVT_L2CA_CONFIG_RSP: /* Upper layer config rsp */ |
| l2cu_process_our_cfg_rsp(p_ccb, p_cfg); |
| |
| p_ccb->config_done |= IB_CFG_DONE; |
| |
| if (p_ccb->config_done & OB_CFG_DONE) { |
| /* Verify two sides are in compatible modes before continuing */ |
| if (p_ccb->our_cfg.fcr.mode != p_ccb->peer_cfg.fcr.mode) { |
| l2cu_send_peer_disc_req(p_ccb); |
| log::warn( |
| "Calling Disconnect_Ind_Cb(Incompatible CFG), CID: 0x{:04x} No " |
| "Conf Needed", |
| p_ccb->local_cid); |
| l2cu_release_ccb(p_ccb); |
| (*disconnect_ind)(local_cid, false); |
| break; |
| } |
| |
| p_ccb->config_done |= RECONFIG_FLAG; |
| p_ccb->chnl_state = CST_OPEN; |
| l2c_link_adjust_chnl_allocation(); |
| alarm_cancel(p_ccb->l2c_ccb_timer); |
| } |
| |
| l2cu_send_peer_config_rsp(p_ccb, p_cfg); |
| |
| /* If using eRTM and waiting for an ACK, restart the ACK timer */ |
| if (p_ccb->fcrb.wait_ack) { |
| l2c_fcr_start_timer(p_ccb); |
| } |
| |
| if (p_ccb->config_done & RECONFIG_FLAG) { |
| // Notify only once |
| bluetooth::shim::GetSnoopLogger()->SetL2capChannelOpen( |
| p_ccb->p_lcb->Handle(), p_ccb->local_cid, p_ccb->remote_cid, p_ccb->p_rcb->psm, |
| p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_BASIC_MODE); |
| } |
| |
| /* See if we can forward anything on the hold queue */ |
| if ((p_ccb->chnl_state == CST_OPEN) && (!fixed_queue_is_empty(p_ccb->xmit_hold_q))) { |
| l2c_link_check_send_pkts(p_ccb->p_lcb, 0, NULL); |
| } |
| break; |
| |
| case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper wants to disconnect */ |
| l2cu_send_peer_disc_req(p_ccb); |
| p_ccb->chnl_state = CST_W4_L2CAP_DISCONNECT_RSP; |
| alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_DISCONNECT_TIMEOUT_MS, |
| l2c_ccb_timer_timeout, p_ccb); |
| break; |
| |
| case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ |
| log::debug("Calling DataInd_Cb(), CID: 0x{:04x}", p_ccb->local_cid); |
| if (p_ccb->local_cid >= L2CAP_FIRST_FIXED_CHNL && p_ccb->local_cid <= L2CAP_LAST_FIXED_CHNL) { |
| if (p_ccb->local_cid < L2CAP_BASE_APPL_CID) { |
| if (l2cb.fixed_reg[p_ccb->local_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb != |
| nullptr) { |
| p_ccb->metrics.rx(static_cast<BT_HDR*>(p_data)->len); |
| l2cu_fixed_channel_data_cb(p_lcb, p_ccb->local_cid, reinterpret_cast<BT_HDR*>(p_data)); |
| } else { |
| if (p_data != nullptr) { |
| osi_free_and_reset(&p_data); |
| } |
| } |
| break; |
| } |
| } |
| if (p_data) { |
| p_ccb->metrics.rx(static_cast<BT_HDR*>(p_data)->len); |
| } |
| (*p_ccb->p_rcb->api.pL2CA_DataInd_Cb)(p_ccb->local_cid, (BT_HDR*)p_data); |
| break; |
| |
| case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ |
| if (p_ccb->config_done & OB_CFG_DONE) { |
| l2c_enqueue_peer_data(p_ccb, (BT_HDR*)p_data); |
| } else { |
| osi_free(p_data); |
| } |
| break; |
| |
| case L2CEVT_TIMEOUT: |
| if (p_ccb->ecoc) { |
| for (temp_p_ccb = p_lcb->ccb_queue.p_first_ccb; temp_p_ccb; |
| temp_p_ccb = temp_p_ccb->p_next_ccb) { |
| if ((temp_p_ccb->in_use) && (temp_p_ccb->reconfig_started)) { |
| (*temp_p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb)(temp_p_ccb->local_cid, false); |
| l2cu_release_ccb(temp_p_ccb); |
| } |
| } |
| |
| acl_disconnect_from_handle(p_ccb->p_lcb->Handle(), HCI_ERR_CONN_CAUSE_LOCAL_HOST, |
| "stack::l2cap::l2c_csm::l2c_csm_config timeout"); |
| return; |
| } |
| |
| l2cu_send_peer_disc_req(p_ccb); |
| log::debug("Calling Disconnect_Ind_Cb(), CID: 0x{:04x} No Conf Needed", p_ccb->local_cid); |
| l2cu_release_ccb(p_ccb); |
| (*disconnect_ind)(local_cid, false); |
| break; |
| default: |
| log::error("Handling unexpected event:{}", l2c_csm_get_event_name(event)); |
| } |
| log::verbose("Exit chnl_state={} [{}], event={} [{}]", channel_state_text(p_ccb->chnl_state), |
| p_ccb->chnl_state, l2c_csm_get_event_name(event), event); |
| } |
| |
| /******************************************************************************* |
| * |
| * Function l2c_csm_open |
| * |
| * Description This function handles events when the channel is in |
| * OPEN state. |
| * |
| * Returns void |
| * |
| ******************************************************************************/ |
| static void l2c_csm_open(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data) { |
| uint16_t local_cid = p_ccb->local_cid; |
| tL2CAP_CFG_INFO* p_cfg; |
| tL2C_CHNL_STATE tempstate; |
| uint8_t tempcfgdone; |
| uint8_t cfg_result = L2CAP_PEER_CFG_DISCONNECT; |
| uint16_t credit = 0; |
| tL2CAP_LE_CFG_INFO* p_le_cfg = (tL2CAP_LE_CFG_INFO*)p_data; |
| |
| log::verbose("LCID: 0x{:04x} st: OPEN evt: {} psm: {}", p_ccb->local_cid, |
| l2c_csm_get_event_name(event), psm_to_text(p_ccb->p_rcb->psm)); |
| |
| switch (event) { |
| case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */ |
| log::debug("Calling Disconnect_Ind_Cb(), CID: 0x{:04x} No Conf Needed", p_ccb->local_cid); |
| power_telemetry::GetInstance().LogChannelDisconnected( |
| p_ccb->p_rcb->psm, p_ccb->local_cid, p_ccb->remote_id, p_ccb->p_lcb->remote_bd_addr); |
| l2cu_release_ccb(p_ccb); |
| if (p_ccb->p_rcb) { |
| (*p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb)(local_cid, false); |
| } |
| break; |
| |
| case L2CEVT_L2CAP_CREDIT_BASED_RECONFIG_REQ: |
| /* For ecoc reconfig is handled below in l2c_ble. In case of success |
| * let us notify upper layer about the reconfig |
| */ |
| if (p_le_cfg) { |
| log::debug("Calling LeReconfigCompleted_Cb(), CID: 0x{:04x}", p_ccb->local_cid); |
| (*p_ccb->p_rcb->api.pL2CA_CreditBasedReconfigCompleted_Cb)( |
| p_ccb->p_lcb->remote_bd_addr, p_ccb->local_cid, false, p_le_cfg); |
| } |
| break; |
| |
| case L2CEVT_L2CAP_CONFIG_REQ: /* Peer config request */ |
| p_cfg = (tL2CAP_CFG_INFO*)p_data; |
| |
| tempstate = p_ccb->chnl_state; |
| tempcfgdone = p_ccb->config_done; |
| p_ccb->chnl_state = CST_CONFIG; |
| // clear cached configuration in case reconfig takes place later |
| p_ccb->peer_cfg.mtu_present = false; |
| p_ccb->peer_cfg.flush_to_present = false; |
| p_ccb->peer_cfg.qos_present = false; |
| p_ccb->config_done &= ~IB_CFG_DONE; |
| |
| alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_CFG_TIMEOUT_MS, l2c_ccb_timer_timeout, |
| p_ccb); |
| if (p_cfg) { |
| cfg_result = l2cu_process_peer_cfg_req(p_ccb, p_cfg); |
| } |
| if (cfg_result == L2CAP_PEER_CFG_OK) { |
| (*p_ccb->p_rcb->api.pL2CA_ConfigInd_Cb)(p_ccb->local_cid, p_cfg); |
| l2c_csm_send_config_rsp_ok(p_ccb, p_cfg->flags & L2CAP_CFG_FLAGS_MASK_CONT); |
| } else if (cfg_result == L2CAP_PEER_CFG_UNACCEPTABLE) { |
| /* Error in config parameters: reset state and config flag */ |
| alarm_cancel(p_ccb->l2c_ccb_timer); |
| p_ccb->chnl_state = tempstate; |
| p_ccb->config_done = tempcfgdone; |
| l2cu_send_peer_config_rsp(p_ccb, p_cfg); |
| } else { |
| /* L2CAP_PEER_CFG_DISCONNECT */ |
| /* Disconnect if channels are incompatible |
| * Note this should not occur if reconfigure |
| * since this should have never passed original config. |
| */ |
| l2cu_disconnect_chnl(p_ccb); |
| } |
| break; |
| |
| case L2CEVT_L2CAP_DISCONNECT_REQ: /* Peer disconnected request */ |
| if (p_ccb->p_lcb->transport != BT_TRANSPORT_LE) { |
| if (!BTM_SetLinkPolicyActiveMode(p_ccb->p_lcb->remote_bd_addr)) { |
| log::warn("Unable to set link policy active"); |
| } |
| } |
| |
| p_ccb->chnl_state = CST_W4_L2CA_DISCONNECT_RSP; |
| alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_DISCONNECT_TIMEOUT_MS, |
| l2c_ccb_timer_timeout, p_ccb); |
| log::debug("Calling Disconnect_Ind_Cb(), CID: 0x{:04x} Conf Needed", p_ccb->local_cid); |
| power_telemetry::GetInstance().LogChannelDisconnected( |
| p_ccb->p_rcb->psm, p_ccb->local_cid, p_ccb->remote_id, p_ccb->p_lcb->remote_bd_addr); |
| (*p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb)(p_ccb->local_cid, true); |
| l2c_csm_send_disconnect_rsp(p_ccb); |
| break; |
| |
| case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ |
| if (p_data && (p_ccb->p_rcb)) { |
| uint16_t package_len = ((BT_HDR*)p_data)->len; |
| if (p_ccb->p_rcb->api.pL2CA_DataInd_Cb) { |
| p_ccb->metrics.rx(static_cast<BT_HDR*>(p_data)->len); |
| (*p_ccb->p_rcb->api.pL2CA_DataInd_Cb)(p_ccb->local_cid, (BT_HDR*)p_data); |
| } |
| |
| power_telemetry::GetInstance().LogRxBytes(p_ccb->p_rcb->psm, p_ccb->local_cid, |
| p_ccb->remote_id, p_ccb->p_lcb->remote_bd_addr, |
| package_len); |
| } |
| break; |
| |
| case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper wants to disconnect */ |
| if (p_ccb->p_lcb->transport != BT_TRANSPORT_LE) { |
| /* Make sure we are not in sniff mode */ |
| if (!BTM_SetLinkPolicyActiveMode(p_ccb->p_lcb->remote_bd_addr)) { |
| log::warn("Unable to set link policy active"); |
| } |
| } |
| power_telemetry::GetInstance().LogChannelDisconnected( |
| p_ccb->p_rcb->psm, p_ccb->local_cid, p_ccb->remote_id, p_ccb->p_lcb->remote_bd_addr); |
| if (p_ccb->p_lcb->transport == BT_TRANSPORT_LE) { |
| l2cble_send_peer_disc_req(p_ccb); |
| } else { |
| l2cu_send_peer_disc_req(p_ccb); |
| } |
| |
| p_ccb->chnl_state = CST_W4_L2CAP_DISCONNECT_RSP; |
| alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_DISCONNECT_TIMEOUT_MS, |
| l2c_ccb_timer_timeout, p_ccb); |
| break; |
| |
| case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ |
| if (p_data) { |
| uint16_t package_len = ((BT_HDR*)p_data)->len; |
| l2c_enqueue_peer_data(p_ccb, (BT_HDR*)p_data); |
| l2c_link_check_send_pkts(p_ccb->p_lcb, 0, NULL); |
| power_telemetry::GetInstance().LogTxBytes(p_ccb->p_rcb->psm, p_ccb->local_cid, |
| p_ccb->remote_id, p_ccb->p_lcb->remote_bd_addr, |
| package_len); |
| } |
| break; |
| |
| case L2CEVT_L2CA_CREDIT_BASED_RECONFIG_REQ: |
| p_ccb->chnl_state = CST_CONFIG; |
| p_ccb->config_done &= ~OB_CFG_DONE; |
| |
| if (p_data) { |
| l2cu_send_credit_based_reconfig_req(p_ccb, (tL2CAP_LE_CFG_INFO*)p_data); |
| |
| alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_CFG_TIMEOUT_MS, l2c_ccb_timer_timeout, |
| p_ccb); |
| } |
| break; |
| |
| case L2CEVT_L2CA_CONFIG_REQ: /* Upper layer config req */ |
| log::error( |
| "Dropping L2CAP re-config request because there is no usage and " |
| "should not be invoked"); |
| break; |
| |
| case L2CEVT_TIMEOUT: |
| /* Process the monitor/retransmission time-outs in flow control/retrans |
| * mode */ |
| if (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) { |
| l2c_fcr_proc_tout(p_ccb); |
| } |
| break; |
| |
| case L2CEVT_ACK_TIMEOUT: |
| l2c_fcr_proc_ack_tout(p_ccb); |
| break; |
| |
| case L2CEVT_L2CA_SEND_FLOW_CONTROL_CREDIT: |
| if (p_data) { |
| log::debug("Sending credit"); |
| credit = *(uint16_t*)p_data; |
| l2cble_send_flow_control_credit(p_ccb, credit); |
| } |
| break; |
| |
| case L2CEVT_L2CAP_RECV_FLOW_CONTROL_CREDIT: |
| if (p_data) { |
| credit = *(uint16_t*)p_data; |
| log::debug("Credits received {}", credit); |
| if ((p_ccb->peer_conn_cfg.credits + credit) > L2CAP_LE_CREDIT_MAX) { |
| /* we have received credits more than max coc credits, |
| * so disconnecting the Le Coc Channel |
| */ |
| l2cble_send_peer_disc_req(p_ccb); |
| } else { |
| p_ccb->peer_conn_cfg.credits += credit; |
| l2c_link_check_send_pkts(p_ccb->p_lcb, 0, NULL); |
| } |
| } |
| break; |
| default: |
| log::error("Handling unexpected event:{}", l2c_csm_get_event_name(event)); |
| } |
| log::verbose("Exit chnl_state={} [{}], event={} [{}]", channel_state_text(p_ccb->chnl_state), |
| p_ccb->chnl_state, l2c_csm_get_event_name(event), event); |
| } |
| |
| /******************************************************************************* |
| * |
| * Function l2c_csm_w4_l2cap_disconnect_rsp |
| * |
| * Description This function handles events when the channel is in |
| * CST_W4_L2CAP_DISCONNECT_RSP state. |
| * |
| * Returns void |
| * |
| ******************************************************************************/ |
| static void l2c_csm_w4_l2cap_disconnect_rsp(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data) { |
| tL2CA_DISCONNECT_CFM_CB* disconnect_cfm = p_ccb->p_rcb->api.pL2CA_DisconnectCfm_Cb; |
| uint16_t local_cid = p_ccb->local_cid; |
| |
| log::debug("LCID: 0x{:04x} st: W4_L2CAP_DISC_RSP evt: {} psm: {}", p_ccb->local_cid, |
| l2c_csm_get_event_name(event), psm_to_text(p_ccb->p_rcb->psm)); |
| |
| switch (event) { |
| case L2CEVT_L2CAP_DISCONNECT_RSP: /* Peer disconnect response */ |
| l2cu_release_ccb(p_ccb); |
| if (disconnect_cfm != nullptr) { |
| (*disconnect_cfm)(local_cid, L2CAP_DISC_OK); |
| } |
| break; |
| |
| case L2CEVT_L2CAP_DISCONNECT_REQ: /* Peer disconnect request */ |
| l2cu_send_peer_disc_rsp(p_ccb->p_lcb, p_ccb->remote_id, p_ccb->local_cid, p_ccb->remote_cid); |
| l2cu_release_ccb(p_ccb); |
| if (disconnect_cfm != nullptr) { |
| (*disconnect_cfm)(local_cid, L2CAP_DISC_OK); |
| } |
| break; |
| |
| case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */ |
| case L2CEVT_TIMEOUT: /* Timeout */ |
| l2cu_release_ccb(p_ccb); |
| if (disconnect_cfm != nullptr) { |
| (*disconnect_cfm)(local_cid, L2CAP_DISC_TIMEOUT); |
| } |
| |
| break; |
| |
| case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ |
| case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ |
| osi_free(p_data); |
| break; |
| default: |
| log::error("Handling unexpected event:{}", l2c_csm_get_event_name(event)); |
| } |
| log::verbose("Exit chnl_state={} [{}], event={} [{}]", channel_state_text(p_ccb->chnl_state), |
| p_ccb->chnl_state, l2c_csm_get_event_name(event), event); |
| } |
| |
| /******************************************************************************* |
| * |
| * Function l2c_csm_w4_l2ca_disconnect_rsp |
| * |
| * Description This function handles events when the channel is in |
| * CST_W4_L2CA_DISCONNECT_RSP state. |
| * |
| * Returns void |
| * |
| ******************************************************************************/ |
| static void l2c_csm_w4_l2ca_disconnect_rsp(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data) { |
| tL2CA_DISCONNECT_IND_CB* disconnect_ind = p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb; |
| uint16_t local_cid = p_ccb->local_cid; |
| |
| log::debug("LCID: 0x{:04x} st: W4_L2CA_DISC_RSP evt: {} psm: {}", p_ccb->local_cid, |
| l2c_csm_get_event_name(event), psm_to_text(p_ccb->p_rcb->psm)); |
| |
| switch (event) { |
| case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */ |
| log::debug("Calling Disconnect_Ind_Cb(), CID: 0x{:04x} No Conf Needed", p_ccb->local_cid); |
| l2cu_release_ccb(p_ccb); |
| (*disconnect_ind)(local_cid, false); |
| break; |
| |
| case L2CEVT_TIMEOUT: |
| l2cu_send_peer_disc_rsp(p_ccb->p_lcb, p_ccb->remote_id, p_ccb->local_cid, p_ccb->remote_cid); |
| log::debug("Calling Disconnect_Ind_Cb(), CID: 0x{:04x} No Conf Needed", p_ccb->local_cid); |
| l2cu_release_ccb(p_ccb); |
| (*disconnect_ind)(local_cid, false); |
| break; |
| |
| case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper disconnect request */ |
| case L2CEVT_L2CA_DISCONNECT_RSP: /* Upper disconnect response */ |
| l2cu_send_peer_disc_rsp(p_ccb->p_lcb, p_ccb->remote_id, p_ccb->local_cid, p_ccb->remote_cid); |
| l2cu_release_ccb(p_ccb); |
| break; |
| |
| case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ |
| case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ |
| osi_free(p_data); |
| break; |
| default: |
| log::error("Handling unexpected event:{}", l2c_csm_get_event_name(event)); |
| } |
| log::verbose("Exit chnl_state={} [{}], event={} [{}]", channel_state_text(p_ccb->chnl_state), |
| p_ccb->chnl_state, l2c_csm_get_event_name(event), event); |
| } |
| |
| static std::string l2c_csm_get_event_name(const tL2CEVT& event) { |
| switch (event) { |
| CASE_RETURN_STRING(L2CEVT_LP_CONNECT_CFM); |
| CASE_RETURN_STRING(L2CEVT_LP_CONNECT_CFM_NEG); |
| CASE_RETURN_STRING(L2CEVT_LP_CONNECT_IND); |
| CASE_RETURN_STRING(L2CEVT_LP_DISCONNECT_IND); |
| CASE_RETURN_STRING(L2CEVT_SEC_COMP); |
| CASE_RETURN_STRING(L2CEVT_SEC_COMP_NEG); |
| CASE_RETURN_STRING(L2CEVT_L2CAP_CONNECT_REQ); |
| CASE_RETURN_STRING(L2CEVT_L2CAP_CONNECT_RSP); |
| CASE_RETURN_STRING(L2CEVT_L2CAP_CONNECT_RSP_PND); |
| CASE_RETURN_STRING(L2CEVT_L2CAP_CONNECT_RSP_NEG); |
| CASE_RETURN_STRING(L2CEVT_L2CAP_CONFIG_REQ); |
| CASE_RETURN_STRING(L2CEVT_L2CAP_CONFIG_RSP); |
| CASE_RETURN_STRING(L2CEVT_L2CAP_CONFIG_RSP_NEG); |
| CASE_RETURN_STRING(L2CEVT_L2CAP_DISCONNECT_REQ); |
| CASE_RETURN_STRING(L2CEVT_L2CAP_DISCONNECT_RSP); |
| CASE_RETURN_STRING(L2CEVT_L2CAP_DATA); |
| |
| CASE_RETURN_STRING(L2CEVT_L2CA_CONNECT_REQ); |
| CASE_RETURN_STRING(L2CEVT_L2CA_CONNECT_RSP); |
| CASE_RETURN_STRING(L2CEVT_L2CA_CONNECT_RSP_NEG); |
| CASE_RETURN_STRING(L2CEVT_L2CA_CONFIG_REQ); |
| CASE_RETURN_STRING(L2CEVT_L2CA_CONFIG_RSP); |
| CASE_RETURN_STRING(L2CEVT_L2CA_DISCONNECT_REQ); |
| CASE_RETURN_STRING(L2CEVT_L2CA_DISCONNECT_RSP); |
| CASE_RETURN_STRING(L2CEVT_L2CA_DATA_READ); |
| CASE_RETURN_STRING(L2CEVT_L2CA_DATA_WRITE); |
| CASE_RETURN_STRING(L2CEVT_TIMEOUT); |
| CASE_RETURN_STRING(L2CEVT_SEC_RE_SEND_CMD); |
| CASE_RETURN_STRING(L2CEVT_L2CAP_INFO_RSP); |
| CASE_RETURN_STRING(L2CEVT_ACK_TIMEOUT); |
| CASE_RETURN_STRING(L2CEVT_L2CA_SEND_FLOW_CONTROL_CREDIT); |
| CASE_RETURN_STRING(L2CEVT_L2CA_CREDIT_BASED_CONNECT_REQ); |
| CASE_RETURN_STRING(L2CEVT_L2CA_CREDIT_BASED_CONNECT_RSP); |
| CASE_RETURN_STRING(L2CEVT_L2CA_CREDIT_BASED_CONNECT_RSP_NEG); |
| CASE_RETURN_STRING(L2CEVT_L2CA_CREDIT_BASED_RECONFIG_REQ); |
| CASE_RETURN_STRING(L2CEVT_L2CAP_RECV_FLOW_CONTROL_CREDIT); |
| CASE_RETURN_STRING(L2CEVT_L2CAP_CREDIT_BASED_CONNECT_REQ); |
| CASE_RETURN_STRING(L2CEVT_L2CAP_CREDIT_BASED_CONNECT_RSP); |
| CASE_RETURN_STRING(L2CEVT_L2CAP_CREDIT_BASED_CONNECT_RSP_NEG); |
| CASE_RETURN_STRING(L2CEVT_L2CAP_CREDIT_BASED_RECONFIG_REQ); |
| CASE_RETURN_STRING(L2CEVT_L2CAP_CREDIT_BASED_RECONFIG_RSP); |
| } |
| RETURN_UNKNOWN_TYPE_STRING(tL2CEVT, event); |
| } |
| |
| /******************************************************************************* |
| * |
| * Function l2c_enqueue_peer_data |
| * |
| * Description Enqueues data destined for the peer in the ccb. Handles |
| * FCR segmentation and checks for congestion. |
| * |
| * Returns void |
| * |
| ******************************************************************************/ |
| void l2c_enqueue_peer_data(tL2C_CCB* p_ccb, BT_HDR* p_buf) { |
| log::assert_that(p_ccb != nullptr, "assert failed: p_ccb != nullptr"); |
| |
| p_ccb->metrics.tx(p_buf->len); |
| |
| uint8_t* p; |
| |
| if (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_BASIC_MODE) { |
| p_buf->event = 0; |
| } else { |
| /* Save the channel ID for faster counting */ |
| p_buf->event = p_ccb->local_cid; |
| |
| /* Step back to add the L2CAP header */ |
| p_buf->offset -= L2CAP_PKT_OVERHEAD; |
| p_buf->len += L2CAP_PKT_OVERHEAD; |
| |
| /* Set the pointer to the beginning of the data */ |
| p = (uint8_t*)(p_buf + 1) + p_buf->offset; |
| |
| /* Now the L2CAP header */ |
| UINT16_TO_STREAM(p, p_buf->len - L2CAP_PKT_OVERHEAD); |
| UINT16_TO_STREAM(p, p_ccb->remote_cid); |
| } |
| |
| if (p_ccb->xmit_hold_q == NULL) { |
| log::error( |
| "empty queue: p_ccb = {} p_ccb->in_use = {} p_ccb->chnl_state = {} " |
| "p_ccb->local_cid = {} p_ccb->remote_cid = {}", |
| std::format_ptr(p_ccb), p_ccb->in_use, p_ccb->chnl_state, p_ccb->local_cid, |
| p_ccb->remote_cid); |
| } else { |
| fixed_queue_enqueue(p_ccb->xmit_hold_q, p_buf); |
| } |
| |
| l2cu_check_channel_congestion(p_ccb); |
| |
| /* if new packet is higher priority than serving ccb and it is not overrun */ |
| if ((p_ccb->p_lcb->rr_pri > p_ccb->ccb_priority) && |
| (p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].quota > 0)) { |
| /* send out higher priority packet */ |
| p_ccb->p_lcb->rr_pri = p_ccb->ccb_priority; |
| } |
| |
| /* if we are doing a round robin scheduling, set the flag */ |
| if (p_ccb->p_lcb->link_xmit_quota == 0) { |
| l2cb.check_round_robin = true; |
| } |
| } |