1 /*
2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "storage/storage_module.h"
18 
19 #include <gmock/gmock.h>
20 #include <gtest/gtest.h>
21 
22 #include <chrono>
23 #include <cstdio>
24 #include <ctime>
25 #include <filesystem>
26 #include <iomanip>
27 #include <optional>
28 #include <thread>
29 
30 #include "module.h"
31 #include "os/files.h"
32 #include "storage/config_cache.h"
33 #include "storage/device.h"
34 #include "storage/legacy_config_file.h"
35 
36 namespace testing {
37 
38 using bluetooth::TestModuleRegistry;
39 using bluetooth::hci::Address;
40 using bluetooth::storage::ConfigCache;
41 using bluetooth::storage::Device;
42 using bluetooth::storage::LegacyConfigFile;
43 using bluetooth::storage::StorageModule;
44 
45 static const std::chrono::milliseconds kTestConfigSaveDelay = std::chrono::milliseconds(100);
46 // Assume it takes at most 1 second to write the file
47 static const std::chrono::milliseconds kTestConfigSaveWaitDelay =
48     kTestConfigSaveDelay + std::chrono::milliseconds(1000);
49 
ParseTimestamp(const std::string & timestamp,const std::string & format)50 static std::optional<std::chrono::system_clock::time_point> ParseTimestamp(
51     const std::string& timestamp, const std::string& format) {
52   std::istringstream ss(timestamp);
53   // 1. Parse to time_t from timestamp that may not contain day light saving information
54   std::tm no_dst_tm = {};
55   ss >> std::get_time(&no_dst_tm, format.c_str());
56   if (ss.fail()) {
57     return std::nullopt;
58   }
59   // 2. Make a copy of the parsed result so that we can set tm_isdst bit later
60   auto dst_tm = no_dst_tm;
61   auto no_dst_time_t = std::mktime(&no_dst_tm);
62   if (no_dst_time_t == -1) {
63     return std::nullopt;
64   }
65   // 3. Convert time_t to tm again, but let system decide if day light saving should be set at that date and time
66   auto dst_tm_only = std::localtime(&no_dst_time_t);
67   // 4. Set the correct tm_isdst bit
68   dst_tm.tm_isdst = dst_tm_only->tm_isdst;
69   auto dst_time_t = std::mktime(&dst_tm);
70   if (dst_time_t == -1) {
71     return std::nullopt;
72   }
73   // 5. Parse is to time point
74   return std::chrono::system_clock::from_time_t(dst_time_t);
75 }
76 
77 class TestStorageModule : public StorageModule {
78  public:
TestStorageModule(std::string config_file_path,std::chrono::milliseconds config_save_delay,size_t temp_devices_capacity,bool is_restricted_mode,bool is_single_user_mode)79   TestStorageModule(
80       std::string config_file_path,
81       std::chrono::milliseconds config_save_delay,
82       size_t temp_devices_capacity,
83       bool is_restricted_mode,
84       bool is_single_user_mode)
85       : StorageModule(
86             std::move(config_file_path),
87             config_save_delay,
88             temp_devices_capacity,
89             is_restricted_mode,
90             is_single_user_mode) {}
91 
GetConfigCachePublic()92   ConfigCache* GetConfigCachePublic() {
93     return StorageModule::GetConfigCache();
94   }
95 
GetMemoryOnlyConfigCachePublic()96   ConfigCache* GetMemoryOnlyConfigCachePublic() {
97     return StorageModule::GetMemoryOnlyConfigCache();
98   }
99 
SaveImmediatelyPublic()100   void SaveImmediatelyPublic() {
101     StorageModule::SaveImmediately();
102   }
103 };
104 
105 class StorageModuleTest : public Test {
106  protected:
SetUp()107   void SetUp() override {
108     temp_dir_ = std::filesystem::temp_directory_path();
109     temp_config_ = temp_dir_ / "temp_config.txt";
110     temp_backup_config_ = temp_dir_ / "temp_config.bak";
111     DeleteConfigFiles();
112     ASSERT_FALSE(std::filesystem::exists(temp_config_));
113     ASSERT_FALSE(std::filesystem::exists(temp_backup_config_));
114   }
115 
TearDown()116   void TearDown() override {
117     DeleteConfigFiles();
118   }
119 
DeleteConfigFiles()120   void DeleteConfigFiles() {
121     if (std::filesystem::exists(temp_config_)) {
122       ASSERT_TRUE(std::filesystem::remove(temp_config_));
123     }
124     if (std::filesystem::exists(temp_backup_config_)) {
125       ASSERT_TRUE(std::filesystem::remove(temp_backup_config_));
126     }
127   }
128 
129   std::filesystem::path temp_dir_;
130   std::filesystem::path temp_config_;
131   std::filesystem::path temp_backup_config_;
132 };
133 
TEST_F(StorageModuleTest,empty_config_no_op_test)134 TEST_F(StorageModuleTest, empty_config_no_op_test) {
135   // Actual test
136   auto time_before = std::chrono::system_clock::now();
137   auto* storage = new TestStorageModule(temp_config_.string(), kTestConfigSaveDelay, 10, false, false);
138   TestModuleRegistry test_registry;
139   test_registry.InjectTestModule(&StorageModule::Factory, storage);
140   test_registry.StopAll();
141   auto time_after = std::chrono::system_clock::now();
142 
143   // Verify states after test
144   ASSERT_TRUE(std::filesystem::exists(temp_config_));
145 
146   // Verify config after test
147   auto config = LegacyConfigFile::FromPath(temp_config_.string()).Read(10);
148   ASSERT_TRUE(config);
149   ASSERT_TRUE(config->HasSection(StorageModule::kInfoSection));
150   ASSERT_THAT(
151       config->GetProperty(StorageModule::kInfoSection, StorageModule::kFileSourceProperty), Optional(StrEq("Empty")));
152 
153   // Verify file creation timestamp falls between time_before and time_after
154   auto timestamp = config->GetProperty(StorageModule::kInfoSection, StorageModule::kTimeCreatedProperty);
155   ASSERT_TRUE(timestamp);
156   auto file_time = ParseTimestamp(*timestamp, StorageModule::kTimeCreatedFormat);
157   ASSERT_TRUE(file_time);
158   ASSERT_GE(std::chrono::duration_cast<std::chrono::seconds>(time_after - *file_time).count(), 0);
159   ASSERT_GE(std::chrono::duration_cast<std::chrono::seconds>(*file_time - time_before).count(), 0);
160 }
161 
162 static const std::string kReadTestConfig =
163     "[Info]\n"
164     "FileSource = Empty\n"
165     "TimeCreated = 2020-05-20 01:20:56\n"
166     "\n"
167     "[Metrics]\n"
168     "Salt256Bit = 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef\n"
169     "\n"
170     "[Adapter]\n"
171     "Address = 01:02:03:ab:cd:ef\n"
172     "LE_LOCAL_KEY_IRK = fedcba0987654321fedcba0987654321\n"
173     "LE_LOCAL_KEY_IR = fedcba0987654321fedcba0987654322\n"
174     "LE_LOCAL_KEY_DHK = fedcba0987654321fedcba0987654323\n"
175     "LE_LOCAL_KEY_ER = fedcba0987654321fedcba0987654324\n"
176     "ScanMode = 2\n"
177     "DiscoveryTimeout = 120\n"
178     "\n"
179     "[01:02:03:ab:cd:ea]\n"
180     "name = hello world\n"
181     "LinkKey = fedcba0987654321fedcba0987654328\n"
182     "\n";
183 
184 static const std::string kReadTestConfigCorrected =
185     "[Info]\n"
186     "FileSource = Empty\n"
187     "TimeCreated = 2020-05-20 01:20:56\n"
188     "\n"
189     "[Metrics]\n"
190     "Salt256Bit = 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef\n"
191     "\n"
192     "[Adapter]\n"
193     "Address = 01:02:03:ab:cd:ef\n"
194     "LE_LOCAL_KEY_IRK = fedcba0987654321fedcba0987654321\n"
195     "LE_LOCAL_KEY_IR = fedcba0987654321fedcba0987654322\n"
196     "LE_LOCAL_KEY_DHK = fedcba0987654321fedcba0987654323\n"
197     "LE_LOCAL_KEY_ER = fedcba0987654321fedcba0987654324\n"
198     "ScanMode = 2\n"
199     "DiscoveryTimeout = 120\n"
200     "\n"
201     "[01:02:03:ab:cd:ea]\n"
202     "name = hello world\n"
203     "LinkKey = fedcba0987654321fedcba0987654328\n"
204     "DevType = 1\n"
205     "\n";
206 
TEST_F(StorageModuleTest,read_existing_config_test)207 TEST_F(StorageModuleTest, read_existing_config_test) {
208   ASSERT_TRUE(bluetooth::os::WriteToFile(temp_config_.string(), kReadTestConfig));
209   // Actual test
210 
211   // Set up
212   auto* storage = new TestStorageModule(temp_config_.string(), kTestConfigSaveDelay, 10, false, false);
213   TestModuleRegistry test_registry;
214   test_registry.InjectTestModule(&StorageModule::Factory, storage);
215 
216   // Test
217   ASSERT_NE(storage->GetConfigCachePublic(), nullptr);
218   ASSERT_TRUE(storage->GetConfigCachePublic()->HasSection("Metrics"));
219   ASSERT_THAT(storage->GetConfigCachePublic()->GetPersistentSections(), ElementsAre("01:02:03:ab:cd:ea"));
220   ASSERT_THAT(
221       storage->GetConfigCachePublic()->GetProperty(StorageModule::kAdapterSection, "Address"),
222       Optional(StrEq("01:02:03:ab:cd:ef")));
223 
224   // Tear down
225   test_registry.StopAll();
226 
227   // Verify states after test
228   ASSERT_TRUE(std::filesystem::exists(temp_config_));
229 
230   // Verify config after test
231   auto config = bluetooth::os::ReadSmallFile(temp_config_.string());
232   ASSERT_TRUE(config);
233   ASSERT_EQ(*config, kReadTestConfigCorrected);
234 }
235 
TEST_F(StorageModuleTest,save_config_test)236 TEST_F(StorageModuleTest, save_config_test) {
237   // Prepare config file
238   ASSERT_TRUE(bluetooth::os::WriteToFile(temp_config_.string(), kReadTestConfig));
239 
240   // Set up
241   auto* storage = new TestStorageModule(temp_config_.string(), kTestConfigSaveDelay, 10, false, false);
242   TestModuleRegistry test_registry;
243   test_registry.InjectTestModule(&StorageModule::Factory, storage);
244 
245   // Test
246   ASSERT_NE(storage->GetConfigCachePublic(), nullptr);
247 
248   // Change a property
249   ASSERT_THAT(
250       storage->GetConfigCachePublic()->GetProperty("01:02:03:ab:cd:ea", "name"), Optional(StrEq("hello world")));
251   storage->GetConfigCachePublic()->SetProperty("01:02:03:ab:cd:ea", "name", "foo");
252   ASSERT_THAT(storage->GetConfigCachePublic()->GetProperty("01:02:03:ab:cd:ea", "name"), Optional(StrEq("foo")));
253   std::this_thread::sleep_for(kTestConfigSaveWaitDelay);
254   auto config = LegacyConfigFile::FromPath(temp_config_.string()).Read(10);
255   ASSERT_TRUE(config);
256   ASSERT_THAT(config->GetProperty("01:02:03:ab:cd:ea", "name"), Optional(StrEq("foo")));
257 
258   // Remove a property
259   storage->GetConfigCachePublic()->RemoveProperty("01:02:03:ab:cd:ea", "name");
260   std::this_thread::sleep_for(kTestConfigSaveWaitDelay);
261   config = LegacyConfigFile::FromPath(temp_config_.string()).Read(10);
262   ASSERT_TRUE(config);
263   ASSERT_FALSE(config->HasProperty("01:02:03:ab:cd:ea", "name"));
264 
265   // Remove a section
266   storage->GetConfigCachePublic()->RemoveSection("01:02:03:ab:cd:ea");
267   std::this_thread::sleep_for(kTestConfigSaveWaitDelay);
268   config = LegacyConfigFile::FromPath(temp_config_.string()).Read(10);
269   ASSERT_TRUE(config);
270   ASSERT_FALSE(config->HasSection("01:02:03:ab:cd:ea"));
271 
272   // Add a section and save immediately
273   storage->GetConfigCachePublic()->SetProperty("01:02:03:ab:cd:eb", "LinkKey", "123456");
274   storage->SaveImmediatelyPublic();
275   config = LegacyConfigFile::FromPath(temp_config_.string()).Read(10);
276   ASSERT_TRUE(config);
277   ASSERT_TRUE(config->HasSection("01:02:03:ab:cd:eb"));
278 
279   // Tear down
280   test_registry.StopAll();
281 
282   // Verify states after test
283   ASSERT_TRUE(std::filesystem::exists(temp_config_));
284 }
285 
TEST_F(StorageModuleTest,get_bonded_devices_test)286 TEST_F(StorageModuleTest, get_bonded_devices_test) {
287   // Prepare config file
288   ASSERT_TRUE(bluetooth::os::WriteToFile(temp_config_.string(), kReadTestConfig));
289 
290   // Set up
291   auto* storage = new TestStorageModule(temp_config_.string(), kTestConfigSaveDelay, 10, false, false);
292   TestModuleRegistry test_registry;
293   test_registry.InjectTestModule(&StorageModule::Factory, storage);
294 
295   ASSERT_THAT(
296       storage->GetBondedDevices(),
297       ElementsAre(
298           Device(storage->GetConfigCachePublic(), storage->GetMemoryOnlyConfigCachePublic(), "01:02:03:ab:cd:ea")));
299 
300   // Tear down
301   test_registry.StopAll();
302 }
303 
TEST_F(StorageModuleTest,get_adapter_config_test)304 TEST_F(StorageModuleTest, get_adapter_config_test) {
305   // Prepare config file
306   ASSERT_TRUE(bluetooth::os::WriteToFile(temp_config_.string(), kReadTestConfig));
307 
308   // Set up
309   auto* storage = new TestStorageModule(temp_config_.string(), kTestConfigSaveDelay, 10, false, false);
310   TestModuleRegistry test_registry;
311   test_registry.InjectTestModule(&StorageModule::Factory, storage);
312 
313   auto address = Address::FromString("01:02:03:ab:cd:ef");
314   ASSERT_TRUE(address);
315   ASSERT_THAT(storage->GetAdapterConfig().GetAddress(), Optional(Eq(address)));
316 
317   // Tear down
318   test_registry.StopAll();
319 }
320 
321 }  // namespace testing