/* * Copyright 2020 The Android Open Source Project * * 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. */ #include "storage/config_cache.h" #include #include #include #include "hci/enum_helper.h" #include "storage/mutation.h" namespace { bool TrimAfterNewLine(std::string& value) { std::string value_no_newline; size_t newline_position = value.find_first_of('\n'); if (newline_position != std::string::npos) { value.erase(newline_position); return true; } return false; } } // namespace namespace bluetooth { namespace storage { const std::unordered_set kLePropertyNames = { "LE_KEY_PENC", "LE_KEY_PID", "LE_KEY_PCSRK", "LE_KEY_LENC", "LE_KEY_LCSRK"}; const std::unordered_set kClassicPropertyNames = { "LinkKey", "SdpDiMaufacturer", "SdpDiModel", "SdpDiHardwareVersion", "SdpDiVendorSource"}; const std::string ConfigCache::kDefaultSectionName = "Global"; ConfigCache::ConfigCache(size_t temp_device_capacity, std::unordered_set persistent_property_names) : persistent_property_names_(std::move(persistent_property_names)), information_sections_(), persistent_devices_(), temporary_devices_(temp_device_capacity) {} void ConfigCache::SetPersistentConfigChangedCallback(std::function persistent_config_changed_callback) { std::lock_guard lock(mutex_); persistent_config_changed_callback_ = std::move(persistent_config_changed_callback); } ConfigCache::ConfigCache(ConfigCache&& other) noexcept : persistent_config_changed_callback_(std::move(other.persistent_config_changed_callback_)), persistent_property_names_(std::move(other.persistent_property_names_)), information_sections_(std::move(other.information_sections_)), persistent_devices_(std::move(other.persistent_devices_)), temporary_devices_(std::move(other.temporary_devices_)) { // std::function will be in a valid but unspecified state after std::move(), hence resetting it other.persistent_config_changed_callback_ = {}; } ConfigCache& ConfigCache::operator=(ConfigCache&& other) noexcept { if (&other == this) { return *this; } std::lock_guard my_lock(mutex_); std::lock_guard others_lock(other.mutex_); persistent_config_changed_callback_.swap(other.persistent_config_changed_callback_); other.persistent_config_changed_callback_ = {}; persistent_property_names_ = std::move(other.persistent_property_names_); information_sections_ = std::move(other.information_sections_); persistent_devices_ = std::move(other.persistent_devices_); temporary_devices_ = std::move(other.temporary_devices_); return *this; } bool ConfigCache::operator==(const ConfigCache& rhs) const { std::lock_guard my_lock(mutex_); std::lock_guard others_lock(rhs.mutex_); return persistent_property_names_ == rhs.persistent_property_names_ && information_sections_ == rhs.information_sections_ && persistent_devices_ == rhs.persistent_devices_ && temporary_devices_ == rhs.temporary_devices_; } bool ConfigCache::operator!=(const ConfigCache& rhs) const { return !(*this == rhs); } void ConfigCache::Clear() { std::lock_guard lock(mutex_); if (information_sections_.size() > 0) { information_sections_.clear(); PersistentConfigChangedCallback(); } if (persistent_devices_.size() > 0) { persistent_devices_.clear(); PersistentConfigChangedCallback(); } if (temporary_devices_.size() > 0) { temporary_devices_.clear(); } } bool ConfigCache::HasSection(const std::string& section) const { std::lock_guard lock(mutex_); return information_sections_.contains(section) || persistent_devices_.contains(section) || temporary_devices_.contains(section); } bool ConfigCache::HasProperty(const std::string& section, const std::string& property) const { std::lock_guard lock(mutex_); auto section_iter = information_sections_.find(section); if (section_iter != information_sections_.end()) { return section_iter->second.find(property) != section_iter->second.end(); } section_iter = persistent_devices_.find(section); if (section_iter != persistent_devices_.end()) { return section_iter->second.find(property) != section_iter->second.end(); } section_iter = temporary_devices_.find(section); if (section_iter != temporary_devices_.end()) { return section_iter->second.find(property) != section_iter->second.end(); } return false; } std::optional ConfigCache::GetProperty(const std::string& section, const std::string& property) const { std::lock_guard lock(mutex_); auto section_iter = information_sections_.find(section); if (section_iter != information_sections_.end()) { auto property_iter = section_iter->second.find(property); if (property_iter != section_iter->second.end()) { return property_iter->second; } } section_iter = persistent_devices_.find(section); if (section_iter != persistent_devices_.end()) { auto property_iter = section_iter->second.find(property); if (property_iter != section_iter->second.end()) { return property_iter->second; } } section_iter = temporary_devices_.find(section); if (section_iter != temporary_devices_.end()) { auto property_iter = section_iter->second.find(property); if (property_iter != section_iter->second.end()) { return property_iter->second; } } return std::nullopt; } void ConfigCache::SetProperty(std::string section, std::string property, std::string value) { std::lock_guard lock(mutex_); if (TrimAfterNewLine(section) || TrimAfterNewLine(property) || TrimAfterNewLine(value)) { android_errorWriteLog(0x534e4554, "70808273"); } ASSERT_LOG(!section.empty(), "Empty section name not allowed"); ASSERT_LOG(!property.empty(), "Empty property name not allowed"); if (!IsDeviceSection(section)) { auto section_iter = information_sections_.find(section); if (section_iter == information_sections_.end()) { section_iter = information_sections_.try_emplace_back(section, common::ListMap{}).first; } section_iter->second.insert_or_assign(property, std::move(value)); PersistentConfigChangedCallback(); return; } auto section_iter = persistent_devices_.find(section); if (section_iter == persistent_devices_.end() && IsPersistentProperty(property)) { // move paired devices or create new paired device when a link key is set auto section_properties = temporary_devices_.extract(section); if (section_properties) { section_iter = persistent_devices_.try_emplace_back(section, std::move(section_properties->second)).first; } else { section_iter = persistent_devices_.try_emplace_back(section, common::ListMap{}).first; } } if (section_iter != persistent_devices_.end()) { section_iter->second.insert_or_assign(property, std::move(value)); PersistentConfigChangedCallback(); return; } section_iter = temporary_devices_.find(section); if (section_iter == temporary_devices_.end()) { auto triple = temporary_devices_.try_emplace(section, common::ListMap{}); section_iter = std::get<0>(triple); } section_iter->second.insert_or_assign(property, std::move(value)); } bool ConfigCache::RemoveSection(const std::string& section) { std::lock_guard lock(mutex_); // sections are unique among all three maps, hence removing from one of them is enough if (information_sections_.extract(section) || persistent_devices_.extract(section)) { PersistentConfigChangedCallback(); return true; } else { return temporary_devices_.extract(section).has_value(); } } bool ConfigCache::RemoveProperty(const std::string& section, const std::string& property) { std::lock_guard lock(mutex_); auto section_iter = information_sections_.find(section); if (section_iter != information_sections_.end()) { auto value = section_iter->second.extract(property); // if section is empty after removal, remove the whole section as empty section is not allowed if (section_iter->second.size() == 0) { information_sections_.erase(section_iter); } if (value.has_value()) { PersistentConfigChangedCallback(); return true; } else { return false; } } section_iter = persistent_devices_.find(section); if (section_iter != persistent_devices_.end()) { auto value = section_iter->second.extract(property); // if section is empty after removal, remove the whole section as empty section is not allowed if (section_iter->second.size() == 0) { persistent_devices_.erase(section_iter); } else if (value && IsPersistentProperty(property)) { // move unpaired device auto section_properties = persistent_devices_.extract(section); temporary_devices_.insert_or_assign(section, std::move(section_properties->second)); } if (value.has_value()) { PersistentConfigChangedCallback(); return true; } else { return false; } } section_iter = temporary_devices_.find(section); if (section_iter != temporary_devices_.end()) { auto value = section_iter->second.extract(property); if (section_iter->second.size() == 0) { temporary_devices_.erase(section_iter); } return value.has_value(); } return false; } bool ConfigCache::IsDeviceSection(const std::string& section) { return hci::Address::IsValidAddress(section); } bool ConfigCache::IsPersistentProperty(const std::string& property) const { return persistent_property_names_.find(property) != persistent_property_names_.end(); } void ConfigCache::RemoveSectionWithProperty(const std::string& property) { std::lock_guard lock(mutex_); size_t num_persistent_removed = 0; for (auto* config_section : {&information_sections_, &persistent_devices_}) { for (auto it = config_section->begin(); it != config_section->end();) { if (it->second.contains(property)) { LOG_INFO("Removing persistent section %s with property %s", it->first.c_str(), property.c_str()); it = config_section->erase(it); num_persistent_removed++; continue; } it++; } } for (auto it = temporary_devices_.begin(); it != temporary_devices_.end();) { if (it->second.contains(property)) { LOG_INFO("Removing temporary section %s with property %s", it->first.c_str(), property.c_str()); it = temporary_devices_.erase(it); continue; } it++; } if (num_persistent_removed > 0) { PersistentConfigChangedCallback(); } } std::vector ConfigCache::GetPersistentSections() const { std::lock_guard lock(mutex_); std::vector paired_devices; paired_devices.reserve(persistent_devices_.size()); for (const auto& elem : persistent_devices_) { paired_devices.emplace_back(elem.first); } return paired_devices; } void ConfigCache::Commit(std::queue& mutation_entries) { std::lock_guard lock(mutex_); while (!mutation_entries.empty()) { auto entry = std::move(mutation_entries.front()); mutation_entries.pop(); switch (entry.entry_type) { case MutationEntry::EntryType::SET: SetProperty(std::move(entry.section), std::move(entry.property), std::move(entry.value)); break; case MutationEntry::EntryType::REMOVE_PROPERTY: RemoveProperty(entry.section, entry.property); break; case MutationEntry::EntryType::REMOVE_SECTION: RemoveSection(entry.section); break; // do not write a default case so that when a new enum is defined, compilation would fail automatically } } } std::string ConfigCache::SerializeToLegacyFormat() const { std::lock_guard lock(mutex_); std::stringstream serialized; for (const auto* config_section : {&information_sections_, &persistent_devices_}) { for (const auto& section : *config_section) { serialized << "[" << section.first << "]" << std::endl; for (const auto& property : section.second) { serialized << property.first << " = " << property.second << std::endl; } serialized << std::endl; } } return serialized.str(); } std::vector ConfigCache::GetSectionNamesWithProperty( const std::string& property) const { std::lock_guard lock(mutex_); std::vector result; for (auto* config_section : {&information_sections_, &persistent_devices_}) { for (const auto& elem : *config_section) { auto it = elem.second.find(property); if (it != elem.second.end()) { result.emplace_back(SectionAndPropertyValue{.section = elem.first, .property = it->second}); continue; } } } for (const auto& elem : temporary_devices_) { auto it = elem.second.find(property); if (it != elem.second.end()) { result.emplace_back(SectionAndPropertyValue{.section = elem.first, .property = it->second}); continue; } } return result; } namespace { bool FixDeviceTypeInconsistencyInSection( const std::string& section_name, common::ListMap& device_section_entries) { if (!hci::Address::IsValidAddress(section_name)) { return false; } bool is_le = false; bool is_classic = false; // default hci::DeviceType device_type = hci::DeviceType::BR_EDR; for (const auto& entry : device_section_entries) { if (kLePropertyNames.find(entry.first) != kLePropertyNames.end()) { is_le = true; } if (kClassicPropertyNames.find(entry.first) != kClassicPropertyNames.end()) { is_classic = true; } } if (is_classic && is_le) { device_type = hci::DeviceType::DUAL; } else if (is_classic) { device_type = hci::DeviceType::BR_EDR; } else if (is_le) { device_type = hci::DeviceType::LE; } bool inconsistent = true; std::string device_type_str = std::to_string(device_type); auto it = device_section_entries.find("DevType"); if (it != device_section_entries.end()) { inconsistent = device_type_str != it->second; if (inconsistent) { it->second = std::move(device_type_str); } } else { device_section_entries.insert_or_assign("DevType", std::move(device_type_str)); } return inconsistent; } } // namespace bool ConfigCache::FixDeviceTypeInconsistencies() { std::lock_guard lock(mutex_); bool persistent_device_changed = false; for (auto* config_section : {&information_sections_, &persistent_devices_}) { for (auto& elem : *config_section) { if (FixDeviceTypeInconsistencyInSection(elem.first, elem.second)) { persistent_device_changed = true; } } } bool temp_device_changed = false; for (auto& elem : temporary_devices_) { if (FixDeviceTypeInconsistencyInSection(elem.first, elem.second)) { temp_device_changed = true; } } if (persistent_device_changed) { PersistentConfigChangedCallback(); } return persistent_device_changed || temp_device_changed; } bool ConfigCache::HasAtLeastOneMatchingPropertiesInSection( const std::string& section, const std::unordered_set& property_names) const { std::lock_guard lock(mutex_); const common::ListMap* section_ptr; if (!IsDeviceSection(section)) { auto section_iter = information_sections_.find(section); if (section_iter == information_sections_.end()) { return false; } section_ptr = §ion_iter->second; } else { auto section_iter = persistent_devices_.find(section); if (section_iter == persistent_devices_.end()) { section_iter = temporary_devices_.find(section); if (section_iter == temporary_devices_.end()) { return false; } } section_ptr = §ion_iter->second; } for (const auto& property : *section_ptr) { if (property_names.count(property.first) > 0) { return true; } } return false; } bool ConfigCache::IsPersistentSection(const std::string& section) const { std::lock_guard lock(mutex_); return persistent_devices_.contains(section); } } // namespace storage } // namespace bluetooth