/* * 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/storage_module.h" #include #include #include #include #include #include #include #include #include #include "common/bind.h" #include "module.h" #include "os/fake_timer/fake_timerfd.h" #include "os/files.h" #include "storage/config_cache.h" #include "storage/config_keys.h" #include "storage/device.h" #include "storage/legacy_config_file.h" namespace testing { using bluetooth::TestModuleRegistry; using bluetooth::hci::Address; using bluetooth::os::fake_timer::fake_timerfd_advance; using bluetooth::storage::ConfigCache; using bluetooth::storage::Device; using bluetooth::storage::LegacyConfigFile; using bluetooth::storage::StorageModule; static const std::chrono::milliseconds kTestConfigSaveDelay = std::chrono::milliseconds(100); static const size_t kTestTempDevicesCapacity = 10; class TestStorageModule : public StorageModule { public: TestStorageModule( std::string config_file_path, std::chrono::milliseconds config_save_delay, bool is_restricted_mode, bool is_single_user_mode) : StorageModule( std::move(config_file_path), config_save_delay, kTestTempDevicesCapacity, is_restricted_mode, is_single_user_mode) {} ConfigCache* GetMemoryOnlyConfigCachePublic() { return StorageModule::GetMemoryOnlyConfigCache(); } bool HasSectionPublic(const std::string& section) const { return StorageModule::HasSection(section); } bool HasPropertyPublic(const std::string& section, const std::string& property) const { return HasProperty(section, property); } std::optional GetPropertyPublic( const std::string& section, const std::string& property) const { return GetProperty(section, property); } void SetPropertyPublic(std::string section, std::string property, std::string value) { return SetProperty(section, property, value); } std::vector GetPersistentSectionsPublic() const { return GetPersistentSections(); } bool RemovePropertyPublic(const std::string& section, const std::string& property) { return RemoveProperty(section, property); } void ConvertEncryptOrDecryptKeyIfNeededPublic() { return ConvertEncryptOrDecryptKeyIfNeeded(); } void RemoveSectionWithPropertyPublic(const std::string& property) { return RemoveSectionWithProperty(property); } void RemoveSectionPublic(const std::string& section) { return RemoveSection(section); } }; class StorageModuleTest : public Test { protected: void SetUp() override { temp_dir_ = std::filesystem::temp_directory_path(); temp_config_ = temp_dir_ / "temp_config.txt"; DeleteConfigFiles(); ASSERT_FALSE(std::filesystem::exists(temp_config_)); } void TearDown() override { test_registry_.StopAll(); DeleteConfigFiles(); } void DeleteConfigFiles() { if (std::filesystem::exists(temp_config_)) { ASSERT_TRUE(std::filesystem::remove(temp_config_)); } } void FakeTimerAdvance(std::chrono::milliseconds time) { auto handler = test_registry_.GetTestModuleHandler(&StorageModule::Factory); handler->Post(bluetooth::common::BindOnce(fake_timerfd_advance, time.count())); } bool WaitForReactorIdle(std::chrono::milliseconds time) { bool stopped = test_registry_.GetTestThread().GetReactor()->WaitForIdle(std::chrono::seconds(2)); if (!stopped) { return false; } FakeTimerAdvance(time); return test_registry_.GetTestThread().GetReactor()->WaitForIdle(std::chrono::seconds(2)); } bluetooth::os::Handler* handler_; TestModuleRegistry test_registry_; std::filesystem::path temp_dir_; std::filesystem::path temp_config_; }; TEST_F(StorageModuleTest, empty_config_no_op_test) { // Verify state before test ASSERT_FALSE(std::filesystem::exists(temp_config_)); // Actual test auto* storage = new TestStorageModule(temp_config_.string(), kTestConfigSaveDelay, false, false); test_registry_.InjectTestModule(&StorageModule::Factory, storage); test_registry_.StopAll(); // Verify states after test ASSERT_TRUE(std::filesystem::exists(temp_config_)); // Verify config after test auto config = LegacyConfigFile::FromPath(temp_config_.string()).Read(kTestTempDevicesCapacity); ASSERT_TRUE(config); ASSERT_TRUE(config->HasSection(StorageModule::kInfoSection)); } static const std::string kReadTestConfig = "[Info]\n" "TimeCreated = 2020-05-20 01:20:56\n" "\n" "[Metrics]\n" "Salt256Bit = 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef\n" "\n" "[Adapter]\n" "Address = 01:02:03:ab:cd:ef\n" "LE_LOCAL_KEY_IRK = fedcba0987654321fedcba0987654321\n" "LE_LOCAL_KEY_IR = fedcba0987654321fedcba0987654322\n" "LE_LOCAL_KEY_DHK = fedcba0987654321fedcba0987654323\n" "LE_LOCAL_KEY_ER = fedcba0987654321fedcba0987654324\n" "ScanMode = 2\n" "DiscoveryTimeout = 120\n" "\n" "[01:02:03:ab:cd:ea]\n" "Name = hello world\n" "LinkKey = fedcba0987654321fedcba0987654328\n" "\n"; TEST_F(StorageModuleTest, read_existing_config_test) { ASSERT_TRUE(bluetooth::os::WriteToFile(temp_config_.string(), kReadTestConfig)); // Actual test // Set up auto* storage = new TestStorageModule(temp_config_.string(), kTestConfigSaveDelay, false, false); test_registry_.InjectTestModule(&StorageModule::Factory, storage); // Test ASSERT_TRUE(storage->HasSectionPublic("Metrics")); ASSERT_THAT(storage->GetPersistentSectionsPublic(), ElementsAre("01:02:03:ab:cd:ea")); ASSERT_THAT( storage->GetPropertyPublic(StorageModule::kAdapterSection, BTIF_STORAGE_KEY_ADDRESS), Optional(StrEq("01:02:03:ab:cd:ef"))); // Tear down test_registry_.StopAll(); // Verify states after test ASSERT_TRUE(std::filesystem::exists(temp_config_)); // Verify config after test auto config = bluetooth::os::ReadSmallFile(temp_config_.string()); ASSERT_TRUE(config); ASSERT_EQ(*config, kReadTestConfig); } TEST_F(StorageModuleTest, save_config_test) { // Prepare config file ASSERT_TRUE(bluetooth::os::WriteToFile(temp_config_.string(), kReadTestConfig)); // Set up auto* storage = new TestStorageModule(temp_config_.string(), kTestConfigSaveDelay, false, false); test_registry_.InjectTestModule(&StorageModule::Factory, storage); // Test // Change a property ASSERT_THAT( storage->GetPropertyPublic("01:02:03:ab:cd:ea", BTIF_STORAGE_KEY_NAME), Optional(StrEq("hello world"))); storage->SetPropertyPublic("01:02:03:ab:cd:ea", BTIF_STORAGE_KEY_NAME, "foo"); ASSERT_THAT( storage->GetPropertyPublic("01:02:03:ab:cd:ea", BTIF_STORAGE_KEY_NAME), Optional(StrEq("foo"))); ASSERT_TRUE(WaitForReactorIdle(kTestConfigSaveDelay)); auto config = LegacyConfigFile::FromPath(temp_config_.string()).Read(kTestTempDevicesCapacity); ASSERT_TRUE(config); ASSERT_THAT( config->GetProperty("01:02:03:ab:cd:ea", BTIF_STORAGE_KEY_NAME), Optional(StrEq("foo"))); // Remove a property storage->RemovePropertyPublic("01:02:03:ab:cd:ea", BTIF_STORAGE_KEY_NAME); ASSERT_TRUE(WaitForReactorIdle(kTestConfigSaveDelay)); bluetooth::log::info("After waiting 2"); config = LegacyConfigFile::FromPath(temp_config_.string()).Read(kTestTempDevicesCapacity); ASSERT_TRUE(config); ASSERT_FALSE(config->HasProperty("01:02:03:ab:cd:ea", BTIF_STORAGE_KEY_NAME)); // Remove a section storage->RemoveSectionPublic("01:02:03:ab:cd:ea"); ASSERT_TRUE(WaitForReactorIdle(kTestConfigSaveDelay)); bluetooth::log::info("After waiting 3"); config = LegacyConfigFile::FromPath(temp_config_.string()).Read(kTestTempDevicesCapacity); ASSERT_TRUE(config); ASSERT_FALSE(config->HasSection("01:02:03:ab:cd:ea")); // Tear down test_registry_.StopAll(); // Verify states after test ASSERT_TRUE(std::filesystem::exists(temp_config_)); } TEST_F(StorageModuleTest, get_bonded_devices_test) { // Prepare config file ASSERT_TRUE(bluetooth::os::WriteToFile(temp_config_.string(), kReadTestConfig)); // Set up auto* storage = new TestStorageModule(temp_config_.string(), kTestConfigSaveDelay, false, false); test_registry_.InjectTestModule(&StorageModule::Factory, storage); ASSERT_EQ(storage->GetBondedDevices().size(), 1u); auto address = Address::FromString("01:02:03:ab:cd:ea"); ASSERT_EQ(address, storage->GetBondedDevices()[0].GetAddress()); // Tear down test_registry_.StopAll(); } TEST_F(StorageModuleTest, unchanged_config_causes_no_write) { // Prepare config file ASSERT_TRUE(bluetooth::os::WriteToFile(temp_config_.string(), kReadTestConfig)); // Set up auto* storage = new TestStorageModule(temp_config_.string(), kTestConfigSaveDelay, false, false); test_registry_.InjectTestModule(&StorageModule::Factory, storage); ASSERT_EQ(storage->GetBondedDevices().size(), 1u); auto address = Address::FromString("01:02:03:ab:cd:ea"); ASSERT_EQ(address, storage->GetBondedDevices()[0].GetAddress()); // Remove the file after it was read, so we can check if it was written with exists() DeleteConfigFiles(); // Tear down test_registry_.StopAll(); ASSERT_FALSE(std::filesystem::exists(temp_config_)); } TEST_F(StorageModuleTest, changed_config_causes_a_write) { // Prepare config file ASSERT_TRUE(bluetooth::os::WriteToFile(temp_config_.string(), kReadTestConfig)); // Set up auto* storage = new TestStorageModule(temp_config_.string(), kTestConfigSaveDelay, false, false); test_registry_.InjectTestModule(&StorageModule::Factory, storage); // Remove the file after it was read, so we can check if it was written with exists() DeleteConfigFiles(); // Change a property storage->SetPropertyPublic("01:02:03:ab:cd:ea", BTIF_STORAGE_KEY_NAME, "foo"); ASSERT_TRUE(WaitForReactorIdle(std::chrono::milliseconds(1))); // Tear down test_registry_.StopAll(); ASSERT_TRUE(std::filesystem::exists(temp_config_)); } TEST_F(StorageModuleTest, no_config_causes_a_write) { // Set up auto* storage = new TestStorageModule(temp_config_.string(), kTestConfigSaveDelay, false, false); test_registry_.InjectTestModule(&StorageModule::Factory, storage); // Tear down test_registry_.StopAll(); ASSERT_TRUE(std::filesystem::exists(temp_config_)); } } // namespace testing