blob: 76835e015d9fc9b67312b8662d45d729feb47944 [file] [log] [blame]
Dennis Shen0d1c5622023-12-01 21:04:29 +00001/*
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
Dennis Shenb65b3502024-01-04 15:57:42 +000017pub mod flag_table;
Dennis Shenadc7b732023-12-11 18:59:13 +000018pub mod package_table;
19
20use anyhow::{anyhow, Result};
21use std::collections::{hash_map::DefaultHasher, HashMap, HashSet};
22use std::hash::{Hash, Hasher};
23use std::path::PathBuf;
Dennis Shen0d1c5622023-12-01 21:04:29 +000024
25use crate::commands::OutputFile;
26use crate::protos::{ProtoParsedFlag, ProtoParsedFlags};
Dennis Shenb65b3502024-01-04 15:57:42 +000027use crate::storage::{flag_table::FlagTable, package_table::PackageTable};
Dennis Shenadc7b732023-12-11 18:59:13 +000028
29pub const FILE_VERSION: u32 = 1;
30
31pub const HASH_PRIMES: [u32; 29] = [
Dennis Shenb65b3502024-01-04 15:57:42 +000032 7, 17, 29, 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, 49157, 98317, 196613, 393241,
Dennis Shenadc7b732023-12-11 18:59:13 +000033 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611,
34 402653189, 805306457, 1610612741,
35];
36
37/// Get the right hash table size given number of entries in the table. Use a
38/// load factor of 0.5 for performance.
39pub fn get_table_size(entries: u32) -> Result<u32> {
40 HASH_PRIMES
41 .iter()
42 .find(|&&num| num >= 2 * entries)
43 .copied()
44 .ok_or(anyhow!("Number of packages is too large"))
45}
46
47/// Get the corresponding bucket index given the key and number of buckets
48pub fn get_bucket_index<T: Hash>(val: &T, num_buckets: u32) -> u32 {
49 let mut s = DefaultHasher::new();
50 val.hash(&mut s);
51 (s.finish() % num_buckets as u64) as u32
52}
Dennis Shen0d1c5622023-12-01 21:04:29 +000053
54pub struct FlagPackage<'a> {
55 pub package_name: &'a str,
56 pub package_id: u32,
57 pub flag_names: HashSet<&'a str>,
58 pub boolean_flags: Vec<&'a ProtoParsedFlag>,
59 pub boolean_offset: u32,
60}
61
62impl<'a> FlagPackage<'a> {
63 fn new(package_name: &'a str, package_id: u32) -> Self {
64 FlagPackage {
65 package_name,
66 package_id,
67 flag_names: HashSet::new(),
68 boolean_flags: vec![],
69 boolean_offset: 0,
70 }
71 }
72
73 fn insert(&mut self, pf: &'a ProtoParsedFlag) {
74 if self.flag_names.insert(pf.name()) {
75 self.boolean_flags.push(pf);
76 }
77 }
78}
79
80pub fn group_flags_by_package<'a, I>(parsed_flags_vec_iter: I) -> Vec<FlagPackage<'a>>
81where
82 I: Iterator<Item = &'a ProtoParsedFlags>,
83{
84 // group flags by package
85 let mut packages: Vec<FlagPackage<'a>> = Vec::new();
Dennis Shenadc7b732023-12-11 18:59:13 +000086 let mut package_index: HashMap<&str, usize> = HashMap::new();
Dennis Shen0d1c5622023-12-01 21:04:29 +000087 for parsed_flags in parsed_flags_vec_iter {
88 for parsed_flag in parsed_flags.parsed_flag.iter() {
89 let index = *(package_index.entry(parsed_flag.package()).or_insert(packages.len()));
90 if index == packages.len() {
91 packages.push(FlagPackage::new(parsed_flag.package(), index as u32));
92 }
93 packages[index].insert(parsed_flag);
94 }
95 }
96
97 // calculate package flag value start offset, in flag value file, each boolean
98 // is stored as two bytes, the first byte will be the flag value. the second
99 // byte is flag info byte, which is a bitmask to indicate the status of a flag
100 let mut boolean_offset = 0;
101 for p in packages.iter_mut() {
102 p.boolean_offset = boolean_offset;
103 boolean_offset += 2 * p.boolean_flags.len() as u32;
104 }
105
106 packages
107}
108
109pub fn generate_storage_files<'a, I>(
Dennis Shenadc7b732023-12-11 18:59:13 +0000110 container: &str,
Dennis Shen0d1c5622023-12-01 21:04:29 +0000111 parsed_flags_vec_iter: I,
112) -> Result<Vec<OutputFile>>
113where
114 I: Iterator<Item = &'a ProtoParsedFlags>,
115{
Dennis Shenadc7b732023-12-11 18:59:13 +0000116 let packages = group_flags_by_package(parsed_flags_vec_iter);
117
118 // create and serialize package map
119 let package_table = PackageTable::new(container, &packages)?;
120 let package_table_file_path = PathBuf::from("package.map");
121 let package_table_file =
122 OutputFile { contents: package_table.as_bytes(), path: package_table_file_path };
123
Dennis Shenb65b3502024-01-04 15:57:42 +0000124 // create and serialize flag map
125 let flag_table = FlagTable::new(container, &packages)?;
126 let flag_table_file_path = PathBuf::from("flag.map");
127 let flag_table_file =
128 OutputFile { contents: flag_table.as_bytes(), path: flag_table_file_path };
129
130 Ok(vec![package_table_file, flag_table_file])
Dennis Shen0d1c5622023-12-01 21:04:29 +0000131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136 use crate::Input;
137
Dennis Shenadc7b732023-12-11 18:59:13 +0000138 /// Read and parse bytes as u32
139 pub fn read_u32_from_bytes(buf: &[u8], head: &mut usize) -> Result<u32> {
140 let val = u32::from_le_bytes(buf[*head..*head + 4].try_into()?);
141 *head += 4;
142 Ok(val)
143 }
144
145 /// Read and parse bytes as string
146 pub fn read_str_from_bytes(buf: &[u8], head: &mut usize) -> Result<String> {
147 let num_bytes = read_u32_from_bytes(buf, head)? as usize;
148 let val = String::from_utf8(buf[*head..*head + num_bytes].to_vec())?;
149 *head += num_bytes;
150 Ok(val)
151 }
152
Dennis Shen0d1c5622023-12-01 21:04:29 +0000153 pub fn parse_all_test_flags() -> Vec<ProtoParsedFlags> {
154 let aconfig_files = [
155 (
156 "com.android.aconfig.storage.test_1",
Dennis Shenb65b3502024-01-04 15:57:42 +0000157 "storage_test_1.aconfig",
158 include_bytes!("../../tests/storage_test_1.aconfig").as_slice(),
Dennis Shen0d1c5622023-12-01 21:04:29 +0000159 ),
160 (
161 "com.android.aconfig.storage.test_2",
162 "storage_test_2.aconfig",
163 include_bytes!("../../tests/storage_test_2.aconfig").as_slice(),
164 ),
Dennis Shen8bab8592023-12-20 17:45:00 +0000165 (
166 "com.android.aconfig.storage.test_4",
167 "storage_test_4.aconfig",
168 include_bytes!("../../tests/storage_test_4.aconfig").as_slice(),
169 ),
Dennis Shen0d1c5622023-12-01 21:04:29 +0000170 ];
171
172 aconfig_files
173 .into_iter()
174 .map(|(pkg, file, content)| {
175 let bytes = crate::commands::parse_flags(
176 pkg,
177 Some("system"),
178 vec![Input {
179 source: format!("tests/{}", file).to_string(),
180 reader: Box::new(content),
181 }],
182 vec![],
183 crate::commands::DEFAULT_FLAG_PERMISSION,
184 )
185 .unwrap();
186 crate::protos::parsed_flags::try_from_binary_proto(&bytes).unwrap()
187 })
188 .collect()
189 }
190
191 #[test]
192 fn test_flag_package() {
193 let caches = parse_all_test_flags();
194 let packages = group_flags_by_package(caches.iter());
195
196 for pkg in packages.iter() {
197 let pkg_name = pkg.package_name;
198 assert_eq!(pkg.flag_names.len(), pkg.boolean_flags.len());
199 for pf in pkg.boolean_flags.iter() {
200 assert!(pkg.flag_names.contains(pf.name()));
201 assert_eq!(pf.package(), pkg_name);
202 }
203 }
204
Dennis Shen8bab8592023-12-20 17:45:00 +0000205 assert_eq!(packages.len(), 3);
Dennis Shen0d1c5622023-12-01 21:04:29 +0000206
207 assert_eq!(packages[0].package_name, "com.android.aconfig.storage.test_1");
208 assert_eq!(packages[0].package_id, 0);
Dennis Shenb65b3502024-01-04 15:57:42 +0000209 assert_eq!(packages[0].flag_names.len(), 3);
Dennis Shen0d1c5622023-12-01 21:04:29 +0000210 assert!(packages[0].flag_names.contains("enabled_rw"));
211 assert!(packages[0].flag_names.contains("disabled_rw"));
212 assert!(packages[0].flag_names.contains("enabled_ro"));
Dennis Shen0d1c5622023-12-01 21:04:29 +0000213 assert_eq!(packages[0].boolean_offset, 0);
214
215 assert_eq!(packages[1].package_name, "com.android.aconfig.storage.test_2");
216 assert_eq!(packages[1].package_id, 1);
217 assert_eq!(packages[1].flag_names.len(), 3);
218 assert!(packages[1].flag_names.contains("enabled_ro"));
219 assert!(packages[1].flag_names.contains("disabled_ro"));
220 assert!(packages[1].flag_names.contains("enabled_fixed_ro"));
Dennis Shenb65b3502024-01-04 15:57:42 +0000221 assert_eq!(packages[1].boolean_offset, 6);
Dennis Shen8bab8592023-12-20 17:45:00 +0000222
223 assert_eq!(packages[2].package_name, "com.android.aconfig.storage.test_4");
224 assert_eq!(packages[2].package_id, 2);
225 assert_eq!(packages[2].flag_names.len(), 2);
226 assert!(packages[2].flag_names.contains("enabled_ro"));
227 assert!(packages[2].flag_names.contains("enabled_fixed_ro"));
Dennis Shenb65b3502024-01-04 15:57:42 +0000228 assert_eq!(packages[2].boolean_offset, 12);
Dennis Shen0d1c5622023-12-01 21:04:29 +0000229 }
230}