1 /*
2 * Copyright (C) 2023 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 //! `aconfig_storage_file` is a crate that defines aconfig storage file format, it
18 //! also includes apis to read flags from storage files. It provides three apis to
19 //! interface with storage files:
20 //!
21 //! 1, function to get package flag value start offset
22 //! pub fn get_package_offset(container: &str, package: &str) -> `Result<Option<PackageOffset>>>`
23 //!
24 //! 2, function to get flag offset within a specific package
25 //! pub fn get_flag_offset(container: &str, package_id: u32, flag: &str) -> `Result<Option<u16>>>`
26 //!
27 //! 3, function to get the actual flag value given the global offset (combined package and
28 //! flag offset).
29 //! pub fn get_boolean_flag_value(container: &str, offset: u32) -> `Result<bool>`
30 //!
31 //! Note these are low level apis that are expected to be only used in auto generated flag
32 //! apis. DO NOT DIRECTLY USE THESE APIS IN YOUR SOURCE CODE. For auto generated flag apis
33 //! please refer to the g3doc go/android-flags
34
35 pub mod flag_info;
36 pub mod flag_table;
37 pub mod flag_value;
38 pub mod package_table;
39 pub mod protos;
40 pub mod sip_hasher13;
41 pub mod test_utils;
42
43 use anyhow::anyhow;
44 use serde::{Deserialize, Serialize};
45 use std::cmp::Ordering;
46 use std::fs::File;
47 use std::hash::Hasher;
48 use std::io::Read;
49
50 pub use crate::flag_info::{FlagInfoBit, FlagInfoHeader, FlagInfoList, FlagInfoNode};
51 pub use crate::flag_table::{FlagTable, FlagTableHeader, FlagTableNode};
52 pub use crate::flag_value::{FlagValueHeader, FlagValueList};
53 pub use crate::package_table::{PackageTable, PackageTableHeader, PackageTableNode};
54 pub use crate::sip_hasher13::SipHasher13;
55
56 use crate::AconfigStorageError::{
57 BytesParseFail, HashTableSizeLimit, InvalidFlagValueType, InvalidStoredFlagType,
58 };
59
60 /// The max storage file version from which we can safely read/write. May be
61 /// experimental.
62 pub const MAX_SUPPORTED_FILE_VERSION: u32 = 2;
63
64 /// The newest fully-released version. Unless otherwise specified, this is the
65 /// version we will write.
66 pub const DEFAULT_FILE_VERSION: u32 = 1;
67
68 /// Good hash table prime number
69 pub(crate) const HASH_PRIMES: [u32; 29] = [
70 7, 17, 29, 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, 49157, 98317, 196613, 393241,
71 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611,
72 402653189, 805306457, 1610612741,
73 ];
74
75 /// Storage file type enum
76 #[derive(Clone, Debug, PartialEq, Eq)]
77 pub enum StorageFileType {
78 PackageMap = 0,
79 FlagMap = 1,
80 FlagVal = 2,
81 FlagInfo = 3,
82 }
83
84 impl TryFrom<&str> for StorageFileType {
85 type Error = anyhow::Error;
86
try_from(value: &str) -> std::result::Result<Self, Self::Error>87 fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
88 match value {
89 "package_map" => Ok(Self::PackageMap),
90 "flag_map" => Ok(Self::FlagMap),
91 "flag_val" => Ok(Self::FlagVal),
92 "flag_info" => Ok(Self::FlagInfo),
93 _ => Err(anyhow!(
94 "Invalid storage file type, valid types are package_map|flag_map|flag_val|flag_info"
95 )),
96 }
97 }
98 }
99
100 impl TryFrom<u8> for StorageFileType {
101 type Error = anyhow::Error;
102
try_from(value: u8) -> Result<Self, Self::Error>103 fn try_from(value: u8) -> Result<Self, Self::Error> {
104 match value {
105 x if x == Self::PackageMap as u8 => Ok(Self::PackageMap),
106 x if x == Self::FlagMap as u8 => Ok(Self::FlagMap),
107 x if x == Self::FlagVal as u8 => Ok(Self::FlagVal),
108 x if x == Self::FlagInfo as u8 => Ok(Self::FlagInfo),
109 _ => Err(anyhow!("Invalid storage file type")),
110 }
111 }
112 }
113
114 /// Flag type enum as stored by storage file
115 /// ONLY APPEND, NEVER REMOVE FOR BACKWARD COMPATIBILITY. THE MAX IS U16.
116 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
117 pub enum StoredFlagType {
118 ReadWriteBoolean = 0,
119 ReadOnlyBoolean = 1,
120 FixedReadOnlyBoolean = 2,
121 }
122
123 impl TryFrom<u16> for StoredFlagType {
124 type Error = AconfigStorageError;
125
try_from(value: u16) -> Result<Self, Self::Error>126 fn try_from(value: u16) -> Result<Self, Self::Error> {
127 match value {
128 x if x == Self::ReadWriteBoolean as u16 => Ok(Self::ReadWriteBoolean),
129 x if x == Self::ReadOnlyBoolean as u16 => Ok(Self::ReadOnlyBoolean),
130 x if x == Self::FixedReadOnlyBoolean as u16 => Ok(Self::FixedReadOnlyBoolean),
131 _ => Err(InvalidStoredFlagType(anyhow!("Invalid stored flag type"))),
132 }
133 }
134 }
135
136 /// Flag value type enum, one FlagValueType maps to many StoredFlagType
137 /// ONLY APPEND, NEVER REMOVE FOR BACKWARD COMPATIBILITY. THE MAX IS U16
138 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
139 pub enum FlagValueType {
140 Boolean = 0,
141 }
142
143 impl TryFrom<StoredFlagType> for FlagValueType {
144 type Error = AconfigStorageError;
145
try_from(value: StoredFlagType) -> Result<Self, Self::Error>146 fn try_from(value: StoredFlagType) -> Result<Self, Self::Error> {
147 match value {
148 StoredFlagType::ReadWriteBoolean => Ok(Self::Boolean),
149 StoredFlagType::ReadOnlyBoolean => Ok(Self::Boolean),
150 StoredFlagType::FixedReadOnlyBoolean => Ok(Self::Boolean),
151 }
152 }
153 }
154
155 impl TryFrom<u16> for FlagValueType {
156 type Error = AconfigStorageError;
157
try_from(value: u16) -> Result<Self, Self::Error>158 fn try_from(value: u16) -> Result<Self, Self::Error> {
159 match value {
160 x if x == Self::Boolean as u16 => Ok(Self::Boolean),
161 _ => Err(InvalidFlagValueType(anyhow!("Invalid flag value type"))),
162 }
163 }
164 }
165
166 /// Storage query api error
167 #[non_exhaustive]
168 #[derive(thiserror::Error, Debug)]
169 pub enum AconfigStorageError {
170 #[error("failed to read the file")]
171 FileReadFail(#[source] anyhow::Error),
172
173 #[error("fail to parse protobuf")]
174 ProtobufParseFail(#[source] anyhow::Error),
175
176 #[error("storage files not found for this container")]
177 StorageFileNotFound(#[source] anyhow::Error),
178
179 #[error("fail to map storage file")]
180 MapFileFail(#[source] anyhow::Error),
181
182 #[error("fail to get mapped file")]
183 ObtainMappedFileFail(#[source] anyhow::Error),
184
185 #[error("fail to flush mapped storage file")]
186 MapFlushFail(#[source] anyhow::Error),
187
188 #[error("number of items in hash table exceed limit")]
189 HashTableSizeLimit(#[source] anyhow::Error),
190
191 #[error("failed to parse bytes into data")]
192 BytesParseFail(#[source] anyhow::Error),
193
194 #[error("cannot parse storage files with a higher version")]
195 HigherStorageFileVersion(#[source] anyhow::Error),
196
197 #[error("invalid storage file byte offset")]
198 InvalidStorageFileOffset(#[source] anyhow::Error),
199
200 #[error("failed to create file")]
201 FileCreationFail(#[source] anyhow::Error),
202
203 #[error("invalid stored flag type")]
204 InvalidStoredFlagType(#[source] anyhow::Error),
205
206 #[error("invalid flag value type")]
207 InvalidFlagValueType(#[source] anyhow::Error),
208 }
209
210 /// Get the right hash table size given number of entries in the table. Use a
211 /// load factor of 0.5 for performance.
get_table_size(entries: u32) -> Result<u32, AconfigStorageError>212 pub fn get_table_size(entries: u32) -> Result<u32, AconfigStorageError> {
213 HASH_PRIMES
214 .iter()
215 .find(|&&num| num >= 2 * entries)
216 .copied()
217 .ok_or(HashTableSizeLimit(anyhow!("Number of items in a hash table exceeds limit")))
218 }
219
220 /// Get the corresponding bucket index given the key and number of buckets
get_bucket_index(val: &[u8], num_buckets: u32) -> u32221 pub(crate) fn get_bucket_index(val: &[u8], num_buckets: u32) -> u32 {
222 let mut s = SipHasher13::new();
223 s.write(val);
224 s.write_u8(0xff);
225 let ret = (s.finish() % num_buckets as u64) as u32;
226 ret
227 }
228
229 /// Read and parse bytes as u8
read_u8_from_bytes(buf: &[u8], head: &mut usize) -> Result<u8, AconfigStorageError>230 pub fn read_u8_from_bytes(buf: &[u8], head: &mut usize) -> Result<u8, AconfigStorageError> {
231 let val =
232 u8::from_le_bytes(buf[*head..*head + 1].try_into().map_err(|errmsg| {
233 BytesParseFail(anyhow!("fail to parse u8 from bytes: {}", errmsg))
234 })?);
235 *head += 1;
236 Ok(val)
237 }
238
239 /// Read and parse bytes as u16
read_u16_from_bytes( buf: &[u8], head: &mut usize, ) -> Result<u16, AconfigStorageError>240 pub(crate) fn read_u16_from_bytes(
241 buf: &[u8],
242 head: &mut usize,
243 ) -> Result<u16, AconfigStorageError> {
244 let val =
245 u16::from_le_bytes(buf[*head..*head + 2].try_into().map_err(|errmsg| {
246 BytesParseFail(anyhow!("fail to parse u16 from bytes: {}", errmsg))
247 })?);
248 *head += 2;
249 Ok(val)
250 }
251
252 /// Read and parse the first 4 bytes of buf as u32.
read_u32_from_start_of_bytes(buf: &[u8]) -> Result<u32, AconfigStorageError>253 pub fn read_u32_from_start_of_bytes(buf: &[u8]) -> Result<u32, AconfigStorageError> {
254 read_u32_from_bytes(buf, &mut 0)
255 }
256
257 /// Read and parse bytes as u32
read_u32_from_bytes(buf: &[u8], head: &mut usize) -> Result<u32, AconfigStorageError>258 pub fn read_u32_from_bytes(buf: &[u8], head: &mut usize) -> Result<u32, AconfigStorageError> {
259 let val =
260 u32::from_le_bytes(buf[*head..*head + 4].try_into().map_err(|errmsg| {
261 BytesParseFail(anyhow!("fail to parse u32 from bytes: {}", errmsg))
262 })?);
263 *head += 4;
264 Ok(val)
265 }
266
267 // Read and parse bytes as u64
read_u64_from_bytes(buf: &[u8], head: &mut usize) -> Result<u64, AconfigStorageError>268 pub fn read_u64_from_bytes(buf: &[u8], head: &mut usize) -> Result<u64, AconfigStorageError> {
269 let val =
270 u64::from_le_bytes(buf[*head..*head + 8].try_into().map_err(|errmsg| {
271 BytesParseFail(anyhow!("fail to parse u64 from bytes: {}", errmsg))
272 })?);
273 *head += 8;
274 Ok(val)
275 }
276
277 /// Read and parse bytes as string
read_str_from_bytes( buf: &[u8], head: &mut usize, ) -> Result<String, AconfigStorageError>278 pub(crate) fn read_str_from_bytes(
279 buf: &[u8],
280 head: &mut usize,
281 ) -> Result<String, AconfigStorageError> {
282 let num_bytes = read_u32_from_bytes(buf, head)? as usize;
283 let val = String::from_utf8(buf[*head..*head + num_bytes].to_vec())
284 .map_err(|errmsg| BytesParseFail(anyhow!("fail to parse string from bytes: {}", errmsg)))?;
285 *head += num_bytes;
286 Ok(val)
287 }
288
289 /// Read in storage file as bytes
read_file_to_bytes(file_path: &str) -> Result<Vec<u8>, AconfigStorageError>290 pub fn read_file_to_bytes(file_path: &str) -> Result<Vec<u8>, AconfigStorageError> {
291 let mut file = File::open(file_path).map_err(|errmsg| {
292 AconfigStorageError::FileReadFail(anyhow!("Failed to open file {}: {}", file_path, errmsg))
293 })?;
294 let mut buffer = Vec::new();
295 file.read_to_end(&mut buffer).map_err(|errmsg| {
296 AconfigStorageError::FileReadFail(anyhow!(
297 "Failed to read bytes from file {}: {}",
298 file_path,
299 errmsg
300 ))
301 })?;
302 Ok(buffer)
303 }
304
305 /// Flag value summary
306 #[derive(Debug, PartialEq)]
307 pub struct FlagValueSummary {
308 pub package_name: String,
309 pub flag_name: String,
310 pub flag_value: String,
311 pub value_type: StoredFlagType,
312 }
313
314 /// List flag values from storage files
list_flags( package_map: &str, flag_map: &str, flag_val: &str, ) -> Result<Vec<FlagValueSummary>, AconfigStorageError>315 pub fn list_flags(
316 package_map: &str,
317 flag_map: &str,
318 flag_val: &str,
319 ) -> Result<Vec<FlagValueSummary>, AconfigStorageError> {
320 let package_table = PackageTable::from_bytes(&read_file_to_bytes(package_map)?)?;
321 let flag_table = FlagTable::from_bytes(&read_file_to_bytes(flag_map)?)?;
322 let flag_value_list = FlagValueList::from_bytes(&read_file_to_bytes(flag_val)?)?;
323
324 let mut package_info = vec![("", 0); package_table.header.num_packages as usize];
325 for node in package_table.nodes.iter() {
326 package_info[node.package_id as usize] = (&node.package_name, node.boolean_start_index);
327 }
328
329 let mut flags = Vec::new();
330 for node in flag_table.nodes.iter() {
331 let (package_name, boolean_start_index) = package_info[node.package_id as usize];
332 let flag_index = boolean_start_index + node.flag_index as u32;
333 let flag_value = flag_value_list.booleans[flag_index as usize];
334 flags.push(FlagValueSummary {
335 package_name: String::from(package_name),
336 flag_name: node.flag_name.clone(),
337 flag_value: flag_value.to_string(),
338 value_type: node.flag_type,
339 });
340 }
341
342 flags.sort_by(|v1, v2| match v1.package_name.cmp(&v2.package_name) {
343 Ordering::Equal => v1.flag_name.cmp(&v2.flag_name),
344 other => other,
345 });
346 Ok(flags)
347 }
348
349 /// Flag value and info summary
350 #[derive(Debug, PartialEq)]
351 pub struct FlagValueAndInfoSummary {
352 pub package_name: String,
353 pub flag_name: String,
354 pub flag_value: String,
355 pub value_type: StoredFlagType,
356 pub is_readwrite: bool,
357 pub has_server_override: bool,
358 pub has_local_override: bool,
359 }
360
361 /// List flag values and info from storage files
list_flags_with_info( package_map: &str, flag_map: &str, flag_val: &str, flag_info: &str, ) -> Result<Vec<FlagValueAndInfoSummary>, AconfigStorageError>362 pub fn list_flags_with_info(
363 package_map: &str,
364 flag_map: &str,
365 flag_val: &str,
366 flag_info: &str,
367 ) -> Result<Vec<FlagValueAndInfoSummary>, AconfigStorageError> {
368 let package_table = PackageTable::from_bytes(&read_file_to_bytes(package_map)?)?;
369 let flag_table = FlagTable::from_bytes(&read_file_to_bytes(flag_map)?)?;
370 let flag_value_list = FlagValueList::from_bytes(&read_file_to_bytes(flag_val)?)?;
371 let flag_info = FlagInfoList::from_bytes(&read_file_to_bytes(flag_info)?)?;
372
373 let mut package_info = vec![("", 0); package_table.header.num_packages as usize];
374 for node in package_table.nodes.iter() {
375 package_info[node.package_id as usize] = (&node.package_name, node.boolean_start_index);
376 }
377
378 let mut flags = Vec::new();
379 for node in flag_table.nodes.iter() {
380 let (package_name, boolean_start_index) = package_info[node.package_id as usize];
381 let flag_index = boolean_start_index + node.flag_index as u32;
382 let flag_value = flag_value_list.booleans[flag_index as usize];
383 let flag_attribute = flag_info.nodes[flag_index as usize].attributes;
384 flags.push(FlagValueAndInfoSummary {
385 package_name: String::from(package_name),
386 flag_name: node.flag_name.clone(),
387 flag_value: flag_value.to_string(),
388 value_type: node.flag_type,
389 is_readwrite: flag_attribute & (FlagInfoBit::IsReadWrite as u8) != 0,
390 has_server_override: flag_attribute & (FlagInfoBit::HasServerOverride as u8) != 0,
391 has_local_override: flag_attribute & (FlagInfoBit::HasLocalOverride as u8) != 0,
392 });
393 }
394
395 flags.sort_by(|v1, v2| match v1.package_name.cmp(&v2.package_name) {
396 Ordering::Equal => v1.flag_name.cmp(&v2.flag_name),
397 other => other,
398 });
399 Ok(flags)
400 }
401
402 // *************************************** //
403 // CC INTERLOP
404 // *************************************** //
405
406 // Exported rust data structure and methods, c++ code will be generated
407 #[cxx::bridge]
408 mod ffi {
409 /// flag value summary cxx return
410 pub struct FlagValueSummaryCXX {
411 pub package_name: String,
412 pub flag_name: String,
413 pub flag_value: String,
414 pub value_type: String,
415 }
416
417 /// flag value and info summary cxx return
418 pub struct FlagValueAndInfoSummaryCXX {
419 pub package_name: String,
420 pub flag_name: String,
421 pub flag_value: String,
422 pub value_type: String,
423 pub is_readwrite: bool,
424 pub has_server_override: bool,
425 pub has_local_override: bool,
426 }
427
428 /// list flag result cxx return
429 pub struct ListFlagValueResultCXX {
430 pub query_success: bool,
431 pub error_message: String,
432 pub flags: Vec<FlagValueSummaryCXX>,
433 }
434
435 /// list flag with info result cxx return
436 pub struct ListFlagValueAndInfoResultCXX {
437 pub query_success: bool,
438 pub error_message: String,
439 pub flags: Vec<FlagValueAndInfoSummaryCXX>,
440 }
441
442 // Rust export to c++
443 extern "Rust" {
list_flags_cxx( package_map: &str, flag_map: &str, flag_val: &str, ) -> ListFlagValueResultCXX444 pub fn list_flags_cxx(
445 package_map: &str,
446 flag_map: &str,
447 flag_val: &str,
448 ) -> ListFlagValueResultCXX;
449
list_flags_with_info_cxx( package_map: &str, flag_map: &str, flag_val: &str, flag_info: &str, ) -> ListFlagValueAndInfoResultCXX450 pub fn list_flags_with_info_cxx(
451 package_map: &str,
452 flag_map: &str,
453 flag_val: &str,
454 flag_info: &str,
455 ) -> ListFlagValueAndInfoResultCXX;
456 }
457 }
458
459 /// implement flag value summary cxx return type
460 impl ffi::FlagValueSummaryCXX {
new(summary: FlagValueSummary) -> Self461 pub(crate) fn new(summary: FlagValueSummary) -> Self {
462 Self {
463 package_name: summary.package_name,
464 flag_name: summary.flag_name,
465 flag_value: summary.flag_value,
466 value_type: format!("{:?}", summary.value_type),
467 }
468 }
469 }
470
471 /// implement flag value and info summary cxx return type
472 impl ffi::FlagValueAndInfoSummaryCXX {
new(summary: FlagValueAndInfoSummary) -> Self473 pub(crate) fn new(summary: FlagValueAndInfoSummary) -> Self {
474 Self {
475 package_name: summary.package_name,
476 flag_name: summary.flag_name,
477 flag_value: summary.flag_value,
478 value_type: format!("{:?}", summary.value_type),
479 is_readwrite: summary.is_readwrite,
480 has_server_override: summary.has_server_override,
481 has_local_override: summary.has_local_override,
482 }
483 }
484 }
485
486 /// implement list flag cxx interlop
list_flags_cxx( package_map: &str, flag_map: &str, flag_val: &str, ) -> ffi::ListFlagValueResultCXX487 pub fn list_flags_cxx(
488 package_map: &str,
489 flag_map: &str,
490 flag_val: &str,
491 ) -> ffi::ListFlagValueResultCXX {
492 match list_flags(package_map, flag_map, flag_val) {
493 Ok(summary) => ffi::ListFlagValueResultCXX {
494 query_success: true,
495 error_message: String::new(),
496 flags: summary.into_iter().map(ffi::FlagValueSummaryCXX::new).collect(),
497 },
498 Err(errmsg) => ffi::ListFlagValueResultCXX {
499 query_success: false,
500 error_message: format!("{:?}", errmsg),
501 flags: Vec::new(),
502 },
503 }
504 }
505
506 /// implement list flag with info cxx interlop
list_flags_with_info_cxx( package_map: &str, flag_map: &str, flag_val: &str, flag_info: &str, ) -> ffi::ListFlagValueAndInfoResultCXX507 pub fn list_flags_with_info_cxx(
508 package_map: &str,
509 flag_map: &str,
510 flag_val: &str,
511 flag_info: &str,
512 ) -> ffi::ListFlagValueAndInfoResultCXX {
513 match list_flags_with_info(package_map, flag_map, flag_val, flag_info) {
514 Ok(summary) => ffi::ListFlagValueAndInfoResultCXX {
515 query_success: true,
516 error_message: String::new(),
517 flags: summary.into_iter().map(ffi::FlagValueAndInfoSummaryCXX::new).collect(),
518 },
519 Err(errmsg) => ffi::ListFlagValueAndInfoResultCXX {
520 query_success: false,
521 error_message: format!("{:?}", errmsg),
522 flags: Vec::new(),
523 },
524 }
525 }
526
527 #[cfg(test)]
528 mod tests {
529 use super::*;
530 use crate::test_utils::{
531 create_test_flag_info_list, create_test_flag_table, create_test_flag_value_list,
532 create_test_package_table, write_bytes_to_temp_file,
533 };
534
535 #[test]
536 // this test point locks down the flag list api
test_list_flag()537 fn test_list_flag() {
538 let package_table =
539 write_bytes_to_temp_file(&create_test_package_table(DEFAULT_FILE_VERSION).into_bytes())
540 .unwrap();
541 let flag_table =
542 write_bytes_to_temp_file(&create_test_flag_table(DEFAULT_FILE_VERSION).into_bytes())
543 .unwrap();
544 let flag_value_list = write_bytes_to_temp_file(
545 &create_test_flag_value_list(DEFAULT_FILE_VERSION).into_bytes(),
546 )
547 .unwrap();
548
549 let package_table_path = package_table.path().display().to_string();
550 let flag_table_path = flag_table.path().display().to_string();
551 let flag_value_list_path = flag_value_list.path().display().to_string();
552
553 let flags =
554 list_flags(&package_table_path, &flag_table_path, &flag_value_list_path).unwrap();
555 let expected = [
556 FlagValueSummary {
557 package_name: String::from("com.android.aconfig.storage.test_1"),
558 flag_name: String::from("disabled_rw"),
559 value_type: StoredFlagType::ReadWriteBoolean,
560 flag_value: String::from("false"),
561 },
562 FlagValueSummary {
563 package_name: String::from("com.android.aconfig.storage.test_1"),
564 flag_name: String::from("enabled_ro"),
565 value_type: StoredFlagType::ReadOnlyBoolean,
566 flag_value: String::from("true"),
567 },
568 FlagValueSummary {
569 package_name: String::from("com.android.aconfig.storage.test_1"),
570 flag_name: String::from("enabled_rw"),
571 value_type: StoredFlagType::ReadWriteBoolean,
572 flag_value: String::from("true"),
573 },
574 FlagValueSummary {
575 package_name: String::from("com.android.aconfig.storage.test_2"),
576 flag_name: String::from("disabled_rw"),
577 value_type: StoredFlagType::ReadWriteBoolean,
578 flag_value: String::from("false"),
579 },
580 FlagValueSummary {
581 package_name: String::from("com.android.aconfig.storage.test_2"),
582 flag_name: String::from("enabled_fixed_ro"),
583 value_type: StoredFlagType::FixedReadOnlyBoolean,
584 flag_value: String::from("true"),
585 },
586 FlagValueSummary {
587 package_name: String::from("com.android.aconfig.storage.test_2"),
588 flag_name: String::from("enabled_ro"),
589 value_type: StoredFlagType::ReadOnlyBoolean,
590 flag_value: String::from("true"),
591 },
592 FlagValueSummary {
593 package_name: String::from("com.android.aconfig.storage.test_4"),
594 flag_name: String::from("enabled_fixed_ro"),
595 value_type: StoredFlagType::FixedReadOnlyBoolean,
596 flag_value: String::from("true"),
597 },
598 FlagValueSummary {
599 package_name: String::from("com.android.aconfig.storage.test_4"),
600 flag_name: String::from("enabled_rw"),
601 value_type: StoredFlagType::ReadWriteBoolean,
602 flag_value: String::from("true"),
603 },
604 ];
605 assert_eq!(flags, expected);
606 }
607
608 #[test]
609 // this test point locks down the flag list with info api
test_list_flag_with_info()610 fn test_list_flag_with_info() {
611 let package_table =
612 write_bytes_to_temp_file(&create_test_package_table(DEFAULT_FILE_VERSION).into_bytes())
613 .unwrap();
614 let flag_table =
615 write_bytes_to_temp_file(&create_test_flag_table(DEFAULT_FILE_VERSION).into_bytes())
616 .unwrap();
617 let flag_value_list = write_bytes_to_temp_file(
618 &create_test_flag_value_list(DEFAULT_FILE_VERSION).into_bytes(),
619 )
620 .unwrap();
621 let flag_info_list = write_bytes_to_temp_file(
622 &create_test_flag_info_list(DEFAULT_FILE_VERSION).into_bytes(),
623 )
624 .unwrap();
625
626 let package_table_path = package_table.path().display().to_string();
627 let flag_table_path = flag_table.path().display().to_string();
628 let flag_value_list_path = flag_value_list.path().display().to_string();
629 let flag_info_list_path = flag_info_list.path().display().to_string();
630
631 let flags = list_flags_with_info(
632 &package_table_path,
633 &flag_table_path,
634 &flag_value_list_path,
635 &flag_info_list_path,
636 )
637 .unwrap();
638 let expected = [
639 FlagValueAndInfoSummary {
640 package_name: String::from("com.android.aconfig.storage.test_1"),
641 flag_name: String::from("disabled_rw"),
642 value_type: StoredFlagType::ReadWriteBoolean,
643 flag_value: String::from("false"),
644 is_readwrite: true,
645 has_server_override: false,
646 has_local_override: false,
647 },
648 FlagValueAndInfoSummary {
649 package_name: String::from("com.android.aconfig.storage.test_1"),
650 flag_name: String::from("enabled_ro"),
651 value_type: StoredFlagType::ReadOnlyBoolean,
652 flag_value: String::from("true"),
653 is_readwrite: false,
654 has_server_override: false,
655 has_local_override: false,
656 },
657 FlagValueAndInfoSummary {
658 package_name: String::from("com.android.aconfig.storage.test_1"),
659 flag_name: String::from("enabled_rw"),
660 value_type: StoredFlagType::ReadWriteBoolean,
661 flag_value: String::from("true"),
662 is_readwrite: true,
663 has_server_override: false,
664 has_local_override: false,
665 },
666 FlagValueAndInfoSummary {
667 package_name: String::from("com.android.aconfig.storage.test_2"),
668 flag_name: String::from("disabled_rw"),
669 value_type: StoredFlagType::ReadWriteBoolean,
670 flag_value: String::from("false"),
671 is_readwrite: true,
672 has_server_override: false,
673 has_local_override: false,
674 },
675 FlagValueAndInfoSummary {
676 package_name: String::from("com.android.aconfig.storage.test_2"),
677 flag_name: String::from("enabled_fixed_ro"),
678 value_type: StoredFlagType::FixedReadOnlyBoolean,
679 flag_value: String::from("true"),
680 is_readwrite: false,
681 has_server_override: false,
682 has_local_override: false,
683 },
684 FlagValueAndInfoSummary {
685 package_name: String::from("com.android.aconfig.storage.test_2"),
686 flag_name: String::from("enabled_ro"),
687 value_type: StoredFlagType::ReadOnlyBoolean,
688 flag_value: String::from("true"),
689 is_readwrite: false,
690 has_server_override: false,
691 has_local_override: false,
692 },
693 FlagValueAndInfoSummary {
694 package_name: String::from("com.android.aconfig.storage.test_4"),
695 flag_name: String::from("enabled_fixed_ro"),
696 value_type: StoredFlagType::FixedReadOnlyBoolean,
697 flag_value: String::from("true"),
698 is_readwrite: false,
699 has_server_override: false,
700 has_local_override: false,
701 },
702 FlagValueAndInfoSummary {
703 package_name: String::from("com.android.aconfig.storage.test_4"),
704 flag_name: String::from("enabled_rw"),
705 value_type: StoredFlagType::ReadWriteBoolean,
706 flag_value: String::from("true"),
707 is_readwrite: true,
708 has_server_override: false,
709 has_local_override: false,
710 },
711 ];
712 assert_eq!(flags, expected);
713 }
714 }
715