/****************************************************************************** * * Copyright 2009-2014 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. * ******************************************************************************/ /******************************************************************************* * * Filename: btif_gatt_client.c * * Description: GATT client implementation * ******************************************************************************/ #define LOG_TAG "bt_btif_gattc" #include #include #include #include #include #include #include #include #include #include "bta/include/bta_sec_api.h" #include "bta_api.h" #include "bta_gatt_api.h" #include "btif_common.h" #include "btif_config.h" #include "btif_gatt.h" #include "btif_gatt_util.h" #include "gatt_api.h" #include "hci/controller_interface.h" #include "internal_include/bte_appl.h" #include "main/shim/entry.h" #include "osi/include/allocator.h" #include "stack/include/acl_api.h" #include "stack/include/acl_api_types.h" #include "stack/include/btm_ble_sec_api.h" #include "stack/include/main_thread.h" #include "storage/config_keys.h" #include "types/ble_address_with_type.h" #include "types/bluetooth/uuid.h" #include "types/bt_transport.h" #include "types/raw_address.h" using base::Bind; using base::Owned; using bluetooth::Uuid; using namespace bluetooth; using std::vector; bool btif_get_address_type(const RawAddress& bda, tBLE_ADDR_TYPE* p_addr_type); bool btif_get_device_type(const RawAddress& bda, int* p_device_type); static bt_status_t btif_gattc_test_command_impl( int command, const btgatt_test_params_t* params); extern const btgatt_callbacks_t* bt_gatt_callbacks; typedef struct { tGATT_IF gatt_if; uint16_t conn_id; } btif_test_cb_t; static const char* disc_name[GATT_DISC_MAX] = {"Unknown", "GATT_DISC_SRVC_ALL", "GATT_DISC_SRVC_BY_UUID", "GATT_DISC_INC_SRVC", "GATT_DISC_CHAR", "GATT_DISC_CHAR_DSCPT"}; static btif_test_cb_t test_cb; /******************************************************************************* * Constants & Macros ******************************************************************************/ #define CLI_CBACK_WRAP_IN_JNI(P_CBACK, P_CBACK_WRAP) \ do { \ if (bt_gatt_callbacks && bt_gatt_callbacks->client->P_CBACK) { \ log::verbose("HAL bt_gatt_callbacks->client->{}", #P_CBACK); \ do_in_jni_thread(P_CBACK_WRAP); \ } else { \ ASSERTC(0, "Callback is NULL", 0); \ } \ } while (0) #define CLI_CBACK_IN_JNI(P_CBACK, ...) \ do { \ if (bt_gatt_callbacks && bt_gatt_callbacks->client->P_CBACK) { \ log::verbose("HAL bt_gatt_callbacks->client->{}", #P_CBACK); \ do_in_jni_thread(Bind(bt_gatt_callbacks->client->P_CBACK, __VA_ARGS__)); \ } else { \ ASSERTC(0, "Callback is NULL", 0); \ } \ } while (0) #define CHECK_BTGATT_INIT() \ do { \ if (bt_gatt_callbacks == NULL) { \ log::warn("BTGATT not initialized"); \ return BT_STATUS_NOT_READY; \ } else { \ log::debug(""); \ } \ } while (0) namespace { uint8_t rssi_request_client_if; static void btif_gattc_upstreams_evt(uint16_t event, char* p_param) { log::debug("Event {} [{}]", gatt_client_event_text(static_cast(event)), event); tBTA_GATTC* p_data = (tBTA_GATTC*)p_param; switch (event) { case BTA_GATTC_EXEC_EVT: { HAL_CBACK(bt_gatt_callbacks, client->execute_write_cb, p_data->exec_cmpl.conn_id, p_data->exec_cmpl.status); break; } case BTA_GATTC_SEARCH_CMPL_EVT: { HAL_CBACK(bt_gatt_callbacks, client->search_complete_cb, p_data->search_cmpl.conn_id, p_data->search_cmpl.status); break; } case BTA_GATTC_NOTIF_EVT: { btgatt_notify_params_t data; data.bda = p_data->notify.bda; memcpy(data.value, p_data->notify.value, p_data->notify.len); data.handle = p_data->notify.handle; data.is_notify = p_data->notify.is_notify; data.len = p_data->notify.len; HAL_CBACK(bt_gatt_callbacks, client->notify_cb, p_data->notify.conn_id, data); if (!p_data->notify.is_notify) BTA_GATTC_SendIndConfirm(p_data->notify.conn_id, p_data->notify.cid); break; } case BTA_GATTC_OPEN_EVT: { log::debug("BTA_GATTC_OPEN_EVT {}", p_data->open.remote_bda); HAL_CBACK(bt_gatt_callbacks, client->open_cb, p_data->open.conn_id, p_data->open.status, p_data->open.client_if, p_data->open.remote_bda); if (GATT_DEF_BLE_MTU_SIZE != p_data->open.mtu && p_data->open.mtu) { HAL_CBACK(bt_gatt_callbacks, client->configure_mtu_cb, p_data->open.conn_id, p_data->open.status, p_data->open.mtu); } if (p_data->open.status == GATT_SUCCESS) btif_gatt_check_encrypted_link(p_data->open.remote_bda, p_data->open.transport); break; } case BTA_GATTC_CLOSE_EVT: { HAL_CBACK(bt_gatt_callbacks, client->close_cb, p_data->close.conn_id, p_data->close.status, p_data->close.client_if, p_data->close.remote_bda); break; } case BTA_GATTC_DEREG_EVT: case BTA_GATTC_SEARCH_RES_EVT: case BTA_GATTC_CANCEL_OPEN_EVT: case BTA_GATTC_SRVC_DISC_DONE_EVT: log::debug("Ignoring event ({})", event); break; case BTA_GATTC_CFG_MTU_EVT: { HAL_CBACK(bt_gatt_callbacks, client->configure_mtu_cb, p_data->cfg_mtu.conn_id, p_data->cfg_mtu.status, p_data->cfg_mtu.mtu); break; } case BTA_GATTC_CONGEST_EVT: HAL_CBACK(bt_gatt_callbacks, client->congestion_cb, p_data->congest.conn_id, p_data->congest.congested); break; case BTA_GATTC_PHY_UPDATE_EVT: HAL_CBACK(bt_gatt_callbacks, client->phy_updated_cb, p_data->phy_update.conn_id, p_data->phy_update.tx_phy, p_data->phy_update.rx_phy, p_data->phy_update.status); break; case BTA_GATTC_CONN_UPDATE_EVT: HAL_CBACK(bt_gatt_callbacks, client->conn_updated_cb, p_data->conn_update.conn_id, p_data->conn_update.interval, p_data->conn_update.latency, p_data->conn_update.timeout, p_data->conn_update.status); break; case BTA_GATTC_SRVC_CHG_EVT: HAL_CBACK(bt_gatt_callbacks, client->service_changed_cb, p_data->service_changed.conn_id); break; case BTA_GATTC_SUBRATE_CHG_EVT: HAL_CBACK(bt_gatt_callbacks, client->subrate_chg_cb, p_data->subrate_chg.conn_id, p_data->subrate_chg.subrate_factor, p_data->subrate_chg.latency, p_data->subrate_chg.cont_num, p_data->subrate_chg.timeout, p_data->subrate_chg.status); break; default: log::error("Unhandled event ({})!", event); break; } } static void bta_gattc_cback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) { log::debug("gatt client callback event:{} [{}]", gatt_client_event_text(event), event); bt_status_t status = btif_transfer_context(btif_gattc_upstreams_evt, (uint16_t)event, (char*)p_data, sizeof(tBTA_GATTC), NULL); ASSERTC(status == BT_STATUS_SUCCESS, "Context transfer failed!", status); } void btm_read_rssi_cb(void* p_void) { tBTM_RSSI_RESULT* p_result = (tBTM_RSSI_RESULT*)p_void; if (!p_result) return; CLI_CBACK_IN_JNI(read_remote_rssi_cb, rssi_request_client_if, p_result->rem_bda, p_result->rssi, p_result->status); } /******************************************************************************* * Client API Functions ******************************************************************************/ static bt_status_t btif_gattc_register_app(const Uuid& uuid, bool eatt_support) { CHECK_BTGATT_INIT(); return do_in_jni_thread(Bind( [](const Uuid& uuid, bool eatt_support) { BTA_GATTC_AppRegister( bta_gattc_cback, base::Bind( [](const Uuid& uuid, uint8_t client_id, uint8_t status) { do_in_jni_thread(Bind( [](const Uuid& uuid, uint8_t client_id, uint8_t status) { HAL_CBACK(bt_gatt_callbacks, client->register_client_cb, status, client_id, uuid); }, uuid, client_id, status)); }, uuid), eatt_support); }, uuid, eatt_support)); } static void btif_gattc_unregister_app_impl(int client_if) { BTA_GATTC_AppDeregister(client_if); } static bt_status_t btif_gattc_unregister_app(int client_if) { CHECK_BTGATT_INIT(); return do_in_jni_thread(Bind(&btif_gattc_unregister_app_impl, client_if)); } void btif_gattc_open_impl(int client_if, RawAddress address, tBLE_ADDR_TYPE addr_type, bool is_direct, int transport_p, bool opportunistic, int initiating_phys) { int device_type = BT_DEVICE_TYPE_UNKNOWN; tBT_TRANSPORT transport = (tBT_TRANSPORT)BT_TRANSPORT_LE; if (addr_type == BLE_ADDR_RANDOM) { device_type = BT_DEVICE_TYPE_BLE; BTA_DmAddBleDevice(address, addr_type, device_type); } else { // Ensure device is in inquiry database addr_type = BLE_ADDR_PUBLIC; if (btif_get_address_type(address, &addr_type) && btif_get_device_type(address, &device_type) && device_type != BT_DEVICE_TYPE_BREDR) { BTA_DmAddBleDevice(address, addr_type, device_type); } } // Check for background connections if (!is_direct) { // Check for privacy 1.0 and 1.1 controller and do not start background // connection if RPA offloading is not supported, since it will not // connect after change of random address if (!bluetooth::shim::GetController()->SupportsBlePrivacy() && (addr_type == BLE_ADDR_RANDOM) && BTM_BLE_IS_RESOLVE_BDA(address)) { tBTM_BLE_VSC_CB vnd_capabilities; BTM_BleGetVendorCapabilities(&vnd_capabilities); if (!vnd_capabilities.rpa_offloading) { HAL_CBACK(bt_gatt_callbacks, client->open_cb, 0, BT_STATUS_UNSUPPORTED, client_if, address); return; } } } // Determine transport if (transport_p != BT_TRANSPORT_AUTO) { transport = transport_p; } else { switch (device_type) { case BT_DEVICE_TYPE_BREDR: transport = BT_TRANSPORT_BR_EDR; break; case BT_DEVICE_TYPE_BLE: transport = BT_TRANSPORT_LE; break; case BT_DEVICE_TYPE_DUMO: if (addr_type == BLE_ADDR_RANDOM) transport = BT_TRANSPORT_LE; else transport = BT_TRANSPORT_BR_EDR; break; default: log::error("Unknown device type {}", device_type); break; } } // Connect! log::info("Transport={}, device type={}, address type ={}, phy={}", transport, device_type, addr_type, initiating_phys); tBTM_BLE_CONN_TYPE type = is_direct ? BTM_BLE_DIRECT_CONNECTION : BTM_BLE_BKG_CONNECT_ALLOW_LIST; BTA_GATTC_Open(client_if, address, addr_type, type, transport, opportunistic, initiating_phys); } static bt_status_t btif_gattc_open(int client_if, const RawAddress& bd_addr, uint8_t addr_type, bool is_direct, int transport, bool opportunistic, int initiating_phys) { CHECK_BTGATT_INIT(); // Closure will own this value and free it. return do_in_jni_thread(Bind(&btif_gattc_open_impl, client_if, bd_addr, addr_type, is_direct, transport, opportunistic, initiating_phys)); } void btif_gattc_close_impl(int client_if, RawAddress address, int conn_id) { log::info("client_if={}, conn_id={}, address={}", client_if, conn_id, address); // Disconnect established connections if (conn_id != 0) { BTA_GATTC_Close(conn_id); } else { BTA_GATTC_CancelOpen(client_if, address, true); } // Cancel pending background connections (remove from acceptlist) BTA_GATTC_CancelOpen(client_if, address, false); } static bt_status_t btif_gattc_close(int client_if, const RawAddress& bd_addr, int conn_id) { CHECK_BTGATT_INIT(); return do_in_jni_thread( Bind(&btif_gattc_close_impl, client_if, bd_addr, conn_id)); } static bt_status_t btif_gattc_refresh(int client_if, const RawAddress& bd_addr) { CHECK_BTGATT_INIT(); return do_in_jni_thread(Bind(&BTA_GATTC_Refresh, bd_addr)); } static bt_status_t btif_gattc_search_service(int conn_id, const Uuid* filter_uuid) { CHECK_BTGATT_INIT(); if (filter_uuid) { return do_in_jni_thread( Bind(&BTA_GATTC_ServiceSearchRequest, conn_id, *filter_uuid)); } else { return do_in_jni_thread(Bind(&BTA_GATTC_ServiceSearchAllRequest, conn_id)); } } static void btif_gattc_discover_service_by_uuid(int conn_id, const Uuid& uuid) { do_in_jni_thread(Bind(&BTA_GATTC_DiscoverServiceByUuid, conn_id, uuid)); } void btif_gattc_get_gatt_db_impl(int conn_id) { btgatt_db_element_t* db = NULL; int count = 0; BTA_GATTC_GetGattDb(conn_id, 0x0000, 0xFFFF, &db, &count); HAL_CBACK(bt_gatt_callbacks, client->get_gatt_db_cb, conn_id, db, count); osi_free(db); } static bt_status_t btif_gattc_get_gatt_db(int conn_id) { CHECK_BTGATT_INIT(); return do_in_jni_thread(Bind(&btif_gattc_get_gatt_db_impl, conn_id)); } void read_char_cb(uint16_t conn_id, tGATT_STATUS status, uint16_t handle, uint16_t len, uint8_t* value, void* data) { btgatt_read_params_t params = { .handle = handle, .value.len = len, .value_type = 0x00, /* GATTC_READ_VALUE_TYPE_VALUE */ .status = status, }; log::assert_that(len <= GATT_MAX_ATTR_LEN, "assert failed: len <= GATT_MAX_ATTR_LEN"); if (len > 0) memcpy(params.value.value, value, len); CLI_CBACK_IN_JNI(read_characteristic_cb, conn_id, status, params); } static bt_status_t btif_gattc_read_char(int conn_id, uint16_t handle, int auth_req) { CHECK_BTGATT_INIT(); return do_in_jni_thread(Bind(&BTA_GATTC_ReadCharacteristic, conn_id, handle, auth_req, read_char_cb, nullptr)); } void read_using_char_uuid_cb(uint16_t conn_id, tGATT_STATUS status, uint16_t handle, uint16_t len, uint8_t* value, void* data) { btgatt_read_params_t params = { .handle = handle, .value.len = len, .value_type = 0x00, /* GATTC_READ_VALUE_TYPE_VALUE */ .status = status, }; log::assert_that(len <= GATT_MAX_ATTR_LEN, "assert failed: len <= GATT_MAX_ATTR_LEN"); if (len > 0) memcpy(params.value.value, value, len); CLI_CBACK_IN_JNI(read_characteristic_cb, conn_id, status, params); } static bt_status_t btif_gattc_read_using_char_uuid(int conn_id, const Uuid& uuid, uint16_t s_handle, uint16_t e_handle, int auth_req) { CHECK_BTGATT_INIT(); return do_in_jni_thread(Bind(&BTA_GATTC_ReadUsingCharUuid, conn_id, uuid, s_handle, e_handle, auth_req, read_using_char_uuid_cb, nullptr)); } void read_desc_cb(uint16_t conn_id, tGATT_STATUS status, uint16_t handle, uint16_t len, uint8_t* value, void* data) { btgatt_read_params_t params; params.value_type = 0x00 /* GATTC_READ_VALUE_TYPE_VALUE */; params.status = status; params.handle = handle; params.value.len = len; log::assert_that(len <= GATT_MAX_ATTR_LEN, "assert failed: len <= GATT_MAX_ATTR_LEN"); if (len > 0) memcpy(params.value.value, value, len); CLI_CBACK_IN_JNI(read_descriptor_cb, conn_id, status, params); } static bt_status_t btif_gattc_read_char_descr(int conn_id, uint16_t handle, int auth_req) { CHECK_BTGATT_INIT(); return do_in_jni_thread(Bind(&BTA_GATTC_ReadCharDescr, conn_id, handle, auth_req, read_desc_cb, nullptr)); } void write_char_cb(uint16_t conn_id, tGATT_STATUS status, uint16_t handle, uint16_t len, const uint8_t* value, void* data) { std::vector val(value, value + len); CLI_CBACK_WRAP_IN_JNI( write_characteristic_cb, base::BindOnce( [](write_characteristic_callback cb, uint16_t conn_id, tGATT_STATUS status, uint16_t handle, std::vector moved_value) { cb(conn_id, status, handle, moved_value.size(), moved_value.data()); }, bt_gatt_callbacks->client->write_characteristic_cb, conn_id, status, handle, std::move(val))); } static bt_status_t btif_gattc_write_char(int conn_id, uint16_t handle, int write_type, int auth_req, const uint8_t* val, size_t len) { CHECK_BTGATT_INIT(); std::vector value(val, val + len); if (value.size() > GATT_MAX_ATTR_LEN) value.resize(GATT_MAX_ATTR_LEN); return do_in_jni_thread(Bind(&BTA_GATTC_WriteCharValue, conn_id, handle, write_type, std::move(value), auth_req, write_char_cb, nullptr)); } void write_descr_cb(uint16_t conn_id, tGATT_STATUS status, uint16_t handle, uint16_t len, const uint8_t* value, void* data) { std::vector val(value, value + len); CLI_CBACK_WRAP_IN_JNI( write_descriptor_cb, base::BindOnce( [](write_descriptor_callback cb, uint16_t conn_id, tGATT_STATUS status, uint16_t handle, std::vector moved_value) { cb(conn_id, status, handle, moved_value.size(), moved_value.data()); }, bt_gatt_callbacks->client->write_descriptor_cb, conn_id, status, handle, std::move(val))); } static bt_status_t btif_gattc_write_char_descr(int conn_id, uint16_t handle, int auth_req, const uint8_t* val, size_t len) { CHECK_BTGATT_INIT(); std::vector value(val, val + len); if (value.size() > GATT_MAX_ATTR_LEN) value.resize(GATT_MAX_ATTR_LEN); return do_in_jni_thread(Bind(&BTA_GATTC_WriteCharDescr, conn_id, handle, std::move(value), auth_req, write_descr_cb, nullptr)); } static bt_status_t btif_gattc_execute_write(int conn_id, int execute) { CHECK_BTGATT_INIT(); return do_in_jni_thread( Bind(&BTA_GATTC_ExecuteWrite, conn_id, (uint8_t)execute)); } static void btif_gattc_reg_for_notification_impl(tGATT_IF client_if, const RawAddress& bda, uint16_t handle) { tGATT_STATUS status = BTA_GATTC_RegisterForNotifications(client_if, bda, handle); // TODO(jpawlowski): conn_id is currently unused HAL_CBACK(bt_gatt_callbacks, client->register_for_notification_cb, /* conn_id */ 0, 1, status, handle); } bt_status_t btif_gattc_reg_for_notification(int client_if, const RawAddress& bd_addr, uint16_t handle) { CHECK_BTGATT_INIT(); return do_in_jni_thread( Bind(base::IgnoreResult(&btif_gattc_reg_for_notification_impl), client_if, bd_addr, handle)); } static void btif_gattc_dereg_for_notification_impl(tGATT_IF client_if, const RawAddress& bda, uint16_t handle) { tGATT_STATUS status = BTA_GATTC_DeregisterForNotifications(client_if, bda, handle); // TODO(jpawlowski): conn_id is currently unused HAL_CBACK(bt_gatt_callbacks, client->register_for_notification_cb, /* conn_id */ 0, 0, status, handle); } bt_status_t btif_gattc_dereg_for_notification(int client_if, const RawAddress& bd_addr, uint16_t handle) { CHECK_BTGATT_INIT(); return do_in_jni_thread( Bind(base::IgnoreResult(&btif_gattc_dereg_for_notification_impl), client_if, bd_addr, handle)); } static bt_status_t btif_gattc_read_remote_rssi(int client_if, const RawAddress& bd_addr) { CHECK_BTGATT_INIT(); rssi_request_client_if = client_if; return do_in_jni_thread( Bind(base::IgnoreResult(&BTM_ReadRSSI), bd_addr, btm_read_rssi_cb)); } static bt_status_t btif_gattc_configure_mtu(int conn_id, int mtu) { CHECK_BTGATT_INIT(); return do_in_jni_thread( Bind(base::IgnoreResult( static_cast(&BTA_GATTC_ConfigureMTU)), conn_id, mtu)); } static void btif_gattc_conn_parameter_update_impl( RawAddress addr, int min_interval, int max_interval, int latency, int timeout, uint16_t min_ce_len, uint16_t max_ce_len) { if (BTA_DmGetConnectionState(addr)) BTA_DmBleUpdateConnectionParams(addr, min_interval, max_interval, latency, timeout, min_ce_len, max_ce_len); else BTA_DmSetBlePrefConnParams(addr, min_interval, max_interval, latency, timeout); } bt_status_t btif_gattc_conn_parameter_update(const RawAddress& bd_addr, int min_interval, int max_interval, int latency, int timeout, uint16_t min_ce_len, uint16_t max_ce_len) { CHECK_BTGATT_INIT(); return do_in_jni_thread(Bind( base::IgnoreResult(&btif_gattc_conn_parameter_update_impl), bd_addr, min_interval, max_interval, latency, timeout, min_ce_len, max_ce_len)); } static bt_status_t btif_gattc_set_preferred_phy(const RawAddress& bd_addr, uint8_t tx_phy, uint8_t rx_phy, uint16_t phy_options) { CHECK_BTGATT_INIT(); do_in_main_thread(FROM_HERE, Bind(&BTM_BleSetPhy, bd_addr, tx_phy, rx_phy, phy_options)); return BT_STATUS_SUCCESS; } static bt_status_t btif_gattc_read_phy( const RawAddress& bd_addr, base::Callback cb) { CHECK_BTGATT_INIT(); do_in_main_thread(FROM_HERE, Bind(&BTM_BleReadPhy, bd_addr, jni_thread_wrapper(cb))); return BT_STATUS_SUCCESS; } static int btif_gattc_get_device_type(const RawAddress& bd_addr) { int device_type = 0; if (btif_config_get_int(bd_addr.ToString().c_str(), BTIF_STORAGE_KEY_DEV_TYPE, &device_type)) return device_type; return 0; } static bt_status_t btif_gattc_test_command(int command, const btgatt_test_params_t& params) { return btif_gattc_test_command_impl(command, ¶ms); } static void btif_gattc_subrate_request_impl(RawAddress addr, int subrate_min, int subrate_max, int max_latency, int cont_num, int sup_timeout) { if (BTA_DmGetConnectionState(addr)) { BTA_DmBleSubrateRequest(addr, subrate_min, subrate_max, max_latency, cont_num, sup_timeout); } } static bt_status_t btif_gattc_subrate_request(const RawAddress& bd_addr, int subrate_min, int subrate_max, int max_latency, int cont_num, int sup_timeout) { CHECK_BTGATT_INIT(); return do_in_jni_thread( Bind(base::IgnoreResult(&btif_gattc_subrate_request_impl), bd_addr, subrate_min, subrate_max, max_latency, cont_num, sup_timeout)); } static void btif_test_connect_cback(tGATT_IF, const RawAddress&, uint16_t conn_id, bool connected, tGATT_DISCONN_REASON, tBT_TRANSPORT) { log::info("conn_id={}, connected={}", conn_id, connected); test_cb.conn_id = connected ? conn_id : 0; } static void btif_test_command_complete_cback(uint16_t conn_id, tGATTC_OPTYPE op, tGATT_STATUS status, tGATT_CL_COMPLETE* p_data) { log::info("op_code=0x{:02x}, conn_id=0x{:x}. status=0x{:x}", op, conn_id, status); switch (op) { case GATTC_OPTYPE_READ: case GATTC_OPTYPE_WRITE: case GATTC_OPTYPE_CONFIG: case GATTC_OPTYPE_EXE_WRITE: case GATTC_OPTYPE_NOTIFICATION: break; case GATTC_OPTYPE_INDICATION: if (GATTC_SendHandleValueConfirm(conn_id, p_data->cid) != GATT_SUCCESS) log::error( "Unable to send handle value confirmation conn_id:0x{:x} " "cid:0x{:04x}", conn_id, p_data->cid); break; default: log::info("Unknown op_code (0x{:02x})", op); break; } } static void btif_test_discovery_result_cback(uint16_t /* conn_id */, tGATT_DISC_TYPE disc_type, tGATT_DISC_RES* p_data) { log::info("------ GATT Discovery result {:<22s} -------", disc_name[disc_type]); log::info("Attribute handle: 0x{:04x} ({})", p_data->handle, p_data->handle); if (disc_type != GATT_DISC_CHAR_DSCPT) { log::info("Attribute type: {}", p_data->type.ToString()); } switch (disc_type) { case GATT_DISC_SRVC_ALL: log::info("Handle range: 0x{:04x} ~ 0x{:04x} ({} ~ {})", p_data->handle, p_data->value.group_value.e_handle, p_data->handle, p_data->value.group_value.e_handle); log::info("Service UUID: {}", p_data->value.group_value.service_type.ToString()); break; case GATT_DISC_SRVC_BY_UUID: log::info("Handle range: 0x{:04x} ~ 0x{:04x} ({} ~ {})", p_data->handle, p_data->value.handle, p_data->handle, p_data->value.handle); break; case GATT_DISC_INC_SRVC: log::info("Handle range: 0x{:04x} ~ 0x{:04x} ({} ~ {})", p_data->value.incl_service.s_handle, p_data->value.incl_service.e_handle, p_data->value.incl_service.s_handle, p_data->value.incl_service.e_handle); log::info("Service UUID: {}", p_data->value.incl_service.service_type.ToString()); break; case GATT_DISC_CHAR: log::info("Properties: 0x{:02x}", p_data->value.dclr_value.char_prop); log::info("Characteristic UUID: {}", p_data->value.dclr_value.char_uuid.ToString()); break; case GATT_DISC_CHAR_DSCPT: log::info("Descriptor UUID: {}", p_data->type.ToString()); break; case GATT_DISC_MAX: log::error("Unknown discovery item"); break; } log::info("-----------------------------------------------------------"); } static void btif_test_discovery_complete_cback(uint16_t /* conn_id */, tGATT_DISC_TYPE /* disc_type */, tGATT_STATUS status) { log::info("status={}", status); } static tGATT_CBACK btif_test_callbacks = { btif_test_connect_cback, btif_test_command_complete_cback, btif_test_discovery_result_cback, btif_test_discovery_complete_cback, NULL, NULL, NULL, NULL, NULL, NULL, }; } // namespace static bt_status_t btif_gattc_test_command_impl( int command, const btgatt_test_params_t* params) { switch (command) { case 0x01: /* Enable */ { log::info("ENABLE - enable={}", params->u1); if (params->u1) { std::array tmp; tmp.fill(0xAE); test_cb.gatt_if = GATT_Register(bluetooth::Uuid::From128BitBE(tmp), std::string("GattTest"), &btif_test_callbacks, false); GATT_StartIf(test_cb.gatt_if); } else { GATT_Deregister(test_cb.gatt_if); test_cb.gatt_if = 0; } break; } case 0x02: /* Connect */ { log::info("CONNECT - device={} (dev_type={}, addr_type={})", *params->bda1, params->u1, params->u2); if (params->u1 == BT_DEVICE_TYPE_BLE) BTM_SecAddBleDevice(*params->bda1, BT_DEVICE_TYPE_BLE, static_cast(params->u2)); if (!GATT_Connect(test_cb.gatt_if, *params->bda1, BTM_BLE_DIRECT_CONNECTION, BT_TRANSPORT_LE, false)) { log::error("GATT_Connect failed!"); } break; } case 0x03: /* Disconnect */ { log::info("DISCONNECT - conn_id={}", test_cb.conn_id); if (GATT_Disconnect(test_cb.conn_id) != GATT_SUCCESS) log::error("Unable to disconnect"); break; } case 0x04: /* Discover */ { if (params->u1 >= GATT_DISC_MAX) { log::error("DISCOVER - Invalid type ({})!", params->u1); return (bt_status_t)0; } log::info("DISCOVER ({}), conn_id={}, uuid={}, handles=0x{:04x}-0x{:04x}", disc_name[params->u1], test_cb.conn_id, params->uuid1->ToString(), params->u2, params->u3); if (GATTC_Discover(test_cb.conn_id, static_cast(params->u1), params->u2, params->u3, *params->uuid1) != GATT_SUCCESS) log::error("Unable to discover"); break; } case 0xF0: /* Pairing configuration */ log::info("Setting pairing config auth={}, iocaps={}, keys={}/{}/{}", params->u1, params->u2, params->u3, params->u4, params->u5); bte_appl_cfg.ble_auth_req = params->u1; bte_appl_cfg.ble_io_cap = params->u2; bte_appl_cfg.ble_init_key = params->u3; bte_appl_cfg.ble_resp_key = params->u4; bte_appl_cfg.ble_max_key_size = params->u5; break; default: log::error("UNKNOWN TEST COMMAND 0x{:02x}", command); break; } return (bt_status_t)0; } const btgatt_client_interface_t btgattClientInterface = { btif_gattc_register_app, btif_gattc_unregister_app, btif_gattc_open, btif_gattc_close, btif_gattc_refresh, btif_gattc_search_service, btif_gattc_discover_service_by_uuid, btif_gattc_read_char, btif_gattc_read_using_char_uuid, btif_gattc_write_char, btif_gattc_read_char_descr, btif_gattc_write_char_descr, btif_gattc_execute_write, btif_gattc_reg_for_notification, btif_gattc_dereg_for_notification, btif_gattc_read_remote_rssi, btif_gattc_get_device_type, btif_gattc_configure_mtu, btif_gattc_conn_parameter_update, btif_gattc_set_preferred_phy, btif_gattc_read_phy, btif_gattc_test_command, btif_gattc_get_gatt_db, btif_gattc_subrate_request, };