blob: 76835e015d9fc9b67312b8662d45d729feb47944 [file] [log] [blame]
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
pub mod flag_table;
pub mod package_table;
use anyhow::{anyhow, Result};
use std::collections::{hash_map::DefaultHasher, HashMap, HashSet};
use std::hash::{Hash, Hasher};
use std::path::PathBuf;
use crate::commands::OutputFile;
use crate::protos::{ProtoParsedFlag, ProtoParsedFlags};
use crate::storage::{flag_table::FlagTable, package_table::PackageTable};
pub const FILE_VERSION: u32 = 1;
pub const HASH_PRIMES: [u32; 29] = [
7, 17, 29, 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, 49157, 98317, 196613, 393241,
786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611,
402653189, 805306457, 1610612741,
];
/// Get the right hash table size given number of entries in the table. Use a
/// load factor of 0.5 for performance.
pub fn get_table_size(entries: u32) -> Result<u32> {
HASH_PRIMES
.iter()
.find(|&&num| num >= 2 * entries)
.copied()
.ok_or(anyhow!("Number of packages is too large"))
}
/// Get the corresponding bucket index given the key and number of buckets
pub fn get_bucket_index<T: Hash>(val: &T, num_buckets: u32) -> u32 {
let mut s = DefaultHasher::new();
val.hash(&mut s);
(s.finish() % num_buckets as u64) as u32
}
pub struct FlagPackage<'a> {
pub package_name: &'a str,
pub package_id: u32,
pub flag_names: HashSet<&'a str>,
pub boolean_flags: Vec<&'a ProtoParsedFlag>,
pub boolean_offset: u32,
}
impl<'a> FlagPackage<'a> {
fn new(package_name: &'a str, package_id: u32) -> Self {
FlagPackage {
package_name,
package_id,
flag_names: HashSet::new(),
boolean_flags: vec![],
boolean_offset: 0,
}
}
fn insert(&mut self, pf: &'a ProtoParsedFlag) {
if self.flag_names.insert(pf.name()) {
self.boolean_flags.push(pf);
}
}
}
pub fn group_flags_by_package<'a, I>(parsed_flags_vec_iter: I) -> Vec<FlagPackage<'a>>
where
I: Iterator<Item = &'a ProtoParsedFlags>,
{
// group flags by package
let mut packages: Vec<FlagPackage<'a>> = Vec::new();
let mut package_index: HashMap<&str, usize> = HashMap::new();
for parsed_flags in parsed_flags_vec_iter {
for parsed_flag in parsed_flags.parsed_flag.iter() {
let index = *(package_index.entry(parsed_flag.package()).or_insert(packages.len()));
if index == packages.len() {
packages.push(FlagPackage::new(parsed_flag.package(), index as u32));
}
packages[index].insert(parsed_flag);
}
}
// calculate package flag value start offset, in flag value file, each boolean
// is stored as two bytes, the first byte will be the flag value. the second
// byte is flag info byte, which is a bitmask to indicate the status of a flag
let mut boolean_offset = 0;
for p in packages.iter_mut() {
p.boolean_offset = boolean_offset;
boolean_offset += 2 * p.boolean_flags.len() as u32;
}
packages
}
pub fn generate_storage_files<'a, I>(
container: &str,
parsed_flags_vec_iter: I,
) -> Result<Vec<OutputFile>>
where
I: Iterator<Item = &'a ProtoParsedFlags>,
{
let packages = group_flags_by_package(parsed_flags_vec_iter);
// create and serialize package map
let package_table = PackageTable::new(container, &packages)?;
let package_table_file_path = PathBuf::from("package.map");
let package_table_file =
OutputFile { contents: package_table.as_bytes(), path: package_table_file_path };
// create and serialize flag map
let flag_table = FlagTable::new(container, &packages)?;
let flag_table_file_path = PathBuf::from("flag.map");
let flag_table_file =
OutputFile { contents: flag_table.as_bytes(), path: flag_table_file_path };
Ok(vec![package_table_file, flag_table_file])
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Input;
/// Read and parse bytes as u32
pub fn read_u32_from_bytes(buf: &[u8], head: &mut usize) -> Result<u32> {
let val = u32::from_le_bytes(buf[*head..*head + 4].try_into()?);
*head += 4;
Ok(val)
}
/// Read and parse bytes as string
pub fn read_str_from_bytes(buf: &[u8], head: &mut usize) -> Result<String> {
let num_bytes = read_u32_from_bytes(buf, head)? as usize;
let val = String::from_utf8(buf[*head..*head + num_bytes].to_vec())?;
*head += num_bytes;
Ok(val)
}
pub fn parse_all_test_flags() -> Vec<ProtoParsedFlags> {
let aconfig_files = [
(
"com.android.aconfig.storage.test_1",
"storage_test_1.aconfig",
include_bytes!("../../tests/storage_test_1.aconfig").as_slice(),
),
(
"com.android.aconfig.storage.test_2",
"storage_test_2.aconfig",
include_bytes!("../../tests/storage_test_2.aconfig").as_slice(),
),
(
"com.android.aconfig.storage.test_4",
"storage_test_4.aconfig",
include_bytes!("../../tests/storage_test_4.aconfig").as_slice(),
),
];
aconfig_files
.into_iter()
.map(|(pkg, file, content)| {
let bytes = crate::commands::parse_flags(
pkg,
Some("system"),
vec![Input {
source: format!("tests/{}", file).to_string(),
reader: Box::new(content),
}],
vec![],
crate::commands::DEFAULT_FLAG_PERMISSION,
)
.unwrap();
crate::protos::parsed_flags::try_from_binary_proto(&bytes).unwrap()
})
.collect()
}
#[test]
fn test_flag_package() {
let caches = parse_all_test_flags();
let packages = group_flags_by_package(caches.iter());
for pkg in packages.iter() {
let pkg_name = pkg.package_name;
assert_eq!(pkg.flag_names.len(), pkg.boolean_flags.len());
for pf in pkg.boolean_flags.iter() {
assert!(pkg.flag_names.contains(pf.name()));
assert_eq!(pf.package(), pkg_name);
}
}
assert_eq!(packages.len(), 3);
assert_eq!(packages[0].package_name, "com.android.aconfig.storage.test_1");
assert_eq!(packages[0].package_id, 0);
assert_eq!(packages[0].flag_names.len(), 3);
assert!(packages[0].flag_names.contains("enabled_rw"));
assert!(packages[0].flag_names.contains("disabled_rw"));
assert!(packages[0].flag_names.contains("enabled_ro"));
assert_eq!(packages[0].boolean_offset, 0);
assert_eq!(packages[1].package_name, "com.android.aconfig.storage.test_2");
assert_eq!(packages[1].package_id, 1);
assert_eq!(packages[1].flag_names.len(), 3);
assert!(packages[1].flag_names.contains("enabled_ro"));
assert!(packages[1].flag_names.contains("disabled_ro"));
assert!(packages[1].flag_names.contains("enabled_fixed_ro"));
assert_eq!(packages[1].boolean_offset, 6);
assert_eq!(packages[2].package_name, "com.android.aconfig.storage.test_4");
assert_eq!(packages[2].package_id, 2);
assert_eq!(packages[2].flag_names.len(), 2);
assert!(packages[2].flag_names.contains("enabled_ro"));
assert!(packages[2].flag_names.contains("enabled_fixed_ro"));
assert_eq!(packages[2].boolean_offset, 12);
}
}