Merge "aconfig: create aconfig_storage_file binary to print the storage files" into main
diff --git a/tools/aconfig/aconfig_storage_file/Android.bp b/tools/aconfig/aconfig_storage_file/Android.bp
index 6be3c19..1d74f69 100644
--- a/tools/aconfig/aconfig_storage_file/Android.bp
+++ b/tools/aconfig/aconfig_storage_file/Android.bp
@@ -10,6 +10,7 @@
     rustlibs: [
         "libanyhow",
         "libthiserror",
+        "libtempfile",
     ],
 }
 
diff --git a/tools/aconfig/aconfig_storage_file/Cargo.toml b/tools/aconfig/aconfig_storage_file/Cargo.toml
index c4e2670..6a9483e 100644
--- a/tools/aconfig/aconfig_storage_file/Cargo.toml
+++ b/tools/aconfig/aconfig_storage_file/Cargo.toml
@@ -15,6 +15,7 @@
 tempfile = "3.9.0"
 cxx = "1.0"
 thiserror = "1.0.56"
+clap = { version = "4.1.8", features = ["derive"] }
 
 [build-dependencies]
 protobuf-codegen = "3.2.0"
diff --git a/tools/aconfig/aconfig_storage_file/src/flag_table.rs b/tools/aconfig/aconfig_storage_file/src/flag_table.rs
index 61cf371..d6e5c62 100644
--- a/tools/aconfig/aconfig_storage_file/src/flag_table.rs
+++ b/tools/aconfig/aconfig_storage_file/src/flag_table.rs
@@ -20,9 +20,10 @@
 use crate::AconfigStorageError::{self, BytesParseFail};
 use crate::{get_bucket_index, read_str_from_bytes, read_u16_from_bytes, read_u32_from_bytes};
 use anyhow::anyhow;
+use std::fmt;
 
 /// Flag table header struct
-#[derive(PartialEq, Debug)]
+#[derive(PartialEq)]
 pub struct FlagTableHeader {
     pub version: u32,
     pub container: String,
@@ -32,6 +33,23 @@
     pub node_offset: u32,
 }
 
+/// Implement debug print trait for header
+impl fmt::Debug for FlagTableHeader {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        writeln!(
+            f,
+            "Version: {}, Container: {}, File Size: {}",
+            self.version, self.container, self.file_size
+        )?;
+        writeln!(
+            f,
+            "Num of Flags: {}, Bucket Offset:{}, Node Offset: {}",
+            self.num_flags, self.bucket_offset, self.node_offset
+        )?;
+        Ok(())
+    }
+}
+
 impl FlagTableHeader {
     /// Serialize to bytes
     pub fn as_bytes(&self) -> Vec<u8> {
@@ -62,7 +80,7 @@
 }
 
 /// Flag table node struct
-#[derive(PartialEq, Debug, Clone)]
+#[derive(PartialEq, Clone)]
 pub struct FlagTableNode {
     pub package_id: u32,
     pub flag_name: String,
@@ -71,6 +89,18 @@
     pub next_offset: Option<u32>,
 }
 
+/// Implement debug print trait for node
+impl fmt::Debug for FlagTableNode {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        writeln!(
+            f,
+            "Package Id: {}, Flag: {}, Type: {}, Offset: {}, Next: {:?}",
+            self.package_id, self.flag_name, self.flag_type, self.flag_id, self.next_offset
+        )?;
+        Ok(())
+    }
+}
+
 impl FlagTableNode {
     /// Serialize to bytes
     pub fn as_bytes(&self) -> Vec<u8> {
@@ -108,13 +138,28 @@
     }
 }
 
-#[derive(PartialEq, Debug)]
+#[derive(PartialEq)]
 pub struct FlagTable {
     pub header: FlagTableHeader,
     pub buckets: Vec<Option<u32>>,
     pub nodes: Vec<FlagTableNode>,
 }
 
