/****************************************************************************** * * Copyright (C) 2016 The Linux Foundation * Copyright 2015 Google, Inc. * * 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. * ******************************************************************************/ #define LOG_TAG "bt_device_interop" #include "device/include/interop.h" #include #include #include #include #include #include #include // For memcmp #include #include #include #include #include #include #include "btcore/include/module.h" #include "btif/include/btif_storage.h" #include "device/include/interop_config.h" #include "device/include/interop_database.h" #include "osi/include/allocator.h" #include "osi/include/compat.h" #include "osi/include/config.h" #include "osi/include/list.h" #include "osi/include/osi.h" #include "types/raw_address.h" // TODO(b/369381361) Enfore -Wmissing-prototypes #pragma GCC diagnostic ignored "-Wmissing-prototypes" using namespace bluetooth; #ifdef __ANDROID__ static const char* INTEROP_DYNAMIC_FILE_PATH = "/data/misc/bluedroid/interop_database_dynamic.conf"; static const char* INTEROP_STATIC_FILE_PATH = "/apex/com.android.btservices/etc/bluetooth/interop_database.conf"; #elif TARGET_FLOSS #include #include static const std::filesystem::path kDynamicConfigFileConfigFile = std::filesystem::temp_directory_path() / "interop_database_dynamic.conf"; static const char* INTEROP_DYNAMIC_FILE_PATH = kDynamicConfigFileConfigFile.c_str(); static const char* INTEROP_STATIC_FILE_PATH = "/var/lib/bluetooth/interop_database.conf"; #else // !TARGET_FLOSS and !__ANDROID__ #include #include static const std::filesystem::path kDynamicConfigFileConfigFile = std::filesystem::temp_directory_path() / "interop_database_dynamic.conf"; static const char* INTEROP_DYNAMIC_FILE_PATH = kDynamicConfigFileConfigFile.c_str(); static const std::filesystem::path kStaticConfigFileConfigFile = std::filesystem::temp_directory_path() / "interop_database.conf"; static const char* INTEROP_STATIC_FILE_PATH = kStaticConfigFileConfigFile.c_str(); #endif // __ANDROID__ #define CASE_RETURN_STR(const) \ case const: \ return #const; static list_t* interop_list = NULL; bool interop_is_initialized = false; // protects operations on |interop_list| pthread_mutex_t interop_list_lock; // protects operations on |config| static pthread_mutex_t file_lock; static std::unique_ptr config_static; static std::unique_ptr config_dynamic; static const char* UNKNOWN_INTEROP_FEATURE = "UNKNOWN"; // map from feature name to feature id static std::map feature_name_id_map; // Macro used to find the total number of feature_types #define NO_OF_FEATURES(x) (sizeof(x) / sizeof((x)[0])) #define SECTION_MAX_LENGTH (249) #define KEY_MAX_LENGTH (249) #define VALID_VNDR_PRDT_LEN (13) #define VALID_MNFR_STR_LEN (6) #define VALID_SSR_LAT_LEN (15) #define VALID_VERSION_LEN (6) #define VALID_LMP_VERSION_LEN (20) #define VALID_ADDR_RANGE_LEN (35) #define VENDOR_VALUE_SEPARATOR "-" #define ADDR_BASED "Address_Based" #define ADDR_RANGE_BASED "Address_Range_Based" #define NAME_BASED "Name_Based" #define MNFR_BASED "Manufacturer_Based" #define VNDR_PRDT_BASED "Vndr_Prdt_Based" #define SSR_MAX_LAT_BASED "SSR_Max_Lat_Based" #define VERSION_BASED "Version_Based" #define LMP_VERSION_BASED "LMP_Version_Based" typedef struct { char* key; char* value; } interop_entry_t; typedef struct { char* name; list_t* entries; } interop_section_t; typedef struct { RawAddress addr; uint16_t max_lat; interop_feature_t feature; } interop_hid_ssr_max_lat_t; typedef struct { uint16_t version; interop_feature_t feature; } interop_version_t; typedef struct { RawAddress addr; uint8_t lmp_ver; uint16_t lmp_sub_ver; interop_feature_t feature; } interop_lmp_version_t; typedef enum { INTEROP_BL_TYPE_ADDR = 0, INTEROP_BL_TYPE_NAME, INTEROP_BL_TYPE_MANUFACTURE, INTEROP_BL_TYPE_VNDR_PRDT, INTEROP_BL_TYPE_SSR_MAX_LAT, INTEROP_BL_TYPE_VERSION, INTEROP_BL_TYPE_LMP_VERSION, INTEROP_BL_TYPE_ADDR_RANGE, } interop_bl_type; typedef enum { INTEROP_ENTRY_TYPE_STATIC = 1 << 0, INTEROP_ENTRY_TYPE_DYNAMIC = 1 << 1 } interop_entry_type; typedef struct { interop_bl_type bl_type; interop_entry_type bl_entry_type; union { interop_addr_entry_t addr_entry; interop_name_entry_t name_entry; interop_manufacturer_t mnfr_entry; interop_hid_multitouch_t vnr_pdt_entry; interop_hid_ssr_max_lat_t ssr_max_lat_entry; interop_version_t version_entry; interop_lmp_version_t lmp_version_entry; interop_addr_range_entry_t addr_range_entry; } entry_type; } interop_db_entry_t; namespace std { template <> struct formatter : enum_formatter {}; } // namespace std static const char* interop_feature_string_(const interop_feature_t feature); static void interop_free_entry_(void* data); static void interop_lazy_init_(void); // Config related functions static void interop_config_cleanup(void); // This function is used to initialize the interop list and load the entries // from file static void load_config(); static void interop_database_add_(interop_db_entry_t* db_entry, bool persist); static bool interop_database_remove_(interop_db_entry_t* entry); static bool interop_database_match(interop_db_entry_t* entry, interop_db_entry_t** ret_entry, interop_entry_type entry_type); static void interop_config_flush(void); static bool interop_config_remove(const std::string& section, const std::string& key); // Interface functions bool interop_match_addr(const interop_feature_t feature, const RawAddress* addr) { log::assert_that(addr != nullptr, "assert failed: addr != nullptr"); return interop_database_match_addr(feature, addr); } bool interop_match_name(const interop_feature_t feature, const char* name) { log::assert_that(name != nullptr, "assert failed: name != nullptr"); return interop_database_match_name(feature, name); } bool interop_match_addr_or_name(const interop_feature_t feature, const RawAddress* addr, bt_status_t (*get_remote_device_property)(const RawAddress*, bt_property_t*)) { log::assert_that(addr != nullptr, "assert failed: addr != nullptr"); log::assert_that(get_remote_device_property != nullptr, "assert failed: get_remote_device_property != nullptr"); bt_bdname_t bdname; bt_property_t prop_name; if (interop_match_addr(feature, addr)) { return true; } BTIF_STORAGE_FILL_PROPERTY(&prop_name, BT_PROPERTY_BDNAME, sizeof(bt_bdname_t), bdname.name); if (get_remote_device_property(addr, &prop_name) != BT_STATUS_SUCCESS) { return false; } if (strlen((const char*)bdname.name) == 0) { return false; } return interop_match_name(feature, (const char*)bdname.name); } bool interop_match_manufacturer(const interop_feature_t feature, uint16_t manufacturer) { return interop_database_match_manufacturer(feature, manufacturer); } bool interop_match_vendor_product_ids(const interop_feature_t feature, uint16_t vendor_id, uint16_t product_id) { return interop_database_match_vndr_prdt(feature, vendor_id, product_id); } bool interop_match_addr_get_max_lat(const interop_feature_t feature, const RawAddress* addr, uint16_t* max_lat) { return interop_database_match_addr_get_max_lat(feature, addr, max_lat); } void interop_database_add(const uint16_t feature, const RawAddress* addr, size_t length) { log::assert_that(addr != nullptr, "assert failed: addr != nullptr"); log::assert_that(length > 0, "assert failed: length > 0"); log::assert_that(length < sizeof(RawAddress), "assert failed: length < sizeof(RawAddress)"); interop_database_add_addr(feature, addr, length); } void interop_database_clear() { log::debug("interop_is_initialized: {} interop_list: {}", interop_is_initialized, std::format_ptr(interop_list)); if (interop_is_initialized && interop_list) { for (int feature = BEGINNING_OF_INTEROP_LIST; feature != END_OF_INTEROP_LIST; feature++) { interop_database_remove_feature((interop_feature_t)feature); } } } static void interop_init_feature_name_id_map() { log::debug(""); feature_name_id_map.clear(); int feature; for (feature = BEGINNING_OF_INTEROP_LIST; feature < END_OF_INTEROP_LIST; feature++) { const char* feature_name = interop_feature_string_((interop_feature_t)feature); if (!strcmp(UNKNOWN_INTEROP_FEATURE, feature_name)) { continue; } feature_name_id_map.insert({feature_name, feature}); } } // Module life-cycle functions static future_t* interop_init(void) { interop_init_feature_name_id_map(); interop_lazy_init_(); interop_is_initialized = true; return future_new_immediate(FUTURE_SUCCESS); } static future_t* interop_clean_up(void) { pthread_mutex_lock(&interop_list_lock); list_free(interop_list); interop_list = NULL; interop_is_initialized = false; pthread_mutex_unlock(&interop_list_lock); pthread_mutex_destroy(&interop_list_lock); interop_config_cleanup(); return future_new_immediate(FUTURE_SUCCESS); } EXPORT_SYMBOL module_t interop_module = { .name = INTEROP_MODULE, .init = interop_init, .start_up = NULL, .shut_down = NULL, .clean_up = interop_clean_up, .dependencies = {NULL}, }; // Local functions static const char* interop_feature_string_(const interop_feature_t feature) { switch (feature) { CASE_RETURN_STR(INTEROP_DISABLE_LE_SECURE_CONNECTIONS) CASE_RETURN_STR(INTEROP_AUTO_RETRY_PAIRING) CASE_RETURN_STR(INTEROP_DISABLE_ABSOLUTE_VOLUME) CASE_RETURN_STR(INTEROP_DISABLE_AUTO_PAIRING) CASE_RETURN_STR(INTEROP_KEYBOARD_REQUIRES_FIXED_PIN) CASE_RETURN_STR(INTEROP_2MBPS_LINK_ONLY) CASE_RETURN_STR(INTEROP_HID_PREF_CONN_SUP_TIMEOUT_3S) CASE_RETURN_STR(INTEROP_GATTC_NO_SERVICE_CHANGED_IND) CASE_RETURN_STR(INTEROP_DISABLE_SDP_AFTER_PAIRING) CASE_RETURN_STR(INTEROP_DISABLE_AUTH_FOR_HID_POINTING) CASE_RETURN_STR(INTEROP_REMOVE_HID_DIG_DESCRIPTOR) CASE_RETURN_STR(INTEROP_DISABLE_SNIFF_DURING_SCO) CASE_RETURN_STR(INTEROP_INCREASE_AG_CONN_TIMEOUT) CASE_RETURN_STR(INTEROP_DISABLE_LE_CONN_PREFERRED_PARAMS) CASE_RETURN_STR(INTEROP_DISABLE_AAC_CODEC) CASE_RETURN_STR(INTEROP_DISABLE_AAC_VBR_CODEC) CASE_RETURN_STR(INTEROP_DYNAMIC_ROLE_SWITCH) CASE_RETURN_STR(INTEROP_DISABLE_ROLE_SWITCH) CASE_RETURN_STR(INTEROP_DISABLE_ROLE_SWITCH_POLICY) CASE_RETURN_STR(INTEROP_HFP_1_7_DENYLIST) CASE_RETURN_STR(INTEROP_ADV_PBAP_VER_1_1) CASE_RETURN_STR(INTEROP_UPDATE_HID_SSR_MAX_LAT) CASE_RETURN_STR(INTEROP_DISABLE_AVDTP_RECONFIGURE) CASE_RETURN_STR(INTEROP_DISABLE_HF_INDICATOR) CASE_RETURN_STR(INTEROP_DISABLE_LE_CONN_UPDATES) CASE_RETURN_STR(INTEROP_DELAY_SCO_FOR_MT_CALL) CASE_RETURN_STR(INTEROP_DISABLE_CODEC_NEGOTIATION) CASE_RETURN_STR(INTEROP_DISABLE_PLAYER_APPLICATION_SETTING_CMDS) CASE_RETURN_STR(INTEROP_ENABLE_AAC_CODEC) CASE_RETURN_STR(INTEROP_DISABLE_CONNECTION_AFTER_COLLISION) CASE_RETURN_STR(INTEROP_AVRCP_BROWSE_OPEN_CHANNEL_COLLISION) CASE_RETURN_STR(INTEROP_ADV_PBAP_VER_1_2) CASE_RETURN_STR(INTEROP_DISABLE_PCE_SDP_AFTER_PAIRING) CASE_RETURN_STR(INTEROP_DISABLE_SNIFF_LINK_DURING_SCO) CASE_RETURN_STR(INTEROP_DISABLE_SNIFF_DURING_CALL) CASE_RETURN_STR(INTEROP_HID_HOST_LIMIT_SNIFF_INTERVAL) CASE_RETURN_STR(INTEROP_DISABLE_REFRESH_ACCEPT_SIG_TIMER) CASE_RETURN_STR(INTEROP_SKIP_INCOMING_STATE) CASE_RETURN_STR(INTEROP_NOT_UPDATE_AVRCP_PAUSED_TO_REMOTE) CASE_RETURN_STR(INTEROP_PHONE_POLICY_INCREASED_DELAY_CONNECT_OTHER_PROFILES) CASE_RETURN_STR(INTEROP_PHONE_POLICY_REDUCED_DELAY_CONNECT_OTHER_PROFILES) CASE_RETURN_STR(INTEROP_HFP_FAKE_INCOMING_CALL_INDICATOR) CASE_RETURN_STR(INTEROP_HFP_SEND_CALL_INDICATORS_BACK_TO_BACK) CASE_RETURN_STR(INTEROP_SETUP_SCO_WITH_NO_DELAY_AFTER_SLC_DURING_CALL) CASE_RETURN_STR(INTEROP_ENABLE_PREFERRED_CONN_PARAMETER) CASE_RETURN_STR(INTEROP_RETRY_SCO_AFTER_REMOTE_REJECT_SCO) CASE_RETURN_STR(INTEROP_DELAY_SCO_FOR_MO_CALL) CASE_RETURN_STR(INTEROP_CHANGE_HID_VID_PID) CASE_RETURN_STR(END_OF_INTEROP_LIST) CASE_RETURN_STR(INTEROP_HFP_1_8_DENYLIST) CASE_RETURN_STR(INTEROP_DISABLE_ROLE_SWITCH_DURING_CONNECTION) CASE_RETURN_STR(INTEROP_DISABLE_NAME_REQUEST) CASE_RETURN_STR(INTEROP_AVRCP_1_4_ONLY) CASE_RETURN_STR(INTEROP_DISABLE_SNIFF) CASE_RETURN_STR(INTEROP_DISABLE_AVDTP_SUSPEND) CASE_RETURN_STR(INTEROP_SLC_SKIP_BIND_COMMAND) CASE_RETURN_STR(INTEROP_AVRCP_1_3_ONLY) CASE_RETURN_STR(INTEROP_DISABLE_ROBUST_CACHING); CASE_RETURN_STR(INTEROP_HFP_1_7_ALLOWLIST); CASE_RETURN_STR(INTEROP_HFP_1_9_ALLOWLIST); CASE_RETURN_STR(INTEROP_IGNORE_DISC_BEFORE_SIGNALLING_TIMEOUT); CASE_RETURN_STR(INTEROP_SUSPEND_ATT_TRAFFIC_DURING_PAIRING); CASE_RETURN_STR(INTEROP_INSERT_CALL_WHEN_SCO_START); CASE_RETURN_STR(INTEROP_DELAY_AUTH); CASE_RETURN_STR(INTEROP_MULTIPLE_HOGP_SERVICE_CHOOSE_THIRD); CASE_RETURN_STR(INTEROP_A2DP_SKIP_SDP_DURING_RECONNECTION); CASE_RETURN_STR(INTEROP_HID_PREF_CONN_ZERO_LATENCY); CASE_RETURN_STR(INTEROP_HOGP_LONG_REPORT); CASE_RETURN_STR(INTEROP_HOGP_FORCE_MTU_EXCHANGE); } return UNKNOWN_INTEROP_FEATURE; } static void interop_free_entry_(void* data) { interop_db_entry_t* entry = (interop_db_entry_t*)data; osi_free(entry); } static void interop_lazy_init_(void) { pthread_mutex_init(&interop_list_lock, NULL); if (interop_list == NULL) { interop_list = list_new(interop_free_entry_); load_config(); } } // interop config related functions static int interop_config_init(void) { struct stat sts; pthread_mutex_init(&file_lock, NULL); pthread_mutex_lock(&file_lock); if (!stat(INTEROP_STATIC_FILE_PATH, &sts) && sts.st_size) { if (!(config_static = config_new(INTEROP_STATIC_FILE_PATH))) { log::warn("unable to load static config file for : {}", INTEROP_STATIC_FILE_PATH); } } if (!config_static && !(config_static = config_new_empty())) { goto error; } if (!stat(INTEROP_DYNAMIC_FILE_PATH, &sts) && sts.st_size) { if (!(config_dynamic = config_new(INTEROP_DYNAMIC_FILE_PATH))) { log::warn("unable to load dynamic config file for : {}", INTEROP_DYNAMIC_FILE_PATH); } } if (!config_dynamic && !(config_dynamic = config_new_empty())) { goto error; } pthread_mutex_unlock(&file_lock); return 0; error: config_static.reset(); config_dynamic.reset(); pthread_mutex_unlock(&file_lock); return -1; } static void interop_config_flush(void) { log::assert_that(config_dynamic.get() != NULL, "assert failed: config_dynamic.get() != NULL"); pthread_mutex_lock(&file_lock); config_save(*config_dynamic, INTEROP_DYNAMIC_FILE_PATH); pthread_mutex_unlock(&file_lock); } static bool interop_config_remove(const std::string& section, const std::string& key) { log::assert_that(config_dynamic.get() != NULL, "assert failed: config_dynamic.get() != NULL"); pthread_mutex_lock(&file_lock); bool ret = config_remove_key(config_dynamic.get(), section, key); pthread_mutex_unlock(&file_lock); return ret; } static bool interop_config_remove_section(const std::string& section) { log::assert_that(config_dynamic.get() != NULL, "assert failed: config_dynamic.get() != NULL"); pthread_mutex_lock(&file_lock); bool ret = config_remove_section(config_dynamic.get(), section); pthread_mutex_unlock(&file_lock); return ret; } static bool interop_config_set_str(const std::string& section, const std::string& key, const std::string& value) { log::assert_that(config_dynamic.get() != NULL, "assert failed: config_dynamic.get() != NULL"); pthread_mutex_lock(&file_lock); config_set_string(config_dynamic.get(), section, key, value); pthread_mutex_unlock(&file_lock); return true; } int interop_feature_name_to_feature_id(const char* feature_name) { if (feature_name == NULL) { return -1; } auto it = feature_name_id_map.find(std::string(feature_name)); if (it == feature_name_id_map.end()) { log::warn("feature does not exist: {}", feature_name); return -1; } return it->second; } static bool interop_config_add_or_remove(interop_db_entry_t* db_entry, bool add) { bool status = true; std::string key; std::string value; interop_feature_t feature; // add it to the config file as well switch (db_entry->bl_type) { case INTEROP_BL_TYPE_ADDR: { interop_addr_entry_t addr_entry = db_entry->entry_type.addr_entry; const std::string bdstr = addr_entry.addr.ToColonSepHexString().substr(0, addr_entry.length * 3 - 1); feature = db_entry->entry_type.addr_entry.feature; key.assign(bdstr); value.assign(ADDR_BASED); break; } case INTEROP_BL_TYPE_NAME: { feature = db_entry->entry_type.name_entry.feature; key.assign(db_entry->entry_type.name_entry.name); value.assign(NAME_BASED); break; } case INTEROP_BL_TYPE_MANUFACTURE: { char m_facturer[KEY_MAX_LENGTH] = {'\0'}; snprintf(m_facturer, sizeof(m_facturer), "0x%04x", db_entry->entry_type.mnfr_entry.manufacturer); feature = db_entry->entry_type.mnfr_entry.feature; key.assign(m_facturer); value.assign(MNFR_BASED); break; } case INTEROP_BL_TYPE_VNDR_PRDT: { char m_vnr_pdt[KEY_MAX_LENGTH] = {'\0'}; snprintf(m_vnr_pdt, sizeof(m_vnr_pdt), "0x%04x-0x%04x", db_entry->entry_type.vnr_pdt_entry.vendor_id, db_entry->entry_type.vnr_pdt_entry.product_id); feature = db_entry->entry_type.vnr_pdt_entry.feature; key.assign(m_vnr_pdt); value.assign(VNDR_PRDT_BASED); break; } case INTEROP_BL_TYPE_SSR_MAX_LAT: { interop_hid_ssr_max_lat_t ssr_entry = db_entry->entry_type.ssr_max_lat_entry; char m_ssr_max_lat[KEY_MAX_LENGTH] = {'\0'}; const std::string bdstr = ssr_entry.addr.ToColonSepHexString().substr(0, 3 * 3 - 1); snprintf(m_ssr_max_lat, sizeof(m_ssr_max_lat), "%s-0x%04x", bdstr.c_str(), db_entry->entry_type.ssr_max_lat_entry.max_lat); feature = db_entry->entry_type.ssr_max_lat_entry.feature; key.assign(m_ssr_max_lat); value.assign(SSR_MAX_LAT_BASED); break; } case INTEROP_BL_TYPE_VERSION: { char m_version[KEY_MAX_LENGTH] = {'\0'}; snprintf(m_version, sizeof(m_version), "0x%04x", db_entry->entry_type.version_entry.version); feature = db_entry->entry_type.version_entry.feature; key.assign(m_version); value.assign(VERSION_BASED); break; } case INTEROP_BL_TYPE_LMP_VERSION: { interop_lmp_version_t lmp_version_entry = db_entry->entry_type.lmp_version_entry; char m_lmp_version[KEY_MAX_LENGTH] = {'\0'}; const std::string bdstr = lmp_version_entry.addr.ToColonSepHexString().substr(0, 3 * 3 - 1); snprintf(m_lmp_version, sizeof(m_lmp_version), "%s-0x%02x-0x%04x", bdstr.c_str(), db_entry->entry_type.lmp_version_entry.lmp_ver, db_entry->entry_type.lmp_version_entry.lmp_sub_ver); feature = db_entry->entry_type.lmp_version_entry.feature; key.assign(m_lmp_version); value.assign(LMP_VERSION_BASED); break; } default: log::error("bl_type: {} not handled", db_entry->bl_type); status = false; break; } if (status) { if (add) { interop_config_set_str(interop_feature_string_(feature), key, value); } else { interop_config_remove(interop_feature_string_(feature), key); } interop_config_flush(); } return status; } static void interop_database_add_(interop_db_entry_t* db_entry, bool persist) { interop_db_entry_t* ret_entry = NULL; bool match_found = interop_database_match( db_entry, &ret_entry, (interop_entry_type)(INTEROP_ENTRY_TYPE_STATIC | INTEROP_ENTRY_TYPE_DYNAMIC)); if (match_found) { // return as the entry is already present log::debug("Entry is already present in the list"); return; } pthread_mutex_lock(&interop_list_lock); if (interop_list) { list_append(interop_list, db_entry); } pthread_mutex_unlock(&interop_list_lock); if (!persist) { // return if the persist option is not set return; } interop_config_add_or_remove(db_entry, true); } static bool interop_database_match(interop_db_entry_t* entry, interop_db_entry_t** ret_entry, interop_entry_type entry_type) { log::assert_that(entry != nullptr, "assert failed: entry != nullptr"); bool found = false; pthread_mutex_lock(&interop_list_lock); if (interop_list == NULL || list_length(interop_list) == 0) { pthread_mutex_unlock(&interop_list_lock); return false; } const list_node_t* node = list_begin(interop_list); while (node != list_end(interop_list)) { interop_db_entry_t* db_entry = (interop_db_entry_t*)list_node(node); log::assert_that(db_entry != nullptr, "assert failed: db_entry != nullptr"); if (entry->bl_type != db_entry->bl_type) { node = list_next(node); continue; } if ((entry_type == INTEROP_ENTRY_TYPE_STATIC) || (entry_type == INTEROP_ENTRY_TYPE_DYNAMIC)) { if (entry->bl_entry_type != db_entry->bl_entry_type) { node = list_next(node); continue; } } switch (db_entry->bl_type) { case INTEROP_BL_TYPE_ADDR: { interop_addr_entry_t* src = &entry->entry_type.addr_entry; interop_addr_entry_t* cur = &db_entry->entry_type.addr_entry; if ((src->feature == cur->feature) && (!memcmp(&src->addr, &cur->addr, cur->length))) { /* cur len is used to remove src entry from config file, when * interop_database_remove_addr is called. */ src->length = cur->length; found = true; } break; } case INTEROP_BL_TYPE_NAME: { interop_name_entry_t* src = &entry->entry_type.name_entry; interop_name_entry_t* cur = &db_entry->entry_type.name_entry; if ((src->feature == cur->feature) && (strcasestr(src->name, cur->name) == src->name)) { found = true; } break; } case INTEROP_BL_TYPE_MANUFACTURE: { interop_manufacturer_t* src = &entry->entry_type.mnfr_entry; interop_manufacturer_t* cur = &db_entry->entry_type.mnfr_entry; if (src->feature == cur->feature && src->manufacturer == cur->manufacturer) { found = true; } break; } case INTEROP_BL_TYPE_VNDR_PRDT: { interop_hid_multitouch_t* src = &entry->entry_type.vnr_pdt_entry; interop_hid_multitouch_t* cur = &db_entry->entry_type.vnr_pdt_entry; if ((src->feature == cur->feature) && (src->vendor_id == cur->vendor_id) && (src->product_id == cur->product_id)) { found = true; } break; } case INTEROP_BL_TYPE_SSR_MAX_LAT: { interop_hid_ssr_max_lat_t* src = &entry->entry_type.ssr_max_lat_entry; interop_hid_ssr_max_lat_t* cur = &db_entry->entry_type.ssr_max_lat_entry; if ((src->feature == cur->feature) && !memcmp(&src->addr, &cur->addr, 3)) { found = true; } break; } case INTEROP_BL_TYPE_VERSION: { interop_version_t* src = &entry->entry_type.version_entry; interop_version_t* cur = &db_entry->entry_type.version_entry; if ((src->feature == cur->feature) && (src->version == cur->version)) { found = true; } break; } case INTEROP_BL_TYPE_LMP_VERSION: { interop_lmp_version_t* src = &entry->entry_type.lmp_version_entry; interop_lmp_version_t* cur = &db_entry->entry_type.lmp_version_entry; if ((src->feature == cur->feature) && (!memcmp(&src->addr, &cur->addr, 3))) { found = true; } break; } case INTEROP_BL_TYPE_ADDR_RANGE: { interop_addr_range_entry_t* src = &entry->entry_type.addr_range_entry; interop_addr_range_entry_t* cur = &db_entry->entry_type.addr_range_entry; // src->addr_start has the actual address, which need to be searched in // the range if ((src->feature == cur->feature) && (src->addr_start >= cur->addr_start) && (src->addr_start <= cur->addr_end)) { found = true; } break; } default: log::error("bl_type: {} not handled", db_entry->bl_type); break; } if (found && ret_entry) { *ret_entry = db_entry; break; } node = list_next(node); } pthread_mutex_unlock(&interop_list_lock); return found; } static bool interop_database_remove_(interop_db_entry_t* entry) { interop_db_entry_t* ret_entry = NULL; if (!interop_database_match(entry, &ret_entry, (interop_entry_type)(INTEROP_ENTRY_TYPE_DYNAMIC))) { log::error("Entry not found in the list"); return false; } // first remove it from linked list pthread_mutex_lock(&interop_list_lock); list_remove(interop_list, (void*)ret_entry); pthread_mutex_unlock(&interop_list_lock); return interop_config_add_or_remove(entry, false); } static char* trim(char* str) { while (isspace(*str)) { ++str; } if (!*str) { return str; } char* end_str = str + strlen(str) - 1; while (end_str > str && isspace(*end_str)) { --end_str; } end_str[1] = '\0'; return str; } bool token_to_ul(char* token, uint16_t* ul) { char* e; bool ret_value = false; token = trim(token); errno = 0; *ul = (uint16_t)strtoul(token, &e, 16); if ((e != NULL) && errno != EINVAL && errno != ERANGE) { ret_value = true; } return ret_value; } static bool get_vendor_product_id(char* vendorstr, uint16_t* vendor, uint16_t* product) { char* token; char* saveptr = NULL; bool ret_value = false; if ((token = strtok_r(vendorstr, VENDOR_VALUE_SEPARATOR, &saveptr)) != NULL) { ret_value = token_to_ul(token, vendor); } if (ret_value && (token = strtok_r(NULL, VENDOR_VALUE_SEPARATOR, &saveptr)) != NULL) { ret_value = token_to_ul(token, product); } return ret_value; } static bool get_addr_maxlat(char* str, char* bdaddrstr, uint16_t* max_lat) { char* token; char* saveptr = NULL; bool ret_value = false; if ((token = strtok_r(str, VENDOR_VALUE_SEPARATOR, &saveptr)) != NULL) { trim(token); osi_strlcpy(bdaddrstr, token, KEY_MAX_LENGTH); } else { return false; } if ((token = strtok_r(NULL, VENDOR_VALUE_SEPARATOR, &saveptr)) != NULL) { ret_value = token_to_ul(token, max_lat); } return ret_value; } static bool get_addr_range(char* str, RawAddress* addr_start, RawAddress* addr_end) { char* token; char* saveptr = NULL; bool ret_value = false; char addr_start_str[18] = {'\0'}; char addr_end_str[18] = {'\0'}; if ((token = strtok_r(str, VENDOR_VALUE_SEPARATOR, &saveptr)) != NULL) { trim(token); osi_strlcpy(addr_start_str, token, 18); if (!RawAddress::FromString(addr_start_str, *addr_start)) { return false; } } else { return false; } if ((token = strtok_r(NULL, VENDOR_VALUE_SEPARATOR, &saveptr)) != NULL) { trim(token); osi_strlcpy(addr_end_str, token, 18); if (RawAddress::FromString(addr_end_str, *addr_end)) { ret_value = true; } } return ret_value; } static bool get_addr_lmp_ver(char* str, char* bdaddrstr, uint8_t* lmp_ver, uint16_t* lmp_sub_ver) { char* token; char* saveptr = NULL; char* e; if ((token = strtok_r(str, VENDOR_VALUE_SEPARATOR, &saveptr)) != NULL) { trim(token); osi_strlcpy(bdaddrstr, token, KEY_MAX_LENGTH); } else { return false; } if ((token = strtok_r(NULL, VENDOR_VALUE_SEPARATOR, &saveptr)) != NULL) { trim(token); errno = 0; *lmp_ver = (uint8_t)strtoul(token, &e, 16); if (errno == EINVAL || errno == ERANGE) { return false; } } else { return false; } if ((token = strtok_r(NULL, VENDOR_VALUE_SEPARATOR, &saveptr)) != NULL) { return token_to_ul(token, lmp_sub_ver); } return false; } static bool load_to_database(int feature, const char* key, const char* value, interop_entry_type entry_type) { if (!strncasecmp(value, ADDR_BASED, strlen(ADDR_BASED))) { RawAddress addr; int len = 0; len = (strlen(key) + 1) / 3; if (len < 3 || len > 4) { log::warn("Ignoring as invalid entry for Address {}", key); return false; } std::string bdstr(key); std::string append_str(":00"); for (int i = 6; i > len; i--) { bdstr.append(append_str); } if (!RawAddress::FromString(bdstr, addr)) { log::warn("key {} or Bluetooth Address {} is invalid, not added to interop list", key, addr); return false; } interop_db_entry_t* entry = (interop_db_entry_t*)osi_calloc(sizeof(interop_db_entry_t)); entry->bl_type = INTEROP_BL_TYPE_ADDR; entry->bl_entry_type = entry_type; entry->entry_type.addr_entry.addr = addr; entry->entry_type.addr_entry.feature = (interop_feature_t)feature; entry->entry_type.addr_entry.length = len; interop_database_add_(entry, false); } else if (!strncasecmp(value, NAME_BASED, strlen(NAME_BASED))) { if (strlen(key) > KEY_MAX_LENGTH - 1) { log::warn("ignoring {} due to invalid length", key); return false; } interop_db_entry_t* entry = (interop_db_entry_t*)osi_calloc(sizeof(interop_db_entry_t)); entry->bl_type = INTEROP_BL_TYPE_NAME; entry->bl_entry_type = entry_type; osi_strlcpy(entry->entry_type.name_entry.name, key, sizeof(entry->entry_type.name_entry.name)); entry->entry_type.name_entry.feature = (interop_feature_t)feature; entry->entry_type.name_entry.length = strlen(key); interop_database_add_(entry, false); } else if (!strncasecmp(value, MNFR_BASED, strlen(MNFR_BASED))) { uint16_t manufacturer; if (strlen(key) != VALID_MNFR_STR_LEN) { log::warn("ignoring {} due to invalid Manufacturer id in config file", key); return false; } if (token_to_ul((char*)key, &manufacturer) == false) { return false; } interop_db_entry_t* entry = (interop_db_entry_t*)osi_calloc(sizeof(interop_db_entry_t)); entry->bl_type = INTEROP_BL_TYPE_MANUFACTURE; entry->bl_entry_type = entry_type; entry->entry_type.mnfr_entry.feature = (interop_feature_t)feature; entry->entry_type.mnfr_entry.manufacturer = manufacturer; interop_database_add_(entry, false); } else if (!strncasecmp(value, VNDR_PRDT_BASED, strlen(VNDR_PRDT_BASED))) { uint16_t vendor_id; uint16_t product_id = 0; char tmp_key[VALID_VNDR_PRDT_LEN + 1] = {'\0'}; if (strlen(key) != VALID_VNDR_PRDT_LEN) { log::warn("ignoring {} due to invalid vendor/product id in config file", key); return false; } osi_strlcpy(tmp_key, key, VALID_VNDR_PRDT_LEN + 1); if (!get_vendor_product_id(tmp_key, &vendor_id, &product_id)) { log::warn("Error in parsing vendor/product id {}", key); return false; } interop_db_entry_t* entry = (interop_db_entry_t*)osi_calloc(sizeof(interop_db_entry_t)); entry->bl_type = INTEROP_BL_TYPE_VNDR_PRDT; entry->bl_entry_type = entry_type; entry->entry_type.vnr_pdt_entry.feature = (interop_feature_t)feature; entry->entry_type.vnr_pdt_entry.vendor_id = vendor_id; entry->entry_type.vnr_pdt_entry.product_id = product_id; interop_database_add_(entry, false); } else if (!strncasecmp(value, SSR_MAX_LAT_BASED, strlen(SSR_MAX_LAT_BASED))) { uint16_t max_lat; char tmp_key[KEY_MAX_LENGTH] = {'\0'}; char bdaddr_str[KEY_MAX_LENGTH] = {'\0'}; if (strlen(key) != VALID_SSR_LAT_LEN) { log::warn("ignoring {} due to invalid key for ssr max lat in config file", key); return false; } osi_strlcpy(tmp_key, key, KEY_MAX_LENGTH); if (!get_addr_maxlat(tmp_key, bdaddr_str, &max_lat)) { log::warn("Error in parsing address and max_lat {}", key); return false; } int len = 0; len = (strlen(bdaddr_str) + 1) / 3; if (len != 3) { log::warn("Ignoring as invalid entry for Address {}", bdaddr_str); return false; } std::string bdstr(bdaddr_str); std::string append_str(":00:00:00"); RawAddress addr; bdstr.append(append_str); if (!RawAddress::FromString(bdstr, addr)) { log::warn("key {} or Bluetooth Address {} is invalid, not added to interop list", key, addr); return false; } interop_db_entry_t* entry = (interop_db_entry_t*)osi_calloc(sizeof(interop_db_entry_t)); entry->bl_type = INTEROP_BL_TYPE_SSR_MAX_LAT; entry->bl_entry_type = entry_type; entry->entry_type.ssr_max_lat_entry.feature = (interop_feature_t)feature; entry->entry_type.ssr_max_lat_entry.addr = addr; entry->entry_type.ssr_max_lat_entry.max_lat = max_lat; interop_database_add_(entry, false); } else if (!strncasecmp(value, VERSION_BASED, strlen(VERSION_BASED))) { uint16_t version; if (strlen(key) != VALID_VERSION_LEN) { log::warn("ignoring {} due to invalid version in config file", key); return false; } if (token_to_ul((char*)key, &version) == false) { return false; } interop_db_entry_t* entry = (interop_db_entry_t*)osi_calloc(sizeof(interop_db_entry_t)); entry->bl_type = INTEROP_BL_TYPE_VERSION; entry->bl_entry_type = entry_type; entry->entry_type.version_entry.feature = (interop_feature_t)feature; entry->entry_type.version_entry.version = version; interop_database_add_(entry, false); } else if (!strncasecmp(value, LMP_VERSION_BASED, strlen(LMP_VERSION_BASED))) { uint8_t lmp_ver; uint16_t lmp_sub_ver; char tmp_key[KEY_MAX_LENGTH] = {'\0'}; char bdaddr_str[KEY_MAX_LENGTH] = {'\0'}; if (strlen(key) != VALID_LMP_VERSION_LEN) { log::warn("ignoring {} due to invalid key for lmp ver in config file", key); return false; } osi_strlcpy(tmp_key, key, KEY_MAX_LENGTH); if (!get_addr_lmp_ver(tmp_key, bdaddr_str, &lmp_ver, &lmp_sub_ver)) { log::warn("Error in parsing address and lmp_ver {}", key); return false; } int len = 0; len = (strlen(bdaddr_str) + 1) / 3; if (len != 3) { log::warn("Ignoring as invalid entry for Address {}", bdaddr_str); return false; } std::string bdstr(key); std::string append_str(":00:00:00"); RawAddress addr; bdstr.append(append_str); if (!RawAddress::FromString(bdstr, addr)) { log::warn("key {} or Bluetooth Address {} is invalid, not added to interop list", key, addr); return false; } interop_db_entry_t* entry = (interop_db_entry_t*)osi_calloc(sizeof(interop_db_entry_t)); entry->bl_type = INTEROP_BL_TYPE_LMP_VERSION; entry->bl_entry_type = entry_type; entry->entry_type.lmp_version_entry.feature = (interop_feature_t)feature; entry->entry_type.lmp_version_entry.addr = addr; entry->entry_type.lmp_version_entry.lmp_ver = lmp_ver; entry->entry_type.lmp_version_entry.lmp_sub_ver = lmp_sub_ver; interop_database_add_(entry, false); } else if (!strncasecmp(value, ADDR_RANGE_BASED, strlen(ADDR_RANGE_BASED))) { RawAddress addr_start; RawAddress addr_end; char tmp_key[KEY_MAX_LENGTH] = {'\0'}; if (strlen(key) != VALID_ADDR_RANGE_LEN) { log::warn("Ignoring as invalid entry for Address range {}", key); return false; } osi_strlcpy(tmp_key, key, VALID_ADDR_RANGE_LEN + 1); if (!get_addr_range(tmp_key, &addr_start, &addr_end)) { log::warn("key: {} addr_start {} or addr end {} is added to interop list", key, addr_start, addr_end); return false; } interop_db_entry_t* entry = (interop_db_entry_t*)osi_calloc(sizeof(interop_db_entry_t)); entry->bl_type = INTEROP_BL_TYPE_ADDR_RANGE; entry->bl_entry_type = entry_type; entry->entry_type.addr_range_entry.addr_start = addr_start; entry->entry_type.addr_range_entry.addr_end = addr_end; entry->entry_type.addr_range_entry.feature = (interop_feature_t)feature; interop_database_add_(entry, false); } log::verbose("feature:: {}, key :: {}, value :: {}", feature, key, value); return true; } static void load_config() { int init_status = interop_config_init(); if (init_status == -1) { log::error("Error in initializing interop static config file"); return; } pthread_mutex_lock(&file_lock); for (const section_t& sec : config_static.get()->sections) { int feature = -1; if ((feature = interop_feature_name_to_feature_id(sec.name.c_str())) != -1) { for (const entry_t& entry : sec.entries) { load_to_database(feature, entry.key.c_str(), entry.value.c_str(), INTEROP_ENTRY_TYPE_STATIC); } } } // We no longer need the static config file config_static.reset(); for (const section_t& sec : config_dynamic.get()->sections) { int feature = -1; if ((feature = interop_feature_name_to_feature_id(sec.name.c_str())) != -1) { for (const entry_t& entry : sec.entries) { load_to_database(feature, entry.key.c_str(), entry.value.c_str(), INTEROP_ENTRY_TYPE_DYNAMIC); } } } pthread_mutex_unlock(&file_lock); } static void interop_config_cleanup(void) { interop_config_flush(); pthread_mutex_lock(&file_lock); config_static.reset(); config_dynamic.reset(); pthread_mutex_unlock(&file_lock); pthread_mutex_destroy(&file_lock); } void interop_database_add_addr(const uint16_t feature, const RawAddress* addr, size_t length) { log::assert_that(addr != nullptr, "assert failed: addr != nullptr"); log::assert_that(length > 0, "assert failed: length > 0"); log::assert_that(length < sizeof(RawAddress), "assert failed: length < sizeof(RawAddress)"); interop_db_entry_t* entry = (interop_db_entry_t*)osi_calloc(sizeof(interop_db_entry_t)); entry->bl_type = INTEROP_BL_TYPE_ADDR; entry->bl_entry_type = INTEROP_ENTRY_TYPE_DYNAMIC; memcpy(&entry->entry_type.addr_entry.addr, addr, length); entry->entry_type.addr_entry.feature = (interop_feature_t)feature; entry->entry_type.addr_entry.length = length; interop_database_add_(entry, true); } void interop_database_add_name(const uint16_t feature, const char* name) { log::assert_that(name != nullptr, "assert failed: name != nullptr"); const size_t name_length = strlen(name); log::assert_that(name_length < KEY_MAX_LENGTH, "assert failed: name_length < KEY_MAX_LENGTH"); interop_db_entry_t* entry = (interop_db_entry_t*)osi_calloc(sizeof(interop_db_entry_t)); entry->bl_type = INTEROP_BL_TYPE_NAME; entry->bl_entry_type = INTEROP_ENTRY_TYPE_DYNAMIC; osi_strlcpy(entry->entry_type.name_entry.name, name, sizeof(entry->entry_type.name_entry.name)); entry->entry_type.name_entry.feature = (interop_feature_t)feature; entry->entry_type.name_entry.length = name_length; interop_database_add_(entry, true); } void interop_database_add_manufacturer(const interop_feature_t feature, uint16_t manufacturer) { interop_db_entry_t* entry = (interop_db_entry_t*)osi_calloc(sizeof(interop_db_entry_t)); entry->bl_type = INTEROP_BL_TYPE_MANUFACTURE; entry->bl_entry_type = INTEROP_ENTRY_TYPE_DYNAMIC; entry->entry_type.mnfr_entry.feature = feature; entry->entry_type.mnfr_entry.manufacturer = manufacturer; interop_database_add_(entry, true); } void interop_database_add_vndr_prdt(const interop_feature_t feature, uint16_t vendor_id, uint16_t product_id) { interop_db_entry_t* entry = (interop_db_entry_t*)osi_calloc(sizeof(interop_db_entry_t)); entry->bl_type = INTEROP_BL_TYPE_VNDR_PRDT; entry->bl_entry_type = INTEROP_ENTRY_TYPE_DYNAMIC; entry->entry_type.vnr_pdt_entry.feature = (interop_feature_t)feature; entry->entry_type.vnr_pdt_entry.vendor_id = vendor_id; entry->entry_type.vnr_pdt_entry.product_id = product_id; interop_database_add_(entry, true); } void interop_database_add_addr_max_lat(const interop_feature_t feature, const RawAddress* addr, uint16_t max_lat) { log::assert_that(addr != nullptr, "assert failed: addr != nullptr"); interop_db_entry_t* entry = (interop_db_entry_t*)osi_calloc(sizeof(interop_db_entry_t)); entry->bl_type = INTEROP_BL_TYPE_SSR_MAX_LAT; entry->bl_entry_type = INTEROP_ENTRY_TYPE_DYNAMIC; entry->entry_type.ssr_max_lat_entry.addr = *addr; entry->entry_type.ssr_max_lat_entry.feature = feature; entry->entry_type.ssr_max_lat_entry.max_lat = max_lat; interop_database_add_(entry, true); } void interop_database_add_version(const interop_feature_t feature, uint16_t version) { interop_db_entry_t* entry = (interop_db_entry_t*)osi_calloc(sizeof(interop_db_entry_t)); entry->bl_type = INTEROP_BL_TYPE_VERSION; entry->bl_entry_type = INTEROP_ENTRY_TYPE_DYNAMIC; entry->entry_type.version_entry.feature = (interop_feature_t)feature; entry->entry_type.version_entry.version = version; interop_database_add_(entry, true); } void interop_database_add_addr_lmp_version(const interop_feature_t feature, const RawAddress* addr, uint8_t lmp_ver, uint16_t lmp_sub_ver) { log::assert_that(addr != nullptr, "assert failed: addr != nullptr"); interop_db_entry_t* entry = (interop_db_entry_t*)osi_calloc(sizeof(interop_db_entry_t)); entry->bl_type = INTEROP_BL_TYPE_LMP_VERSION; entry->bl_entry_type = INTEROP_ENTRY_TYPE_DYNAMIC; entry->entry_type.lmp_version_entry.addr = *addr; entry->entry_type.lmp_version_entry.feature = feature; entry->entry_type.lmp_version_entry.lmp_ver = lmp_ver; entry->entry_type.lmp_version_entry.lmp_sub_ver = lmp_sub_ver; interop_database_add_(entry, true); } bool interop_database_match_manufacturer(const interop_feature_t feature, uint16_t manufacturer) { interop_db_entry_t entry; entry.bl_type = INTEROP_BL_TYPE_MANUFACTURE; entry.entry_type.mnfr_entry.feature = feature; entry.entry_type.mnfr_entry.manufacturer = manufacturer; if (interop_database_match( &entry, NULL, (interop_entry_type)(INTEROP_ENTRY_TYPE_STATIC | INTEROP_ENTRY_TYPE_DYNAMIC))) { log::warn("Device with manufacturer id: {} is a match for interop workaround {}", manufacturer, interop_feature_string_(feature)); return true; } return false; } bool interop_database_match_name(const interop_feature_t feature, const char* name) { char trim_name[KEY_MAX_LENGTH] = {'\0'}; log::assert_that(name != nullptr, "assert failed: name != nullptr"); osi_strlcpy(trim_name, name, KEY_MAX_LENGTH); interop_db_entry_t entry; entry.bl_type = INTEROP_BL_TYPE_NAME; osi_strlcpy(entry.entry_type.name_entry.name, trim(trim_name), KEY_MAX_LENGTH); entry.entry_type.name_entry.feature = (interop_feature_t)feature; entry.entry_type.name_entry.length = strlen(entry.entry_type.name_entry.name); if (interop_database_match( &entry, NULL, (interop_entry_type)(INTEROP_ENTRY_TYPE_STATIC | INTEROP_ENTRY_TYPE_DYNAMIC))) { log::warn("Device with name: {} is a match for interop workaround {}", name, interop_feature_string_(feature)); return true; } return false; } bool interop_database_match_addr(const interop_feature_t feature, const RawAddress* addr) { log::assert_that(addr != nullptr, "assert failed: addr != nullptr"); interop_db_entry_t entry; entry.bl_type = INTEROP_BL_TYPE_ADDR; entry.entry_type.addr_entry.addr = *addr; entry.entry_type.addr_entry.feature = (interop_feature_t)feature; entry.entry_type.addr_entry.length = sizeof(RawAddress); if (interop_database_match( &entry, NULL, (interop_entry_type)(INTEROP_ENTRY_TYPE_STATIC | INTEROP_ENTRY_TYPE_DYNAMIC))) { log::warn("Device {} is a match for interop workaround {}.", *addr, interop_feature_string_(feature)); return true; } entry.bl_type = INTEROP_BL_TYPE_ADDR_RANGE; entry.bl_entry_type = INTEROP_ENTRY_TYPE_STATIC; entry.entry_type.addr_range_entry.addr_start = *addr; entry.entry_type.addr_range_entry.feature = (interop_feature_t)feature; if (interop_database_match(&entry, NULL, (interop_entry_type)(INTEROP_ENTRY_TYPE_STATIC))) { log::warn("Device {} is a match for interop workaround {}.", *addr, interop_feature_string_(feature)); return true; } return false; } bool interop_database_match_vndr_prdt(const interop_feature_t feature, uint16_t vendor_id, uint16_t product_id) { interop_db_entry_t entry; entry.bl_type = INTEROP_BL_TYPE_VNDR_PRDT; entry.entry_type.vnr_pdt_entry.feature = (interop_feature_t)feature; entry.entry_type.vnr_pdt_entry.vendor_id = vendor_id; entry.entry_type.vnr_pdt_entry.product_id = product_id; if (interop_database_match( &entry, NULL, (interop_entry_type)(INTEROP_ENTRY_TYPE_STATIC | INTEROP_ENTRY_TYPE_DYNAMIC))) { log::warn("Device with vendor_id: {} product_id: {} is a match for interop workaround {}", vendor_id, product_id, interop_feature_string_(feature)); return true; } return false; } bool interop_database_match_addr_get_max_lat(const interop_feature_t feature, const RawAddress* addr, uint16_t* max_lat) { interop_db_entry_t entry; interop_db_entry_t* ret_entry = NULL; entry.bl_type = INTEROP_BL_TYPE_SSR_MAX_LAT; entry.entry_type.ssr_max_lat_entry.feature = feature; entry.entry_type.ssr_max_lat_entry.addr = *addr; entry.entry_type.ssr_max_lat_entry.feature = feature; if (interop_database_match( &entry, &ret_entry, (interop_entry_type)(INTEROP_ENTRY_TYPE_STATIC | INTEROP_ENTRY_TYPE_DYNAMIC))) { log::warn("Device {} is a match for interop workaround {}.", *addr, interop_feature_string_(feature)); *max_lat = ret_entry->entry_type.ssr_max_lat_entry.max_lat; return true; } return false; } bool interop_database_match_version(const interop_feature_t feature, uint16_t version) { interop_db_entry_t entry; entry.bl_type = INTEROP_BL_TYPE_VERSION; entry.entry_type.version_entry.feature = (interop_feature_t)feature; entry.entry_type.version_entry.version = version; if (interop_database_match( &entry, NULL, (interop_entry_type)(INTEROP_ENTRY_TYPE_STATIC | INTEROP_ENTRY_TYPE_DYNAMIC))) { log::warn("Device with version: 0x{:04x} is a match for interop workaround {}", version, interop_feature_string_(feature)); return true; } return false; } bool interop_database_match_addr_get_lmp_ver(const interop_feature_t feature, const RawAddress* addr, uint8_t* lmp_ver, uint16_t* lmp_sub_ver) { interop_db_entry_t entry; interop_db_entry_t* ret_entry = NULL; entry.bl_type = INTEROP_BL_TYPE_LMP_VERSION; entry.entry_type.lmp_version_entry.feature = feature; entry.entry_type.lmp_version_entry.addr = *addr; entry.entry_type.lmp_version_entry.feature = feature; if (interop_database_match( &entry, &ret_entry, (interop_entry_type)(INTEROP_ENTRY_TYPE_STATIC | INTEROP_ENTRY_TYPE_DYNAMIC))) { log::warn("Device {} is a match for interop workaround {}.", *addr, interop_feature_string_(feature)); *lmp_ver = ret_entry->entry_type.lmp_version_entry.lmp_ver; *lmp_sub_ver = ret_entry->entry_type.lmp_version_entry.lmp_sub_ver; return true; } return false; } bool interop_database_remove_name(const interop_feature_t feature, const char* name) { log::assert_that(name != nullptr, "assert failed: name != nullptr"); interop_db_entry_t entry; entry.bl_type = INTEROP_BL_TYPE_NAME; entry.bl_entry_type = INTEROP_ENTRY_TYPE_DYNAMIC; osi_strlcpy(entry.entry_type.name_entry.name, name, 20); entry.entry_type.name_entry.feature = (interop_feature_t)feature; entry.entry_type.name_entry.length = strlen(entry.entry_type.name_entry.name); if (interop_database_remove_(&entry)) { log::warn("Device with name: {} is removed from interop workaround {}", name, interop_feature_string_(feature)); return true; } return false; } bool interop_database_remove_manufacturer(const interop_feature_t feature, uint16_t manufacturer) { interop_db_entry_t entry; entry.bl_type = INTEROP_BL_TYPE_MANUFACTURE; entry.bl_entry_type = INTEROP_ENTRY_TYPE_DYNAMIC; entry.entry_type.mnfr_entry.feature = feature; entry.entry_type.mnfr_entry.manufacturer = manufacturer; if (interop_database_remove_(&entry)) { log::warn("Device with manufacturer id: {} is removed from interop workaround {}", manufacturer, interop_feature_string_(feature)); return true; } return false; } bool interop_database_remove_addr(const interop_feature_t feature, const RawAddress* addr) { log::assert_that(addr != nullptr, "assert failed: addr != nullptr"); interop_db_entry_t entry; entry.bl_type = INTEROP_BL_TYPE_ADDR; entry.bl_entry_type = INTEROP_ENTRY_TYPE_DYNAMIC; entry.entry_type.addr_entry.addr = *addr; entry.entry_type.addr_entry.feature = (interop_feature_t)feature; entry.entry_type.addr_entry.length = sizeof(RawAddress); if (interop_database_remove_(&entry)) { log::warn("Device {} is a removed from interop workaround {}.", *addr, interop_feature_string_(feature)); return true; } return false; } bool interop_database_remove_feature(const interop_feature_t feature) { if (interop_list == NULL || list_length(interop_list) == 0) { return false; } list_node_t* node = list_begin(interop_list); while (node != list_end(interop_list)) { interop_db_entry_t* entry = static_cast(list_node(node)); log::assert_that(entry != nullptr, "assert failed: entry != nullptr"); bool entry_match = false; if (entry->bl_entry_type == INTEROP_ENTRY_TYPE_DYNAMIC) { switch (entry->bl_type) { case INTEROP_BL_TYPE_ADDR: if (entry->entry_type.addr_entry.feature == feature) { entry_match = true; } break; case INTEROP_BL_TYPE_NAME: if (entry->entry_type.name_entry.feature == feature) { entry_match = true; } break; case INTEROP_BL_TYPE_MANUFACTURE: if (entry->entry_type.mnfr_entry.feature == feature) { entry_match = true; } break; case INTEROP_BL_TYPE_VNDR_PRDT: if (entry->entry_type.vnr_pdt_entry.feature == feature) { entry_match = true; } break; case INTEROP_BL_TYPE_SSR_MAX_LAT: if (entry->entry_type.ssr_max_lat_entry.feature == feature) { entry_match = true; } break; case INTEROP_BL_TYPE_VERSION: if (entry->entry_type.version_entry.feature == feature) { entry_match = true; } break; case INTEROP_BL_TYPE_LMP_VERSION: if (entry->entry_type.lmp_version_entry.feature == feature) { entry_match = true; } break; default: break; } } node = list_next(node); if (entry_match) { pthread_mutex_lock(&interop_list_lock); list_remove(interop_list, (void*)entry); pthread_mutex_unlock(&interop_list_lock); } } for (const section_t& sec : config_dynamic.get()->sections) { if (feature == interop_feature_name_to_feature_id(sec.name.c_str())) { log::warn("found feature - {}", interop_feature_string_(feature)); interop_config_remove_section(sec.name); return true; } } return false; } bool interop_database_remove_vndr_prdt(const interop_feature_t feature, uint16_t vendor_id, uint16_t product_id) { interop_db_entry_t entry; entry.bl_type = INTEROP_BL_TYPE_VNDR_PRDT; entry.bl_entry_type = INTEROP_ENTRY_TYPE_DYNAMIC; entry.entry_type.vnr_pdt_entry.feature = (interop_feature_t)feature; entry.entry_type.vnr_pdt_entry.vendor_id = vendor_id; entry.entry_type.vnr_pdt_entry.product_id = product_id; if (interop_database_remove_(&entry)) { log::warn("Device with vendor_id: {} product_id: {} is removed from interop workaround {}", vendor_id, product_id, interop_feature_string_(feature)); return true; } return false; } bool interop_database_remove_addr_max_lat(const interop_feature_t feature, const RawAddress* addr, uint16_t max_lat) { interop_db_entry_t entry; entry.bl_type = INTEROP_BL_TYPE_SSR_MAX_LAT; entry.bl_entry_type = INTEROP_ENTRY_TYPE_DYNAMIC; entry.entry_type.ssr_max_lat_entry.addr = *addr; entry.entry_type.ssr_max_lat_entry.feature = feature; entry.entry_type.ssr_max_lat_entry.max_lat = max_lat; if (interop_database_remove_(&entry)) { log::warn("Device {} is a removed from interop workaround {}.", *addr, interop_feature_string_(feature)); return true; } return false; } bool interop_database_remove_version(const interop_feature_t feature, uint16_t version) { interop_db_entry_t entry; entry.bl_type = INTEROP_BL_TYPE_VERSION; entry.bl_entry_type = INTEROP_ENTRY_TYPE_DYNAMIC; entry.entry_type.version_entry.feature = (interop_feature_t)feature; entry.entry_type.version_entry.version = version; if (interop_database_remove_(&entry)) { log::warn("Device with version: 0x{:04x} is removed from interop workaround {}", version, interop_feature_string_(feature)); return true; } return false; } bool interop_database_remove_addr_lmp_version(const interop_feature_t feature, const RawAddress* addr, uint8_t lmp_ver, uint16_t lmp_sub_ver) { interop_db_entry_t entry; entry.bl_type = INTEROP_BL_TYPE_LMP_VERSION; entry.bl_entry_type = INTEROP_ENTRY_TYPE_DYNAMIC; entry.entry_type.lmp_version_entry.addr = *addr; entry.entry_type.lmp_version_entry.feature = feature; entry.entry_type.lmp_version_entry.lmp_ver = lmp_ver; entry.entry_type.lmp_version_entry.lmp_sub_ver = lmp_sub_ver; if (interop_database_remove_(&entry)) { log::warn("Device {} is a removed from interop workaround {}.", *addr, interop_feature_string_(feature)); return true; } return false; }