aconfig: move aconfig_storage_metadata proto and its apis to
aconfig_storage_file crate

Bug: b/321077378
Test: m aconfig_storage_file.test; m aconfig_storage_read_api.test
Change-Id: Ifeeeff62dc09e172b7e88c45860d2febccc29570
diff --git a/tools/aconfig/aconfig_storage_file/Android.bp b/tools/aconfig/aconfig_storage_file/Android.bp
index 1d74f69..c089d54 100644
--- a/tools/aconfig/aconfig_storage_file/Android.bp
+++ b/tools/aconfig/aconfig_storage_file/Android.bp
@@ -11,6 +11,9 @@
         "libanyhow",
         "libthiserror",
         "libtempfile",
+        "libprotobuf",
+        "libclap",
+        "libaconfig_storage_protos",
     ],
 }
 
@@ -26,3 +29,25 @@
     test_suites: ["general-tests"],
     defaults: ["aconfig_storage_file.defaults"],
 }
+
+rust_protobuf {
+    name: "libaconfig_storage_protos",
+    protos: ["protos/aconfig_storage_metadata.proto"],
+    crate_name: "aconfig_storage_protos",
+    source_stem: "aconfig_storage_protos",
+    host_supported: true,
+}
+
+cc_library_static {
+    name: "libaconfig_storage_protos_cc",
+    proto: {
+        export_proto_headers: true,
+        type: "lite",
+    },
+    srcs: ["protos/aconfig_storage_metadata.proto"],
+    apex_available: [
+        "//apex_available:platform",
+        "//apex_available:anyapex",
+    ],
+    host_supported: true,
+}
diff --git a/tools/aconfig/aconfig_storage_file/Cargo.toml b/tools/aconfig/aconfig_storage_file/Cargo.toml
index 6a9483e..9b9a615 100644
--- a/tools/aconfig/aconfig_storage_file/Cargo.toml
+++ b/tools/aconfig/aconfig_storage_file/Cargo.toml
@@ -9,11 +9,8 @@
 
 [dependencies]
 anyhow = "1.0.69"
-memmap2 = "0.8.0"
 protobuf = "3.2.0"
-once_cell = "1.19.0"
 tempfile = "3.9.0"
-cxx = "1.0"
 thiserror = "1.0.56"
 clap = { version = "4.1.8", features = ["derive"] }
 
diff --git a/tools/aconfig/aconfig_storage_file/build.rs b/tools/aconfig/aconfig_storage_file/build.rs
new file mode 100644
index 0000000..1feeb60
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/build.rs
@@ -0,0 +1,17 @@
+use protobuf_codegen::Codegen;
+
+fn main() {
+    let proto_files = vec!["protos/aconfig_storage_metadata.proto"];
+
+    // tell cargo to only re-run the build script if any of the proto files has changed
+    for path in &proto_files {
+        println!("cargo:rerun-if-changed={}", path);
+    }
+
+    Codegen::new()
+        .pure()
+        .include("protos")
+        .inputs(proto_files)
+        .cargo_out_dir("aconfig_storage_protos")
+        .run_from_script();
+}
diff --git a/tools/aconfig/aconfig_storage_file/protos/aconfig_storage_metadata.proto b/tools/aconfig/aconfig_storage_file/protos/aconfig_storage_metadata.proto
new file mode 100644
index 0000000..c6728bd
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/protos/aconfig_storage_metadata.proto
@@ -0,0 +1,34 @@
+// 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
+
+// This is the schema definition for aconfig files. Modifications need to be
+// either backwards compatible, or include updates to all aconfig files in the
+// Android tree.
+
+syntax = "proto2";
+
+package android.aconfig_storage_metadata;
+
+message storage_file_info {
+  optional uint32 version = 1;
+  optional string container = 2;
+  optional string package_map = 3;
+  optional string flag_map = 4;
+  optional string flag_val = 5;
+  optional int64 timestamp = 6;
+}
+
+message storage_files {
+  repeated storage_file_info files = 1;
+};
diff --git a/tools/aconfig/aconfig_storage_file/src/lib.rs b/tools/aconfig/aconfig_storage_file/src/lib.rs
index 7544838..ec41a4e 100644
--- a/tools/aconfig/aconfig_storage_file/src/lib.rs
+++ b/tools/aconfig/aconfig_storage_file/src/lib.rs
@@ -35,6 +35,7 @@
 pub mod flag_table;
 pub mod flag_value;
 pub mod package_table;
