/* * Copyright (C) 2023 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_read_api` is a crate that defines read apis to read flags from storage //! files. It provides four apis to interface with storage files: //! //! 1, function to get package read context //! pub fn get_packager_read_context(container: &str, package: &str) //! -> `Result>>` //! //! 2, function to get flag read context //! pub fn get_flag_read_context(container: &str, package_id: u32, flag: &str) //! -> `Result>>` //! //! 3, function to get the actual flag value given the global index (combined package and //! flag index). //! pub fn get_boolean_flag_value(container: &str, offset: u32) -> `Result` //! //! 4, function to get storage file version without mmapping the file. //! pub fn get_storage_file_version(file_path: &str) -> Result //! //! 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 pub mod flag_info_query; pub mod flag_table_query; pub mod flag_value_query; pub mod mapped_file; pub mod package_table_query; pub use aconfig_storage_file::{AconfigStorageError, FlagValueType, StorageFileType}; pub use flag_table_query::FlagReadContext; pub use package_table_query::PackageReadContext; use aconfig_storage_file::{read_u32_from_bytes, FILE_VERSION}; use flag_info_query::find_flag_attribute; use flag_table_query::find_flag_read_context; use flag_value_query::find_boolean_flag_value; use package_table_query::find_package_read_context; use anyhow::anyhow; pub use memmap2::Mmap; use std::fs::File; use std::io::Read; /// Storage file location pub const STORAGE_LOCATION: &str = "/metadata/aconfig"; /// Get read only mapped storage files. /// /// \input container: the flag package container /// \input file_type: stoarge file type enum /// \return a result of read only mapped file /// /// # Safety /// /// The memory mapped file may have undefined behavior if there are writes to this /// file after being mapped. Ensure no writes can happen to this file while this /// mapping stays alive. pub unsafe fn get_mapped_storage_file( container: &str, file_type: StorageFileType, ) -> Result { unsafe { crate::mapped_file::get_mapped_file(STORAGE_LOCATION, container, file_type) } } /// Get package read context for a specific package. /// /// \input file: mapped package file /// \input package: package name /// /// \return /// If a package is found, it returns Ok(Some(PackageReadContext)) /// If a package is not found, it returns Ok(None) /// If errors out, it returns an Err(errmsg) pub fn get_package_read_context( file: &Mmap, package: &str, ) -> Result, AconfigStorageError> { find_package_read_context(file, package) } /// Get flag read context for a specific flag. /// /// \input file: mapped flag file /// \input package_id: package id obtained from package mapping file /// \input flag: flag name /// /// \return /// If a flag is found, it returns Ok(Some(FlagReadContext)) /// If a flag is not found, it returns Ok(None) /// If errors out, it returns an Err(errmsg) pub fn get_flag_read_context( file: &Mmap, package_id: u32, flag: &str, ) -> Result, AconfigStorageError> { find_flag_read_context(file, package_id, flag) } /// Get the boolean flag value. /// /// \input file: mapped flag file /// \input index: boolean flag offset /// /// \return /// If the provide offset is valid, it returns the boolean flag value, otherwise it /// returns the error message. pub fn get_boolean_flag_value(file: &Mmap, index: u32) -> Result { find_boolean_flag_value(file, index) } /// 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 { 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 the flag attribute. /// /// \input file: mapped flag info file /// \input flag_type: flag value type /// \input flag_index: flag index /// /// \return /// If the provide offset is valid, it returns the flag attribute bitfiled, otherwise it /// returns the error message. pub fn get_flag_attribute( file: &Mmap, flag_type: FlagValueType, flag_index: u32, ) -> Result { find_flag_attribute(file, flag_type, flag_index) } // *************************************** // // 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 PackageReadContextQueryCXX { pub query_success: bool, pub error_message: String, pub package_exists: bool, pub package_id: u32, pub boolean_start_index: u32, } // Flag table query return for cc interlop pub struct FlagReadContextQueryCXX { pub query_success: bool, pub error_message: String, pub flag_exists: bool, pub flag_type: u16, pub flag_index: u16, } // Flag value query return for cc interlop pub struct BooleanFlagValueQueryCXX { pub query_success: bool, pub error_message: String, pub flag_value: bool, } // Flag info query return for cc interlop pub struct FlagAttributeQueryCXX { pub query_success: bool, pub error_message: String, pub flag_attribute: u8, } // Rust export to c++ extern "Rust" { pub fn get_storage_file_version_cxx(file_path: &str) -> VersionNumberQueryCXX; pub fn get_package_read_context_cxx( file: &[u8], package: &str, ) -> PackageReadContextQueryCXX; pub fn get_flag_read_context_cxx( file: &[u8], package_id: u32, flag: &str, ) -> FlagReadContextQueryCXX; pub fn get_boolean_flag_value_cxx(file: &[u8], offset: u32) -> BooleanFlagValueQueryCXX; pub fn get_flag_attribute_cxx( file: &[u8], flag_type: u16, flag_index: u32, ) -> FlagAttributeQueryCXX; } } /// Implement the package offset interlop return type, create from actual package offset api return type impl ffi::PackageReadContextQueryCXX { pub(crate) fn new( offset_result: Result, 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_start_index: offset.boolean_start_index, }, None => Self { query_success: true, error_message: String::from(""), package_exists: false, package_id: 0, boolean_start_index: 0, }, }, Err(errmsg) => Self { query_success: false, error_message: format!("{:?}", errmsg), package_exists: false, package_id: 0, boolean_start_index: 0, }, } } } /// Implement the flag offset interlop return type, create from actual flag offset api return type impl ffi::FlagReadContextQueryCXX { pub(crate) fn new(offset_result: Result, 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_type: offset.flag_type as u16, flag_index: offset.flag_index, }, None => Self { query_success: true, error_message: String::from(""), flag_exists: false, flag_type: 0u16, flag_index: 0u16, }, }, Err(errmsg) => Self { query_success: false, error_message: format!("{:?}", errmsg), flag_exists: false, flag_type: 0u16, flag_index: 0u16, }, } } } /// 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) -> 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 flag info interlop return type, create from actual flag info api return type impl ffi::FlagAttributeQueryCXX { pub(crate) fn new(info_result: Result) -> Self { match info_result { Ok(info) => { Self { query_success: true, error_message: String::from(""), flag_attribute: info } } Err(errmsg) => Self { query_success: false, error_message: format!("{:?}", errmsg), flag_attribute: 0u8, }, } } } /// 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) -> 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 read context cc interlop pub fn get_package_read_context_cxx(file: &[u8], package: &str) -> ffi::PackageReadContextQueryCXX { ffi::PackageReadContextQueryCXX::new(find_package_read_context(file, package)) } /// Get flag read context cc interlop pub fn get_flag_read_context_cxx( file: &[u8], package_id: u32, flag: &str, ) -> ffi::FlagReadContextQueryCXX { ffi::FlagReadContextQueryCXX::new(find_flag_read_context(file, package_id, flag)) } /// Get boolean flag value cc interlop pub fn get_boolean_flag_value_cxx(file: &[u8], offset: u32) -> ffi::BooleanFlagValueQueryCXX { ffi::BooleanFlagValueQueryCXX::new(find_boolean_flag_value(file, offset)) } /// Get flag attribute cc interlop pub fn get_flag_attribute_cxx( file: &[u8], flag_type: u16, flag_index: u32, ) -> ffi::FlagAttributeQueryCXX { match FlagValueType::try_from(flag_type) { Ok(value_type) => { ffi::FlagAttributeQueryCXX::new(find_flag_attribute(file, value_type, flag_index)) } Err(errmsg) => ffi::FlagAttributeQueryCXX::new(Err(errmsg)), } } /// 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)) } #[cfg(test)] mod tests { use super::*; use crate::mapped_file::get_mapped_file; use aconfig_storage_file::{FlagInfoBit, StoredFlagType}; use rand::Rng; use std::fs; fn create_test_storage_files() -> String { let mut rng = rand::thread_rng(); let number: u32 = rng.gen(); let storage_dir = String::from("/tmp/") + &number.to_string(); if std::fs::metadata(&storage_dir).is_ok() { fs::remove_dir_all(&storage_dir).unwrap(); } let maps_dir = storage_dir.clone() + "/maps"; let boot_dir = storage_dir.clone() + "/boot"; fs::create_dir(&storage_dir).unwrap(); fs::create_dir(&maps_dir).unwrap(); fs::create_dir(&boot_dir).unwrap(); let package_map = storage_dir.clone() + "/maps/mockup.package.map"; let flag_map = storage_dir.clone() + "/maps/mockup.flag.map"; let flag_val = storage_dir.clone() + "/boot/mockup.val"; let flag_info = storage_dir.clone() + "/boot/mockup.info"; fs::copy("./tests/package.map", &package_map).unwrap(); fs::copy("./tests/flag.map", &flag_map).unwrap(); fs::copy("./tests/flag.val", &flag_val).unwrap(); fs::copy("./tests/flag.info", &flag_info).unwrap(); return storage_dir; } #[test] // this test point locks down flag package read context query fn test_package_context_query() { let storage_dir = create_test_storage_files(); let package_mapped_file = unsafe { get_mapped_file(&storage_dir, "mockup", StorageFileType::PackageMap).unwrap() }; let package_context = get_package_read_context(&package_mapped_file, "com.android.aconfig.storage.test_1") .unwrap() .unwrap(); let expected_package_context = PackageReadContext { package_id: 0, boolean_start_index: 0 }; assert_eq!(package_context, expected_package_context); let package_context = get_package_read_context(&package_mapped_file, "com.android.aconfig.storage.test_2") .unwrap() .unwrap(); let expected_package_context = PackageReadContext { package_id: 1, boolean_start_index: 3 }; assert_eq!(package_context, expected_package_context); let package_context = get_package_read_context(&package_mapped_file, "com.android.aconfig.storage.test_4") .unwrap() .unwrap(); let expected_package_context = PackageReadContext { package_id: 2, boolean_start_index: 6 }; assert_eq!(package_context, expected_package_context); } #[test] // this test point locks down flag read context query fn test_flag_context_query() { let storage_dir = create_test_storage_files(); let flag_mapped_file = unsafe { get_mapped_file(&storage_dir, "mockup", StorageFileType::FlagMap).unwrap() }; let baseline = vec![ (0, "enabled_ro", StoredFlagType::ReadOnlyBoolean, 1u16), (0, "enabled_rw", StoredFlagType::ReadWriteBoolean, 2u16), (2, "enabled_rw", StoredFlagType::ReadWriteBoolean, 1u16), (1, "disabled_rw", StoredFlagType::ReadWriteBoolean, 0u16), (1, "enabled_fixed_ro", StoredFlagType::FixedReadOnlyBoolean, 1u16), (1, "enabled_ro", StoredFlagType::ReadOnlyBoolean, 2u16), (2, "enabled_fixed_ro", StoredFlagType::FixedReadOnlyBoolean, 0u16), (0, "disabled_rw", StoredFlagType::ReadWriteBoolean, 0u16), ]; for (package_id, flag_name, flag_type, flag_index) in baseline.into_iter() { let flag_context = get_flag_read_context(&flag_mapped_file, package_id, flag_name).unwrap().unwrap(); assert_eq!(flag_context.flag_type, flag_type); assert_eq!(flag_context.flag_index, flag_index); } } #[test] // this test point locks down flag value query fn test_flag_value_query() { let storage_dir = create_test_storage_files(); let flag_value_file = unsafe { get_mapped_file(&storage_dir, "mockup", StorageFileType::FlagVal).unwrap() }; let baseline: Vec = vec![false, true, true, false, true, true, true, true]; for (offset, expected_value) in baseline.into_iter().enumerate() { let flag_value = get_boolean_flag_value(&flag_value_file, offset as u32).unwrap(); assert_eq!(flag_value, expected_value); } } #[test] // this test point locks donw flag info query fn test_flag_info_query() { let storage_dir = create_test_storage_files(); let flag_info_file = unsafe { get_mapped_file(&storage_dir, "mockup", StorageFileType::FlagInfo).unwrap() }; let is_rw: Vec = vec![true, false, true, true, false, false, false, true]; for (offset, expected_value) in is_rw.into_iter().enumerate() { let attribute = get_flag_attribute(&flag_info_file, FlagValueType::Boolean, offset as u32).unwrap(); assert_eq!((attribute & FlagInfoBit::IsReadWrite as u8) != 0u8, expected_value); assert!((attribute & FlagInfoBit::HasServerOverride as u8) == 0u8); assert!((attribute & FlagInfoBit::HasLocalOverride as u8) == 0u8); } } #[test] // this test point locks down flag storage file version number query api fn test_storage_version_query() { 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); assert_eq!(get_storage_file_version("./tests/flag.info").unwrap(), 1); } }