aconfig: add a storage file read api to get file version number

1, Add unit tests in aconfig_storage_file crate to lock down that the
first four bytes of serialized storage files must be the version number.

2, Add a storage file read api to read version number directly from a
file without mmapping the file. This api is need by aconfig storage
daemon.

3, Update integration tests (rust and c++) to include this api.

Bug: b/321077378
Test: atest aconfig_storage_file; atest aconfig_storage_read_api; atest
aconfig_storage_read_api.test.rust; atest
aconfig_storage_read_api.test.cpp

Change-Id: I432893d43895ad5022fcb614aa14de89acd9d220
diff --git a/tools/aconfig/aconfig_storage_file/src/flag_table.rs b/tools/aconfig/aconfig_storage_file/src/flag_table.rs
index e1791d1..61cf371 100644
--- a/tools/aconfig/aconfig_storage_file/src/flag_table.rs
+++ b/tools/aconfig/aconfig_storage_file/src/flag_table.rs
@@ -172,7 +172,7 @@
 
     pub fn create_test_flag_table() -> FlagTable {
         let header = FlagTableHeader {
-            version: crate::FILE_VERSION,
+            version: 1234,
             container: String::from("system"),
             file_size: 320,
             num_flags: 8,
@@ -231,4 +231,15 @@
         assert!(reinterpreted_table.is_ok());
         assert_eq!(&flag_table, &reinterpreted_table.unwrap());
     }
+
+    #[test]
+    // this test point locks down that version number should be at the top of serialized
+    // bytes
+    fn test_version_number() {
+        let flag_table = create_test_flag_table();
+        let bytes = &flag_table.as_bytes();
+        let mut head = 0;
+        let version = read_u32_from_bytes(bytes, &mut head).unwrap();
+        assert_eq!(version, 1234)
+    }
 }
diff --git a/tools/aconfig/aconfig_storage_file/src/flag_value.rs b/tools/aconfig/aconfig_storage_file/src/flag_value.rs
index 8356847..62e94ef 100644
--- a/tools/aconfig/aconfig_storage_file/src/flag_value.rs
+++ b/tools/aconfig/aconfig_storage_file/src/flag_value.rs
@@ -92,7 +92,7 @@
 
     pub fn create_test_flag_value_list() -> FlagValueList {
         let header = FlagValueHeader {
-            version: crate::FILE_VERSION,
+            version: 1234,
             container: String::from("system"),
             file_size: 34,
             num_flags: 8,
@@ -116,4 +116,15 @@
         assert!(reinterpreted_value_list.is_ok());
         assert_eq!(&flag_value_list, &reinterpreted_value_list.unwrap());
     }
+
+    #[test]
+    // this test point locks down that version number should be at the top of serialized
+    // bytes
+    fn test_version_number() {
+        let flag_value_list = create_test_flag_value_list();
+        let bytes = &flag_value_list.as_bytes();
+        let mut head = 0;
+        let version = read_u32_from_bytes(bytes, &mut head).unwrap();
+        assert_eq!(version, 1234)
+    }
 }
diff --git a/tools/aconfig/aconfig_storage_file/src/package_table.rs b/tools/aconfig/aconfig_storage_file/src/package_table.rs
index 28310a8..1c21179 100644
--- a/tools/aconfig/aconfig_storage_file/src/package_table.rs
+++ b/tools/aconfig/aconfig_storage_file/src/package_table.rs
@@ -159,7 +159,7 @@
 
     pub fn create_test_package_table() -> PackageTable {
         let header = PackageTableHeader {
-            version: crate::FILE_VERSION,
+            version: 1234,
             container: String::from("system"),
             file_size: 208,
             num_packages: 3,
@@ -208,4 +208,15 @@
         assert!(reinterpreted_table.is_ok());
         assert_eq!(&package_table, &reinterpreted_table.unwrap());
     }
+
+    #[test]
+    // this test point locks down that version number should be at the top of serialized
+    // bytes
+    fn test_version_number() {
+        let package_table = create_test_package_table();
+        let bytes = &package_table.as_bytes();
+        let mut head = 0;
+        let version = read_u32_from_bytes(bytes, &mut head).unwrap();
+        assert_eq!(version, 1234)
+    }
 }