+/// Implement debug print trait for flag table
+impl fmt::Debug for FlagTable {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        writeln!(f, "Header:")?;
+        write!(f, "{:?}", self.header)?;
+        writeln!(f, "Buckets:")?;
+        writeln!(f, "{:?}", self.buckets)?;
+        writeln!(f, "Nodes:")?;
+        for node in self.nodes.iter() {
+            write!(f, "{:?}", node)?;
+        }
+        Ok(())
+    }
+}
+
 /// Flag table struct
 impl FlagTable {
     /// Serialize to bytes
@@ -156,60 +201,7 @@
 #[cfg(test)]
 mod tests {
     use super::*;
-
-    impl FlagTableNode {
-        // create test baseline, syntactic sugar
-        fn new_expected(
-            package_id: u32,
-            flag_name: &str,
-            flag_type: u16,
-            flag_id: u16,
-            next_offset: Option<u32>,
-        ) -> Self {
-            Self { package_id, flag_name: flag_name.to_string(), flag_type, flag_id, next_offset }
-        }
-    }
-
-    pub fn create_test_flag_table() -> FlagTable {
-        let header = FlagTableHeader {
-            version: 1234,
-            container: String::from("system"),
-            file_size: 320,
-            num_flags: 8,
-            bucket_offset: 30,
-            node_offset: 98,
-        };
-        let buckets: Vec<Option<u32>> = vec![
-            Some(98),
-            Some(124),
-            None,
-            None,
-            None,
-            Some(177),
-            None,
-            Some(203),
-            None,
-            Some(261),
-            None,
-            None,
-            None,
-            None,
-            None,
-            Some(293),
-            None,
-        ];
-        let nodes = vec![
-            FlagTableNode::new_expected(0, "enabled_ro", 1, 1, None),
-            FlagTableNode::new_expected(0, "enabled_rw", 1, 2, Some(150)),
-            FlagTableNode::new_expected(1, "disabled_ro", 1, 0, None),
-            FlagTableNode::new_expected(2, "enabled_ro", 1, 1, None),
-            FlagTableNode::new_expected(1, "enabled_fixed_ro", 1, 1, Some(235)),
-            FlagTableNode::new_expected(1, "enabled_ro", 1, 2, None),
-            FlagTableNode::new_expected(2, "enabled_fixed_ro", 1, 0, None),
-            FlagTableNode::new_expected(0, "disabled_rw", 1, 0, None),
-        ];
-        FlagTable { header, buckets, nodes }
-    }
+    use crate::test_utils::create_test_flag_table;
 
     #[test]
     // this test point locks down the table serialization
diff --git a/tools/aconfig/aconfig_storage_file/src/flag_value.rs b/tools/aconfig/aconfig_storage_file/src/flag_value.rs
index 62e94ef..021546c 100644
--- a/tools/aconfig/aconfig_storage_file/src/flag_value.rs
+++ b/tools/aconfig/aconfig_storage_file/src/flag_value.rs
@@ -19,9 +19,10 @@
 
 use crate::AconfigStorageError;
 use crate::{read_str_from_bytes, read_u32_from_bytes, read_u8_from_bytes};
+use std::fmt;
 
 /// Flag value header struct
-#[derive(PartialEq, Debug)]
+#[derive(PartialEq)]
 pub struct FlagValueHeader {
     pub version: u32,
     pub container: String,
@@ -30,6 +31,23 @@
     pub boolean_value_offset: u32,
 }
 
+/// Implement debug print trait for header
+impl fmt::Debug for FlagValueHeader {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        writeln!(
+            f,
+            "Version: {}, Container: {}, File Size: {}",
+            self.version, self.container, self.file_size
+        )?;
+        writeln!(
+            f,
+            "Num of Flags: {}, Value Offset:{}",
+            self.num_flags, self.boolean_value_offset
+        )?;
+        Ok(())
+    }
+}
+
 impl FlagValueHeader {
     /// Serialize to bytes
     pub fn as_bytes(&self) -> Vec<u8> {
@@ -58,12 +76,23 @@
 }
 
 /// Flag value list struct
-#[derive(PartialEq, Debug)]
+#[derive(PartialEq)]
 pub struct FlagValueList {
     pub header: FlagValueHeader,
     pub booleans: Vec<bool>,
 }
 
+/// Implement debug print trait for flag value
+impl fmt::Debug for FlagValueList {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        writeln!(f, "Header:")?;
+        write!(f, "{:?}", self.header)?;
+        writeln!(f, "Values:")?;
+        writeln!(f, "{:?}", self.booleans)?;
+        Ok(())
+    }
+}
+
 impl FlagValueList {
     /// Serialize to bytes
     pub fn as_bytes(&self) -> Vec<u8> {
@@ -89,18 +118,7 @@
 #[cfg(test)]
 mod tests {
     use super::*;
-
-    pub fn create_test_flag_value_list() -> FlagValueList {
-        let header = FlagValueHeader {
-            version: 1234,
-            container: String::from("system"),
-            file_size: 34,
-            num_flags: 8,
-            boolean_value_offset: 26,
-        };
-        let booleans: Vec<bool> = vec![false, true, false, false, true, true, false, true];
-        FlagValueList { header, booleans }
-    }
+    use crate::test_utils::create_test_flag_value_list;
 
     #[test]
     // this test point locks down the value list serialization
diff --git a/tools/aconfig/aconfig_storage_file/src/lib.rs b/tools/aconfig/aconfig_storage_file/src/lib.rs
index e06e149..7544838 100644
--- a/tools/aconfig/aconfig_storage_file/src/lib.rs
+++ b/tools/aconfig/aconfig_storage_file/src/lib.rs
@@ -36,9 +36,14 @@
 pub mod flag_value;
 pub mod package_table;
 
+#[cfg(test)]
+mod test_utils;
+
 use anyhow::anyhow;
 use std::collections::hash_map::DefaultHasher;
+use std::fs::File;
 use std::hash::{Hash, Hasher};
+use std::io::Read;
 
 pub use crate::flag_table::{FlagTable, FlagTableHeader, FlagTableNode};
 pub use crate::flag_value::{FlagValueHeader, FlagValueList};
@@ -166,4 +171,88 @@
 
     #[error("invalid storage file byte offset")]
     InvalidStorageFileOffset(#[source] anyhow::Error),
+
+    #[error("failed to create file")]
+    FileCreationFail(#[source] anyhow::Error),
+}
+
+/// Read in storage file as bytes
+pub fn read_file_to_bytes(file_path: &str) -> Result<Vec<u8>, AconfigStorageError> {
+    let mut file = File::open(file_path).map_err(|errmsg| {
+        AconfigStorageError::FileReadFail(anyhow!("Failed to open file {}: {}", file_path, errmsg))
+    })?;
+    let mut buffer = Vec::new();
+    file.read_to_end(&mut buffer).map_err(|errmsg| {
+        AconfigStorageError::FileReadFail(anyhow!(
+            "Failed to read 4 bytes from file {}: {}",
+            file_path,
+            errmsg
+        ))
+    })?;
+    Ok(buffer)
+}
+
+/// List flag values from storage files
+pub fn list_flags(
+    package_map: &str,
+    flag_map: &str,
+    flag_val: &str,
+) -> Result<Vec<(String, bool)>, 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)?)?;
+    let flag_value_list = FlagValueList::from_bytes(&read_file_to_bytes(flag_val)?)?;
+
+    let mut package_info = vec![("", 0); package_table.header.num_packages as usize];
+    for node in package_table.nodes.iter() {
+        package_info[node.package_id as usize] = (&node.package_name, node.boolean_offset);
+    }
+
+    let mut flags = Vec::new();
+    for node in flag_table.nodes.iter() {
+        let (package_name, package_offset) = package_info[node.package_id as usize];
+        let full_flag_name = String::from(package_name) + "/" + &node.flag_name;
+        let flag_offset = package_offset + node.flag_id as u32;
+        let flag_value = flag_value_list.booleans[flag_offset as usize];
+        flags.push((full_flag_name, flag_value));
+    }
+
+    flags.sort_by(|v1, v2| v1.0.cmp(&v2.0));
+    Ok(flags)
+}
+
+#[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,
+    };
+
+    #[test]
+    // this test point locks down the flag list api
+    fn test_list_flag() {
+        let package_table =
+            write_bytes_to_temp_file(&create_test_package_table().as_bytes()).unwrap();
+        let flag_table = write_bytes_to_temp_file(&create_test_flag_table().as_bytes()).unwrap();
+        let flag_value_list =
+            write_bytes_to_temp_file(&create_test_flag_value_list().as_bytes()).unwrap();
+
+        let package_table_path = package_table.path().display().to_string();
+        let flag_table_path = flag_table.path().display().to_string();
+        let flag_value_list_path = flag_value_list.path().display().to_string();
+
+        let flags =
+            list_flags(&package_table_path, &flag_table_path, &flag_value_list_path).unwrap();
+        let expected = [
+            (String::from("com.android.aconfig.storage.test_1/disabled_rw"), false),
+            (String::from("com.android.aconfig.storage.test_1/enabled_ro"), true),
+            (String::from("com.android.aconfig.storage.test_1/enabled_rw"), false),
+            (String::from("com.android.aconfig.storage.test_2/disabled_ro"), false),
+            (String::from("com.android.aconfig.storage.test_2/enabled_fixed_ro"), true),
+            (String::from("com.android.aconfig.storage.test_2/enabled_ro"), true),
+            (String::from("com.android.aconfig.storage.test_4/enabled_fixed_ro"), false),
+            (String::from("com.android.aconfig.storage.test_4/enabled_ro"), true),
+        ];
+        assert_eq!(flags, expected);
+    }
 }
