1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 // When building with the Android tool-chain
18 //
19 //   - an external crate `aconfig_storage_metadata_protos` will be generated
20 //   - the feature "cargo" will be disabled
21 //
22 // When building with cargo
23 //
24 //   - a local sub-module will be generated in OUT_DIR and included in this file
25 //   - the feature "cargo" will be enabled
26 //
27 // This module hides these differences from the rest of the codebase.
28 
29 // ---- When building with the Android tool-chain ----
30 #[cfg(not(feature = "cargo"))]
31 mod auto_generated {
32     pub use aconfig_storage_protos::aconfig_storage_metadata as ProtoStorage;
33     pub use ProtoStorage::Storage_file_info as ProtoStorageFileInfo;
34     pub use ProtoStorage::Storage_files as ProtoStorageFiles;
35 }
36 
37 // ---- When building with cargo ----
38 #[cfg(feature = "cargo")]
39 mod auto_generated {
40     // include! statements should be avoided (because they import file contents verbatim), but
41     // because this is only used during local development, and only if using cargo instead of the
42     // Android tool-chain, we allow it
43     include!(concat!(env!("OUT_DIR"), "/aconfig_storage_protos/mod.rs"));
44     pub use aconfig_storage_metadata::Storage_file_info as ProtoStorageFileInfo;
45     pub use aconfig_storage_metadata::Storage_files as ProtoStorageFiles;
46 }
47 
48 // ---- Common for both the Android tool-chain and cargo ----
49 pub use auto_generated::*;
50 
51 use anyhow::Result;
52 use protobuf::Message;
53 use std::io::Write;
54 use tempfile::NamedTempFile;
55 
56 pub mod storage_record_pb {
57     use super::*;
58     use anyhow::ensure;
59 
try_from_binary_proto(bytes: &[u8]) -> Result<ProtoStorageFiles>60     pub fn try_from_binary_proto(bytes: &[u8]) -> Result<ProtoStorageFiles> {
61         let message: ProtoStorageFiles = protobuf::Message::parse_from_bytes(bytes)?;
62         verify_fields(&message)?;
63         Ok(message)
64     }
65 
verify_fields(storage_files: &ProtoStorageFiles) -> Result<()>66     pub fn verify_fields(storage_files: &ProtoStorageFiles) -> Result<()> {
67         for storage_file_info in storage_files.files.iter() {
68             ensure!(
69                 !storage_file_info.package_map().is_empty(),
70                 "invalid storage file record: missing package map file for container {}",
71                 storage_file_info.container()
72             );
73             ensure!(
74                 !storage_file_info.flag_map().is_empty(),
75                 "invalid storage file record: missing flag map file for container {}",
76                 storage_file_info.container()
77             );
78             ensure!(
79                 !storage_file_info.flag_val().is_empty(),
80                 "invalid storage file record: missing flag val file for container {}",
81                 storage_file_info.container()
82             );
83         }
84         Ok(())
85     }
86 
get_binary_proto_from_text_proto(text_proto: &str) -> Result<Vec<u8>>87     pub fn get_binary_proto_from_text_proto(text_proto: &str) -> Result<Vec<u8>> {
88         let storage_files: ProtoStorageFiles = protobuf::text_format::parse_from_str(text_proto)?;
89         let mut binary_proto = Vec::new();
90         storage_files.write_to_vec(&mut binary_proto)?;
91         Ok(binary_proto)
92     }
93 
write_proto_to_temp_file(text_proto: &str) -> Result<NamedTempFile>94     pub fn write_proto_to_temp_file(text_proto: &str) -> Result<NamedTempFile> {
95         let bytes = get_binary_proto_from_text_proto(text_proto).unwrap();
96         let mut file = NamedTempFile::new()?;
97         let _ = file.write_all(&bytes);
98         Ok(file)
99     }
100 }
101 
102 #[cfg(test)]
103 mod tests {
104     use super::*;
105 
106     #[test]
test_parse_storage_record_pb()107     fn test_parse_storage_record_pb() {
108         let text_proto = r#"
109 files {
110     version: 0
111     container: "system"
112     package_map: "/system/etc/package.map"
113     flag_map: "/system/etc/flag.map"
114     flag_val: "/metadata/aconfig/system.val"
115     timestamp: 12345
116 }
117 files {
118     version: 1
119     container: "product"
120     package_map: "/product/etc/package.map"
121     flag_map: "/product/etc/flag.map"
122     flag_val: "/metadata/aconfig/product.val"
123     timestamp: 54321
124 }
125 "#;
126         let binary_proto_bytes =
127             storage_record_pb::get_binary_proto_from_text_proto(text_proto).unwrap();
128         let storage_files = storage_record_pb::try_from_binary_proto(&binary_proto_bytes).unwrap();
129         assert_eq!(storage_files.files.len(), 2);
130         let system_file = &storage_files.files[0];
131         assert_eq!(system_file.version(), 0);
132         assert_eq!(system_file.container(), "system");
133         assert_eq!(system_file.package_map(), "/system/etc/package.map");
134         assert_eq!(system_file.flag_map(), "/system/etc/flag.map");
135         assert_eq!(system_file.flag_val(), "/metadata/aconfig/system.val");
136         assert_eq!(system_file.timestamp(), 12345);
137         let product_file = &storage_files.files[1];
138         assert_eq!(product_file.version(), 1);
139         assert_eq!(product_file.container(), "product");
140         assert_eq!(product_file.package_map(), "/product/etc/package.map");
141         assert_eq!(product_file.flag_map(), "/product/etc/flag.map");
142         assert_eq!(product_file.flag_val(), "/metadata/aconfig/product.val");
143         assert_eq!(product_file.timestamp(), 54321);
144     }
145 
146     #[test]
test_parse_invalid_storage_record_pb()147     fn test_parse_invalid_storage_record_pb() {
148         let text_proto = r#"
149 files {
150     version: 0
151     container: "system"
152     package_map: ""
153     flag_map: "/system/etc/flag.map"
154     flag_val: "/metadata/aconfig/system.val"
155     timestamp: 12345
156 }
157 "#;
158         let binary_proto_bytes =
159             storage_record_pb::get_binary_proto_from_text_proto(text_proto).unwrap();
160         let err = storage_record_pb::try_from_binary_proto(&binary_proto_bytes).unwrap_err();
161         assert_eq!(
162             format!("{:?}", err),
163             "invalid storage file record: missing package map file for container system"
164         );
165 
166         let text_proto = r#"
167 files {
168     version: 0
169     container: "system"
170     package_map: "/system/etc/package.map"
171     flag_map: ""
172     flag_val: "/metadata/aconfig/system.val"
173     timestamp: 12345
174 }
175 "#;
176         let binary_proto_bytes =
177             storage_record_pb::get_binary_proto_from_text_proto(text_proto).unwrap();
178         let err = storage_record_pb::try_from_binary_proto(&binary_proto_bytes).unwrap_err();
179         assert_eq!(
180             format!("{:?}", err),
181             "invalid storage file record: missing flag map file for container system"
182         );
183 
184         let text_proto = r#"
185 files {
186     version: 0
187     container: "system"
188     package_map: "/system/etc/package.map"
189     flag_map: "/system/etc/flag.map"
190     flag_val: ""
191     timestamp: 12345
192 }
193 "#;
194         let binary_proto_bytes =
195             storage_record_pb::get_binary_proto_from_text_proto(text_proto).unwrap();
196         let err = storage_record_pb::try_from_binary_proto(&binary_proto_bytes).unwrap_err();
197         assert_eq!(
198             format!("{:?}", err),
199             "invalid storage file record: missing flag val file for container system"
200         );
201     }
202 }
203