diff --git a/tools/aconfig/aconfig_storage_read_api/Android.bp b/tools/aconfig/aconfig_storage_read_api/Android.bp
index 43697b3..5f4329c 100644
--- a/tools/aconfig/aconfig_storage_read_api/Android.bp
+++ b/tools/aconfig/aconfig_storage_read_api/Android.bp
@@ -60,27 +60,6 @@
     host_supported: true,
 }
 
-genrule {
-    name: "ro.package.map",
-    out: ["tests/tmp.ro.package.map"],
-    srcs: ["tests/package.map"],
-    cmd: "rm -f $(out);cp -f $(in) $(out);chmod -w $(out)",
-}
-
-genrule {
-    name: "ro.flag.map",
-    out: ["tests/tmp.ro.flag.map"],
-    srcs: ["tests/flag.map"],
-    cmd: "rm -f $(out);cp -f $(in) $(out);chmod -w $(out)",
-}
-
-genrule {
-    name: "ro.flag.val",
-    out: ["tests/tmp.ro.flag.val"],
-    srcs: ["tests/flag.val"],
-    cmd: "rm -f $(out);cp -f $(in) $(out);chmod -w $(out)",
-}
-
 // cxx source codegen from rust api
 genrule {
     name: "libcxx_aconfig_storage_read_api_bridge_code",
@@ -119,3 +98,24 @@
     whole_static_libs: ["libaconfig_storage_read_api_cxx_bridge"],
     export_include_dirs: ["include"],
 }
