1 /*
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 
17 use anyhow::Result;
18 
19 use aconfig_storage_file::{
20     get_table_size, PackageTable, PackageTableHeader, PackageTableNode, StorageFileType,
21     FILE_VERSION,
22 };
23 
24 use crate::storage::FlagPackage;
25 
new_header(container: &str, num_packages: u32) -> PackageTableHeader26 fn new_header(container: &str, num_packages: u32) -> PackageTableHeader {
27     PackageTableHeader {
28         version: FILE_VERSION,
29         container: String::from(container),
30         file_type: StorageFileType::PackageMap as u8,
31         file_size: 0,
32         num_packages,
33         bucket_offset: 0,
34         node_offset: 0,
35     }
36 }
37 
38 // a struct that contains PackageTableNode and a bunch of other information to help
39 // package table creation
40 #[derive(PartialEq, Debug)]
41 struct PackageTableNodeWrapper {
42     pub node: PackageTableNode,
43     pub bucket_index: u32,
44 }
45 
46 impl PackageTableNodeWrapper {
new(package: &FlagPackage, num_buckets: u32) -> Self47     fn new(package: &FlagPackage, num_buckets: u32) -> Self {
48         let node = PackageTableNode {
49             package_name: String::from(package.package_name),
50             package_id: package.package_id,
51             boolean_start_index: package.boolean_start_index,
52             next_offset: None,
53         };
54         let bucket_index = PackageTableNode::find_bucket_index(package.package_name, num_buckets);
55         Self { node, bucket_index }
56     }
57 }
58 
create_package_table(container: &str, packages: &[FlagPackage]) -> Result<PackageTable>59 pub fn create_package_table(container: &str, packages: &[FlagPackage]) -> Result<PackageTable> {
60     // create table
61     let num_packages = packages.len() as u32;
62     let num_buckets = get_table_size(num_packages)?;
63     let mut header = new_header(container, num_packages);
64     let mut buckets = vec![None; num_buckets as usize];
65     let mut node_wrappers: Vec<_> =
66         packages.iter().map(|pkg| PackageTableNodeWrapper::new(pkg, num_buckets)).collect();
67 
68     // initialize all header fields
69     header.bucket_offset = header.into_bytes().len() as u32;
70     header.node_offset = header.bucket_offset + num_buckets * 4;
71     header.file_size = header.node_offset
72         + node_wrappers.iter().map(|x| x.node.into_bytes().len()).sum::<usize>() as u32;
73 
74     // sort node_wrappers by bucket index for efficiency
75     node_wrappers.sort_by(|a, b| a.bucket_index.cmp(&b.bucket_index));
76 
77     // fill all node offset
78     let mut offset = header.node_offset;
79     for i in 0..node_wrappers.len() {
80         let node_bucket_idx = node_wrappers[i].bucket_index;
81         let next_node_bucket_idx = if i + 1 < node_wrappers.len() {
82             Some(node_wrappers[i + 1].bucket_index)
83         } else {
84             None
85         };
86 
87         if buckets[node_bucket_idx as usize].is_none() {
88             buckets[node_bucket_idx as usize] = Some(offset);
89         }
90         offset += node_wrappers[i].node.into_bytes().len() as u32;
91 
92         if let Some(index) = next_node_bucket_idx {
93             if index == node_bucket_idx {
94                 node_wrappers[i].node.next_offset = Some(offset);
95             }
96         }
97     }
98 
99     let table = PackageTable {
100         header,
101         buckets,
102         nodes: node_wrappers.into_iter().map(|nw| nw.node).collect(),
103     };
104     Ok(table)
105 }
106 
107 #[cfg(test)]
108 mod tests {
109     use super::*;
110     use crate::storage::{group_flags_by_package, tests::parse_all_test_flags};
111 
create_test_package_table_from_source() -> Result<PackageTable>112     pub fn create_test_package_table_from_source() -> Result<PackageTable> {
113         let caches = parse_all_test_flags();
114         let packages = group_flags_by_package(caches.iter());
115         create_package_table("mockup", &packages)
116     }
117 
118     #[test]
119     // this test point locks down the table creation and each field
test_table_contents()120     fn test_table_contents() {
121         let package_table = create_test_package_table_from_source();
122         assert!(package_table.is_ok());
123         let expected_package_table = aconfig_storage_file::test_utils::create_test_package_table();
124         assert_eq!(package_table.unwrap(), expected_package_table);
125     }
126 }
127