1 /*
2  * Copyright (C) 2023 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 //! `aconfig_storage_read_api` is a crate that defines read apis to read flags from storage
18 //! files. It provides four apis to interface with storage files:
19 //!
20 //! 1, function to get package read context
21 //! pub fn get_packager_read_context(container: &str, package: &str)
22 //! -> `Result<Option<PackageReadContext>>>`
23 //!
24 //! 2, function to get flag read context
25 //! pub fn get_flag_read_context(container: &str, package_id: u32, flag: &str)
26 //! -> `Result<Option<FlagReadContext>>>`
27 //!
28 //! 3, function to get the actual flag value given the global index (combined package and
29 //! flag index).
30 //! pub fn get_boolean_flag_value(container: &str, offset: u32) -> `Result<bool>`
31 //!
32 //! 4, function to get storage file version without mmapping the file.
33 //! pub fn get_storage_file_version(file_path: &str) -> Result<u32, AconfigStorageError>
34 //!
35 //! Note these are low level apis that are expected to be only used in auto generated flag
36 //! apis. DO NOT DIRECTLY USE THESE APIS IN YOUR SOURCE CODE. For auto generated flag apis
37 //! please refer to the g3doc go/android-flags
38 
39 pub mod flag_info_query;
40 pub mod flag_table_query;
41 pub mod flag_value_query;
42 pub mod mapped_file;
43 pub mod package_table_query;
44 
45 pub use aconfig_storage_file::{AconfigStorageError, FlagValueType, StorageFileType};
46 pub use flag_table_query::FlagReadContext;
47 pub use package_table_query::PackageReadContext;
48 
49 use aconfig_storage_file::{read_u32_from_bytes, FILE_VERSION};
50 use flag_info_query::find_flag_attribute;
51 use flag_table_query::find_flag_read_context;
52 use flag_value_query::find_boolean_flag_value;
53 use package_table_query::find_package_read_context;
54 
55 use anyhow::anyhow;
56 pub use memmap2::Mmap;
57 use std::fs::File;
58 use std::io::Read;
59 
60 /// Storage file location
61 pub const STORAGE_LOCATION: &str = "/metadata/aconfig";
62 
63 /// Get read only mapped storage files.
64 ///
65 /// \input container: the flag package container
66 /// \input file_type: stoarge file type enum
67 /// \return a result of read only mapped file
68 ///
69 /// # Safety
70 ///
71 /// The memory mapped file may have undefined behavior if there are writes to this
72 /// file after being mapped. Ensure no writes can happen to this file while this
73 /// mapping stays alive.
get_mapped_storage_file( container: &str, file_type: StorageFileType, ) -> Result<Mmap, AconfigStorageError>74 pub unsafe fn get_mapped_storage_file(
75     container: &str,
76     file_type: StorageFileType,
77 ) -> Result<Mmap, AconfigStorageError> {
78     unsafe { crate::mapped_file::get_mapped_file(STORAGE_LOCATION, container, file_type) }
79 }
80 
81 /// Get package read context for a specific package.
82 ///
83 /// \input file: mapped package file
84 /// \input package: package name
85 ///
86 /// \return
87 /// If a package is found, it returns Ok(Some(PackageReadContext))
88 /// If a package is not found, it returns Ok(None)
89 /// If errors out, it returns an Err(errmsg)
get_package_read_context( file: &Mmap, package: &str, ) -> Result<Option<PackageReadContext>, AconfigStorageError>90 pub fn get_package_read_context(
91     file: &Mmap,
92     package: &str,
93 ) -> Result<Option<PackageReadContext>, AconfigStorageError> {
94     find_package_read_context(file, package)
95 }
96 
97 /// Get flag read context for a specific flag.
98 ///
99 /// \input file: mapped flag file
100 /// \input package_id: package id obtained from package mapping file
101 /// \input flag: flag name
102 ///
103 /// \return
104 /// If a flag is found, it returns Ok(Some(FlagReadContext))
105 /// If a flag is not found, it returns Ok(None)
106 /// If errors out, it returns an Err(errmsg)
get_flag_read_context( file: &Mmap, package_id: u32, flag: &str, ) -> Result<Option<FlagReadContext>, AconfigStorageError>107 pub fn get_flag_read_context(
108     file: &Mmap,
109     package_id: u32,
110     flag: &str,
111 ) -> Result<Option<FlagReadContext>, AconfigStorageError> {
112     find_flag_read_context(file, package_id, flag)
113 }
114 
115 /// Get the boolean flag value.
116 ///
117 /// \input file: mapped flag file
118 /// \input index: boolean flag offset
119 ///
120 /// \return
121 /// If the provide offset is valid, it returns the boolean flag value, otherwise it
122 /// returns the error message.
get_boolean_flag_value(file: &Mmap, index: u32) -> Result<bool, AconfigStorageError>123 pub fn get_boolean_flag_value(file: &Mmap, index: u32) -> Result<bool, AconfigStorageError> {
124     find_boolean_flag_value(file, index)
125 }
126 
127 /// Get storage file version number
128 ///
129 /// This function would read the first four bytes of the file and interpret it as the
130 /// version number of the file. There are unit tests in aconfig_storage_file crate to
131 /// lock down that for all storage files, the first four bytes will be the version
132 /// number of the storage file
get_storage_file_version(file_path: &str) -> Result<u32, AconfigStorageError>133 pub fn get_storage_file_version(file_path: &str) -> Result<u32, AconfigStorageError> {
134     let mut file = File::open(file_path).map_err(|errmsg| {
135         AconfigStorageError::FileReadFail(anyhow!("Failed to open file {}: {}", file_path, errmsg))
136     })?;
137     let mut buffer = [0; 4];
138     file.read(&mut buffer).map_err(|errmsg| {
139         AconfigStorageError::FileReadFail(anyhow!(
140             "Failed to read 4 bytes from file {}: {}",
141             file_path,
142             errmsg
143         ))
144     })?;
145     let mut head = 0;
146     read_u32_from_bytes(&buffer, &mut head)
147 }
148 
149 /// Get the flag attribute.
150 ///
151 /// \input file: mapped flag info file
152 /// \input flag_type: flag value type
153 /// \input flag_index: flag index
154 ///
155 /// \return
156 /// If the provide offset is valid, it returns the flag attribute bitfiled, otherwise it
157 /// returns the error message.
get_flag_attribute( file: &Mmap, flag_type: FlagValueType, flag_index: u32, ) -> Result<u8, AconfigStorageError>158 pub fn get_flag_attribute(
159     file: &Mmap,
160     flag_type: FlagValueType,
161     flag_index: u32,
162 ) -> Result<u8, AconfigStorageError> {
163     find_flag_attribute(file, flag_type, flag_index)
164 }
165 
166 // *************************************** //
167 // CC INTERLOP
168 // *************************************** //
169 
170 // Exported rust data structure and methods, c++ code will be generated
171 #[cxx::bridge]
172 mod ffi {
173     // Storage file version query return for cc interlop
174     pub struct VersionNumberQueryCXX {
175         pub query_success: bool,
176         pub error_message: String,
177         pub version_number: u32,
178     }
179 
180     // Package table query return for cc interlop
181     pub struct PackageReadContextQueryCXX {
182         pub query_success: bool,
183         pub error_message: String,
184         pub package_exists: bool,
185         pub package_id: u32,
186         pub boolean_start_index: u32,
187     }
188 
189     // Flag table query return for cc interlop
190     pub struct FlagReadContextQueryCXX {
191         pub query_success: bool,
192         pub error_message: String,
193         pub flag_exists: bool,
194         pub flag_type: u16,
195         pub flag_index: u16,
196     }
197 
198     // Flag value query return for cc interlop
199     pub struct BooleanFlagValueQueryCXX {
200         pub query_success: bool,
201         pub error_message: String,
202         pub flag_value: bool,
203     }
204 
205     // Flag info query return for cc interlop
206     pub struct FlagAttributeQueryCXX {
207         pub query_success: bool,
208         pub error_message: String,
209         pub flag_attribute: u8,
210     }
211 
212     // Rust export to c++
213     extern "Rust" {
get_storage_file_version_cxx(file_path: &str) -> VersionNumberQueryCXX214         pub fn get_storage_file_version_cxx(file_path: &str) -> VersionNumberQueryCXX;
215 
get_package_read_context_cxx( file: &[u8], package: &str, ) -> PackageReadContextQueryCXX216         pub fn get_package_read_context_cxx(
217             file: &[u8],
218             package: &str,
219         ) -> PackageReadContextQueryCXX;
220 
get_flag_read_context_cxx( file: &[u8], package_id: u32, flag: &str, ) -> FlagReadContextQueryCXX221         pub fn get_flag_read_context_cxx(
222             file: &[u8],
223             package_id: u32,
224             flag: &str,
225         ) -> FlagReadContextQueryCXX;
226 
get_boolean_flag_value_cxx(file: &[u8], offset: u32) -> BooleanFlagValueQueryCXX227         pub fn get_boolean_flag_value_cxx(file: &[u8], offset: u32) -> BooleanFlagValueQueryCXX;
228 
get_flag_attribute_cxx( file: &[u8], flag_type: u16, flag_index: u32, ) -> FlagAttributeQueryCXX229         pub fn get_flag_attribute_cxx(
230             file: &[u8],
231             flag_type: u16,
232             flag_index: u32,
233         ) -> FlagAttributeQueryCXX;
234     }
235 }
236 
237 /// Implement the package offset interlop return type, create from actual package offset api return type
238 impl ffi::PackageReadContextQueryCXX {
new( offset_result: Result<Option<PackageReadContext>, AconfigStorageError>, ) -> Self239     pub(crate) fn new(
240         offset_result: Result<Option<PackageReadContext>, AconfigStorageError>,
241     ) -> Self {
242         match offset_result {
243             Ok(offset_opt) => match offset_opt {
244                 Some(offset) => Self {
245                     query_success: true,
246                     error_message: String::from(""),
247                     package_exists: true,
248                     package_id: offset.package_id,
249                     boolean_start_index: offset.boolean_start_index,
250                 },
251                 None => Self {
252                     query_success: true,
253                     error_message: String::from(""),
254                     package_exists: false,
255                     package_id: 0,
256                     boolean_start_index: 0,
257                 },
258             },
259             Err(errmsg) => Self {
260                 query_success: false,
261                 error_message: format!("{:?}", errmsg),
262                 package_exists: false,
263                 package_id: 0,
264                 boolean_start_index: 0,
265             },
266         }
267     }
268 }
269 
270 /// Implement the flag offset interlop return type, create from actual flag offset api return type
271 impl ffi::FlagReadContextQueryCXX {
new(offset_result: Result<Option<FlagReadContext>, AconfigStorageError>) -> Self272     pub(crate) fn new(offset_result: Result<Option<FlagReadContext>, AconfigStorageError>) -> Self {
273         match offset_result {
274             Ok(offset_opt) => match offset_opt {
275                 Some(offset) => Self {
276                     query_success: true,
277                     error_message: String::from(""),
278                     flag_exists: true,
279                     flag_type: offset.flag_type as u16,
280                     flag_index: offset.flag_index,
281                 },
282                 None => Self {
283                     query_success: true,
284                     error_message: String::from(""),
285                     flag_exists: false,
286                     flag_type: 0u16,
287                     flag_index: 0u16,
288                 },
289             },
290             Err(errmsg) => Self {
291                 query_success: false,
292                 error_message: format!("{:?}", errmsg),
293                 flag_exists: false,
294                 flag_type: 0u16,
295                 flag_index: 0u16,
296             },
297         }
298     }
299 }
300 
301 /// Implement the flag value interlop return type, create from actual flag value api return type
302 impl ffi::BooleanFlagValueQueryCXX {
new(value_result: Result<bool, AconfigStorageError>) -> Self303     pub(crate) fn new(value_result: Result<bool, AconfigStorageError>) -> Self {
304         match value_result {
305             Ok(value) => {
306                 Self { query_success: true, error_message: String::from(""), flag_value: value }
307             }
308             Err(errmsg) => Self {
309                 query_success: false,
310                 error_message: format!("{:?}", errmsg),
311                 flag_value: false,
312             },
313         }
314     }
315 }
316 
317 /// Implement the flag info interlop return type, create from actual flag info api return type
318 impl ffi::FlagAttributeQueryCXX {
new(info_result: Result<u8, AconfigStorageError>) -> Self319     pub(crate) fn new(info_result: Result<u8, AconfigStorageError>) -> Self {
320         match info_result {
321             Ok(info) => {
322                 Self { query_success: true, error_message: String::from(""), flag_attribute: info }
323             }
324             Err(errmsg) => Self {
325                 query_success: false,
326                 error_message: format!("{:?}", errmsg),
327                 flag_attribute: 0u8,
328             },
329         }
330     }
331 }
332 
333 /// Implement the storage version number interlop return type, create from actual version number
334 /// api return type
335 impl ffi::VersionNumberQueryCXX {
new(version_result: Result<u32, AconfigStorageError>) -> Self336     pub(crate) fn new(version_result: Result<u32, AconfigStorageError>) -> Self {
337         match version_result {
338             Ok(version) => Self {
339                 query_success: true,
340                 error_message: String::from(""),
341                 version_number: version,
342             },
343             Err(errmsg) => Self {
344                 query_success: false,
345                 error_message: format!("{:?}", errmsg),
346                 version_number: 0,
347             },
348         }
349     }
350 }
351 
352 /// Get package read context cc interlop
get_package_read_context_cxx(file: &[u8], package: &str) -> ffi::PackageReadContextQueryCXX353 pub fn get_package_read_context_cxx(file: &[u8], package: &str) -> ffi::PackageReadContextQueryCXX {
354     ffi::PackageReadContextQueryCXX::new(find_package_read_context(file, package))
355 }
356 
357 /// Get flag read context cc interlop
get_flag_read_context_cxx( file: &[u8], package_id: u32, flag: &str, ) -> ffi::FlagReadContextQueryCXX358 pub fn get_flag_read_context_cxx(
359     file: &[u8],
360     package_id: u32,
361     flag: &str,
362 ) -> ffi::FlagReadContextQueryCXX {
363     ffi::FlagReadContextQueryCXX::new(find_flag_read_context(file, package_id, flag))
364 }
365 
366 /// Get boolean flag value cc interlop
get_boolean_flag_value_cxx(file: &[u8], offset: u32) -> ffi::BooleanFlagValueQueryCXX367 pub fn get_boolean_flag_value_cxx(file: &[u8], offset: u32) -> ffi::BooleanFlagValueQueryCXX {
368     ffi::BooleanFlagValueQueryCXX::new(find_boolean_flag_value(file, offset))
369 }
370 
371 /// Get flag attribute cc interlop
get_flag_attribute_cxx( file: &[u8], flag_type: u16, flag_index: u32, ) -> ffi::FlagAttributeQueryCXX372 pub fn get_flag_attribute_cxx(
373     file: &[u8],
374     flag_type: u16,
375     flag_index: u32,
376 ) -> ffi::FlagAttributeQueryCXX {
377     match FlagValueType::try_from(flag_type) {
378         Ok(value_type) => {
379             ffi::FlagAttributeQueryCXX::new(find_flag_attribute(file, value_type, flag_index))
380         }
381         Err(errmsg) => ffi::FlagAttributeQueryCXX::new(Err(errmsg)),
382     }
383 }
384 
385 /// Get storage version number cc interlop
get_storage_file_version_cxx(file_path: &str) -> ffi::VersionNumberQueryCXX386 pub fn get_storage_file_version_cxx(file_path: &str) -> ffi::VersionNumberQueryCXX {
387     ffi::VersionNumberQueryCXX::new(get_storage_file_version(file_path))
388 }
389 
390 #[cfg(test)]
391 mod tests {
392     use super::*;
393     use crate::mapped_file::get_mapped_file;
394     use aconfig_storage_file::{FlagInfoBit, StoredFlagType};
395     use rand::Rng;
396     use std::fs;
397 
create_test_storage_files() -> String398     fn create_test_storage_files() -> String {
399         let mut rng = rand::thread_rng();
400         let number: u32 = rng.gen();
401         let storage_dir = String::from("/tmp/") + &number.to_string();
402         if std::fs::metadata(&storage_dir).is_ok() {
403             fs::remove_dir_all(&storage_dir).unwrap();
404         }
405         let maps_dir = storage_dir.clone() + "/maps";
406         let boot_dir = storage_dir.clone() + "/boot";
407         fs::create_dir(&storage_dir).unwrap();
408         fs::create_dir(&maps_dir).unwrap();
409         fs::create_dir(&boot_dir).unwrap();
410 
411         let package_map = storage_dir.clone() + "/maps/mockup.package.map";
412         let flag_map = storage_dir.clone() + "/maps/mockup.flag.map";
413         let flag_val = storage_dir.clone() + "/boot/mockup.val";
414         let flag_info = storage_dir.clone() + "/boot/mockup.info";
415         fs::copy("./tests/package.map", &package_map).unwrap();
416         fs::copy("./tests/flag.map", &flag_map).unwrap();
417         fs::copy("./tests/flag.val", &flag_val).unwrap();
418         fs::copy("./tests/flag.info", &flag_info).unwrap();
419 
420         return storage_dir;
421     }
422 
423     #[test]
424     // this test point locks down flag package read context query
test_package_context_query()425     fn test_package_context_query() {
426         let storage_dir = create_test_storage_files();
427         let package_mapped_file = unsafe {
428             get_mapped_file(&storage_dir, "mockup", StorageFileType::PackageMap).unwrap()
429         };
430 
431         let package_context =
432             get_package_read_context(&package_mapped_file, "com.android.aconfig.storage.test_1")
433                 .unwrap()
434                 .unwrap();
435         let expected_package_context = PackageReadContext { package_id: 0, boolean_start_index: 0 };
436         assert_eq!(package_context, expected_package_context);
437 
438         let package_context =
439             get_package_read_context(&package_mapped_file, "com.android.aconfig.storage.test_2")
440                 .unwrap()
441                 .unwrap();
442         let expected_package_context = PackageReadContext { package_id: 1, boolean_start_index: 3 };
443         assert_eq!(package_context, expected_package_context);
444 
445         let package_context =
446             get_package_read_context(&package_mapped_file, "com.android.aconfig.storage.test_4")
447                 .unwrap()
448                 .unwrap();
449         let expected_package_context = PackageReadContext { package_id: 2, boolean_start_index: 6 };
450         assert_eq!(package_context, expected_package_context);
451     }
452 
453     #[test]
454     // this test point locks down flag read context query
test_flag_context_query()455     fn test_flag_context_query() {
456         let storage_dir = create_test_storage_files();
457         let flag_mapped_file =
458             unsafe { get_mapped_file(&storage_dir, "mockup", StorageFileType::FlagMap).unwrap() };
459 
460         let baseline = vec![
461             (0, "enabled_ro", StoredFlagType::ReadOnlyBoolean, 1u16),
462             (0, "enabled_rw", StoredFlagType::ReadWriteBoolean, 2u16),
463             (2, "enabled_rw", StoredFlagType::ReadWriteBoolean, 1u16),
464             (1, "disabled_rw", StoredFlagType::ReadWriteBoolean, 0u16),
465             (1, "enabled_fixed_ro", StoredFlagType::FixedReadOnlyBoolean, 1u16),
466             (1, "enabled_ro", StoredFlagType::ReadOnlyBoolean, 2u16),
467             (2, "enabled_fixed_ro", StoredFlagType::FixedReadOnlyBoolean, 0u16),
468             (0, "disabled_rw", StoredFlagType::ReadWriteBoolean, 0u16),
469         ];
470         for (package_id, flag_name, flag_type, flag_index) in baseline.into_iter() {
471             let flag_context =
472                 get_flag_read_context(&flag_mapped_file, package_id, flag_name).unwrap().unwrap();
473             assert_eq!(flag_context.flag_type, flag_type);
474             assert_eq!(flag_context.flag_index, flag_index);
475         }
476     }
477 
478     #[test]
479     // this test point locks down flag value query
test_flag_value_query()480     fn test_flag_value_query() {
481         let storage_dir = create_test_storage_files();
482         let flag_value_file =
483             unsafe { get_mapped_file(&storage_dir, "mockup", StorageFileType::FlagVal).unwrap() };
484         let baseline: Vec<bool> = vec![false, true, true, false, true, true, true, true];
485         for (offset, expected_value) in baseline.into_iter().enumerate() {
486             let flag_value = get_boolean_flag_value(&flag_value_file, offset as u32).unwrap();
487             assert_eq!(flag_value, expected_value);
488         }
489     }
490 
491     #[test]
492     // this test point locks donw flag info query
test_flag_info_query()493     fn test_flag_info_query() {
494         let storage_dir = create_test_storage_files();
495         let flag_info_file =
496             unsafe { get_mapped_file(&storage_dir, "mockup", StorageFileType::FlagInfo).unwrap() };
497         let is_rw: Vec<bool> = vec![true, false, true, true, false, false, false, true];
498         for (offset, expected_value) in is_rw.into_iter().enumerate() {
499             let attribute =
500                 get_flag_attribute(&flag_info_file, FlagValueType::Boolean, offset as u32).unwrap();
501             assert_eq!((attribute & FlagInfoBit::IsReadWrite as u8) != 0u8, expected_value);
502             assert!((attribute & FlagInfoBit::HasServerOverride as u8) == 0u8);
503             assert!((attribute & FlagInfoBit::HasLocalOverride as u8) == 0u8);
504         }
505     }
506 
507     #[test]
508     // this test point locks down flag storage file version number query api
test_storage_version_query()509     fn test_storage_version_query() {
510         assert_eq!(get_storage_file_version("./tests/package.map").unwrap(), 1);
511         assert_eq!(get_storage_file_version("./tests/flag.map").unwrap(), 1);
512         assert_eq!(get_storage_file_version("./tests/flag.val").unwrap(), 1);
513         assert_eq!(get_storage_file_version("./tests/flag.info").unwrap(), 1);
514     }
515 }
516