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