aconfig: add api to create flag info file based on package map and flag
map file

Bug: b/321077378
Test: atest aconfig_storage_file.test
Change-Id: I957e231bc11db856a8c6753771eaafea2f168352
diff --git a/tools/aconfig/aconfig_storage_file/src/flag_info.rs b/tools/aconfig/aconfig_storage_file/src/flag_info.rs
index c67e70e..3fff263 100644
--- a/tools/aconfig/aconfig_storage_file/src/flag_info.rs
+++ b/tools/aconfig/aconfig_storage_file/src/flag_info.rs
@@ -130,6 +130,11 @@
         let node = Self { attributes: read_u8_from_bytes(bytes, &mut head)? };
         Ok(node)
     }
+
+    /// Create flag info node
+    pub fn create(is_flag_rw: bool) -> Self {
+        Self { attributes: if is_flag_rw { FlagInfoBit::IsReadWrite as u8 } else { 0u8 } }
+    }
 }
 
 /// Flag info list struct
@@ -221,7 +226,7 @@
         let bytes = &flag_info_list.into_bytes();
         let mut head = 0;
         let version = read_u32_from_bytes(bytes, &mut head).unwrap();
-        assert_eq!(version, 1234)
+        assert_eq!(version, 1);
     }
 
     #[test]
diff --git a/tools/aconfig/aconfig_storage_file/src/lib.rs b/tools/aconfig/aconfig_storage_file/src/lib.rs
index 85f1f0a..3b975d9 100644
--- a/tools/aconfig/aconfig_storage_file/src/lib.rs
+++ b/tools/aconfig/aconfig_storage_file/src/lib.rs
@@ -44,14 +44,16 @@
 use std::collections::hash_map::DefaultHasher;
 use std::fs::File;
 use std::hash::{Hash, Hasher};
-use std::io::Read;
+use std::io::{Read, Write};
 
 pub use crate::flag_info::{FlagInfoHeader, FlagInfoList, FlagInfoNode};
 pub use crate::flag_table::{FlagTable, FlagTableHeader, FlagTableNode};
 pub use crate::flag_value::{FlagValueHeader, FlagValueList};
 pub use crate::package_table::{PackageTable, PackageTableHeader, PackageTableNode};
 
-use crate::AconfigStorageError::{BytesParseFail, HashTableSizeLimit, InvalidStoredFlagType};
+use crate::AconfigStorageError::{
+    BytesParseFail, FileCreationFail, HashTableSizeLimit, InvalidStoredFlagType,
+};
 
 /// Storage file version
 pub const FILE_VERSION: u32 = 1;
@@ -164,7 +166,6 @@
     InvalidStoredFlagType(#[source] anyhow::Error),
 }
 
-
 /// 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, AconfigStorageError> {
@@ -263,25 +264,82 @@
         let (package_name, package_offset) = package_info[node.package_id as usize];
         let flag_offset = package_offset + node.flag_id as u32;
         let flag_value = flag_value_list.booleans[flag_offset as usize];
-        flags.push((String::from(package_name), node.flag_name.clone(), node.flag_type, flag_value));
+        flags.push((
+            String::from(package_name),
+            node.flag_name.clone(),
+            node.flag_type,
+            flag_value,
+        ));
     }
 