diff --git a/tools/aconfig/aconfig_storage_file/src/main.rs b/tools/aconfig/aconfig_storage_file/src/main.rs
new file mode 100644
index 0000000..2c7b87c
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/src/main.rs
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ */
+
+//! `aconfig_storage` is a debugging tool to parse storage files
+
+use aconfig_storage_file::{
+    list_flags, read_file_to_bytes, AconfigStorageError, FlagTable, FlagValueList, PackageTable,
+    StorageFileSelection,
+};
+
+use clap::{builder::ArgAction, Arg, Command};
+
+fn cli() -> Command {
+    Command::new("aconfig_storage_file")
+        .subcommand_required(true)
+        .subcommand(
+            Command::new("print")
+                .arg(Arg::new("file").long("file").required(true).action(ArgAction::Set))
+                .arg(
+                    Arg::new("type")
+                        .long("type")
+                        .required(true)
+                        .value_parser(|s: &str| StorageFileSelection::try_from(s)),
+                ),
+        )
+        .subcommand(
+            Command::new("list")
+                .arg(
+                    Arg::new("package_map")
+                        .long("package_map")
+                        .required(true)
+                        .action(ArgAction::Set),
+                )
+                .arg(Arg::new("flag_map").long("flag_map").required(true).action(ArgAction::Set))
+                .arg(Arg::new("flag_val").long("flag_val").required(true).action(ArgAction::Set)),
+        )
+}
+
+fn print_storage_file(
+    file_path: &str,
+    file_type: &StorageFileSelection,
+) -> Result<(), AconfigStorageError> {
+    let bytes = read_file_to_bytes(file_path)?;
+    match file_type {
+        StorageFileSelection::PackageMap => {
+            let package_table = PackageTable::from_bytes(&bytes)?;
+            println!("{:?}", package_table);
+        }
+        StorageFileSelection::FlagMap => {
+            let flag_table = FlagTable::from_bytes(&bytes)?;
+            println!("{:?}", flag_table);
+        }
+        StorageFileSelection::FlagVal => {
+            let flag_value = FlagValueList::from_bytes(&bytes)?;
+            println!("{:?}", flag_value);
+        }
+    }
+    Ok(())
+}
+
+fn main() -> Result<(), AconfigStorageError> {
+    let matches = cli().get_matches();
+    match matches.subcommand() {
+        Some(("print", sub_matches)) => {
+            let file_path = sub_matches.get_one::<String>("file").unwrap();
+            let file_type = sub_matches.get_one::<StorageFileSelection>("type").unwrap();
+            print_storage_file(file_path, file_type)?
+        }
+        Some(("list", sub_matches)) => {
+            let package_map = sub_matches.get_one::<String>("package_map").unwrap();
+            let flag_map = sub_matches.get_one::<String>("flag_map").unwrap();
+            let flag_val = sub_matches.get_one::<String>("flag_val").unwrap();
+            let flags = list_flags(package_map, flag_map, flag_val)?;
+            for flag in flags.iter() {
+                println!("{}: {}", flag.0, flag.1);
+            }
+        }
+        _ => unreachable!(),
+    }
+    Ok(())
+}
diff --git a/tools/aconfig/aconfig_storage_file/src/package_table.rs b/tools/aconfig/aconfig_storage_file/src/package_table.rs
index 1c21179..f7435b0 100644
--- a/tools/aconfig/aconfig_storage_file/src/package_table.rs
+++ b/tools/aconfig/aconfig_storage_file/src/package_table.rs
@@ -20,9 +20,10 @@
 use crate::AconfigStorageError::{self, BytesParseFail};
 use crate::{get_bucket_index, read_str_from_bytes, read_u32_from_bytes};
 use anyhow::anyhow;
