Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 50d87719 authored by Jack He's avatar Jack He
Browse files

GD-Storage: Implement ConfigCache, Mutation, and MutationEntry

* ConfigCache: A quick and thread-safe container for in-memory configs
* Mutation and MutationEntry: Transaction based methods to make multiple
  modifications to ConfigCache atomically

Bug: 157533831
Bug: 157533958
Test: atest bluetooth_test_gd
Tag: #gd-refactor
Change-Id: I550b34071ed96147a4a044a8cee766aab866ae66
parent f2c9a490
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
filegroup {
    name: "BluetoothStorageSources",
    srcs: [
            "config_cache.cc",
            "config_cache_helper.cc",
            "legacy.cc",
            "legacy_osi_config.cc",
            "mutation_entry.cc",
    ],
}

filegroup {
    name: "BluetoothStorageTestSources",
    srcs: [
            "config_cache_test.cc",
            "config_cache_helper_test.cc",
            "legacy_test.cc",
            "legacy_osi_config.cc",
            "mutation_test.cc",
    ],
}
+225 −0
Original line number Diff line number Diff line
/*
 * 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 <ios>
#include <sstream>

#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<std::string_view> kLinkKeyPropertyNames = {
    "LinkKey", "LE_KEY_PENC", "LE_KEY_PID", "LE_KEY_PCSRK", "LE_KEY_LENC", "LE_KEY_LCSRK"};

ConfigCache::ConfigCache(size_t temp_device_capacity)
    : information_sections_(), persistent_devices_(), temporary_devices_(temp_device_capacity) {}

ConfigCache::ConfigCache(ConfigCache&& other) noexcept
    : information_sections_(std::move(other.information_sections_)),
      persistent_devices_(std::move(other.persistent_devices_)),
      temporary_devices_(std::move(other.temporary_devices_)) {}

ConfigCache& ConfigCache::operator=(ConfigCache&& other) noexcept {
  if (&other == this) {
    return *this;
  }
  information_sections_ = std::move(other.information_sections_);
  persistent_devices_ = std::move(other.persistent_devices_);
  temporary_devices_ = std::move(other.temporary_devices_);
  return *this;
}

void ConfigCache::Clear() {
  information_sections_.clear();
  persistent_devices_.clear();
  temporary_devices_.clear();
}

bool ConfigCache::HasSection(const std::string& section) const {
  std::lock_guard<std::recursive_mutex> 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<std::recursive_mutex> 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<std::string> ConfigCache::GetProperty(const std::string& section, const std::string& property) const {
  std::lock_guard<std::recursive_mutex> 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<std::recursive_mutex> lock(mutex_);
  if (TrimAfterNewLine(section) || TrimAfterNewLine(property) || TrimAfterNewLine(value)) {
    android_errorWriteLog(0x534e4554, "70808273");
  }
  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<std::string, std::string>{}).first;
    }
    section_iter->second.insert_or_assign(property, std::move(value));
    return;
  }
  auto section_iter = persistent_devices_.find(section);
  if (section_iter != persistent_devices_.end()) {
    section_iter->second.insert_or_assign(property, std::move(value));
    return;
  }
  section_iter = temporary_devices_.find(section);
  if (section_iter == temporary_devices_.end()) {
    auto triple = temporary_devices_.try_emplace(section, common::ListMap<std::string, std::string>{});
    section_iter = std::get<0>(triple);
  }
  section_iter->second.insert_or_assign(property, std::move(value));
  if (IsLinkKeyProperty(property)) {
    // move paired devices
    auto section_properties = temporary_devices_.extract(section);
    persistent_devices_.insert_or_assign(section, std::move(section_properties->second));
  }
}

bool ConfigCache::RemoveSection(const std::string& section) {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  // sections are unique among all three maps, hence removing from one of them is enough
  return information_sections_.extract(section) || persistent_devices_.extract(section) ||
         temporary_devices_.extract(section);
}

bool ConfigCache::RemoveProperty(const std::string& section, const std::string& property) {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  auto section_iter = information_sections_.find(section);
  if (section_iter != information_sections_.end()) {
    return section_iter->second.extract(property).has_value();
  }
  section_iter = persistent_devices_.find(section);
  if (section_iter != persistent_devices_.end()) {
    auto value = section_iter->second.extract(property);
    if (value && IsLinkKeyProperty(property)) {
      // move unpaired device
      auto section_properties = persistent_devices_.extract(section);
      temporary_devices_.insert_or_assign(section, std::move(section_properties->second));
    }
    return value.has_value();
  }
  section_iter = temporary_devices_.find(section);
  if (section_iter != temporary_devices_.end()) {
    return section_iter->second.extract(property).has_value();
  }
  return false;
}

bool ConfigCache::IsDeviceSection(const std::string& section) {
  return hci::Address::IsValidAddress(section);
}

bool ConfigCache::IsLinkKeyProperty(const std::string& property) {
  return kLinkKeyPropertyNames.find(property) != kLinkKeyPropertyNames.end();
}

void ConfigCache::RemoveRestricted() {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  for (auto& section : persistent_devices_) {
    if (section.second.contains("Restricted")) {
      LOG_DEBUG("Removing restricted device %s", section.first.c_str());
      persistent_devices_.extract(section.first);
    }
  }
}

std::vector<std::string> ConfigCache::GetPersistentDevices() const {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  std::vector<std::string> 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(Mutation& mutation) {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  auto& entries = mutation.entries_;
  while (!entries.empty()) {
    auto entry = std::move(entries.front());
    entries.pop();
    if (entry.is_add) {
      SetProperty(std::move(entry.section), std::move(entry.property), std::move(entry.value));
    } else {
      if (entry.value.empty()) {
        RemoveSection(entry.section);
      } else {
        RemoveProperty(entry.section, entry.property);
      }
    }
  }
}

}  // namespace storage
}  // namespace bluetooth
 No newline at end of file
+94 −0
Original line number Diff line number Diff line
/*
 * 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.
 */