-    flags.sort_by(|v1, v2| {
-        match v1.0.cmp(&v2.0) {
-            Ordering::Equal => v1.1.cmp(&v2.1),
-            other => other,
-        }
+    flags.sort_by(|v1, v2| match v1.0.cmp(&v2.0) {
+        Ordering::Equal => v1.1.cmp(&v2.1),
+        other => other,
     });
     Ok(flags)
 }
 
+/// Create flag info file
+pub fn create_flag_info(
+    package_map: &str,
+    flag_map: &str,
+    flag_info_out: &str,
+) -> Result<(), AconfigStorageError> {
+    let package_table = PackageTable::from_bytes(&read_file_to_bytes(package_map)?)?;
+    let flag_table = FlagTable::from_bytes(&read_file_to_bytes(flag_map)?)?;
+
+    if package_table.header.container != flag_table.header.container {
+        return Err(FileCreationFail(anyhow!(
+            "container for package map {} and flag map {} does not match",
+            package_table.header.container,
+            flag_table.header.container,
+        )));
+    }
+
+    let mut package_offsets = vec![0; package_table.header.num_packages as usize];
+    for node in package_table.nodes.iter() {
+        package_offsets[node.package_id as usize] = node.boolean_offset;
+    }
+
+    let mut is_flag_rw = vec![false; flag_table.header.num_flags as usize];
+    for node in flag_table.nodes.iter() {
+        let flag_offset = package_offsets[node.package_id as usize] + node.flag_id as u32;
+        is_flag_rw[flag_offset as usize] = node.flag_type == StoredFlagType::ReadWriteBoolean;
+    }
+
+    let mut list = FlagInfoList {
+        header: FlagInfoHeader {
+            version: FILE_VERSION,
+            container: flag_table.header.container,
+            file_type: StorageFileType::FlagInfo as u8,
+            file_size: 0,
+            num_flags: flag_table.header.num_flags,
+            boolean_flag_offset: 0,
+        },
+        nodes: is_flag_rw.iter().map(|&rw| FlagInfoNode::create(rw)).collect(),
+    };
+
+    list.header.boolean_flag_offset = list.header.into_bytes().len() as u32;
+    list.header.file_size = list.into_bytes().len() as u32;
+
+    let mut file = File::create(flag_info_out).map_err(|errmsg| {
+        FileCreationFail(anyhow!("fail to create file {}: {}", flag_info_out, errmsg))
+    })?;
+    file.write_all(&list.into_bytes()).map_err(|errmsg| {
+        FileCreationFail(anyhow!("fail to write to file {}: {}", flag_info_out, errmsg))
+    })?;
+
+    Ok(())
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
     use crate::test_utils::{
-        create_test_flag_table, create_test_flag_value_list, create_test_package_table,
-        write_bytes_to_temp_file,
+        create_test_flag_info_list, create_test_flag_table, create_test_flag_value_list,
+        create_test_package_table, write_bytes_to_temp_file,
     };
+    use tempfile::NamedTempFile;
 
     #[test]
     // this test point locks down the flag list api
@@ -309,13 +367,13 @@
                 String::from("com.android.aconfig.storage.test_1"),
                 String::from("enabled_ro"),
                 StoredFlagType::ReadOnlyBoolean,
-                true
+                true,
             ),
             (
                 String::from("com.android.aconfig.storage.test_1"),
                 String::from("enabled_rw"),
                 StoredFlagType::ReadWriteBoolean,
-                true
+                true,
             ),
             (
                 String::from("com.android.aconfig.storage.test_2"),
@@ -333,7 +391,7 @@
                 String::from("com.android.aconfig.storage.test_2"),
                 String::from("enabled_ro"),
                 StoredFlagType::ReadOnlyBoolean,
-                true
+                true,
             ),
             (
                 String::from("com.android.aconfig.storage.test_4"),
@@ -345,9 +403,36 @@
                 String::from("com.android.aconfig.storage.test_4"),
                 String::from("enabled_ro"),
                 StoredFlagType::ReadOnlyBoolean,
-                true
+                true,
             ),
         ];
         assert_eq!(flags, expected);
     }
+
+    fn create_empty_temp_file() -> Result<NamedTempFile, AconfigStorageError> {
+        let file = NamedTempFile::new().map_err(|_| {
+            AconfigStorageError::FileCreationFail(anyhow!("Failed to create temp file"))
+        })?;
+        Ok(file)
+    }
+
+    #[test]
+    // this test point locks down the flag info creation
+    fn test_create_flag_info() {
+        let package_table =
+            write_bytes_to_temp_file(&create_test_package_table().into_bytes()).unwrap();
+        let flag_table = write_bytes_to_temp_file(&create_test_flag_table().into_bytes()).unwrap();
+        let flag_info = create_empty_temp_file().unwrap();
+
+        let package_table_path = package_table.path().display().to_string();
+        let flag_table_path = flag_table.path().display().to_string();
+        let flag_info_path = flag_info.path().display().to_string();
+
+        assert!(create_flag_info(&package_table_path, &flag_table_path, &flag_info_path).is_ok());
+
+        let flag_info =
+            FlagInfoList::from_bytes(&read_file_to_bytes(&flag_info_path).unwrap()).unwrap();
+        let expected_flag_info = create_test_flag_info_list();
+        assert_eq!(flag_info, expected_flag_info);
+    }
 }
diff --git a/tools/aconfig/aconfig_storage_file/src/test_utils.rs b/tools/aconfig/aconfig_storage_file/src/test_utils.rs
index 9198f93..c0f647a 100644
--- a/tools/aconfig/aconfig_storage_file/src/test_utils.rs
+++ b/tools/aconfig/aconfig_storage_file/src/test_utils.rs
@@ -133,14 +133,15 @@
 
 pub fn create_test_flag_info_list() -> FlagInfoList {
     let header = FlagInfoHeader {
-        version: 1234,
+        version: 1,
         container: String::from("mockup"),
         file_type: StorageFileType::FlagInfo as u8,
         file_size: 35,
         num_flags: 8,
         boolean_flag_offset: 27,
     };
-    let nodes: Vec<FlagInfoNode> = vec![FlagInfoNode { attributes: 0 }; 8];
+    let is_flag_rw = [true, false, true, false, false, false, false, false];
+    let nodes = is_flag_rw.iter().map(|&rw| FlagInfoNode::create(rw)).collect();
     FlagInfoList { header, nodes }
 }