+pub mod protos;
 
 #[cfg(test)]
 mod test_utils;
diff --git a/tools/aconfig/aconfig_storage_file/src/protos.rs b/tools/aconfig/aconfig_storage_file/src/protos.rs
new file mode 100644
index 0000000..8b86205
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/src/protos.rs
@@ -0,0 +1,202 @@
+/*
+ * 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.
+ */
+
+// When building with the Android tool-chain
+//
+//   - an external crate `aconfig_storage_metadata_protos` will be generated
+//   - the feature "cargo" will be disabled
+//
+// When building with cargo
+//
+//   - a local sub-module will be generated in OUT_DIR and included in this file
+//   - the feature "cargo" will be enabled
+//
+// This module hides these differences from the rest of the codebase.
+
+// ---- When building with the Android tool-chain ----
+#[cfg(not(feature = "cargo"))]
+mod auto_generated {
+    pub use aconfig_storage_protos::aconfig_storage_metadata as ProtoStorage;
+    pub use ProtoStorage::Storage_file_info as ProtoStorageFileInfo;
+    pub use ProtoStorage::Storage_files as ProtoStorageFiles;
+}
+
+// ---- When building with cargo ----
+#[cfg(feature = "cargo")]
+mod auto_generated {
+    // include! statements should be avoided (because they import file contents verbatim), but
+    // because this is only used during local development, and only if using cargo instead of the
+    // Android tool-chain, we allow it
+    include!(concat!(env!("OUT_DIR"), "/aconfig_storage_protos/mod.rs"));
+    pub use aconfig_storage_metadata::Storage_file_info as ProtoStorageFileInfo;
+    pub use aconfig_storage_metadata::Storage_files as ProtoStorageFiles;
+}
+
+// ---- Common for both the Android tool-chain and cargo ----
+pub use auto_generated::*;
+
+use anyhow::Result;
+use protobuf::Message;
+use std::io::Write;
+use tempfile::NamedTempFile;
+
+pub mod storage_record_pb {
+    use super::*;
+    use anyhow::ensure;
+
+    pub fn try_from_binary_proto(bytes: &[u8]) -> Result<ProtoStorageFiles> {
+        let message: ProtoStorageFiles = protobuf::Message::parse_from_bytes(bytes)?;
+        verify_fields(&message)?;
+        Ok(message)
+    }
+
+    pub fn verify_fields(storage_files: &ProtoStorageFiles) -> Result<()> {
+        for storage_file_info in storage_files.files.iter() {
+            ensure!(
+                !storage_file_info.package_map().is_empty(),
+                "invalid storage file record: missing package map file for container {}",
+                storage_file_info.container()
+            );
+            ensure!(
+                !storage_file_info.flag_map().is_empty(),
+                "invalid storage file record: missing flag map file for container {}",
+                storage_file_info.container()
+            );
+            ensure!(
+                !storage_file_info.flag_val().is_empty(),
+                "invalid storage file record: missing flag val file for container {}",
+                storage_file_info.container()
+            );
+        }
+        Ok(())
+    }
+
+    pub fn get_binary_proto_from_text_proto(text_proto: &str) -> Result<Vec<u8>> {
+        let storage_files: ProtoStorageFiles = protobuf::text_format::parse_from_str(text_proto)?;
+        let mut binary_proto = Vec::new();
+        storage_files.write_to_vec(&mut binary_proto)?;
+        Ok(binary_proto)
+    }
+
+    pub fn write_proto_to_temp_file(text_proto: &str) -> Result<NamedTempFile> {
+        let bytes = get_binary_proto_from_text_proto(text_proto).unwrap();
+        let mut file = NamedTempFile::new()?;
+        let _ = file.write_all(&bytes);
+        Ok(file)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_parse_storage_record_pb() {
+        let text_proto = r#"
+files {
+    version: 0
+    container: "system"
+    package_map: "/system/etc/package.map"
+    flag_map: "/system/etc/flag.map"
+    flag_val: "/metadata/aconfig/system.val"
+    timestamp: 12345
+}
+files {
+    version: 1
+    container: "product"
+    package_map: "/product/etc/package.map"
+    flag_map: "/product/etc/flag.map"
+    flag_val: "/metadata/aconfig/product.val"
+    timestamp: 54321
+}
+"#;
+        let binary_proto_bytes =
+            storage_record_pb::get_binary_proto_from_text_proto(text_proto).unwrap();
+        let storage_files = storage_record_pb::try_from_binary_proto(&binary_proto_bytes).unwrap();
+        assert_eq!(storage_files.files.len(), 2);
+        let system_file = &storage_files.files[0];
+        assert_eq!(system_file.version(), 0);
+        assert_eq!(system_file.container(), "system");
+        assert_eq!(system_file.package_map(), "/system/etc/package.map");
+        assert_eq!(system_file.flag_map(), "/system/etc/flag.map");
+        assert_eq!(system_file.flag_val(), "/metadata/aconfig/system.val");
+        assert_eq!(system_file.timestamp(), 12345);
+        let product_file = &storage_files.files[1];
+        assert_eq!(product_file.version(), 1);
+        assert_eq!(product_file.container(), "product");
+        assert_eq!(product_file.package_map(), "/product/etc/package.map");
+        assert_eq!(product_file.flag_map(), "/product/etc/flag.map");
+        assert_eq!(product_file.flag_val(), "/metadata/aconfig/product.val");
+        assert_eq!(product_file.timestamp(), 54321);
+    }
+
+    #[test]
+    fn test_parse_invalid_storage_record_pb() {
+        let text_proto = r#"
+files {
+    version: 0
+    container: "system"
+    package_map: ""
+    flag_map: "/system/etc/flag.map"
+    flag_val: "/metadata/aconfig/system.val"
+    timestamp: 12345
+}
+"#;
+        let binary_proto_bytes =
+            storage_record_pb::get_binary_proto_from_text_proto(text_proto).unwrap();
+        let err = storage_record_pb::try_from_binary_proto(&binary_proto_bytes).unwrap_err();
+        assert_eq!(
+            format!("{:?}", err),
+            "invalid storage file record: missing package map file for container system"
+        );
+
+        let text_proto = r#"
+files {
+    version: 0
+    container: "system"
+    package_map: "/system/etc/package.map"
+    flag_map: ""
+    flag_val: "/metadata/aconfig/system.val"
+    timestamp: 12345
+}
+"#;
+        let binary_proto_bytes =
+            storage_record_pb::get_binary_proto_from_text_proto(text_proto).unwrap();
+        let err = storage_record_pb::try_from_binary_proto(&binary_proto_bytes).unwrap_err();
+        assert_eq!(
+            format!("{:?}", err),
+            "invalid storage file record: missing flag map file for container system"
+        );
+
+        let text_proto = r#"
+files {
+    version: 0
+    container: "system"
+    package_map: "/system/etc/package.map"
+    flag_map: "/system/etc/flag.map"
+    flag_val: ""
+    timestamp: 12345
+}
+"#;
+        let binary_proto_bytes =
+            storage_record_pb::get_binary_proto_from_text_proto(text_proto).unwrap();
+        let err = storage_record_pb::try_from_binary_proto(&binary_proto_bytes).unwrap_err();
+        assert_eq!(
+            format!("{:?}", err),
+            "invalid storage file record: missing flag val file for container system"
+        );
+    }
+}