blob: 79470347d7e84d9105d3b82b01409e9e57dd5fea [file] [log] [blame]
/******************************************************************************
*
* 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 <assert.h>
#include <base/logging.h>
#include <ctype.h>
#include <fcntl.h>
#include <hardware/bluetooth.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h> // For memcmp
#include <sys/stat.h>
#include <unistd.h>
#include <iostream>
#include <map>
#include <string>
#include <utility>
#include "btcore/include/module.h"
#include "btif/include/btif_storage.h"
#include "check.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/log.h"
#include "osi/include/osi.h"
#include "types/raw_address.h"
#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 <base/files/file_util.h>
#include <filesystem>
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 <base/files/file_util.h>
#include <filesystem>
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;
static list_t* media_player_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<const config_t> config_static;
static std::unique_ptr<config_t> config_dynamic;
static const char* UNKNOWN_INTEROP_FEATURE = "UNKNOWN";
// map from feature name to feature id
static std::map<std::string, int> 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;
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_save_allowlisted_media_players_list(
const config_t* 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) {
CHECK(addr);
return (interop_database_match_addr(feature, addr));
}
bool interop_match_name(const interop_feature_t feature, const char* name) {
CHECK(name);
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*)) {
CHECK(addr);
CHECK(get_remote_device_property);
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) {
CHECK(addr);
CHECK(length > 0);
CHECK(length < sizeof(RawAddress));
interop_database_add_addr(feature, addr, length);
}
void interop_database_clear() {
LOG_DEBUG("interop_is_initialized: %d interop_list: %p",
interop_is_initialized, 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;
list_free(media_player_list);
media_player_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_BROWSE_PLAYER_ALLOW_LIST)
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);
}
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 : %s",
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 : %s",
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) {
CHECK(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) {
CHECK(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) {
CHECK(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) {
CHECK(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: %s", 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: %d 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) {
CHECK(entry);
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);
CHECK(db_entry);
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: %d 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);
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);
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);
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);
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 %s", 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 %s or Bluetooth Address %s is invalid, not added to interop "
"list",
key, ADDRESS_TO_LOGGABLE_CSTR(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 %s 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;
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 %s 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 %s due to invalid vendor/product id in config file",
key);
return false;
}
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 %s", 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 %s due to invalid key for ssr max lat in config file",
key);
return false;
}
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 %s", key);
return false;
}
int len = 0;
len = (strlen(bdaddr_str) + 1) / 3;
if (len != 3) {
LOG_WARN("Ignoring as invalid entry for Address %s", 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 %s or Bluetooth Address %s is invalid, not added to interop "
"list",
key, ADDRESS_TO_LOGGABLE_CSTR(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 %s 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 %s due to invalid key for lmp ver in config file",
key);
return false;
}
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 %s", key);
return false;
}
int len = 0;
len = (strlen(bdaddr_str) + 1) / 3;
if (len != 3) {
LOG_WARN("Ignoring as invalid entry for Address %s", 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 %s or Bluetooth Address %s is invalid, not added to interop "
"list",
key, ADDRESS_TO_LOGGABLE_CSTR(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 %s", key);
return false;
}
strlcpy(tmp_key, key, VALID_ADDR_RANGE_LEN + 1);
if (!get_addr_range(tmp_key, &addr_start, &addr_end)) {
LOG_WARN("key: %s addr_start %s or addr end %s is added to interop list",
key, ADDRESS_TO_LOGGABLE_CSTR(addr_start),
ADDRESS_TO_LOGGABLE_CSTR(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:: %d, key :: %s, value :: %s", 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);
}
}
}
interop_database_save_allowlisted_media_players_list(config_static.get());
// 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) {
CHECK(addr);
CHECK(length > 0);
CHECK(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) {
CHECK(name);
const size_t name_length = strlen(name);
CHECK(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;
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) {
CHECK(addr);
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) {
CHECK(addr);
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: %d is a match for interop workaround %s",
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'};
CHECK(name);
strlcpy(trim_name, name, KEY_MAX_LENGTH);
interop_db_entry_t entry;
entry.bl_type = INTEROP_BL_TYPE_NAME;
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: %s is a match for interop workaround %s", name,
interop_feature_string_(feature));
return true;
}
return false;
}
bool interop_database_match_addr(const interop_feature_t feature,
const RawAddress* addr) {
CHECK(addr);
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 %s is a match for interop workaround %s.",
ADDRESS_TO_LOGGABLE_CSTR(*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 %s is a match for interop workaround %s.",
ADDRESS_TO_LOGGABLE_CSTR(*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: %d product_id: %d is a match for interop "
"workaround %s",
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 %s is a match for interop workaround %s.",
ADDRESS_TO_LOGGABLE_CSTR(*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 %s",
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 %s is a match for interop workaround %s.",
ADDRESS_TO_LOGGABLE_CSTR(*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) {
CHECK(name);
interop_db_entry_t entry;
entry.bl_type = INTEROP_BL_TYPE_NAME;
entry.bl_entry_type = INTEROP_ENTRY_TYPE_DYNAMIC;
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: %s is removed from interop workaround %s", 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: %d is removed from interop workaround %s",
manufacturer, interop_feature_string_(feature));
return true;
}
return false;
}
bool interop_database_remove_addr(const interop_feature_t feature,
const RawAddress* addr) {
CHECK(addr);
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 %s is a removed from interop workaround %s.",
ADDRESS_TO_LOGGABLE_CSTR(*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<interop_db_entry_t*>(list_node(node));
CHECK(entry);
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 - %s", 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: %d product_id: %d is removed from interop "
"workaround %s",
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 %s is a removed from interop workaround %s.",
ADDRESS_TO_LOGGABLE_CSTR(*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 %s",
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 %s is a removed from interop workaround %s.",
ADDRESS_TO_LOGGABLE_CSTR(*addr), interop_feature_string_(feature));
return true;
}
return false;
}
static void delete_media_player_node(void* data) {
std::string* key = static_cast<std::string*>(data);
delete key;
}
static void interop_database_save_allowlisted_media_players_list(
const config_t* config) {
media_player_list = list_new(delete_media_player_node);
for (const section_t& sec : config->sections) {
if (INTEROP_BROWSE_PLAYER_ALLOW_LIST ==
interop_feature_name_to_feature_id(sec.name.c_str())) {
LOG_WARN("found feature - %s", sec.name.c_str());
for (const entry_t& entry : sec.entries) {
list_append(media_player_list, (void*)(new std::string(entry.key)));
}
break;
}
}
}
bool interop_get_allowlisted_media_players_list(list_t* p_bl_devices) {
if (media_player_list == nullptr) return false;
const list_node_t* node = list_begin(media_player_list);
bool found = false;
while (node != list_end(media_player_list)) {
found = true;
std::string* key = (std::string*)list_node(node);
list_append(p_bl_devices, (void*)key->c_str());
node = list_next(node);
}
return found;
}