1 /*
2 * Copyright (C) 2024 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 <string>
18 #include <vector>
19 #include <cstdio>
20
21 #include <sys/stat.h>
22 #include "aconfig_storage/aconfig_storage_read_api.hpp"
23 #include "aconfig_storage/aconfig_storage_write_api.hpp"
24 #include <gtest/gtest.h>
25 #include <android-base/file.h>
26 #include <android-base/result.h>
27
28 #include "rust/cxx.h"
29 #include "aconfig_storage/lib.rs.h"
30
31 using namespace android::base;
32
33 namespace api = aconfig_storage;
34 namespace private_api = aconfig_storage::private_internal_api;
35
36 class AconfigStorageTest : public ::testing::Test {
37 protected:
copy_to_rw_temp_file(std::string const & source_file)38 Result<std::string> copy_to_rw_temp_file(std::string const& source_file) {
39 auto temp_file = std::string(std::tmpnam(nullptr));
40 auto content = std::string();
41 if (!ReadFileToString(source_file, &content)) {
42 return Error() << "failed to read file: " << source_file;
43 }
44 if (!WriteStringToFile(content, temp_file)) {
45 return Error() << "failed to copy file: " << source_file;
46 }
47 if (chmod(temp_file.c_str(),
48 S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH) == -1) {
49 return Error() << "failed to chmod";
50 }
51 return temp_file;
52 }
53
SetUp()54 void SetUp() override {
55 auto const test_dir = android::base::GetExecutableDirectory();
56 flag_val = *copy_to_rw_temp_file(test_dir + "/flag.val");
57 flag_info = *copy_to_rw_temp_file(test_dir + "/flag.info");
58 }
59
TearDown()60 void TearDown() override {
61 std::remove(flag_val.c_str());
62 std::remove(flag_info.c_str());
63 }
64
65 std::string flag_val;
66 std::string flag_info;
67 };
68
69 /// Negative test to lock down the error when mapping a non writeable storage file
TEST_F(AconfigStorageTest,test_non_writable_storage_file_mapping)70 TEST_F(AconfigStorageTest, test_non_writable_storage_file_mapping) {
71 ASSERT_TRUE(chmod(flag_val.c_str(), S_IRUSR | S_IRGRP | S_IROTH) != -1);
72 auto mapped_file_result = api::map_mutable_storage_file(flag_val);
73 ASSERT_FALSE(mapped_file_result.ok());
74 auto it = mapped_file_result.error().message().find("cannot map nonwriteable file");
75 ASSERT_TRUE(it != std::string::npos) << mapped_file_result.error().message();
76 }
77
78 /// Test to lock down storage flag value update api
TEST_F(AconfigStorageTest,test_boolean_flag_value_update)79 TEST_F(AconfigStorageTest, test_boolean_flag_value_update) {
80 auto mapped_file_result = api::map_mutable_storage_file(flag_val);
81 ASSERT_TRUE(mapped_file_result.ok());
82 auto mapped_file = std::unique_ptr<api::MutableMappedStorageFile>(*mapped_file_result);
83
84 for (int offset = 0; offset < 8; ++offset) {
85 auto update_result = api::set_boolean_flag_value(*mapped_file, offset, true);
86 ASSERT_TRUE(update_result.ok());
87 auto value = api::get_boolean_flag_value(*mapped_file, offset);
88 ASSERT_TRUE(value.ok());
89 ASSERT_TRUE(*value);
90 }
91
92 // load the file on disk and check has been updated
93 std::ifstream file(flag_val, std::ios::binary | std::ios::ate);
94 std::streamsize size = file.tellg();
95 file.seekg(0, std::ios::beg);
96
97 std::vector<uint8_t> buffer(size);
98 file.read(reinterpret_cast<char *>(buffer.data()), size);
99
100 auto content = rust::Slice<const uint8_t>(
101 buffer.data(), mapped_file->file_size);
102
103 for (int offset = 0; offset < 8; ++offset) {
104 auto value_cxx = get_boolean_flag_value_cxx(content, offset);
105 ASSERT_TRUE(value_cxx.query_success);
106 ASSERT_TRUE(value_cxx.flag_value);
107 }
108 }
109
110 /// Negative test to lock down the error when querying flag value out of range
TEST_F(AconfigStorageTest,test_invalid_boolean_flag_value_update)111 TEST_F(AconfigStorageTest, test_invalid_boolean_flag_value_update) {
112 auto mapped_file_result = api::map_mutable_storage_file(flag_val);
113 ASSERT_TRUE(mapped_file_result.ok());
114 auto mapped_file = std::unique_ptr<api::MutableMappedStorageFile>(*mapped_file_result);
115 auto update_result = api::set_boolean_flag_value(*mapped_file, 8, true);
116 ASSERT_FALSE(update_result.ok());
117 ASSERT_EQ(update_result.error().message(),
118 std::string("InvalidStorageFileOffset(Flag value offset goes beyond the end of the file.)"));
119 }
120
121 /// Test to lock down storage flag has server override update api
TEST_F(AconfigStorageTest,test_flag_has_server_override_update)122 TEST_F(AconfigStorageTest, test_flag_has_server_override_update) {
123 auto mapped_file_result = api::map_mutable_storage_file(flag_info);
124 ASSERT_TRUE(mapped_file_result.ok());
125 auto mapped_file = std::unique_ptr<api::MutableMappedStorageFile>(*mapped_file_result);
126
127 for (int offset = 0; offset < 8; ++offset) {
128 auto update_result = api::set_flag_has_server_override(
129 *mapped_file, api::FlagValueType::Boolean, offset, true);
130 ASSERT_TRUE(update_result.ok()) << update_result.error();
131 auto attribute = api::get_flag_attribute(
132 *mapped_file, api::FlagValueType::Boolean, offset);
133 ASSERT_TRUE(attribute.ok());
134 ASSERT_TRUE(*attribute & api::FlagInfoBit::HasServerOverride);
135 }
136
137 // load the file on disk and check has been updated
138 std::ifstream file(flag_info, std::ios::binary | std::ios::ate);
139 std::streamsize size = file.tellg();
140 file.seekg(0, std::ios::beg);
141
142 std::vector<uint8_t> buffer(size);
143 file.read(reinterpret_cast<char *>(buffer.data()), size);
144
145 auto content = rust::Slice<const uint8_t>(
146 buffer.data(), mapped_file->file_size);
147
148 for (int offset = 0; offset < 8; ++offset) {
149 auto attribute = get_flag_attribute_cxx(content, api::FlagValueType::Boolean, offset);
150 ASSERT_TRUE(attribute.query_success);
151 ASSERT_TRUE(attribute.flag_attribute & api::FlagInfoBit::HasServerOverride);
152 }
153
154 for (int offset = 0; offset < 8; ++offset) {
155 auto update_result = api::set_flag_has_server_override(
156 *mapped_file, api::FlagValueType::Boolean, offset, false);
157 ASSERT_TRUE(update_result.ok());
158 auto attribute = api::get_flag_attribute(
159 *mapped_file, api::FlagValueType::Boolean, offset);
160 ASSERT_TRUE(attribute.ok());
161 ASSERT_FALSE(*attribute & api::FlagInfoBit::HasServerOverride);
162 }
163
164 std::ifstream file2(flag_info, std::ios::binary);
165 buffer.clear();
166 file2.read(reinterpret_cast<char *>(buffer.data()), size);
167 for (int offset = 0; offset < 8; ++offset) {
168 auto attribute = get_flag_attribute_cxx(content, api::FlagValueType::Boolean, offset);
169 ASSERT_TRUE(attribute.query_success);
170 ASSERT_FALSE(attribute.flag_attribute & api::FlagInfoBit::HasServerOverride);
171 }
172 }
173
174 /// Test to lock down storage flag has local override update api
TEST_F(AconfigStorageTest,test_flag_has_local_override_update)175 TEST_F(AconfigStorageTest, test_flag_has_local_override_update) {
176 auto mapped_file_result = api::map_mutable_storage_file(flag_info);
177 ASSERT_TRUE(mapped_file_result.ok());
178 auto mapped_file = std::unique_ptr<api::MutableMappedStorageFile>(*mapped_file_result);
179
180 for (int offset = 0; offset < 8; ++offset) {
181 auto update_result = api::set_flag_has_local_override(
182 *mapped_file, api::FlagValueType::Boolean, offset, true);
183 ASSERT_TRUE(update_result.ok());
184 auto attribute = api::get_flag_attribute(
185 *mapped_file, api::FlagValueType::Boolean, offset);
186 ASSERT_TRUE(attribute.ok());
187 ASSERT_TRUE(*attribute & api::FlagInfoBit::HasLocalOverride);
188 }
189
190 // load the file on disk and check has been updated
191 std::ifstream file(flag_info, std::ios::binary | std::ios::ate);
192 std::streamsize size = file.tellg();
193 file.seekg(0, std::ios::beg);
194
195 std::vector<uint8_t> buffer(size);
196 file.read(reinterpret_cast<char *>(buffer.data()), size);
197
198 auto content = rust::Slice<const uint8_t>(
199 buffer.data(), mapped_file->file_size);
200
201 for (int offset = 0; offset < 8; ++offset) {
202 auto attribute = get_flag_attribute_cxx(content, api::FlagValueType::Boolean, offset);
203 ASSERT_TRUE(attribute.query_success);
204 ASSERT_TRUE(attribute.flag_attribute & api::FlagInfoBit::HasLocalOverride);
205 }
206
207 for (int offset = 0; offset < 8; ++offset) {
208 auto update_result = api::set_flag_has_local_override(
209 *mapped_file, api::FlagValueType::Boolean, offset, false);
210 ASSERT_TRUE(update_result.ok());
211 auto attribute = api::get_flag_attribute(
212 *mapped_file, api::FlagValueType::Boolean, offset);
213 ASSERT_TRUE(attribute.ok());
214 ASSERT_FALSE(*attribute & api::FlagInfoBit::HasLocalOverride);
215 }
216
217 std::ifstream file2(flag_info, std::ios::binary);
218 buffer.clear();
219 file2.read(reinterpret_cast<char *>(buffer.data()), size);
220 for (int offset = 0; offset < 8; ++offset) {
221 auto attribute = get_flag_attribute_cxx(content, api::FlagValueType::Boolean, offset);
222 ASSERT_TRUE(attribute.query_success);
223 ASSERT_FALSE(attribute.flag_attribute & api::FlagInfoBit::HasLocalOverride);
224 }
225 }
226