/******************************************************************************
 *
 *  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 <bluetooth/log.h>
#include <ctype.h>
#include <fcntl.h>
#include <hardware/bluetooth.h>
#include <pthread.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 "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 <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;

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;

namespace std {
template <>
struct formatter<interop_bl_type> : enum_formatter<interop_bl_type> {};
}  // 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<interop_db_entry_t*>(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;
}
