• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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 use crate::commands::{assign_flag_ids, should_include_flag};
18 use crate::storage::FlagPackage;
19 use aconfig_protos::ProtoFlagPermission;
20 use aconfig_storage_file::{
21     get_table_size, FlagTable, FlagTableHeader, FlagTableNode, StorageFileType, StoredFlagType,
22 };
23 use anyhow::{anyhow, Result};
24 
new_header(container: &str, num_flags: u32, version: u32) -> FlagTableHeader25 fn new_header(container: &str, num_flags: u32, version: u32) -> FlagTableHeader {
26     FlagTableHeader {
27         version,
28         container: String::from(container),
29         file_type: StorageFileType::FlagMap as u8,
30         file_size: 0,
31         num_flags,
32         bucket_offset: 0,
33         node_offset: 0,
34     }
35 }
36 
37 // a struct that contains FlagTableNode and a bunch of other information to help
38 // flag table creation
39 #[derive(PartialEq, Debug, Clone)]
40 struct FlagTableNodeWrapper {
41     pub node: FlagTableNode,
42     pub bucket_index: u32,
43 }
44 
45 impl FlagTableNodeWrapper {
new( package_id: u32, flag_name: &str, flag_type: StoredFlagType, flag_index: u16, num_buckets: u32, ) -> Self46     fn new(
47         package_id: u32,
48         flag_name: &str,
49         flag_type: StoredFlagType,
50         flag_index: u16,
51         num_buckets: u32,
52     ) -> Self {
53         let bucket_index = FlagTableNode::find_bucket_index(package_id, flag_name, num_buckets);
54         let node = FlagTableNode {
55             package_id,
56             flag_name: flag_name.to_string(),
57             flag_type,
58             flag_index,
59             next_offset: None,
60         };
61         Self { node, bucket_index }
62     }
63 
create_nodes(package: &FlagPackage, num_buckets: u32) -> Result<Vec<Self>>64     fn create_nodes(package: &FlagPackage, num_buckets: u32) -> Result<Vec<Self>> {
65         // Exclude system/vendor/product flags that are RO+disabled.
66         let mut filtered_package = package.clone();
67         filtered_package.boolean_flags.retain(|pf| should_include_flag(pf));
68 
69         let flag_ids =
70             assign_flag_ids(package.package_name, filtered_package.boolean_flags.iter().copied())?;
71         filtered_package
72             .boolean_flags
73             .iter()
74             .map(|&pf| {
75                 let fid = flag_ids
76                     .get(pf.name())
77                     .ok_or(anyhow!(format!("missing flag id for {}", pf.name())))?;
78                 let flag_type = if pf.is_fixed_read_only() {
79                     StoredFlagType::FixedReadOnlyBoolean
80                 } else {
81                     match pf.permission() {
82                         ProtoFlagPermission::READ_WRITE => StoredFlagType::ReadWriteBoolean,
83                         ProtoFlagPermission::READ_ONLY => StoredFlagType::ReadOnlyBoolean,
84                     }
85                 };
86                 Ok(Self::new(package.package_id, pf.name(), flag_type, *fid, num_buckets))
87             })
88             .collect::<Result<Vec<_>>>()
89     }
90 }
91 
create_flag_table( container: &str, packages: &[FlagPackage], version: u32, ) -> Result<FlagTable>92 pub fn create_flag_table(
93     container: &str,
94     packages: &[FlagPackage],
95     version: u32,
96 ) -> Result<FlagTable> {
97     // create table
98     let num_flags = packages.iter().map(|pkg| pkg.boolean_flags.len() as u32).sum();
99     let num_buckets = get_table_size(num_flags)?;
100 
101     let mut header = new_header(container, num_flags, version);
102     let mut buckets = vec![None; num_buckets as usize];
103     let mut node_wrappers = packages
104         .iter()
105         .map(|pkg| FlagTableNodeWrapper::create_nodes(pkg, num_buckets))
106         .collect::<Result<Vec<_>>>()?
107         .concat();
108 
109     // initialize all header fields
110     header.bucket_offset = header.into_bytes().len() as u32;
111     header.node_offset = header.bucket_offset + num_buckets * 4;
112     header.file_size = header.node_offset
113         + node_wrappers.iter().map(|x| x.node.into_bytes().len()).sum::<usize>() as u32;
114 
115     // sort nodes by bucket index for efficiency
116     node_wrappers.sort_by(|a, b| a.bucket_index.cmp(&b.bucket_index));
117 
118     // fill all node offset
119     let mut offset = header.node_offset;
120     for i in 0..node_wrappers.len() {
121         let node_bucket_idx = node_wrappers[i].bucket_index;
122         let next_node_bucket_idx = if i + 1 < node_wrappers.len() {
123             Some(node_wrappers[i + 1].bucket_index)
124         } else {
125             None
126         };
127 
128         if buckets[node_bucket_idx as usize].is_none() {
129             buckets[node_bucket_idx as usize] = Some(offset);
130         }
131         offset += node_wrappers[i].node.into_bytes().len() as u32;
132 
133         if let Some(index) = next_node_bucket_idx {
134             if index == node_bucket_idx {
135                 node_wrappers[i].node.next_offset = Some(offset);
136             }
137         }
138     }
139 
140     let table =
141         FlagTable { header, buckets, nodes: node_wrappers.into_iter().map(|nw| nw.node).collect() };
142 
143     Ok(table)
144 }
145 
146 #[cfg(test)]
147 mod tests {
148     use aconfig_storage_file::DEFAULT_FILE_VERSION;
149 
150     use super::*;
151     use crate::storage::{group_flags_by_package, tests::parse_all_test_flags};
152 
create_test_flag_table_from_source() -> Result<FlagTable>153     fn create_test_flag_table_from_source() -> Result<FlagTable> {
154         let caches = parse_all_test_flags();
155         let packages = group_flags_by_package(caches.iter(), DEFAULT_FILE_VERSION);
156         create_flag_table("mockup", &packages, DEFAULT_FILE_VERSION)
157     }
158 
159     #[test]
160     // this test point locks down the table creation and each field
test_table_contents()161     fn test_table_contents() {
162         let flag_table = create_test_flag_table_from_source();
163         assert!(flag_table.is_ok());
164         let expected_flag_table =
165             aconfig_storage_file::test_utils::create_test_flag_table(DEFAULT_FILE_VERSION);
166         assert_eq!(flag_table.unwrap(), expected_flag_table);
167     }
168 }
169