#pragma once

#include <list>
#include <mutex>
#include <optional>
#include <string>
#include <string_view>
#include <unordered_set>
#include <utility>
#include <vector>

#include "common/list_map.h"
#include "common/lru_cache.h"
#include "hci/address.h"
#include "os/utils.h"

namespace bluetooth {
namespace storage {

class Mutation;

// A memory operated section-key-value structured config
//
// This class is thread safe
class ConfigCache {
 public:
  explicit ConfigCache(size_t temp_device_capacity);
  virtual ~ConfigCache() = default;

  // no copy
  DISALLOW_COPY_AND_ASSIGN(ConfigCache);

  // can move
  ConfigCache(ConfigCache&& other) noexcept;
  ConfigCache& operator=(ConfigCache&& other) noexcept;

  // observers

  virtual bool HasSection(const std::string& section) const;
  virtual bool HasProperty(const std::string& section, const std::string& property) const;
  // Get property, return std::nullopt if section or property does not exist
  virtual std::optional<std::string> GetProperty(const std::string& section, const std::string& property) const;
  // Returns a copy of persistent device MAC addresses
  virtual std::vector<std::string> GetPersistentDevices() const;

  // modifiers
  // Commit all mutation entries in sequence while holding the config mutex
  virtual void Commit(Mutation& mutation);
  virtual void SetProperty(std::string section, std::string property, std::string value);
  virtual bool RemoveSection(const std::string& section);
  virtual bool RemoveProperty(const std::string& section, const std::string& property);
  // TODO: have a systematic way of doing this instead of specialized methods
  // Remove devices with "Restricted" property
  virtual void RemoveRestricted();
  // remove all content in this config cache, restore it to the state after the explicit constructor
  virtual void Clear();

  // static methods
  // Check if section is formatted as a MAC address
  static bool IsDeviceSection(const std::string& section);
  // Check if property represent one of those link keys used for paired devices
  static bool IsLinkKeyProperty(const std::string& property);

  // constants
  static constexpr std::string_view kDefaultSectionName = "Global";

