blob: ef1b4f654c8adfbcd4cbae82e3057045970a3861 [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_info;
pub mod flag_table;
pub mod flag_value;
pub mod package_table;
use anyhow::Result;
use std::collections::{HashMap, HashSet};
use crate::commands::compute_flags_fingerprint;
use crate::storage::{
flag_info::create_flag_info, flag_table::create_flag_table, flag_value::create_flag_value,
package_table::create_package_table,
};
use aconfig_protos::{ProtoParsedFlag, ProtoParsedFlags};
use aconfig_storage_file::StorageFileType;
pub struct FlagPackage<'a> {
pub package_name: &'a str,
pub package_id: u32,
pub fingerprint: u64,
pub flag_names: HashSet<&'a str>,
pub boolean_flags: Vec<&'a ProtoParsedFlag>,
// The index of the first boolean flag in this aconfig package among all boolean
// flags in this container.
pub boolean_start_index: u32,
}
impl<'a> FlagPackage<'a> {
fn new(package_name: &'a str, package_id: u32) -> Self {
FlagPackage {
package_name,
package_id,
fingerprint: 0,
flag_names: HashSet::new(),
boolean_flags: vec![],
boolean_start_index: 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, version: u32) -> 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 boolean flag start index for each package
let mut boolean_start_index = 0;
for p in packages.iter_mut() {
p.boolean_start_index = boolean_start_index;
boolean_start_index += p.boolean_flags.len() as u32;
if version > 2 {
let mut flag_names_vec =
p.flag_names.clone().into_iter().map(String::from).collect::<Vec<_>>();
let fingerprint = compute_flags_fingerprint(&mut flag_names_vec);
p.fingerprint = fingerprint;
}
}
packages
}
pub fn generate_storage_file<'a, I>(
container: &str,
parsed_flags_vec_iter: I,
file: &StorageFileType,
version: u32,
) -> Result<Vec<u8>>
where
I: Iterator<Item = &'a ProtoParsedFlags>,
{
let packages = group_flags_by_package(parsed_flags_vec_iter, version);
match file {
StorageFileType::PackageMap => {
let package_table = create_package_table(container, &packages, version)?;
Ok(package_table.into_bytes())
}
StorageFileType::FlagMap => {
let flag_table = create_flag_table(container, &packages, version)?;
Ok(flag_table.into_bytes())
}
StorageFileType::FlagVal => {
let flag_value = create_flag_value(container, &packages, version)?;
Ok(flag_value.into_bytes())
}
StorageFileType::FlagInfo => {
let flag_info = create_flag_info(container, &packages, version)?;
Ok(flag_info.into_bytes())
}
}
}
#[cfg(test)]
mod tests {
use aconfig_storage_file::DEFAULT_FILE_VERSION;
use super::*;
use crate::Input;
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(),
"storage_test_1.value",
include_bytes!("../../tests/storage_test_1.values").as_slice(),
),
(
"com.android.aconfig.storage.test_2",
"storage_test_2.aconfig",
include_bytes!("../../tests/storage_test_2.aconfig").as_slice(),
"storage_test_2.value",
include_bytes!("../../tests/storage_test_2.values").as_slice(),
),
(
"com.android.aconfig.storage.test_4",
"storage_test_4.aconfig",
include_bytes!("../../tests/storage_test_4.aconfig").as_slice(),
"storage_test_4.value",
include_bytes!("../../tests/storage_test_4.values").as_slice(),
),
];
aconfig_files
.into_iter()
.map(|(pkg, aconfig_file, aconfig_content, value_file, value_content)| {
let bytes = crate::commands::parse_flags(
pkg,
Some("system"),
vec![Input {
source: format!("tests/{}", aconfig_file).to_string(),
reader: Box::new(aconfig_content),
}],
vec![Input {
source: format!("tests/{}", value_file).to_string(),
reader: Box::new(value_content),
}],
crate::commands::DEFAULT_FLAG_PERMISSION,
true,
)
.unwrap();
aconfig_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(), DEFAULT_FILE_VERSION);
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_start_index, 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_rw"));
assert!(packages[1].flag_names.contains("enabled_fixed_ro"));
assert_eq!(packages[1].boolean_start_index, 3);
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_rw"));
assert!(packages[2].flag_names.contains("enabled_fixed_ro"));
assert_eq!(packages[2].boolean_start_index, 6);
}
}