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