aconfig: create flag value file

Create flag_value module to create flag value file. Flag value file
contains a header section at the start of the file, followed by a
boolean array.

Bug: b/312243587
Test: atest aconfig.test
Change-Id: If76660189d63073fbd477e1e447240e0cd029604
diff --git a/tools/aconfig/src/storage/flag_table.rs b/tools/aconfig/src/storage/flag_table.rs
index 595217e..3545700 100644
--- a/tools/aconfig/src/storage/flag_table.rs
+++ b/tools/aconfig/src/storage/flag_table.rs
@@ -295,8 +295,6 @@
         };
         assert_eq!(header, &expected_header);
 
-        println!("{:?}", &flag_table.as_ref().unwrap().nodes);
-
         let buckets: &Vec<Option<u32>> = &flag_table.as_ref().unwrap().buckets;
         let expected_bucket: Vec<Option<u32>> = vec![
             Some(98),
@@ -338,9 +336,7 @@
     #[test]
     // this test point locks down the table serialization
     fn test_serialization() {
-        let flag_table = create_test_flag_table();
-        assert!(flag_table.is_ok());
-        let flag_table = flag_table.unwrap();
+        let flag_table = create_test_flag_table().unwrap();
 
         let header: &FlagTableHeader = &flag_table.header;
         let reinterpreted_header = FlagTableHeader::from_bytes(&header.as_bytes());
diff --git a/tools/aconfig/src/storage/flag_value.rs b/tools/aconfig/src/storage/flag_value.rs
new file mode 100644
index 0000000..45f5ec0
--- /dev/null
+++ b/tools/aconfig/src/storage/flag_value.rs
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+use crate::commands::assign_flag_ids;
+use crate::protos::ProtoFlagState;
+use crate::storage::{self, FlagPackage};
+use anyhow::{anyhow, Result};
+
+#[derive(PartialEq, Debug)]
+pub struct FlagValueHeader {
+    pub version: u32,
+    pub container: String,
+    pub file_size: u32,
+    pub num_flags: u32,
+    pub boolean_value_offset: u32,
+}
+
+impl FlagValueHeader {
+    fn new(container: &str, num_flags: u32) -> Self {
+        Self {
+            version: storage::FILE_VERSION,
+            container: String::from(container),
+            file_size: 0,
+            num_flags,
+            boolean_value_offset: 0,
+        }
+    }
+
+    fn as_bytes(&self) -> Vec<u8> {
+        let mut result = Vec::new();
+        result.extend_from_slice(&self.version.to_le_bytes());
+        let container_bytes = self.container.as_bytes();
+        result.extend_from_slice(&(container_bytes.len() as u32).to_le_bytes());
+        result.extend_from_slice(container_bytes);
+        result.extend_from_slice(&self.file_size.to_le_bytes());
+        result.extend_from_slice(&self.num_flags.to_le_bytes());
+        result.extend_from_slice(&self.boolean_value_offset.to_le_bytes());
+        result
+    }
+}
+
+#[derive(PartialEq, Debug)]
+pub struct FlagValueList {
+    pub header: FlagValueHeader,
+    pub booleans: Vec<bool>,
+}
+
+impl FlagValueList {
+    pub fn new(container: &str, packages: &[FlagPackage]) -> Result<Self> {
+        // create list
+        let num_flags = packages.iter().map(|pkg| pkg.boolean_flags.len() as u32).sum();
+
+        let mut list = Self {
+            header: FlagValueHeader::new(container, num_flags),
+            booleans: vec![false; num_flags as usize],
+        };
+
+        for pkg in packages.iter() {
+            let start_offset = pkg.boolean_offset as usize;
+            let flag_ids = assign_flag_ids(pkg.package_name, pkg.boolean_flags.iter().copied())?;
+            for pf in pkg.boolean_flags.iter() {
+                let fid = flag_ids
+                    .get(pf.name())
+                    .ok_or(anyhow!(format!("missing flag id for {}", pf.name())))?;
+
+                list.booleans[start_offset + (*fid as usize)] =
+                    pf.state() == ProtoFlagState::ENABLED;
+            }
+        }
+
+        // initialize all header fields
+        list.header.boolean_value_offset = list.header.as_bytes().len() as u32;
+        list.header.file_size = list.header.boolean_value_offset + num_flags;
+
+        Ok(list)
+    }
+
+    pub fn as_bytes(&self) -> Vec<u8> {
+        [
+            self.header.as_bytes(),
+            self.booleans
+                .iter()
+                .map(|&v| u8::from(v).to_le_bytes())
+                .collect::<Vec<_>>()
+                .concat(),
+        ]
+        .concat()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::storage::{
+        group_flags_by_package, tests::parse_all_test_flags, tests::read_str_from_bytes,
+        tests::read_u32_from_bytes, tests::read_u8_from_bytes,
+    };
+
+    impl FlagValueHeader {
+        // test only method to deserialize back into the header struct
+        fn from_bytes(bytes: &[u8]) -> Result<Self> {
+            let mut head = 0;
+            Ok(Self {
+                version: read_u32_from_bytes(bytes, &mut head)?,
+                container: read_str_from_bytes(bytes, &mut head)?,
+                file_size: read_u32_from_bytes(bytes, &mut head)?,
+                num_flags: read_u32_from_bytes(bytes, &mut head)?,
+                boolean_value_offset: read_u32_from_bytes(bytes, &mut head)?,
+            })
+        }
+    }
+
+    impl FlagValueList {
+        // test only method to deserialize back into the flag value struct
+        fn from_bytes(bytes: &[u8]) -> Result<Self> {
+            let header = FlagValueHeader::from_bytes(bytes)?;
+            let num_flags = header.num_flags;
+            let mut head = header.as_bytes().len();
+            let booleans = (0..num_flags)
+                .map(|_| read_u8_from_bytes(bytes, &mut head).unwrap() == 1)
+                .collect();
+            let list = Self { header, booleans };
+            Ok(list)
+        }
+    }
+
+    pub fn create_test_flag_value_list() -> Result<FlagValueList> {
+        let caches = parse_all_test_flags();
+        let packages = group_flags_by_package(caches.iter());
+        FlagValueList::new("system", &packages)
+    }
+
+    #[test]
+    // this test point locks down the flag value creation and each field
+    fn test_list_contents() {
+        let flag_value_list = create_test_flag_value_list();
+        assert!(flag_value_list.is_ok());
+
+        let header: &FlagValueHeader = &flag_value_list.as_ref().unwrap().header;
+        let expected_header = FlagValueHeader {
+            version: storage::FILE_VERSION,
+            container: String::from("system"),
+            file_size: 34,
+            num_flags: 8,
+            boolean_value_offset: 26,
+        };
+        assert_eq!(header, &expected_header);
+
+        let booleans: &Vec<bool> = &flag_value_list.as_ref().unwrap().booleans;
+        let expected_booleans: Vec<bool> = vec![false; header.num_flags as usize];
+        assert_eq!(booleans, &expected_booleans);
+    }
+
+    #[test]
+    // this test point locks down the value list serialization
+    fn test_serialization() {
+        let flag_value_list = create_test_flag_value_list().unwrap();
+
+        let header: &FlagValueHeader = &flag_value_list.header;
+        let reinterpreted_header = FlagValueHeader::from_bytes(&header.as_bytes());
+        assert!(reinterpreted_header.is_ok());
+        assert_eq!(header, &reinterpreted_header.unwrap());
+
+        let reinterpreted_value_list = FlagValueList::from_bytes(&flag_value_list.as_bytes());
+        assert!(reinterpreted_value_list.is_ok());
+        assert_eq!(&flag_value_list, &reinterpreted_value_list.unwrap());
+    }
+}
diff --git a/tools/aconfig/src/storage/mod.rs b/tools/aconfig/src/storage/mod.rs
index a28fccd..36ea309 100644
--- a/tools/aconfig/src/storage/mod.rs
+++ b/tools/aconfig/src/storage/mod.rs
@@ -15,6 +15,7 @@
  */
 
 pub mod flag_table;
+pub mod flag_value;
 pub mod package_table;
 
 use anyhow::{anyhow, Result};
@@ -24,7 +25,9 @@
 
 use crate::commands::OutputFile;
 use crate::protos::{ProtoParsedFlag, ProtoParsedFlags};
-use crate::storage::{flag_table::FlagTable, package_table::PackageTable};
+use crate::storage::{
+    flag_table::FlagTable, flag_value::FlagValueList, package_table::PackageTable,
+};
 
 pub const FILE_VERSION: u32 = 1;
 
@@ -128,7 +131,13 @@
     let flag_table_file =
         OutputFile { contents: flag_table.as_bytes(), path: flag_table_file_path };
 
-    Ok(vec![package_table_file, flag_table_file])
+    // create and serialize flag value
+    let flag_value = FlagValueList::new(container, &packages)?;
+    let flag_value_file_path = PathBuf::from("flag.val");
+    let flag_value_file =
+        OutputFile { contents: flag_value.as_bytes(), path: flag_value_file_path };
+
+    Ok(vec![package_table_file, flag_table_file, flag_value_file])
 }
 
 #[cfg(test)]
@@ -136,6 +145,13 @@
     use super::*;
     use crate::Input;
 
+    /// Read and parse bytes as u8
+    pub fn read_u8_from_bytes(buf: &[u8], head: &mut usize) -> Result<u8> {
+        let val = u8::from_le_bytes(buf[*head..*head + 1].try_into()?);
+        *head += 1;
+        Ok(val)
+    }
+
     /// Read and parse bytes as u16
     pub fn read_u16_from_bytes(buf: &[u8], head: &mut usize) -> Result<u16> {
         let val = u16::from_le_bytes(buf[*head..*head + 2].try_into()?);
diff --git a/tools/aconfig/src/storage/package_table.rs b/tools/aconfig/src/storage/package_table.rs
index 0ce1349..4036234 100644
--- a/tools/aconfig/src/storage/package_table.rs
+++ b/tools/aconfig/src/storage/package_table.rs
@@ -277,9 +277,7 @@
     #[test]
     // this test point locks down the table serialization
     fn test_serialization() {
-        let package_table = create_test_package_table();
-        assert!(package_table.is_ok());
-        let package_table = package_table.unwrap();
+        let package_table = create_test_package_table().unwrap();
 
         let header: &PackageTableHeader = &package_table.header;
         let reinterpreted_header = PackageTableHeader::from_bytes(&header.as_bytes());