+use std::fmt;
 
 /// Package table header struct
-#[derive(PartialEq, Debug)]
+#[derive(PartialEq)]
 pub struct PackageTableHeader {
     pub version: u32,
     pub container: String,
@@ -32,6 +33,23 @@
     pub node_offset: u32,
 }
 
+/// Implement debug print trait for header
+impl fmt::Debug for PackageTableHeader {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        writeln!(
+            f,
+            "Version: {}, Container: {}, File Size: {}",
+            self.version, self.container, self.file_size
+        )?;
+        writeln!(
+            f,
+            "Num of Packages: {}, Bucket Offset:{}, Node Offset: {}",
+            self.num_packages, self.bucket_offset, self.node_offset
+        )?;
+        Ok(())
+    }
+}
+
 impl PackageTableHeader {
     /// Serialize to bytes
     pub fn as_bytes(&self) -> Vec<u8> {
@@ -62,7 +80,7 @@
 }
 
 /// Package table node struct
-#[derive(PartialEq, Debug)]
+#[derive(PartialEq)]
 pub struct PackageTableNode {
     pub package_name: String,
     pub package_id: u32,
@@ -72,6 +90,18 @@
     pub next_offset: Option<u32>,
 }
 
+/// Implement debug print trait for node
+impl fmt::Debug for PackageTableNode {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        writeln!(
+            f,
+            "Package: {}, Id: {}, Offset: {}, Next: {:?}",
+            self.package_name, self.package_id, self.boolean_offset, self.next_offset
+        )?;
+        Ok(())
+    }
+}
+
 impl PackageTableNode {
     /// Serialize to bytes
     pub fn as_bytes(&self) -> Vec<u8> {
@@ -109,13 +139,28 @@
 }
 
 /// Package table struct
-#[derive(PartialEq, Debug)]
+#[derive(PartialEq)]
 pub struct PackageTable {
     pub header: PackageTableHeader,
     pub buckets: Vec<Option<u32>>,
     pub nodes: Vec<PackageTableNode>,
 }
 
+/// Implement debug print trait for package table
+impl fmt::Debug for PackageTable {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        writeln!(f, "Header:")?;
+        write!(f, "{:?}", self.header)?;
+        writeln!(f, "Buckets:")?;
+        writeln!(f, "{:?}", self.buckets)?;
+        writeln!(f, "Nodes:")?;
+        for node in self.nodes.iter() {
+            write!(f, "{:?}", node)?;
+        }
+        Ok(())
+    }
+}
+
 impl PackageTable {
     /// Serialize to bytes
     pub fn as_bytes(&self) -> Vec<u8> {
@@ -156,38 +201,7 @@
 #[cfg(test)]
 mod tests {
     use super::*;
-
-    pub fn create_test_package_table() -> PackageTable {
-        let header = PackageTableHeader {
-            version: 1234,
-            container: String::from("system"),
-            file_size: 208,
-            num_packages: 3,
-            bucket_offset: 30,
-            node_offset: 58,
-        };
-        let buckets: Vec<Option<u32>> = vec![Some(58), None, None, Some(108), None, None, None];
-        let first_node = PackageTableNode {
-            package_name: String::from("com.android.aconfig.storage.test_2"),
-            package_id: 1,
-            boolean_offset: 3,
-            next_offset: None,
-        };
-        let second_node = PackageTableNode {
-            package_name: String::from("com.android.aconfig.storage.test_1"),
-            package_id: 0,
-            boolean_offset: 0,
-            next_offset: Some(158),
-        };
-        let third_node = PackageTableNode {
-            package_name: String::from("com.android.aconfig.storage.test_4"),
-            package_id: 2,
-            boolean_offset: 6,
-            next_offset: None,
-        };
-        let nodes = vec![first_node, second_node, third_node];
-        PackageTable { header, buckets, nodes }
-    }
+    use crate::test_utils::create_test_package_table;
 
     #[test]
     // this test point locks down the table serialization
diff --git a/tools/aconfig/aconfig_storage_file/src/test_utils.rs b/tools/aconfig/aconfig_storage_file/src/test_utils.rs
new file mode 100644
index 0000000..7780044
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/src/test_utils.rs
@@ -0,0 +1,130 @@
+/*
+ * 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::flag_table::{FlagTable, FlagTableHeader, FlagTableNode};
+use crate::flag_value::{FlagValueHeader, FlagValueList};
+use crate::package_table::{PackageTable, PackageTableHeader, PackageTableNode};
+use crate::AconfigStorageError;
+
+use anyhow::anyhow;
+use std::io::Write;
+use tempfile::NamedTempFile;
+
+pub(crate) fn create_test_package_table() -> PackageTable {
+    let header = PackageTableHeader {
+        version: 1234,
+        container: String::from("system"),
+        file_size: 208,
+        num_packages: 3,
+        bucket_offset: 30,
+        node_offset: 58,
+    };
+    let buckets: Vec<Option<u32>> = vec![Some(58), None, None, Some(108), None, None, None];
+    let first_node = PackageTableNode {
+        package_name: String::from("com.android.aconfig.storage.test_2"),
+        package_id: 1,
+        boolean_offset: 3,
+        next_offset: None,
+    };
+    let second_node = PackageTableNode {
+        package_name: String::from("com.android.aconfig.storage.test_1"),
+        package_id: 0,
+        boolean_offset: 0,
+        next_offset: Some(158),
+    };
+    let third_node = PackageTableNode {
+        package_name: String::from("com.android.aconfig.storage.test_4"),
+        package_id: 2,
+        boolean_offset: 6,
+        next_offset: None,
+    };
+    let nodes = vec![first_node, second_node, third_node];
+    PackageTable { header, buckets, nodes }
+}
+
+impl FlagTableNode {
+    // create test baseline, syntactic sugar
+    fn new_expected(
+        package_id: u32,
+        flag_name: &str,
+        flag_type: u16,
+        flag_id: u16,
+        next_offset: Option<u32>,
+    ) -> Self {
+        Self { package_id, flag_name: flag_name.to_string(), flag_type, flag_id, next_offset }
+    }
+}
+
+pub(crate) fn create_test_flag_table() -> FlagTable {
+    let header = FlagTableHeader {
+        version: 1234,
+        container: String::from("system"),
+        file_size: 320,
+        num_flags: 8,
+        bucket_offset: 30,
+        node_offset: 98,
+    };
+    let buckets: Vec<Option<u32>> = vec![
+        Some(98),
+        Some(124),
+        None,
+        None,
+        None,
+        Some(177),
+        None,
+        Some(203),
+        None,
+        Some(261),
+        None,
+        None,
+        None,
+        None,
+        None,
+        Some(293),
+        None,
+    ];
+    let nodes = vec![
+        FlagTableNode::new_expected(0, "enabled_ro", 1, 1, None),
+        FlagTableNode::new_expected(0, "enabled_rw", 1, 2, Some(150)),
+        FlagTableNode::new_expected(1, "disabled_ro", 1, 0, None),
+        FlagTableNode::new_expected(2, "enabled_ro", 1, 1, None),
+        FlagTableNode::new_expected(1, "enabled_fixed_ro", 1, 1, Some(235)),
+        FlagTableNode::new_expected(1, "enabled_ro", 1, 2, None),
+        FlagTableNode::new_expected(2, "enabled_fixed_ro", 1, 0, None),
+        FlagTableNode::new_expected(0, "disabled_rw", 1, 0, None),
+    ];
+    FlagTable { header, buckets, nodes }
+}
+
+pub(crate) fn create_test_flag_value_list() -> FlagValueList {
+    let header = FlagValueHeader {
+        version: 1234,
+        container: String::from("system"),
+        file_size: 34,
+        num_flags: 8,
+        boolean_value_offset: 26,
+    };
+    let booleans: Vec<bool> = vec![false, true, false, false, true, true, false, true];
+    FlagValueList { header, booleans }
+}
+
+pub(crate) fn write_bytes_to_temp_file(bytes: &[u8]) -> Result<NamedTempFile, AconfigStorageError> {
+    let mut file = NamedTempFile::new().map_err(|_| {
+        AconfigStorageError::FileCreationFail(anyhow!("Failed to create temp file"))
+    })?;
+    let _ = file.write_all(&bytes);
+    Ok(file)
+}