+
+genrule {
+    name: "ro.package.map",
+    out: ["tests/tmp.ro.package.map"],
+    srcs: ["tests/package.map"],
+    cmd: "rm -f $(out);cp -f $(in) $(out);chmod -w $(out)",
+}
+
+genrule {
+    name: "ro.flag.map",
+    out: ["tests/tmp.ro.flag.map"],
+    srcs: ["tests/flag.map"],
+    cmd: "rm -f $(out);cp -f $(in) $(out);chmod -w $(out)",
+}
+
+genrule {
+    name: "ro.flag.val",
+    out: ["tests/tmp.ro.flag.val"],
+    srcs: ["tests/flag.val"],
+    cmd: "rm -f $(out);cp -f $(in) $(out);chmod -w $(out)",
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/aconfig_storage_read_api.cpp b/tools/aconfig/aconfig_storage_read_api/aconfig_storage_read_api.cpp
index 7cf8e38..c2da5ce 100644
--- a/tools/aconfig/aconfig_storage_read_api/aconfig_storage_read_api.cpp
+++ b/tools/aconfig/aconfig_storage_read_api/aconfig_storage_read_api.cpp
@@ -4,12 +4,23 @@
 #include "aconfig_storage/lib.rs.h"
 
 namespace aconfig_storage {
+/// Get storage file version number
+VersionNumberQuery get_storage_file_version(
+    std::string const& file_path) {
+  auto version_cxx = get_storage_file_version_cxx(
+      rust::Str(file_path.c_str()));
+  auto version = VersionNumberQuery();
+  version.query_success = version_cxx.query_success;
+  version.error_message = std::string(version_cxx.error_message.c_str());
+  version.version_number = version_cxx.version_number;
+  return version;
+}
 
 /// Get package offset
 PackageOffsetQuery get_package_offset(
     std::string const& container,
     std::string const& package) {
-  auto offset_cxx =  get_package_offset_cxx(
+  auto offset_cxx = get_package_offset_cxx(
       rust::Str(container.c_str()),
       rust::Str(package.c_str()));
   auto offset = PackageOffsetQuery();
@@ -26,7 +37,7 @@
     std::string const& container,
     uint32_t package_id,
     std::string const& flag_name) {
-  auto offset_cxx =  get_flag_offset_cxx(
+  auto offset_cxx = get_flag_offset_cxx(
       rust::Str(container.c_str()),
       package_id,
       rust::Str(flag_name.c_str()));
@@ -42,7 +53,7 @@
 BooleanFlagValueQuery get_boolean_flag_value(
     std::string const& container,
     uint32_t offset) {
-  auto value_cxx =  get_boolean_flag_value_cxx(
+  auto value_cxx = get_boolean_flag_value_cxx(
       rust::Str(container.c_str()),
       offset);
   auto value = BooleanFlagValueQuery();
diff --git a/tools/aconfig/aconfig_storage_read_api/include/aconfig_storage/aconfig_storage_read_api.hpp b/tools/aconfig/aconfig_storage_read_api/include/aconfig_storage/aconfig_storage_read_api.hpp
index 636fb7e..3741dc2 100644
--- a/tools/aconfig/aconfig_storage_read_api/include/aconfig_storage/aconfig_storage_read_api.hpp
+++ b/tools/aconfig/aconfig_storage_read_api/include/aconfig_storage/aconfig_storage_read_api.hpp
@@ -5,6 +5,13 @@
 
 namespace aconfig_storage {
 
+/// Storage version number query result
+struct VersionNumberQuery {
+  bool query_success;
+  std::string error_message;
+  uint32_t version_number;
+};
+
 /// Package offset query result
 struct PackageOffsetQuery {
   bool query_success;
@@ -29,6 +36,12 @@
   bool flag_value;
 };
 
+/// Get storage file version number
+/// \input file_path: the path to the storage file
+/// \returns a VersionNumberQuery
+VersionNumberQuery get_storage_file_version(
+    std::string const& file_path);
+
 /// Get package offset
 /// \input container: the flag container name
 /// \input package: the flag package name
diff --git a/tools/aconfig/aconfig_storage_read_api/src/lib.rs b/tools/aconfig/aconfig_storage_read_api/src/lib.rs
index 996256b..0da81dd 100644
--- a/tools/aconfig/aconfig_storage_read_api/src/lib.rs
+++ b/tools/aconfig/aconfig_storage_read_api/src/lib.rs
@@ -15,8 +15,7 @@
  */
 
 //! `aconfig_storage_read_api` is a crate that defines read apis to read flags from storage
-//! files. It provides three apis to
-//! interface with storage files:
+//! files. It provides four apis to interface with storage files:
 //!
 //! 1, function to get package flag value start offset
 //! pub fn get_package_offset(container: &str, package: &str) -> `Result<Option<PackageOffset>>>`
@@ -28,6 +27,9 @@
 //! flag offset).
 //! pub fn get_boolean_flag_value(container: &str, offset: u32) -> `Result<bool>`
 //!
+//! 4, function to get storage file version without mmapping the file.
+//! pub fn get_storage_file_version(file_path: &str) -> Result<u32, AconfigStorageError>
+//!
 //! Note these are low level apis that are expected to be only used in auto generated flag
 //! apis. DO NOT DIRECTLY USE THESE APIS IN YOUR SOURCE CODE. For auto generated flag apis
 //! please refer to the g3doc go/android-flags
@@ -42,7 +44,9 @@
 mod test_utils;
 
 pub use crate::protos::ProtoStorageFiles;
-pub use aconfig_storage_file::{AconfigStorageError, StorageFileSelection, FILE_VERSION};
+pub use aconfig_storage_file::{
+    read_u32_from_bytes, AconfigStorageError, StorageFileSelection, FILE_VERSION,
+};
 pub use flag_table_query::FlagOffset;
 pub use package_table_query::PackageOffset;
 
@@ -51,6 +55,10 @@
 use mapped_file::get_mapped_file;
 use package_table_query::find_package_offset;
 
+use anyhow::anyhow;
+use std::fs::File;
+use std::io::Read;
+
 /// Storage file location pb file
 pub const STORAGE_LOCATION_FILE: &str = "/metadata/aconfig/available_storage_file_records.pb";
 
@@ -85,6 +93,28 @@
     find_boolean_flag_value(&mapped_file, offset)
 }
 
+/// Get storage file version number
+///
+/// This function would read the first four bytes of the file and interpret it as the
+/// version number of the file. There are unit tests in aconfig_storage_file crate to
+/// lock down that for all storage files, the first four bytes will be the version
+/// number of the storage file
+pub fn get_storage_file_version(file_path: &str) -> Result<u32, AconfigStorageError> {
+    let mut file = File::open(file_path).map_err(|errmsg| {
+        AconfigStorageError::FileReadFail(anyhow!("Failed to open file {}: {}", file_path, errmsg))
+    })?;
+    let mut buffer = [0; 4];
+    file.read(&mut buffer).map_err(|errmsg| {
+        AconfigStorageError::FileReadFail(anyhow!(
+            "Failed to read 4 bytes from file {}: {}",
+            file_path,
+            errmsg
+        ))
+    })?;
+    let mut head = 0;
+    read_u32_from_bytes(&buffer, &mut head)
+}
+
 /// Get package start offset for flags given the container and package name.
 ///
 /// This function would map the corresponding package map file if has not been mapped yet,
@@ -127,8 +157,20 @@
     get_boolean_flag_value_impl(STORAGE_LOCATION_FILE, container, offset)
 }
 
+// *************************************** //
+// CC INTERLOP
+// *************************************** //
+
+// Exported rust data structure and methods, c++ code will be generated
 #[cxx::bridge]
 mod ffi {
+    // Storage file version query return for cc interlop
+    pub struct VersionNumberQueryCXX {
+        pub query_success: bool,
+        pub error_message: String,
+        pub version_number: u32,
+    }
+
     // Package table query return for cc interlop
     pub struct PackageOffsetQueryCXX {
         pub query_success: bool,
@@ -174,6 +216,8 @@
             offset: u32,
         ) -> BooleanFlagValueQueryCXX;
 
+        pub fn get_storage_file_version_cxx(file_path: &str) -> VersionNumberQueryCXX;
+
         pub fn get_package_offset_cxx(container: &str, package: &str) -> PackageOffsetQueryCXX;
 
         pub fn get_flag_offset_cxx(
@@ -187,6 +231,100 @@
     }
 }
 
+/// Implement the package offset interlop return type, create from actual package offset api return type
+impl ffi::PackageOffsetQueryCXX {
+    pub(crate) fn new(offset_result: Result<Option<PackageOffset>, AconfigStorageError>) -> Self {
+        match offset_result {
+            Ok(offset_opt) => match offset_opt {
+                Some(offset) => Self {
+                    query_success: true,
+                    error_message: String::from(""),
+                    package_exists: true,
+                    package_id: offset.package_id,
+                    boolean_offset: offset.boolean_offset,
+                },
+                None => Self {
+                    query_success: true,
+                    error_message: String::from(""),
+                    package_exists: false,
+                    package_id: 0,
+                    boolean_offset: 0,
+                },
+            },
+            Err(errmsg) => Self {
+                query_success: false,
+                error_message: format!("{:?}", errmsg),
+                package_exists: false,
+                package_id: 0,
+                boolean_offset: 0,
+            },
+        }
+    }
+}
+
+/// Implement the flag offset interlop return type, create from actual flag offset api return type
+impl ffi::FlagOffsetQueryCXX {
+    pub(crate) fn new(offset_result: Result<Option<FlagOffset>, AconfigStorageError>) -> Self {
+        match offset_result {
+            Ok(offset_opt) => match offset_opt {
+                Some(offset) => Self {
+                    query_success: true,
+                    error_message: String::from(""),
+                    flag_exists: true,
+                    flag_offset: offset,
+                },
+                None => Self {
+                    query_success: true,
+                    error_message: String::from(""),
+                    flag_exists: false,
+                    flag_offset: 0,
+                },
+            },
+            Err(errmsg) => Self {
+                query_success: false,
+                error_message: format!("{:?}", errmsg),
+                flag_exists: false,
+                flag_offset: 0,
+            },
+        }
+    }
+}
+
+/// Implement the flag value interlop return type, create from actual flag value api return type
+impl ffi::BooleanFlagValueQueryCXX {
+    pub(crate) fn new(value_result: Result<bool, AconfigStorageError>) -> Self {
+        match value_result {
+            Ok(value) => {
+                Self { query_success: true, error_message: String::from(""), flag_value: value }
+            }
+            Err(errmsg) => Self {
+                query_success: false,
+                error_message: format!("{:?}", errmsg),
+                flag_value: false,
+            },
+        }
+    }
+}
+
+/// Implement the storage version number interlop return type, create from actual version number
+/// api return type
+impl ffi::VersionNumberQueryCXX {
+    pub(crate) fn new(version_result: Result<u32, AconfigStorageError>) -> Self {
+        match version_result {
+            Ok(version) => Self {
+                query_success: true,
+                error_message: String::from(""),
+                version_number: version,
+            },
+            Err(errmsg) => Self {
+                query_success: false,
+                error_message: format!("{:?}", errmsg),
+                version_number: 0,
+            },
+        }
+    }
+}
+
 /// Get package start offset impl cc interlop
 pub fn get_package_offset_cxx_impl(
     pb_file: &str,
@@ -215,6 +353,11 @@
     ffi::BooleanFlagValueQueryCXX::new(get_boolean_flag_value_impl(pb_file, container, offset))
 }
 
+/// Get storage version number cc interlop
+pub fn get_storage_file_version_cxx(file_path: &str) -> ffi::VersionNumberQueryCXX {
+    ffi::VersionNumberQueryCXX::new(get_storage_file_version(file_path))
+}
+
 /// Get package start offset cc interlop
 pub fn get_package_offset_cxx(container: &str, package: &str) -> ffi::PackageOffsetQueryCXX {
     ffi::PackageOffsetQueryCXX::new(get_package_offset(container, package))
@@ -234,78 +377,6 @@
     ffi::BooleanFlagValueQueryCXX::new(get_boolean_flag_value(container, offset))
 }
 
-impl ffi::PackageOffsetQueryCXX {
-    pub(crate) fn new(offset_result: Result<Option<PackageOffset>, AconfigStorageError>) -> Self {
-        match offset_result {
-            Ok(offset_opt) => match offset_opt {
-                Some(offset) => Self {
-                    query_success: true,
-                    error_message: String::from(""),
-                    package_exists: true,
-                    package_id: offset.package_id,
-                    boolean_offset: offset.boolean_offset,
-                },
-                None => Self {
-                    query_success: true,
-                    error_message: String::from(""),
-                    package_exists: false,
-                    package_id: 0,
-                    boolean_offset: 0,
-                },
-            },
-            Err(errmsg) => Self {
-                query_success: false,
-                error_message: format!("{:?}", errmsg),
-                package_exists: false,
-                package_id: 0,
-                boolean_offset: 0,
-            },
-        }
-    }
-}
-
-impl ffi::FlagOffsetQueryCXX {
-    pub(crate) fn new(offset_result: Result<Option<FlagOffset>, AconfigStorageError>) -> Self {
-        match offset_result {
-            Ok(offset_opt) => match offset_opt {
-                Some(offset) => Self {
-                    query_success: true,
-                    error_message: String::from(""),
-                    flag_exists: true,
-                    flag_offset: offset,
-                },
-                None => Self {
-                    query_success: true,
-                    error_message: String::from(""),
-                    flag_exists: false,
-                    flag_offset: 0,
-                },
-            },
-            Err(errmsg) => Self {
-                query_success: false,
-                error_message: format!("{:?}", errmsg),
-                flag_exists: false,
-                flag_offset: 0,
-            },
-        }
-    }
-}
-
-impl ffi::BooleanFlagValueQueryCXX {
-    pub(crate) fn new(value_result: Result<bool, AconfigStorageError>) -> Self {
-        match value_result {
-            Ok(value) => {
-                Self { query_success: true, error_message: String::from(""), flag_value: value }
-            }
-            Err(errmsg) => Self {
-                query_success: false,
-                error_message: format!("{:?}", errmsg),
-                flag_value: false,
-            },
-        }
-    }
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -438,4 +509,13 @@
             assert_eq!(flag_value, expected_value);
         }
     }
+
+    #[test]
+    // this test point locks down flag storage file version number query api
+    fn test_storage_version_query() {
+        let _ro_files = create_test_storage_files(true);
+        assert_eq!(get_storage_file_version("./tests/package.map").unwrap(), 1);
+        assert_eq!(get_storage_file_version("./tests/flag.map").unwrap(), 1);
+        assert_eq!(get_storage_file_version("./tests/flag.val").unwrap(), 1);
+    }
 }
diff --git a/tools/aconfig/aconfig_storage_read_api/src/test_utils.rs b/tools/aconfig/aconfig_storage_read_api/src/test_utils.rs
index 7905d51..588ecba 100644
--- a/tools/aconfig/aconfig_storage_read_api/src/test_utils.rs
+++ b/tools/aconfig/aconfig_storage_read_api/src/test_utils.rs
@@ -51,6 +51,7 @@
     }
 }
 
+#[allow(dead_code)]
 pub(crate) struct TestStorageFile {
     pub file: NamedTempFile,
     pub name: String,
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.cpp b/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.cpp
index b605646..6fecd08 100644
--- a/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.cpp
+++ b/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.cpp
@@ -27,6 +27,7 @@
 using ::aconfig_storage::test_only_api::get_package_offset_impl;
 using ::aconfig_storage::test_only_api::get_flag_offset_impl;
 using ::aconfig_storage::test_only_api::get_boolean_flag_value_impl;
+using ::aconfig_storage::get_storage_file_version;
 
 void write_storage_location_pb_to_file(std::string const& file_path) {
   auto const test_dir = android::base::GetExecutableDirectory();
@@ -45,6 +46,22 @@
       << "Failed to write a file: " << file_path;
 }
 
+TEST(AconfigStorageTest, test_storage_version_query) {
+  auto const test_dir = android::base::GetExecutableDirectory();
+  auto query = get_storage_file_version(test_dir + "/tests/tmp.ro.package.map");
+  ASSERT_EQ(query.error_message, std::string());
+  ASSERT_TRUE(query.query_success);
+  ASSERT_EQ(query.version_number, 1);
+  query = get_storage_file_version(test_dir + "/tests/tmp.ro.flag.map");
+  ASSERT_EQ(query.error_message, std::string());
+  ASSERT_TRUE(query.query_success);
+  ASSERT_EQ(query.version_number, 1);
+  query = get_storage_file_version(test_dir + "/tests/tmp.ro.flag.val");
+  ASSERT_EQ(query.error_message, std::string());
+  ASSERT_TRUE(query.query_success);
+  ASSERT_EQ(query.version_number, 1);
+}
+
 TEST(AconfigStorageTest, test_package_offset_query) {
   auto pb_file = std::string("/tmp/test_package_offset_query.pb");
   write_storage_location_pb_to_file(pb_file);
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.rs b/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.rs
index 9b23ec4..4a65876 100644
--- a/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.rs
+++ b/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.rs
@@ -1,8 +1,8 @@
 #[cfg(not(feature = "cargo"))]
 mod aconfig_storage_rust_test {
     use aconfig_storage_read_api::{
-        get_boolean_flag_value_impl, get_flag_offset_impl, get_package_offset_impl, PackageOffset,
-        ProtoStorageFiles,
+        get_boolean_flag_value_impl, get_flag_offset_impl, get_package_offset_impl,
+        get_storage_file_version, PackageOffset, ProtoStorageFiles,
     };
     use protobuf::Message;
     use std::io::Write;
@@ -171,4 +171,11 @@
             "InvalidStorageFileOffset(Flag value offset goes beyond the end of the file.)"
         );
     }
+
+    #[test]
+    fn test_storage_version_query() {
+        assert_eq!(get_storage_file_version("./tests/tmp.ro.package.map").unwrap(), 1);
+        assert_eq!(get_storage_file_version("./tests/tmp.ro.flag.map").unwrap(), 1);
+        assert_eq!(get_storage_file_version("./tests/tmp.ro.flag.val").unwrap(), 1);
+    }
 }