 private:
  mutable std::recursive_mutex mutex_;
  // Common section that does not relate to remote device, will be written to disk
  common::ListMap<std::string, common::ListMap<std::string, std::string>> information_sections_;
  // Information about persistent devices, normally paired, will be written to disk
  common::ListMap<std::string, common::ListMap<std::string, std::string>> persistent_devices_;
  // Information about temporary devices, normally unpaired, will not be written to disk, will be evicted automatically
  // if capacity exceeds given value during initialization
  common::LruCache<std::string, common::ListMap<std::string, std::string>> temporary_devices_;
};

}  // namespace storage
}  // namespace bluetooth
+94 −0
Original line number Diff line number Diff line
/*
 * 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_helper.h"
#include "common/strings.h"

namespace bluetooth {
namespace storage {

void ConfigCacheHelper::SetBool(const std::string& section, const std::string& property, bool value) {
  config_cache_.SetProperty(section, property, value ? "true" : "false");
}

std::optional<bool> ConfigCacheHelper::GetBool(const std::string& section, const std::string& property) const {
  auto value_str = config_cache_.GetProperty(section, property);
  if (!value_str) {
    return std::nullopt;
  }
  if (*value_str == "true") {
    return true;
  } else if (*value_str == "false") {
    return false;
  } else {
    return std::nullopt;
  }
}

void ConfigCacheHelper::SetUint64(const std::string& section, const std::string& property, uint64_t value) {
  config_cache_.SetProperty(section, property, std::to_string(value));
}

std::optional<uint64_t> ConfigCacheHelper::GetUint64(const std::string& section, const std::string& property) const {
  auto value_str = config_cache_.GetProperty(section, property);
  if (!value_str) {
    return std::nullopt;
  }
  std::size_t pos = 0;
  uint64_t value = std::stoull(*value_str, &pos);
  if (pos != value_str->size()) {
    return std::nullopt;
  }
  return value;
}

void ConfigCacheHelper::SetInt(const std::string& section, const std::string& property, int value) {
  config_cache_.SetProperty(section, property, std::to_string(value));
}

std::optional<int> ConfigCacheHelper::GetInt(const std::string& section, const std::string& property) const {
  auto value_str = config_cache_.GetProperty(section, property);
  if (!value_str) {
    return std::nullopt;
  }
  std::size_t pos = 0;
  int value = std::stoi(*value_str, &pos);
  if (pos != value_str->size()) {
    return std::nullopt;
  }
  return value;
}

void ConfigCacheHelper::SetBin(
    const std::string& section, const std::string& property, const std::vector<uint8_t>& value) {
  config_cache_.SetProperty(section, property, common::ToHexString(value));
}

std::optional<std::vector<uint8_t>> ConfigCacheHelper::GetBin(
    const std::string& section, const std::string& property) const {
  auto value_str = config_cache_.GetProperty(section, property);
  if (!value_str) {
    return std::nullopt;
  }
  auto value = common::FromHexString(*value_str);
  if (!value) {
    LOG_WARN("value_str cannot be parsed to std::vector<uint8_t>");
  }
  return value;
}

}  // namespace storage
}  // namespace bluetooth
 No newline at end of file
+48 −0
Original line number Diff line number Diff line
/*
 * 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.
 */
#pragma once

#include "storage/config_cache.h"

namespace bluetooth {
namespace storage {

// A thin wrapper around ConfigCache and implement more type supports other than std::string
//
// - all SetX methods accept value as copy and std::move() in encouraged
// - all GetX methods return std::optional<X> and std::nullopt if not exist. std::optional<> can be treated as bool
class ConfigCacheHelper {
 public:
  static ConfigCacheHelper FromConfigCache(ConfigCache& config_cache) {
    return ConfigCacheHelper(config_cache);
  }
  explicit ConfigCacheHelper(ConfigCache& config_cache) : config_cache_(config_cache) {}
  virtual ~ConfigCacheHelper() = default;
  virtual void SetBool(const std::string& section, const std::string& property, bool value);
  virtual std::optional<bool> GetBool(const std::string& section, const std::string& property) const;
  virtual void SetUint64(const std::string& section, const std::string& property, uint64_t value);
  virtual std::optional<uint64_t> GetUint64(const std::string& section, const std::string& property) const;
  virtual void SetInt(const std::string& section, const std::string& property, int value);
  virtual std::optional<int> GetInt(const std::string& section, const std::string& property) const;
  virtual void SetBin(const std::string& section, const std::string& property, const std::vector<uint8_t>& value);
  virtual std::optional<std::vector<uint8_t>> GetBin(const std::string& section, const std::string& property) const;

 private:
  ConfigCache& config_cache_;
};

}  // namespace storage
}  // namespace bluetooth
Loading