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 pub mod flag_table;
18 pub mod flag_value;
19 pub mod package_table;
20 
21 use anyhow::{anyhow, Result};
22 use std::collections::{HashMap, HashSet};
23 
24 use crate::storage::{
25     flag_table::create_flag_table, flag_value::create_flag_value,
26     package_table::create_package_table,
27 };
28 use aconfig_protos::{ProtoParsedFlag, ProtoParsedFlags};
29 use aconfig_storage_file::StorageFileType;
30 
31 pub struct FlagPackage<'a> {
32     pub package_name: &'a str,
33     pub package_id: u32,
34     pub flag_names: HashSet<&'a str>,
35     pub boolean_flags: Vec<&'a ProtoParsedFlag>,
36     // The index of the first boolean flag in this aconfig package among all boolean
37     // flags in this container.
38     pub boolean_start_index: u32,
39 }
40 
41 impl<'a> FlagPackage<'a> {
new(package_name: &'a str, package_id: u32) -> Self42     fn new(package_name: &'a str, package_id: u32) -> Self {
43         FlagPackage {
44             package_name,
45             package_id,
46             flag_names: HashSet::new(),
47             boolean_flags: vec![],
48             boolean_start_index: 0,
49         }
50     }
51 
insert(&mut self, pf: &'a ProtoParsedFlag)52     fn insert(&mut self, pf: &'a ProtoParsedFlag) {
53         if self.flag_names.insert(pf.name()) {
54             self.boolean_flags.push(pf);
55         }
56     }
57 }
58 
group_flags_by_package<'a, I>(parsed_flags_vec_iter: I) -> Vec<FlagPackage<'a>> where I: Iterator<Item = &'a ProtoParsedFlags>,59 pub fn group_flags_by_package<'a, I>(parsed_flags_vec_iter: I) -> Vec<FlagPackage<'a>>
60 where
61     I: Iterator<Item = &'a ProtoParsedFlags>,
62 {
63     // group flags by package
64     let mut packages: Vec<FlagPackage<'a>> = Vec::new();
65     let mut package_index: HashMap<&str, usize> = HashMap::new();
66     for parsed_flags in parsed_flags_vec_iter {
67         for parsed_flag in parsed_flags.parsed_flag.iter() {
68             let index = *(package_index.entry(parsed_flag.package()).or_insert(packages.len()));
69             if index == packages.len() {
70                 packages.push(FlagPackage::new(parsed_flag.package(), index as u32));
71             }
72             packages[index].insert(parsed_flag);
73         }
74     }
75 
76     // cacluate boolean flag start index for each package
77     let mut boolean_start_index = 0;
78     for p in packages.iter_mut() {
79         p.boolean_start_index = boolean_start_index;
80         boolean_start_index += p.boolean_flags.len() as u32;
81     }
82 
83     packages
84 }
85 
generate_storage_file<'a, I>( container: &str, parsed_flags_vec_iter: I, file: &StorageFileType, ) -> Result<Vec<u8>> where I: Iterator<Item = &'a ProtoParsedFlags>,86 pub fn generate_storage_file<'a, I>(
87     container: &str,
88     parsed_flags_vec_iter: I,
89     file: &StorageFileType,
90 ) -> Result<Vec<u8>>
91 where
92     I: Iterator<Item = &'a ProtoParsedFlags>,
93 {
94     let packages = group_flags_by_package(parsed_flags_vec_iter);
95 
96     match file {
97         StorageFileType::PackageMap => {
98             let package_table = create_package_table(container, &packages)?;
99             Ok(package_table.into_bytes())
100         }
101         StorageFileType::FlagMap => {
102             let flag_table = create_flag_table(container, &packages)?;
103             Ok(flag_table.into_bytes())
104         }
105         StorageFileType::FlagVal => {
106             let flag_value = create_flag_value(container, &packages)?;
107             Ok(flag_value.into_bytes())
108         }
109         _ => Err(anyhow!("aconfig does not support the creation of this storage file type")),
110     }
111 }
112 
113 #[cfg(test)]
114 mod tests {
115     use super::*;
116     use crate::Input;
117 
parse_all_test_flags() -> Vec<ProtoParsedFlags>118     pub fn parse_all_test_flags() -> Vec<ProtoParsedFlags> {
119         let aconfig_files = [
120             (
121                 "com.android.aconfig.storage.test_1",
122                 "storage_test_1.aconfig",
123                 include_bytes!("../../tests/storage_test_1.aconfig").as_slice(),
124                 "storage_test_1.value",
125                 include_bytes!("../../tests/storage_test_1.values").as_slice(),
126             ),
127             (
128                 "com.android.aconfig.storage.test_2",
129                 "storage_test_2.aconfig",
130                 include_bytes!("../../tests/storage_test_2.aconfig").as_slice(),
131                 "storage_test_2.value",
132                 include_bytes!("../../tests/storage_test_2.values").as_slice(),
133             ),
134             (
135                 "com.android.aconfig.storage.test_4",
136                 "storage_test_4.aconfig",
137                 include_bytes!("../../tests/storage_test_4.aconfig").as_slice(),
138                 "storage_test_4.value",
139                 include_bytes!("../../tests/storage_test_4.values").as_slice(),
140             ),
141         ];
142         aconfig_files
143             .into_iter()
144             .map(|(pkg, aconfig_file, aconfig_content, value_file, value_content)| {
145                 let bytes = crate::commands::parse_flags(
146                     pkg,
147                     Some("system"),
148                     vec![Input {
149                         source: format!("tests/{}", aconfig_file).to_string(),
150                         reader: Box::new(aconfig_content),
151                     }],
152                     vec![Input {
153                         source: format!("tests/{}", value_file).to_string(),
154                         reader: Box::new(value_content),
155                     }],
156                     crate::commands::DEFAULT_FLAG_PERMISSION,
157                 )
158                 .unwrap();
159                 aconfig_protos::parsed_flags::try_from_binary_proto(&bytes).unwrap()
160             })
161             .collect()
162     }
163 
164     #[test]
test_flag_package()165     fn test_flag_package() {
166         let caches = parse_all_test_flags();
167         let packages = group_flags_by_package(caches.iter());
168 
169         for pkg in packages.iter() {
170             let pkg_name = pkg.package_name;
171             assert_eq!(pkg.flag_names.len(), pkg.boolean_flags.len());
172             for pf in pkg.boolean_flags.iter() {
173                 assert!(pkg.flag_names.contains(pf.name()));
174                 assert_eq!(pf.package(), pkg_name);
175             }
176         }
177 
178         assert_eq!(packages.len(), 3);
179 
180         assert_eq!(packages[0].package_name, "com.android.aconfig.storage.test_1");
181         assert_eq!(packages[0].package_id, 0);
182         assert_eq!(packages[0].flag_names.len(), 3);
183         assert!(packages[0].flag_names.contains("enabled_rw"));
184         assert!(packages[0].flag_names.contains("disabled_rw"));
185         assert!(packages[0].flag_names.contains("enabled_ro"));
186         assert_eq!(packages[0].boolean_start_index, 0);
187 
188         assert_eq!(packages[1].package_name, "com.android.aconfig.storage.test_2");
189         assert_eq!(packages[1].package_id, 1);
190         assert_eq!(packages[1].flag_names.len(), 3);
191         assert!(packages[1].flag_names.contains("enabled_ro"));
192         assert!(packages[1].flag_names.contains("disabled_rw"));
193         assert!(packages[1].flag_names.contains("enabled_fixed_ro"));
194         assert_eq!(packages[1].boolean_start_index, 3);
195 
196         assert_eq!(packages[2].package_name, "com.android.aconfig.storage.test_4");
197         assert_eq!(packages[2].package_id, 2);
198         assert_eq!(packages[2].flag_names.len(), 2);
199         assert!(packages[2].flag_names.contains("enabled_rw"));
200         assert!(packages[2].flag_names.contains("enabled_fixed_ro"));
201         assert_eq!(packages[2].boolean_start_index, 6);
202     }
203 }
204