blob: a28fccd9171ee6a12b04d8e62b8dcfe6e77a335c [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>,
Dennis Shend0886502024-01-09 16:49:53 +000059 // offset of the first boolean flag in this flag package with respect to the start of
60 // boolean flag value array in the flag value file
Dennis Shen0d1c5622023-12-01 21:04:29 +000061 pub boolean_offset: u32,
62}
63
64impl<'a> FlagPackage<'a> {
65 fn new(package_name: &'a str, package_id: u32) -> Self {
66 FlagPackage {
67 package_name,
68 package_id,
69 flag_names: HashSet::new(),
70 boolean_flags: vec![],
71 boolean_offset: 0,
72 }
73 }
74
75 fn insert(&mut self, pf: &'a ProtoParsedFlag) {
76 if self.flag_names.insert(pf.name()) {
77 self.boolean_flags.push(pf);
78 }
79 }
80}
81
82pub fn group_flags_by_package<'a, I>(parsed_flags_vec_iter: I) -> Vec<FlagPackage<'a>>
83where
84 I: Iterator<Item = &'a ProtoParsedFlags>,
85{
86 // group flags by package
87 let mut packages: Vec<FlagPackage<'a>> = Vec::new();
Dennis Shenadc7b732023-12-11 18:59:13 +000088 let mut package_index: HashMap<&str, usize> = HashMap::new();
Dennis Shen0d1c5622023-12-01 21:04:29 +000089 for parsed_flags in parsed_flags_vec_iter {
90 for parsed_flag in parsed_flags.parsed_flag.iter() {
91 let index = *(package_index.entry(parsed_flag.package()).or_insert(packages.len()));
92 if index == packages.len() {
93 packages.push(FlagPackage::new(parsed_flag.package(), index as u32));
94 }
95 packages[index].insert(parsed_flag);
96 }
97 }
98
99 // calculate package flag value start offset, in flag value file, each boolean
Dennis Shend0886502024-01-09 16:49:53 +0000100 // is stored as a single byte
Dennis Shen0d1c5622023-12-01 21:04:29 +0000101 let mut boolean_offset = 0;
102 for p in packages.iter_mut() {
103 p.boolean_offset = boolean_offset;
Dennis Shend0886502024-01-09 16:49:53 +0000104 boolean_offset += p.boolean_flags.len() as u32;
Dennis Shen0d1c5622023-12-01 21:04:29 +0000105 }
106
107 packages
108}
109
110pub fn generate_storage_files<'a, I>(
Dennis Shenadc7b732023-12-11 18:59:13 +0000111 container: &str,
Dennis Shen0d1c5622023-12-01 21:04:29 +0000112 parsed_flags_vec_iter: I,
113) -> Result<Vec<OutputFile>>
114where
115 I: Iterator<Item = &'a ProtoParsedFlags>,
116{
Dennis Shenadc7b732023-12-11 18:59:13 +0000117 let packages = group_flags_by_package(parsed_flags_vec_iter);
118
119 // create and serialize package map
120 let package_table = PackageTable::new(container, &packages)?;
121 let package_table_file_path = PathBuf::from("package.map");
122 let package_table_file =
123 OutputFile { contents: package_table.as_bytes(), path: package_table_file_path };
124
Dennis Shenb65b3502024-01-04 15:57:42 +0000125 // create and serialize flag map
126 let flag_table = FlagTable::new(container, &packages)?;
127 let flag_table_file_path = PathBuf::from("flag.map");
128 let flag_table_file =
129 OutputFile { contents: flag_table.as_bytes(), path: flag_table_file_path };
130
131 Ok(vec![package_table_file, flag_table_file])
Dennis Shen0d1c5622023-12-01 21:04:29 +0000132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use crate::Input;
138
Dennis Shend0886502024-01-09 16:49:53 +0000139 /// Read and parse bytes as u16
140 pub fn read_u16_from_bytes(buf: &[u8], head: &mut usize) -> Result<u16> {
141 let val = u16::from_le_bytes(buf[*head..*head + 2].try_into()?);
142 *head += 2;
143 Ok(val)
144 }
145
Dennis Shenadc7b732023-12-11 18:59:13 +0000146 /// Read and parse bytes as u32
147 pub fn read_u32_from_bytes(buf: &[u8], head: &mut usize) -> Result<u32> {
148 let val = u32::from_le_bytes(buf[*head..*head + 4].try_into()?);
149 *head += 4;
150 Ok(val)
151 }
152
153 /// Read and parse bytes as string
154 pub fn read_str_from_bytes(buf: &[u8], head: &mut usize) -> Result<String> {
155 let num_bytes = read_u32_from_bytes(buf, head)? as usize;
156 let val = String::from_utf8(buf[*head..*head + num_bytes].to_vec())?;
157 *head += num_bytes;
158 Ok(val)
159 }
160
Dennis Shen0d1c5622023-12-01 21:04:29 +0000161 pub fn parse_all_test_flags() -> Vec<ProtoParsedFlags> {
162 let aconfig_files = [
163 (
164 "com.android.aconfig.storage.test_1",
Dennis Shenb65b3502024-01-04 15:57:42 +0000165 "storage_test_1.aconfig",
166 include_bytes!("../../tests/storage_test_1.aconfig").as_slice(),
Dennis Shen0d1c5622023-12-01 21:04:29 +0000167 ),
168 (
169 "com.android.aconfig.storage.test_2",
170 "storage_test_2.aconfig",
171 include_bytes!("../../tests/storage_test_2.aconfig").as_slice(),
172 ),
Dennis Shen8bab8592023-12-20 17:45:00 +0000173 (
174 "com.android.aconfig.storage.test_4",
175 "storage_test_4.aconfig",
176 include_bytes!("../../tests/storage_test_4.aconfig").as_slice(),
177 ),
Dennis Shen0d1c5622023-12-01 21:04:29 +0000178 ];
179
180 aconfig_files
181 .into_iter()
182 .map(|(pkg, file, content)| {
183 let bytes = crate::commands::parse_flags(
184 pkg,
185 Some("system"),
186 vec![Input {
187 source: format!("tests/{}", file).to_string(),
188 reader: Box::new(content),
189 }],
190 vec![],
191 crate::commands::DEFAULT_FLAG_PERMISSION,
192 )
193 .unwrap();
194 crate::protos::parsed_flags::try_from_binary_proto(&bytes).unwrap()
195 })
196 .collect()
197 }
198
199 #[test]
200 fn test_flag_package() {
201 let caches = parse_all_test_flags();
202 let packages = group_flags_by_package(caches.iter());
203
204 for pkg in packages.iter() {
205 let pkg_name = pkg.package_name;
206 assert_eq!(pkg.flag_names.len(), pkg.boolean_flags.len());
207 for pf in pkg.boolean_flags.iter() {
208 assert!(pkg.flag_names.contains(pf.name()));
209 assert_eq!(pf.package(), pkg_name);
210 }
211 }
212
Dennis Shen8bab8592023-12-20 17:45:00 +0000213 assert_eq!(packages.len(), 3);
Dennis Shen0d1c5622023-12-01 21:04:29 +0000214
215 assert_eq!(packages[0].package_name, "com.android.aconfig.storage.test_1");
216 assert_eq!(packages[0].package_id, 0);
Dennis Shenb65b3502024-01-04 15:57:42 +0000217 assert_eq!(packages[0].flag_names.len(), 3);
Dennis Shen0d1c5622023-12-01 21:04:29 +0000218 assert!(packages[0].flag_names.contains("enabled_rw"));
219 assert!(packages[0].flag_names.contains("disabled_rw"));
220 assert!(packages[0].flag_names.contains("enabled_ro"));
Dennis Shen0d1c5622023-12-01 21:04:29 +0000221 assert_eq!(packages[0].boolean_offset, 0);
222
223 assert_eq!(packages[1].package_name, "com.android.aconfig.storage.test_2");
224 assert_eq!(packages[1].package_id, 1);
225 assert_eq!(packages[1].flag_names.len(), 3);
226 assert!(packages[1].flag_names.contains("enabled_ro"));
227 assert!(packages[1].flag_names.contains("disabled_ro"));
228 assert!(packages[1].flag_names.contains("enabled_fixed_ro"));
Dennis Shend0886502024-01-09 16:49:53 +0000229 assert_eq!(packages[1].boolean_offset, 3);
Dennis Shen8bab8592023-12-20 17:45:00 +0000230
231 assert_eq!(packages[2].package_name, "com.android.aconfig.storage.test_4");
232 assert_eq!(packages[2].package_id, 2);
233 assert_eq!(packages[2].flag_names.len(), 2);
234 assert!(packages[2].flag_names.contains("enabled_ro"));
235 assert!(packages[2].flag_names.contains("enabled_fixed_ro"));
Dennis Shend0886502024-01-09 16:49:53 +0000236 assert_eq!(packages[2].boolean_offset, 6);
Dennis Shen0d1c5622023-12-01 21:04:29 +0000237 }
238}