diff options
author | Dennis Shen <dzshen@google.com> | 2024-03-04 20:01:44 +0000 |
---|---|---|
committer | Dennis Shen <dzshen@google.com> | 2024-03-04 21:12:43 +0000 |
commit | 61d8c5a79912954891e8897e1bbe7553106e9b8d (patch) | |
tree | bd4caab888408ba75cf7a5b92bddc21652c32d56 | |
parent | 279e018b0a32aa3510c87685a8de68edf9ac1cf3 (diff) | |
download | build-61d8c5a79912954891e8897e1bbe7553106e9b8d.tar.gz |
aconfig: split aconfig_storage_file crate
Current aconfig_storage_file crate contains two things: 1,
aconfig storage file definition (which is used by aconfig crate for
storage file creation) and 2, flag storage file read api. With this
change, we are splitting 2 into its own crate aconfig_storage_read_api
In the long run, there will be 3 crates for aconfig_storage
1, aconfig_storage_file
It contains aconfig storage file definition, as well as a binary to help
print/dump aconfig storage binary files into some readable format.
2, aconfig_storage_read_api
This crate contains flag read apis. This crate depends on
aconfig_storge_file crate. This crate will be used by codegen.
3, aconfig_storage_write_api
This crate contains flag write apis. This crate depends on
aconfig_storage_file crate. This crate will only be used by aconfig
daemon to update flag values.
Bug: b/321077378
Test: atest aconfig_storage_read_api.test; atest
aconfig_storage_file.test; atest aconfig_storage_read_api.test.rust;
atest aconfig_storage_read_api.test.cpp
Change-Id: Ia6afdc40a45c7c59652fbfd123c2b77bf6666dd9
26 files changed, 1049 insertions, 763 deletions
diff --git a/tools/aconfig/Cargo.toml b/tools/aconfig/Cargo.toml index 95f1215766..7112fd468c 100644 --- a/tools/aconfig/Cargo.toml +++ b/tools/aconfig/Cargo.toml @@ -4,6 +4,7 @@ members = [ "aconfig", "aconfig_protos", "aconfig_storage_file", + "aconfig_storage_read_api", "aflags", "printflags" ] diff --git a/tools/aconfig/TEST_MAPPING b/tools/aconfig/TEST_MAPPING index b07596fdd4..26d617268e 100644 --- a/tools/aconfig/TEST_MAPPING +++ b/tools/aconfig/TEST_MAPPING @@ -70,12 +70,16 @@ ], "postsubmit": [ { + // aconfig_storage_read_api unit tests + "name": "aconfig_storage_read_api.test" + }, + { // aconfig_storage read api rust integration tests - "name": "aconfig_storage.test.rust" + "name": "aconfig_storage_read_api.test.rust" }, { // aconfig_storage read api cpp integration tests - "name": "aconfig_storage.test.cpp" + "name": "aconfig_storage_read_api.test.cpp" }, { // aflags CLI unit tests diff --git a/tools/aconfig/aconfig_storage_file/Android.bp b/tools/aconfig/aconfig_storage_file/Android.bp index 8922ba403d..6be3c1978a 100644 --- a/tools/aconfig/aconfig_storage_file/Android.bp +++ b/tools/aconfig/aconfig_storage_file/Android.bp @@ -9,12 +9,6 @@ rust_defaults { srcs: ["src/lib.rs"], rustlibs: [ "libanyhow", - "libaconfig_storage_protos", - "libonce_cell", - "libprotobuf", - "libtempfile", - "libmemmap2", - "libcxx", "libthiserror", ], } @@ -26,91 +20,8 @@ rust_library { defaults: ["aconfig_storage_file.defaults"], } -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)", -} - rust_test_host { name: "aconfig_storage_file.test", test_suites: ["general-tests"], defaults: ["aconfig_storage_file.defaults"], - data: [ - "tests/package.map", - "tests/flag.map", - "tests/flag.val", - ], -} - -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, -} - -genrule { - name: "libcxx_aconfig_storage_bridge_code", - tools: ["cxxbridge"], - cmd: "$(location cxxbridge) $(in) > $(out)", - srcs: ["src/lib.rs"], - out: ["aconfig_storage/lib.rs.cc"], -} - -genrule { - name: "libcxx_aconfig_storage_bridge_header", - tools: ["cxxbridge"], - cmd: "$(location cxxbridge) $(in) --header > $(out)", - srcs: ["src/lib.rs"], - out: ["aconfig_storage/lib.rs.h"], -} - -rust_ffi_static { - name: "libaconfig_storage_cxx_bridge", - crate_name: "aconfig_storage_cxx_bridge", - host_supported: true, - defaults: ["aconfig_storage_file.defaults"], -} - -cc_library_static { - name: "libaconfig_storage_cc", - srcs: ["aconfig_storage.cpp"], - generated_headers: [ - "cxx-bridge-header", - "libcxx_aconfig_storage_bridge_header" - ], - generated_sources: ["libcxx_aconfig_storage_bridge_code"], - whole_static_libs: ["libaconfig_storage_cxx_bridge"], - export_include_dirs: ["include"], } diff --git a/tools/aconfig/aconfig_storage_file/src/flag_table.rs b/tools/aconfig/aconfig_storage_file/src/flag_table.rs index 108804ec26..e1791d1c23 100644 --- a/tools/aconfig/aconfig_storage_file/src/flag_table.rs +++ b/tools/aconfig/aconfig_storage_file/src/flag_table.rs @@ -17,10 +17,9 @@ //! flag table module defines the flag table file format and methods for serialization //! and deserialization -use crate::AconfigStorageError::{self, BytesParseFail, HigherStorageFileVersion}; +use crate::AconfigStorageError::{self, BytesParseFail}; use crate::{get_bucket_index, read_str_from_bytes, read_u16_from_bytes, read_u32_from_bytes}; use anyhow::anyhow; -pub type FlagOffset = u16; /// Flag table header struct #[derive(PartialEq, Debug)] @@ -154,44 +153,6 @@ impl FlagTable { } } -/// Query flag within package offset -pub fn find_flag_offset( - buf: &[u8], - package_id: u32, - flag: &str, -) -> Result<Option<FlagOffset>, AconfigStorageError> { - let interpreted_header = FlagTableHeader::from_bytes(buf)?; - if interpreted_header.version > crate::FILE_VERSION { - return Err(HigherStorageFileVersion(anyhow!( - "Cannot read storage file with a higher version of {} with lib version {}", - interpreted_header.version, - crate::FILE_VERSION - ))); - } - - let num_buckets = (interpreted_header.node_offset - interpreted_header.bucket_offset) / 4; - let bucket_index = FlagTableNode::find_bucket_index(package_id, flag, num_buckets); - - let mut pos = (interpreted_header.bucket_offset + 4 * bucket_index) as usize; - let mut flag_node_offset = read_u32_from_bytes(buf, &mut pos)? as usize; - if flag_node_offset < interpreted_header.node_offset as usize - || flag_node_offset >= interpreted_header.file_size as usize - { - return Ok(None); - } - - loop { - let interpreted_node = FlagTableNode::from_bytes(&buf[flag_node_offset..])?; - if interpreted_node.package_id == package_id && interpreted_node.flag_name == flag { - return Ok(Some(interpreted_node.flag_id)); - } - match interpreted_node.next_offset { - Some(offset) => flag_node_offset = offset as usize, - None => return Ok(None), - } - } -} - #[cfg(test)] mod tests { use super::*; @@ -270,52 +231,4 @@ mod tests { assert!(reinterpreted_table.is_ok()); assert_eq!(&flag_table, &reinterpreted_table.unwrap()); } - - #[test] - // this test point locks down table query - fn test_flag_query() { - let flag_table = create_test_flag_table().as_bytes(); - let baseline = vec![ - (0, "enabled_ro", 1u16), - (0, "enabled_rw", 2u16), - (1, "disabled_ro", 0u16), - (2, "enabled_ro", 1u16), - (1, "enabled_fixed_ro", 1u16), - (1, "enabled_ro", 2u16), - (2, "enabled_fixed_ro", 0u16), - (0, "disabled_rw", 0u16), - ]; - for (package_id, flag_name, expected_offset) in baseline.into_iter() { - let flag_offset = - find_flag_offset(&flag_table[..], package_id, flag_name).unwrap().unwrap(); - assert_eq!(flag_offset, expected_offset); - } - } - - #[test] - // this test point locks down table query of a non exist flag - fn test_not_existed_flag_query() { - let flag_table = create_test_flag_table().as_bytes(); - let flag_offset = find_flag_offset(&flag_table[..], 1, "disabled_fixed_ro").unwrap(); - assert_eq!(flag_offset, None); - let flag_offset = find_flag_offset(&flag_table[..], 2, "disabled_rw").unwrap(); - assert_eq!(flag_offset, None); - } - - #[test] - // this test point locks down query error when file has a higher version - fn test_higher_version_storage_file() { - let mut table = create_test_flag_table(); - table.header.version = crate::FILE_VERSION + 1; - let flag_table = table.as_bytes(); - let error = find_flag_offset(&flag_table[..], 0, "enabled_ro").unwrap_err(); - assert_eq!( - format!("{:?}", error), - format!( - "HigherStorageFileVersion(Cannot read storage file with a higher version of {} with lib version {})", - crate::FILE_VERSION + 1, - crate::FILE_VERSION - ) - ); - } } diff --git a/tools/aconfig/aconfig_storage_file/src/flag_value.rs b/tools/aconfig/aconfig_storage_file/src/flag_value.rs index 0a6a37f979..8356847ebc 100644 --- a/tools/aconfig/aconfig_storage_file/src/flag_value.rs +++ b/tools/aconfig/aconfig_storage_file/src/flag_value.rs @@ -17,9 +17,8 @@ //! flag value module defines the flag value file format and methods for serialization //! and deserialization -use crate::AconfigStorageError::{self, HigherStorageFileVersion, InvalidStorageFileOffset}; +use crate::AconfigStorageError; use crate::{read_str_from_bytes, read_u32_from_bytes, read_u8_from_bytes}; -use anyhow::anyhow; /// Flag value header struct #[derive(PartialEq, Debug)] @@ -87,31 +86,6 @@ impl FlagValueList { } } -/// Query flag value -pub fn find_boolean_flag_value(buf: &[u8], flag_offset: u32) -> Result<bool, AconfigStorageError> { - let interpreted_header = FlagValueHeader::from_bytes(buf)?; - if interpreted_header.version > crate::FILE_VERSION { - return Err(HigherStorageFileVersion(anyhow!( - "Cannot read storage file with a higher version of {} with lib version {}", - interpreted_header.version, - crate::FILE_VERSION - ))); - } - - let mut head = (interpreted_header.boolean_value_offset + flag_offset) as usize; - - // TODO: right now, there is only boolean flags, with more flag value types added - // later, the end of boolean flag value section should be updated (b/322826265). - if head >= interpreted_header.file_size as usize { - return Err(InvalidStorageFileOffset(anyhow!( - "Flag value offset goes beyond the end of the file." - ))); - } - - let val = read_u8_from_bytes(buf, &mut head)?; - Ok(val == 1) -} - #[cfg(test)] mod tests { use super::*; @@ -142,43 +116,4 @@ mod tests { assert!(reinterpreted_value_list.is_ok()); assert_eq!(&flag_value_list, &reinterpreted_value_list.unwrap()); } - - #[test] - // this test point locks down flag value query - fn test_flag_value_query() { - let flag_value_list = create_test_flag_value_list().as_bytes(); - let baseline: Vec<bool> = vec![false, true, false, false, true, true, false, true]; - for (offset, expected_value) in baseline.into_iter().enumerate() { - let flag_value = find_boolean_flag_value(&flag_value_list[..], offset as u32).unwrap(); - assert_eq!(flag_value, expected_value); - } - } - - #[test] - // this test point locks down query beyond the end of boolean section - fn test_boolean_out_of_range() { - let flag_value_list = create_test_flag_value_list().as_bytes(); - let error = find_boolean_flag_value(&flag_value_list[..], 8).unwrap_err(); - assert_eq!( - format!("{:?}", error), - "InvalidStorageFileOffset(Flag value offset goes beyond the end of the file.)" - ); - } - - #[test] - // this test point locks down query error when file has a higher version - fn test_higher_version_storage_file() { - let mut value_list = create_test_flag_value_list(); - value_list.header.version = crate::FILE_VERSION + 1; - let flag_value = value_list.as_bytes(); - let error = find_boolean_flag_value(&flag_value[..], 4).unwrap_err(); - assert_eq!( - format!("{:?}", error), - format!( - "HigherStorageFileVersion(Cannot read storage file with a higher version of {} with lib version {})", - crate::FILE_VERSION + 1, - crate::FILE_VERSION - ) - ); - } } diff --git a/tools/aconfig/aconfig_storage_file/src/lib.rs b/tools/aconfig/aconfig_storage_file/src/lib.rs index f20600d666..e06e14986c 100644 --- a/tools/aconfig/aconfig_storage_file/src/lib.rs +++ b/tools/aconfig/aconfig_storage_file/src/lib.rs @@ -34,21 +34,15 @@ pub mod flag_table; pub mod flag_value; -pub mod mapped_file; pub mod package_table; -pub mod protos; - -#[cfg(test)] -mod test_utils; use anyhow::anyhow; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; -pub use crate::flag_table::{FlagOffset, FlagTable, FlagTableHeader, FlagTableNode}; +pub use crate::flag_table::{FlagTable, FlagTableHeader, FlagTableNode}; pub use crate::flag_value::{FlagValueHeader, FlagValueList}; -pub use crate::package_table::{PackageOffset, PackageTable, PackageTableHeader, PackageTableNode}; -pub use crate::protos::ProtoStorageFiles; +pub use crate::package_table::{PackageTable, PackageTableHeader, PackageTableNode}; use crate::AconfigStorageError::{BytesParseFail, HashTableSizeLimit}; @@ -62,9 +56,6 @@ pub(crate) const HASH_PRIMES: [u32; 29] = [ 402653189, 805306457, 1610612741, ]; -/// Storage file location pb file -pub const STORAGE_LOCATION_FILE: &str = "/metadata/aconfig/available_storage_file_records.pb"; - /// Storage file type enum #[derive(Clone, Debug, PartialEq, Eq)] pub enum StorageFileSelection { @@ -104,7 +95,7 @@ pub(crate) fn get_bucket_index<T: Hash>(val: &T, num_buckets: u32) -> u32 { } /// Read and parse bytes as u8 -pub(crate) fn read_u8_from_bytes(buf: &[u8], head: &mut usize) -> Result<u8, AconfigStorageError> { +pub fn read_u8_from_bytes(buf: &[u8], head: &mut usize) -> Result<u8, AconfigStorageError> { let val = u8::from_le_bytes(buf[*head..*head + 1].try_into().map_err(|errmsg| { BytesParseFail(anyhow!("fail to parse u8 from bytes: {}", errmsg)) @@ -127,10 +118,7 @@ pub(crate) fn read_u16_from_bytes( } /// Read and parse bytes as u32 -pub(crate) fn read_u32_from_bytes( - buf: &[u8], - head: &mut usize, -) -> Result<u32, AconfigStorageError> { +pub fn read_u32_from_bytes(buf: &[u8], head: &mut usize) -> Result<u32, AconfigStorageError> { let val = u32::from_le_bytes(buf[*head..*head + 4].try_into().map_err(|errmsg| { BytesParseFail(anyhow!("fail to parse u32 from bytes: {}", errmsg)) @@ -179,392 +167,3 @@ pub enum AconfigStorageError { #[error("invalid storage file byte offset")] InvalidStorageFileOffset(#[source] anyhow::Error), } - -/// Get package start offset implementation -pub fn get_package_offset_impl( - pb_file: &str, - container: &str, - package: &str, -) -> Result<Option<PackageOffset>, AconfigStorageError> { - let mapped_file = - crate::mapped_file::get_mapped_file(pb_file, container, StorageFileSelection::PackageMap)?; - crate::package_table::find_package_offset(&mapped_file, package) -} - -/// Get flag offset implementation -pub fn get_flag_offset_impl( - pb_file: &str, - container: &str, - package_id: u32, - flag: &str, -) -> Result<Option<FlagOffset>, AconfigStorageError> { - let mapped_file = - crate::mapped_file::get_mapped_file(pb_file, container, StorageFileSelection::FlagMap)?; - crate::flag_table::find_flag_offset(&mapped_file, package_id, flag) -} - -/// Get boolean flag value implementation -pub fn get_boolean_flag_value_impl( - pb_file: &str, - container: &str, - offset: u32, -) -> Result<bool, AconfigStorageError> { - let mapped_file = - crate::mapped_file::get_mapped_file(pb_file, container, StorageFileSelection::FlagVal)?; - crate::flag_value::find_boolean_flag_value(&mapped_file, offset) -} - -/// 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, -/// and then look for the target package in this mapped file. -/// -/// If a package is found, it returns Ok(Some(PackageOffset)) -/// If a package is not found, it returns Ok(None) -/// If errors out such as no such package map file is found, it returns an Err(errmsg) -pub fn get_package_offset( - container: &str, - package: &str, -) -> Result<Option<PackageOffset>, AconfigStorageError> { - get_package_offset_impl(STORAGE_LOCATION_FILE, container, package) -} - -/// Get flag offset within a package given the container name, package id and flag name. -/// -/// This function would map the corresponding flag map file if has not been mapped yet, -/// and then look for the target flag in this mapped file. -/// -/// If a flag is found, it returns Ok(Some(u16)) -/// If a flag is not found, it returns Ok(None) -/// If errors out such as no such flag map file is found, it returns an Err(errmsg) -pub fn get_flag_offset( - container: &str, - package_id: u32, - flag: &str, -) -> Result<Option<FlagOffset>, AconfigStorageError> { - get_flag_offset_impl(STORAGE_LOCATION_FILE, container, package_id, flag) -} - -/// Get the boolean flag value given the container name and flag global offset -/// -/// This function would map the corresponding flag value file if has not been mapped yet, -/// and then look for the target flag value at the specified offset. -/// -/// If flag value file is successfully mapped and the provide offset is valid, it returns -/// the boolean flag value, otherwise it returns the error message. -pub fn get_boolean_flag_value(container: &str, offset: u32) -> Result<bool, AconfigStorageError> { - get_boolean_flag_value_impl(STORAGE_LOCATION_FILE, container, offset) -} - -#[cxx::bridge] -mod ffi { - // Package table query return for cc interlop - pub struct PackageOffsetQueryCXX { - pub query_success: bool, - pub error_message: String, - pub package_exists: bool, - pub package_id: u32, - pub boolean_offset: u32, - } - - // Flag table query return for cc interlop - pub struct FlagOffsetQueryCXX { - pub query_success: bool, - pub error_message: String, - pub flag_exists: bool, - pub flag_offset: u16, - } - - // Flag value query return for cc interlop - pub struct BooleanFlagValueQueryCXX { - pub query_success: bool, - pub error_message: String, - pub flag_value: bool, - } - - // Rust export to c++ - extern "Rust" { - pub fn get_package_offset_cxx_impl( - pb_file: &str, - container: &str, - package: &str, - ) -> PackageOffsetQueryCXX; - - pub fn get_flag_offset_cxx_impl( - pb_file: &str, - container: &str, - package_id: u32, - flag: &str, - ) -> FlagOffsetQueryCXX; - - pub fn get_boolean_flag_value_cxx_impl( - pb_file: &str, - container: &str, - offset: u32, - ) -> BooleanFlagValueQueryCXX; - - pub fn get_package_offset_cxx(container: &str, package: &str) -> PackageOffsetQueryCXX; - - pub fn get_flag_offset_cxx( - container: &str, - package_id: u32, - flag: &str, - ) -> FlagOffsetQueryCXX; - - pub fn get_boolean_flag_value_cxx(container: &str, offset: u32) - -> BooleanFlagValueQueryCXX; - } -} - -/// Get package start offset impl cc interlop -pub fn get_package_offset_cxx_impl( - pb_file: &str, - container: &str, - package: &str, -) -> ffi::PackageOffsetQueryCXX { - ffi::PackageOffsetQueryCXX::new(get_package_offset_impl(pb_file, container, package)) -} - -/// Get flag start offset impl cc interlop -pub fn get_flag_offset_cxx_impl( - pb_file: &str, - container: &str, - package_id: u32, - flag: &str, -) -> ffi::FlagOffsetQueryCXX { - ffi::FlagOffsetQueryCXX::new(get_flag_offset_impl(pb_file, container, package_id, flag)) -} - -/// Get boolean flag value impl cc interlop -pub fn get_boolean_flag_value_cxx_impl( - pb_file: &str, - container: &str, - offset: u32, -) -> ffi::BooleanFlagValueQueryCXX { - ffi::BooleanFlagValueQueryCXX::new(get_boolean_flag_value_impl(pb_file, container, offset)) -} - -/// 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)) -} - -/// Get flag start offset cc interlop -pub fn get_flag_offset_cxx( - container: &str, - package_id: u32, - flag: &str, -) -> ffi::FlagOffsetQueryCXX { - ffi::FlagOffsetQueryCXX::new(get_flag_offset(container, package_id, flag)) -} - -/// Get boolean flag value cc interlop -pub fn get_boolean_flag_value_cxx(container: &str, offset: u32) -> ffi::BooleanFlagValueQueryCXX { - 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::*; - use crate::test_utils::{write_storage_text_to_temp_file, TestStorageFileSet}; - - fn create_test_storage_files(read_only: bool) -> TestStorageFileSet { - TestStorageFileSet::new( - "./tests/package.map", - "./tests/flag.map", - "./tests/flag.val", - read_only, - ) - .unwrap() - } - - #[test] - // this test point locks down flag package offset query - fn test_package_offset_query() { - let ro_files = create_test_storage_files(true); - let text_proto = format!( - r#" -files {{ - version: 0 - container: "system" - package_map: "{}" - flag_map: "{}" - flag_val: "{}" - timestamp: 12345 -}} -"#, - ro_files.package_map.name, ro_files.flag_map.name, ro_files.flag_val.name - ); - - let file = write_storage_text_to_temp_file(&text_proto).unwrap(); - let file_full_path = file.path().display().to_string(); - let package_offset = get_package_offset_impl( - &file_full_path, - "system", - "com.android.aconfig.storage.test_1", - ) - .unwrap() - .unwrap(); - let expected_package_offset = PackageOffset { package_id: 0, boolean_offset: 0 }; - assert_eq!(package_offset, expected_package_offset); - - let package_offset = get_package_offset_impl( - &file_full_path, - "system", - "com.android.aconfig.storage.test_2", - ) - .unwrap() - .unwrap(); - let expected_package_offset = PackageOffset { package_id: 1, boolean_offset: 3 }; - assert_eq!(package_offset, expected_package_offset); - - let package_offset = get_package_offset_impl( - &file_full_path, - "system", - "com.android.aconfig.storage.test_4", - ) - .unwrap() - .unwrap(); - let expected_package_offset = PackageOffset { package_id: 2, boolean_offset: 6 }; - assert_eq!(package_offset, expected_package_offset); - } - - #[test] - // this test point locks down flag offset query - fn test_flag_offset_query() { - let ro_files = create_test_storage_files(true); - let text_proto = format!( - r#" -files {{ - version: 0 - container: "system" - package_map: "{}" - flag_map: "{}" - flag_val: "{}" - timestamp: 12345 -}} -"#, - ro_files.package_map.name, ro_files.flag_map.name, ro_files.flag_val.name - ); - - let file = write_storage_text_to_temp_file(&text_proto).unwrap(); - let file_full_path = file.path().display().to_string(); - let baseline = vec![ - (0, "enabled_ro", 1u16), - (0, "enabled_rw", 2u16), - (1, "disabled_ro", 0u16), - (2, "enabled_ro", 1u16), - (1, "enabled_fixed_ro", 1u16), - (1, "enabled_ro", 2u16), - (2, "enabled_fixed_ro", 0u16), - (0, "disabled_rw", 0u16), - ]; - for (package_id, flag_name, expected_offset) in baseline.into_iter() { - let flag_offset = - get_flag_offset_impl(&file_full_path, "system", package_id, flag_name) - .unwrap() - .unwrap(); - assert_eq!(flag_offset, expected_offset); - } - } - - #[test] - // this test point locks down flag offset query - fn test_flag_value_query() { - let ro_files = create_test_storage_files(true); - let text_proto = format!( - r#" -files {{ - version: 0 - container: "system" - package_map: "{}" - flag_map: "{}" - flag_val: "{}" - timestamp: 12345 -}} -"#, - ro_files.package_map.name, ro_files.flag_map.name, ro_files.flag_val.name - ); - - let file = write_storage_text_to_temp_file(&text_proto).unwrap(); - let file_full_path = file.path().display().to_string(); - let baseline: Vec<bool> = vec![false; 8]; - for (offset, expected_value) in baseline.into_iter().enumerate() { - let flag_value = - get_boolean_flag_value_impl(&file_full_path, "system", offset as u32).unwrap(); - assert_eq!(flag_value, expected_value); - } - } -} diff --git a/tools/aconfig/aconfig_storage_file/src/package_table.rs b/tools/aconfig/aconfig_storage_file/src/package_table.rs index 7308d7b3b2..28310a80f4 100644 --- a/tools/aconfig/aconfig_storage_file/src/package_table.rs +++ b/tools/aconfig/aconfig_storage_file/src/package_table.rs @@ -17,7 +17,7 @@ //! package table module defines the package table file format and methods for serialization //! and deserialization -use crate::AconfigStorageError::{self, BytesParseFail, HigherStorageFileVersion}; +use crate::AconfigStorageError::{self, BytesParseFail}; use crate::{get_bucket_index, read_str_from_bytes, read_u32_from_bytes}; use anyhow::anyhow; @@ -153,53 +153,6 @@ impl PackageTable { } } -/// Package table query return -#[derive(PartialEq, Debug)] -pub struct PackageOffset { - pub package_id: u32, - pub boolean_offset: u32, -} - -/// Query package id and start offset -pub fn find_package_offset( - buf: &[u8], - package: &str, -) -> Result<Option<PackageOffset>, AconfigStorageError> { - let interpreted_header = PackageTableHeader::from_bytes(buf)?; - if interpreted_header.version > crate::FILE_VERSION { - return Err(HigherStorageFileVersion(anyhow!( - "Cannot read storage file with a higher version of {} with lib version {}", - interpreted_header.version, - crate::FILE_VERSION - ))); - } - - let num_buckets = (interpreted_header.node_offset - interpreted_header.bucket_offset) / 4; - let bucket_index = PackageTableNode::find_bucket_index(package, num_buckets); - - let mut pos = (interpreted_header.bucket_offset + 4 * bucket_index) as usize; - let mut package_node_offset = read_u32_from_bytes(buf, &mut pos)? as usize; - if package_node_offset < interpreted_header.node_offset as usize - || package_node_offset >= interpreted_header.file_size as usize - { - return Ok(None); - } - - loop { - let interpreted_node = PackageTableNode::from_bytes(&buf[package_node_offset..])?; - if interpreted_node.package_name == package { - return Ok(Some(PackageOffset { - package_id: interpreted_node.package_id, - boolean_offset: interpreted_node.boolean_offset, - })); - } - match interpreted_node.next_offset { - Some(offset) => package_node_offset = offset as usize, - None => return Ok(None), - } - } -} - #[cfg(test)] mod tests { use super::*; @@ -255,60 +208,4 @@ mod tests { assert!(reinterpreted_table.is_ok()); assert_eq!(&package_table, &reinterpreted_table.unwrap()); } - - #[test] - // this test point locks down table query - fn test_package_query() { - let package_table = create_test_package_table().as_bytes(); - let package_offset = - find_package_offset(&package_table[..], "com.android.aconfig.storage.test_1") - .unwrap() - .unwrap(); - let expected_package_offset = PackageOffset { package_id: 0, boolean_offset: 0 }; - assert_eq!(package_offset, expected_package_offset); - let package_offset = - find_package_offset(&package_table[..], "com.android.aconfig.storage.test_2") - .unwrap() - .unwrap(); - let expected_package_offset = PackageOffset { package_id: 1, boolean_offset: 3 }; - assert_eq!(package_offset, expected_package_offset); - let package_offset = - find_package_offset(&package_table[..], "com.android.aconfig.storage.test_4") - .unwrap() - .unwrap(); - let expected_package_offset = PackageOffset { package_id: 2, boolean_offset: 6 }; - assert_eq!(package_offset, expected_package_offset); - } - - #[test] - // this test point locks down table query of a non exist package - fn test_not_existed_package_query() { - // this will land at an empty bucket - let package_table = create_test_package_table().as_bytes(); - let package_offset = - find_package_offset(&package_table[..], "com.android.aconfig.storage.test_3").unwrap(); - assert_eq!(package_offset, None); - // this will land at the end of a linked list - let package_offset = - find_package_offset(&package_table[..], "com.android.aconfig.storage.test_5").unwrap(); - assert_eq!(package_offset, None); - } - - #[test] - // this test point locks down query error when file has a higher version - fn test_higher_version_storage_file() { - let mut table = create_test_package_table(); - table.header.version = crate::FILE_VERSION + 1; - let package_table = table.as_bytes(); - let error = find_package_offset(&package_table[..], "com.android.aconfig.storage.test_1") - .unwrap_err(); - assert_eq!( - format!("{:?}", error), - format!( - "HigherStorageFileVersion(Cannot read storage file with a higher version of {} with lib version {})", - crate::FILE_VERSION + 1, - crate::FILE_VERSION - ) - ); - } } diff --git a/tools/aconfig/aconfig_storage_read_api/Android.bp b/tools/aconfig/aconfig_storage_read_api/Android.bp new file mode 100644 index 0000000000..43697b31de --- /dev/null +++ b/tools/aconfig/aconfig_storage_read_api/Android.bp @@ -0,0 +1,121 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +rust_defaults { + name: "aconfig_storage_read_api.defaults", + edition: "2021", + lints: "none", + srcs: ["src/lib.rs"], + rustlibs: [ + "libanyhow", + "libaconfig_storage_protos", + "libonce_cell", + "libprotobuf", + "libtempfile", + "libmemmap2", + "libcxx", + "libthiserror", + "libaconfig_storage_file", + ], +} + +rust_library { + name: "libaconfig_storage_read_api", + crate_name: "aconfig_storage_read_api", + host_supported: true, + defaults: ["aconfig_storage_read_api.defaults"], +} + +rust_test_host { + name: "aconfig_storage_read_api.test", + test_suites: ["general-tests"], + defaults: ["aconfig_storage_read_api.defaults"], + data: [ + "tests/package.map", + "tests/flag.map", + "tests/flag.val", + ], +} + +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, +} + +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", + tools: ["cxxbridge"], + cmd: "$(location cxxbridge) $(in) > $(out)", + srcs: ["src/lib.rs"], + out: ["aconfig_storage/lib.rs.cc"], +} + +// cxx header codegen from rust api +genrule { + name: "libcxx_aconfig_storage_read_api_bridge_header", + tools: ["cxxbridge"], + cmd: "$(location cxxbridge) $(in) --header > $(out)", + srcs: ["src/lib.rs"], + out: ["aconfig_storage/lib.rs.h"], +} + +// a static cc lib based on generated code +rust_ffi_static { + name: "libaconfig_storage_read_api_cxx_bridge", + crate_name: "aconfig_storage_read_api_cxx_bridge", + host_supported: true, + defaults: ["aconfig_storage_read_api.defaults"], +} + +// flag read api cc interface +cc_library_static { + name: "libaconfig_storage_read_api_cc", + srcs: ["aconfig_storage_read_api.cpp"], + generated_headers: [ + "cxx-bridge-header", + "libcxx_aconfig_storage_read_api_bridge_header" + ], + generated_sources: ["libcxx_aconfig_storage_read_api_bridge_code"], + whole_static_libs: ["libaconfig_storage_read_api_cxx_bridge"], + export_include_dirs: ["include"], +} diff --git a/tools/aconfig/aconfig_storage_read_api/Cargo.toml b/tools/aconfig/aconfig_storage_read_api/Cargo.toml new file mode 100644 index 0000000000..4c65d40dc7 --- /dev/null +++ b/tools/aconfig/aconfig_storage_read_api/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "aconfig_storage_read_api" +version = "0.1.0" +edition = "2021" + +[features] +default = ["cargo"] +cargo = [] + +[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" +aconfig_storage_file = { path = "../aconfig_storage_file" } + +[build-dependencies] +protobuf-codegen = "3.2.0" +cxx-build = "1.0" diff --git a/tools/aconfig/aconfig_storage_file/aconfig_storage.cpp b/tools/aconfig/aconfig_storage_read_api/aconfig_storage_read_api.cpp index ac640930ac..7cf8e38219 100644 --- a/tools/aconfig/aconfig_storage_file/aconfig_storage.cpp +++ b/tools/aconfig/aconfig_storage_read_api/aconfig_storage_read_api.cpp @@ -1,4 +1,4 @@ -#include "aconfig_storage/aconfig_storage.hpp" +#include "aconfig_storage/aconfig_storage_read_api.hpp" #include "rust/cxx.h" #include "aconfig_storage/lib.rs.h" diff --git a/tools/aconfig/aconfig_storage_file/build.rs b/tools/aconfig/aconfig_storage_read_api/build.rs index 894b71c10f..894b71c10f 100644 --- a/tools/aconfig/aconfig_storage_file/build.rs +++ b/tools/aconfig/aconfig_storage_read_api/build.rs diff --git a/tools/aconfig/aconfig_storage_file/include/aconfig_storage/aconfig_storage.hpp b/tools/aconfig/aconfig_storage_read_api/include/aconfig_storage/aconfig_storage_read_api.hpp index 636fb7eea5..636fb7eea5 100644 --- a/tools/aconfig/aconfig_storage_file/include/aconfig_storage/aconfig_storage.hpp +++ b/tools/aconfig/aconfig_storage_read_api/include/aconfig_storage/aconfig_storage_read_api.hpp diff --git a/tools/aconfig/aconfig_storage_file/protos/aconfig_storage_metadata.proto b/tools/aconfig/aconfig_storage_read_api/protos/aconfig_storage_metadata.proto index c6728bdfee..c6728bdfee 100644 --- a/tools/aconfig/aconfig_storage_file/protos/aconfig_storage_metadata.proto +++ b/tools/aconfig/aconfig_storage_read_api/protos/aconfig_storage_metadata.proto diff --git a/tools/aconfig/aconfig_storage_read_api/src/flag_table_query.rs b/tools/aconfig/aconfig_storage_read_api/src/flag_table_query.rs new file mode 100644 index 0000000000..cc05557a0a --- /dev/null +++ b/tools/aconfig/aconfig_storage_read_api/src/flag_table_query.rs @@ -0,0 +1,175 @@ +/* + * 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. + */ + +//! flag table query module defines the flag table file read from mapped bytes + +use crate::{AconfigStorageError, FILE_VERSION}; +use aconfig_storage_file::{ + flag_table::FlagTableHeader, flag_table::FlagTableNode, read_u32_from_bytes, +}; +use anyhow::anyhow; + +pub type FlagOffset = u16; + +/// Query flag within package offset +pub fn find_flag_offset( + buf: &[u8], + package_id: u32, + flag: &str, +) -> Result<Option<FlagOffset>, AconfigStorageError> { + let interpreted_header = FlagTableHeader::from_bytes(buf)?; + if interpreted_header.version > crate::FILE_VERSION { + return Err(AconfigStorageError::HigherStorageFileVersion(anyhow!( + "Cannot read storage file with a higher version of {} with lib version {}", + interpreted_header.version, + FILE_VERSION + ))); + } + + let num_buckets = (interpreted_header.node_offset - interpreted_header.bucket_offset) / 4; + let bucket_index = FlagTableNode::find_bucket_index(package_id, flag, num_buckets); + + let mut pos = (interpreted_header.bucket_offset + 4 * bucket_index) as usize; + let mut flag_node_offset = read_u32_from_bytes(buf, &mut pos)? as usize; + if flag_node_offset < interpreted_header.node_offset as usize + || flag_node_offset >= interpreted_header.file_size as usize + { + return Ok(None); + } + + loop { + let interpreted_node = FlagTableNode::from_bytes(&buf[flag_node_offset..])?; + if interpreted_node.package_id == package_id && interpreted_node.flag_name == flag { + return Ok(Some(interpreted_node.flag_id)); + } + match interpreted_node.next_offset { + Some(offset) => flag_node_offset = offset as usize, + None => return Ok(None), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use aconfig_storage_file::FlagTable; + + // create test baseline, syntactic sugar + fn new_expected_node( + package_id: u32, + flag_name: &str, + flag_type: u16, + flag_id: u16, + next_offset: Option<u32>, + ) -> FlagTableNode { + FlagTableNode { + package_id, + flag_name: flag_name.to_string(), + flag_type, + flag_id, + next_offset, + } + } + + pub fn create_test_flag_table() -> FlagTable { + let header = FlagTableHeader { + version: crate::FILE_VERSION, + container: String::from("system"), + file_size: 320, + num_flags: 8, + bucket_offset: 30, + node_offset: 98, + }; + let buckets: Vec<Option<u32>> = vec![ + Some(98), + Some(124), + None, + None, + None, + Some(177), + None, + Some(203), + None, + Some(261), + None, + None, + None, + None, + None, + Some(293), + None, + ]; + let nodes = vec![ + new_expected_node(0, "enabled_ro", 1, 1, None), + new_expected_node(0, "enabled_rw", 1, 2, Some(150)), + new_expected_node(1, "disabled_ro", 1, 0, None), + new_expected_node(2, "enabled_ro", 1, 1, None), + new_expected_node(1, "enabled_fixed_ro", 1, 1, Some(235)), + new_expected_node(1, "enabled_ro", 1, 2, None), + new_expected_node(2, "enabled_fixed_ro", 1, 0, None), + new_expected_node(0, "disabled_rw", 1, 0, None), + ]; + FlagTable { header, buckets, nodes } + } + + #[test] + // this test point locks down table query + fn test_flag_query() { + let flag_table = create_test_flag_table().as_bytes(); + let baseline = vec![ + (0, "enabled_ro", 1u16), + (0, "enabled_rw", 2u16), + (1, "disabled_ro", 0u16), + (2, "enabled_ro", 1u16), + (1, "enabled_fixed_ro", 1u16), + (1, "enabled_ro", 2u16), + (2, "enabled_fixed_ro", 0u16), + (0, "disabled_rw", 0u16), + ]; + for (package_id, flag_name, expected_offset) in baseline.into_iter() { + let flag_offset = + find_flag_offset(&flag_table[..], package_id, flag_name).unwrap().unwrap(); + assert_eq!(flag_offset, expected_offset); + } + } + + #[test] + // this test point locks down table query of a non exist flag + fn test_not_existed_flag_query() { + let flag_table = create_test_flag_table().as_bytes(); + let flag_offset = find_flag_offset(&flag_table[..], 1, "disabled_fixed_ro").unwrap(); + assert_eq!(flag_offset, None); + let flag_offset = find_flag_offset(&flag_table[..], 2, "disabled_rw").unwrap(); + assert_eq!(flag_offset, None); + } + + #[test] + // this test point locks down query error when file has a higher version + fn test_higher_version_storage_file() { + let mut table = create_test_flag_table(); + table.header.version = crate::FILE_VERSION + 1; + let flag_table = table.as_bytes(); + let error = find_flag_offset(&flag_table[..], 0, "enabled_ro").unwrap_err(); + assert_eq!( + format!("{:?}", error), + format!( + "HigherStorageFileVersion(Cannot read storage file with a higher version of {} with lib version {})", + crate::FILE_VERSION + 1, + crate::FILE_VERSION + ) + ); + } +} diff --git a/tools/aconfig/aconfig_storage_read_api/src/flag_value_query.rs b/tools/aconfig/aconfig_storage_read_api/src/flag_value_query.rs new file mode 100644 index 0000000000..6ff6b0530d --- /dev/null +++ b/tools/aconfig/aconfig_storage_read_api/src/flag_value_query.rs @@ -0,0 +1,103 @@ +/* + * 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. + */ + +//! flag value query module defines the flag value file read from mapped bytes + +use crate::{AconfigStorageError, FILE_VERSION}; +use aconfig_storage_file::{flag_value::FlagValueHeader, read_u8_from_bytes}; +use anyhow::anyhow; + +/// Query flag value +pub fn find_boolean_flag_value(buf: &[u8], flag_offset: u32) -> Result<bool, AconfigStorageError> { + let interpreted_header = FlagValueHeader::from_bytes(buf)?; + if interpreted_header.version > crate::FILE_VERSION { + return Err(AconfigStorageError::HigherStorageFileVersion(anyhow!( + "Cannot read storage file with a higher version of {} with lib version {}", + interpreted_header.version, + FILE_VERSION + ))); + } + + let mut head = (interpreted_header.boolean_value_offset + flag_offset) as usize; + + // TODO: right now, there is only boolean flags, with more flag value types added + // later, the end of boolean flag value section should be updated (b/322826265). + if head >= interpreted_header.file_size as usize { + return Err(AconfigStorageError::InvalidStorageFileOffset(anyhow!( + "Flag value offset goes beyond the end of the file." + ))); + } + + let val = read_u8_from_bytes(buf, &mut head)?; + Ok(val == 1) +} + +#[cfg(test)] +mod tests { + use super::*; + use aconfig_storage_file::FlagValueList; + + pub fn create_test_flag_value_list() -> FlagValueList { + let header = FlagValueHeader { + version: crate::FILE_VERSION, + container: String::from("system"), + file_size: 34, + num_flags: 8, + boolean_value_offset: 26, + }; + let booleans: Vec<bool> = vec![false, true, false, false, true, true, false, true]; + FlagValueList { header, booleans } + } + + #[test] + // this test point locks down flag value query + fn test_flag_value_query() { + let flag_value_list = create_test_flag_value_list().as_bytes(); + let baseline: Vec<bool> = vec![false, true, false, false, true, true, false, true]; + for (offset, expected_value) in baseline.into_iter().enumerate() { + let flag_value = find_boolean_flag_value(&flag_value_list[..], offset as u32).unwrap(); + assert_eq!(flag_value, expected_value); + } + } + + #[test] + // this test point locks down query beyond the end of boolean section + fn test_boolean_out_of_range() { + let flag_value_list = create_test_flag_value_list().as_bytes(); + let error = find_boolean_flag_value(&flag_value_list[..], 8).unwrap_err(); + assert_eq!( + format!("{:?}", error), + "InvalidStorageFileOffset(Flag value offset goes beyond the end of the file.)" + ); + } + + #[test] + // this test point locks down query error when file has a higher version + fn test_higher_version_storage_file() { + let mut value_list = create_test_flag_value_list(); + value_list.header.version = crate::FILE_VERSION + 1; + let flag_value = value_list.as_bytes(); + let error = find_boolean_flag_value(&flag_value[..], 4).unwrap_err(); + assert_eq!( + format!("{:?}", error), + format!( + "HigherStorageFileVersion(Cannot read storage file with a higher version of {} with lib version {})", + crate::FILE_VERSION + 1, + crate::FILE_VERSION + ) + ); + } +} diff --git a/tools/aconfig/aconfig_storage_read_api/src/lib.rs b/tools/aconfig/aconfig_storage_read_api/src/lib.rs new file mode 100644 index 0000000000..996256b13c --- /dev/null +++ b/tools/aconfig/aconfig_storage_read_api/src/lib.rs @@ -0,0 +1,441 @@ +/* + * 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 three 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>>>` +//! +//! 2, function to get flag offset within a specific package +//! pub fn get_flag_offset(container: &str, package_id: u32, flag: &str) -> `Result<Option<u16>>>` +//! +//! 3, function to get the actual flag value given the global offset (combined package and +//! flag offset). +//! pub fn get_boolean_flag_value(container: &str, offset: u32) -> `Result<bool>` +//! +//! 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_table_query; +pub mod flag_value_query; +pub mod mapped_file; +pub mod package_table_query; +pub mod protos; + +#[cfg(test)] +mod test_utils; + +pub use crate::protos::ProtoStorageFiles; +pub use aconfig_storage_file::{AconfigStorageError, StorageFileSelection, FILE_VERSION}; +pub use flag_table_query::FlagOffset; +pub use package_table_query::PackageOffset; + +use flag_table_query::find_flag_offset; +use flag_value_query::find_boolean_flag_value; +use mapped_file::get_mapped_file; +use package_table_query::find_package_offset; + +/// Storage file location pb file +pub const STORAGE_LOCATION_FILE: &str = "/metadata/aconfig/available_storage_file_records.pb"; + +/// Get package start offset implementation +pub fn get_package_offset_impl( + pb_file: &str, + container: &str, + package: &str, +) -> Result<Option<PackageOffset>, AconfigStorageError> { + let mapped_file = get_mapped_file(pb_file, container, StorageFileSelection::PackageMap)?; + find_package_offset(&mapped_file, package) +} + +/// Get flag offset implementation +pub fn get_flag_offset_impl( + pb_file: &str, + container: &str, + package_id: u32, + flag: &str, +) -> Result<Option<FlagOffset>, AconfigStorageError> { + let mapped_file = get_mapped_file(pb_file, container, StorageFileSelection::FlagMap)?; + find_flag_offset(&mapped_file, package_id, flag) +} + +/// Get boolean flag value implementation +pub fn get_boolean_flag_value_impl( + pb_file: &str, + container: &str, + offset: u32, +) -> Result<bool, AconfigStorageError> { + let mapped_file = get_mapped_file(pb_file, container, StorageFileSelection::FlagVal)?; + find_boolean_flag_value(&mapped_file, offset) +} + +/// 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, +/// and then look for the target package in this mapped file. +/// +/// If a package is found, it returns Ok(Some(PackageOffset)) +/// If a package is not found, it returns Ok(None) +/// If errors out such as no such package map file is found, it returns an Err(errmsg) +pub fn get_package_offset( + container: &str, + package: &str, +) -> Result<Option<PackageOffset>, AconfigStorageError> { + get_package_offset_impl(STORAGE_LOCATION_FILE, container, package) +} + +/// Get flag offset within a package given the container name, package id and flag name. +/// +/// This function would map the corresponding flag map file if has not been mapped yet, +/// and then look for the target flag in this mapped file. +/// +/// If a flag is found, it returns Ok(Some(u16)) +/// If a flag is not found, it returns Ok(None) +/// If errors out such as no such flag map file is found, it returns an Err(errmsg) +pub fn get_flag_offset( + container: &str, + package_id: u32, + flag: &str, +) -> Result<Option<FlagOffset>, AconfigStorageError> { + get_flag_offset_impl(STORAGE_LOCATION_FILE, container, package_id, flag) +} + +/// Get the boolean flag value given the container name and flag global offset +/// +/// This function would map the corresponding flag value file if has not been mapped yet, +/// and then look for the target flag value at the specified offset. +/// +/// If flag value file is successfully mapped and the provide offset is valid, it returns +/// the boolean flag value, otherwise it returns the error message. +pub fn get_boolean_flag_value(container: &str, offset: u32) -> Result<bool, AconfigStorageError> { + get_boolean_flag_value_impl(STORAGE_LOCATION_FILE, container, offset) +} + +#[cxx::bridge] +mod ffi { + // Package table query return for cc interlop + pub struct PackageOffsetQueryCXX { + pub query_success: bool, + pub error_message: String, + pub package_exists: bool, + pub package_id: u32, + pub boolean_offset: u32, + } + + // Flag table query return for cc interlop + pub struct FlagOffsetQueryCXX { + pub query_success: bool, + pub error_message: String, + pub flag_exists: bool, + pub flag_offset: u16, + } + + // Flag value query return for cc interlop + pub struct BooleanFlagValueQueryCXX { + pub query_success: bool, + pub error_message: String, + pub flag_value: bool, + } + + // Rust export to c++ + extern "Rust" { + pub fn get_package_offset_cxx_impl( + pb_file: &str, + container: &str, + package: &str, + ) -> PackageOffsetQueryCXX; + + pub fn get_flag_offset_cxx_impl( + pb_file: &str, + container: &str, + package_id: u32, + flag: &str, + ) -> FlagOffsetQueryCXX; + + pub fn get_boolean_flag_value_cxx_impl( + pb_file: &str, + container: &str, + offset: u32, + ) -> BooleanFlagValueQueryCXX; + + pub fn get_package_offset_cxx(container: &str, package: &str) -> PackageOffsetQueryCXX; + + pub fn get_flag_offset_cxx( + container: &str, + package_id: u32, + flag: &str, + ) -> FlagOffsetQueryCXX; + + pub fn get_boolean_flag_value_cxx(container: &str, offset: u32) + -> BooleanFlagValueQueryCXX; + } +} + +/// Get package start offset impl cc interlop +pub fn get_package_offset_cxx_impl( + pb_file: &str, + container: &str, + package: &str, +) -> ffi::PackageOffsetQueryCXX { + ffi::PackageOffsetQueryCXX::new(get_package_offset_impl(pb_file, container, package)) +} + +/// Get flag start offset impl cc interlop +pub fn get_flag_offset_cxx_impl( + pb_file: &str, + container: &str, + package_id: u32, + flag: &str, +) -> ffi::FlagOffsetQueryCXX { + ffi::FlagOffsetQueryCXX::new(get_flag_offset_impl(pb_file, container, package_id, flag)) +} + +/// Get boolean flag value impl cc interlop +pub fn get_boolean_flag_value_cxx_impl( + pb_file: &str, + container: &str, + offset: u32, +) -> ffi::BooleanFlagValueQueryCXX { + ffi::BooleanFlagValueQueryCXX::new(get_boolean_flag_value_impl(pb_file, container, offset)) +} + +/// 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)) +} + +/// Get flag start offset cc interlop +pub fn get_flag_offset_cxx( + container: &str, + package_id: u32, + flag: &str, +) -> ffi::FlagOffsetQueryCXX { + ffi::FlagOffsetQueryCXX::new(get_flag_offset(container, package_id, flag)) +} + +/// Get boolean flag value cc interlop +pub fn get_boolean_flag_value_cxx(container: &str, offset: u32) -> ffi::BooleanFlagValueQueryCXX { + 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::*; + use crate::test_utils::{write_storage_text_to_temp_file, TestStorageFileSet}; + + fn create_test_storage_files(read_only: bool) -> TestStorageFileSet { + TestStorageFileSet::new( + "./tests/package.map", + "./tests/flag.map", + "./tests/flag.val", + read_only, + ) + .unwrap() + } + + #[test] + // this test point locks down flag package offset query + fn test_package_offset_query() { + let ro_files = create_test_storage_files(true); + let text_proto = format!( + r#" +files {{ + version: 0 + container: "system" + package_map: "{}" + flag_map: "{}" + flag_val: "{}" + timestamp: 12345 +}} +"#, + ro_files.package_map.name, ro_files.flag_map.name, ro_files.flag_val.name + ); + + let file = write_storage_text_to_temp_file(&text_proto).unwrap(); + let file_full_path = file.path().display().to_string(); + let package_offset = get_package_offset_impl( + &file_full_path, + "system", + "com.android.aconfig.storage.test_1", + ) + .unwrap() + .unwrap(); + let expected_package_offset = PackageOffset { package_id: 0, boolean_offset: 0 }; + assert_eq!(package_offset, expected_package_offset); + + let package_offset = get_package_offset_impl( + &file_full_path, + "system", + "com.android.aconfig.storage.test_2", + ) + .unwrap() + .unwrap(); + let expected_package_offset = PackageOffset { package_id: 1, boolean_offset: 3 }; + assert_eq!(package_offset, expected_package_offset); + + let package_offset = get_package_offset_impl( + &file_full_path, + "system", + "com.android.aconfig.storage.test_4", + ) + .unwrap() + .unwrap(); + let expected_package_offset = PackageOffset { package_id: 2, boolean_offset: 6 }; + assert_eq!(package_offset, expected_package_offset); + } + + #[test] + // this test point locks down flag offset query + fn test_flag_offset_query() { + let ro_files = create_test_storage_files(true); + let text_proto = format!( + r#" +files {{ + version: 0 + container: "system" + package_map: "{}" + flag_map: "{}" + flag_val: "{}" + timestamp: 12345 +}} +"#, + ro_files.package_map.name, ro_files.flag_map.name, ro_files.flag_val.name + ); + + let file = write_storage_text_to_temp_file(&text_proto).unwrap(); + let file_full_path = file.path().display().to_string(); + let baseline = vec![ + (0, "enabled_ro", 1u16), + (0, "enabled_rw", 2u16), + (1, "disabled_ro", 0u16), + (2, "enabled_ro", 1u16), + (1, "enabled_fixed_ro", 1u16), + (1, "enabled_ro", 2u16), + (2, "enabled_fixed_ro", 0u16), + (0, "disabled_rw", 0u16), + ]; + for (package_id, flag_name, expected_offset) in baseline.into_iter() { + let flag_offset = + get_flag_offset_impl(&file_full_path, "system", package_id, flag_name) + .unwrap() + .unwrap(); + assert_eq!(flag_offset, expected_offset); + } + } + + #[test] + // this test point locks down flag offset query + fn test_flag_value_query() { + let ro_files = create_test_storage_files(true); + let text_proto = format!( + r#" +files {{ + version: 0 + container: "system" + package_map: "{}" + flag_map: "{}" + flag_val: "{}" + timestamp: 12345 +}} +"#, + ro_files.package_map.name, ro_files.flag_map.name, ro_files.flag_val.name + ); + + let file = write_storage_text_to_temp_file(&text_proto).unwrap(); + let file_full_path = file.path().display().to_string(); + let baseline: Vec<bool> = vec![false; 8]; + for (offset, expected_value) in baseline.into_iter().enumerate() { + let flag_value = + get_boolean_flag_value_impl(&file_full_path, "system", offset as u32).unwrap(); + assert_eq!(flag_value, expected_value); + } + } +} diff --git a/tools/aconfig/aconfig_storage_file/src/mapped_file.rs b/tools/aconfig/aconfig_storage_read_api/src/mapped_file.rs index d8f25700d0..d8f25700d0 100644 --- a/tools/aconfig/aconfig_storage_file/src/mapped_file.rs +++ b/tools/aconfig/aconfig_storage_read_api/src/mapped_file.rs diff --git a/tools/aconfig/aconfig_storage_read_api/src/package_table_query.rs b/tools/aconfig/aconfig_storage_read_api/src/package_table_query.rs new file mode 100644 index 0000000000..6d2ed5f71d --- /dev/null +++ b/tools/aconfig/aconfig_storage_read_api/src/package_table_query.rs @@ -0,0 +1,164 @@ +/* + * 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. + */ + +//! package table query module defines the package table file read from mapped bytes + +use crate::{AconfigStorageError, FILE_VERSION}; +use aconfig_storage_file::{ + package_table::PackageTableHeader, package_table::PackageTableNode, read_u32_from_bytes, +}; +use anyhow::anyhow; + +/// Package table query return +#[derive(PartialEq, Debug)] +pub struct PackageOffset { + pub package_id: u32, + pub boolean_offset: u32, +} + +/// Query package id and start offset +pub fn find_package_offset( + buf: &[u8], + package: &str, +) -> Result<Option<PackageOffset>, AconfigStorageError> { + let interpreted_header = PackageTableHeader::from_bytes(buf)?; + if interpreted_header.version > FILE_VERSION { + return Err(AconfigStorageError::HigherStorageFileVersion(anyhow!( + "Cannot read storage file with a higher version of {} with lib version {}", + interpreted_header.version, + FILE_VERSION + ))); + } + + let num_buckets = (interpreted_header.node_offset - interpreted_header.bucket_offset) / 4; + let bucket_index = PackageTableNode::find_bucket_index(package, num_buckets); + + let mut pos = (interpreted_header.bucket_offset + 4 * bucket_index) as usize; + let mut package_node_offset = read_u32_from_bytes(buf, &mut pos)? as usize; + if package_node_offset < interpreted_header.node_offset as usize + || package_node_offset >= interpreted_header.file_size as usize + { + return Ok(None); + } + + loop { + let interpreted_node = PackageTableNode::from_bytes(&buf[package_node_offset..])?; + if interpreted_node.package_name == package { + return Ok(Some(PackageOffset { + package_id: interpreted_node.package_id, + boolean_offset: interpreted_node.boolean_offset, + })); + } + match interpreted_node.next_offset { + Some(offset) => package_node_offset = offset as usize, + None => return Ok(None), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use aconfig_storage_file::PackageTable; + + pub fn create_test_package_table() -> PackageTable { + let header = PackageTableHeader { + version: crate::FILE_VERSION, + container: String::from("system"), + file_size: 208, + num_packages: 3, + bucket_offset: 30, + node_offset: 58, + }; + let buckets: Vec<Option<u32>> = vec![Some(58), None, None, Some(108), None, None, None]; + let first_node = PackageTableNode { + package_name: String::from("com.android.aconfig.storage.test_2"), + package_id: 1, + boolean_offset: 3, + next_offset: None, + }; + let second_node = PackageTableNode { + package_name: String::from("com.android.aconfig.storage.test_1"), + package_id: 0, + boolean_offset: 0, + next_offset: Some(158), + }; + let third_node = PackageTableNode { + package_name: String::from("com.android.aconfig.storage.test_4"), + package_id: 2, + boolean_offset: 6, + next_offset: None, + }; + let nodes = vec![first_node, second_node, third_node]; + PackageTable { header, buckets, nodes } + } + + #[test] + // this test point locks down table query + fn test_package_query() { + let package_table = create_test_package_table().as_bytes(); + let package_offset = + find_package_offset(&package_table[..], "com.android.aconfig.storage.test_1") + .unwrap() + .unwrap(); + let expected_package_offset = PackageOffset { package_id: 0, boolean_offset: 0 }; + assert_eq!(package_offset, expected_package_offset); + let package_offset = + find_package_offset(&package_table[..], "com.android.aconfig.storage.test_2") + .unwrap() + .unwrap(); + let expected_package_offset = PackageOffset { package_id: 1, boolean_offset: 3 }; + assert_eq!(package_offset, expected_package_offset); + let package_offset = + find_package_offset(&package_table[..], "com.android.aconfig.storage.test_4") + .unwrap() + .unwrap(); + let expected_package_offset = PackageOffset { package_id: 2, boolean_offset: 6 }; + assert_eq!(package_offset, expected_package_offset); + } + + #[test] + // this test point locks down table query of a non exist package + fn test_not_existed_package_query() { + // this will land at an empty bucket + let package_table = create_test_package_table().as_bytes(); + let package_offset = + find_package_offset(&package_table[..], "com.android.aconfig.storage.test_3").unwrap(); + assert_eq!(package_offset, None); + // this will land at the end of a linked list + let package_offset = + find_package_offset(&package_table[..], "com.android.aconfig.storage.test_5").unwrap(); + assert_eq!(package_offset, None); + } + + #[test] + // this test point locks down query error when file has a higher version + fn test_higher_version_storage_file() { + let mut table = create_test_package_table(); + table.header.version = crate::FILE_VERSION + 1; + let package_table = table.as_bytes(); + let error = find_package_offset(&package_table[..], "com.android.aconfig.storage.test_1") + .unwrap_err(); + assert_eq!( + format!("{:?}", error), + format!( + "HigherStorageFileVersion(Cannot read storage file with a higher version of {} with lib version {})", + crate::FILE_VERSION + 1, + crate::FILE_VERSION + ) + ); + } +} diff --git a/tools/aconfig/aconfig_storage_file/src/protos.rs b/tools/aconfig/aconfig_storage_read_api/src/protos.rs index 37df3e1eb5..37df3e1eb5 100644 --- a/tools/aconfig/aconfig_storage_file/src/protos.rs +++ b/tools/aconfig/aconfig_storage_read_api/src/protos.rs diff --git a/tools/aconfig/aconfig_storage_file/src/test_utils.rs b/tools/aconfig/aconfig_storage_read_api/src/test_utils.rs index 7905d51e1b..7905d51e1b 100644 --- a/tools/aconfig/aconfig_storage_file/src/test_utils.rs +++ b/tools/aconfig/aconfig_storage_read_api/src/test_utils.rs diff --git a/tools/aconfig/aconfig_storage_file/tests/Android.bp b/tools/aconfig/aconfig_storage_read_api/tests/Android.bp index b951273e1b..0bfc7bf53e 100644 --- a/tools/aconfig/aconfig_storage_file/tests/Android.bp +++ b/tools/aconfig/aconfig_storage_read_api/tests/Android.bp @@ -1,11 +1,11 @@ rust_test { - name: "aconfig_storage.test.rust", + name: "aconfig_storage_read_api.test.rust", srcs: [ - "storage_lib_rust_test.rs" + "storage_read_api_test.rs" ], rustlibs: [ "libanyhow", - "libaconfig_storage_file", + "libaconfig_storage_read_api", "libprotobuf", "libtempfile", ], @@ -18,15 +18,15 @@ rust_test { } cc_test { - name: "aconfig_storage.test.cpp", + name: "aconfig_storage_read_api.test.cpp", srcs: [ - "storage_lib_cc_test.cpp", + "storage_read_api_test.cpp", ], static_libs: [ "libgmock", "libaconfig_storage_protos_cc", "libprotobuf-cpp-lite", - "libaconfig_storage_cc", + "libaconfig_storage_read_api_cc", "libbase", "liblog", ], diff --git a/tools/aconfig/aconfig_storage_file/tests/flag.map b/tools/aconfig/aconfig_storage_read_api/tests/flag.map Binary files differindex 43b6f9a640..43b6f9a640 100644 --- a/tools/aconfig/aconfig_storage_file/tests/flag.map +++ b/tools/aconfig/aconfig_storage_read_api/tests/flag.map diff --git a/tools/aconfig/aconfig_storage_file/tests/flag.val b/tools/aconfig/aconfig_storage_read_api/tests/flag.val Binary files differindex f39f8d3aaf..f39f8d3aaf 100644 --- a/tools/aconfig/aconfig_storage_file/tests/flag.val +++ b/tools/aconfig/aconfig_storage_read_api/tests/flag.val diff --git a/tools/aconfig/aconfig_storage_file/tests/package.map b/tools/aconfig/aconfig_storage_read_api/tests/package.map Binary files differindex 8ed4767a56..8ed4767a56 100644 --- a/tools/aconfig/aconfig_storage_file/tests/package.map +++ b/tools/aconfig/aconfig_storage_read_api/tests/package.map diff --git a/tools/aconfig/aconfig_storage_file/tests/storage_lib_cc_test.cpp b/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.cpp index 7d5ba0a7f1..b605646919 100644 --- a/tools/aconfig/aconfig_storage_file/tests/storage_lib_cc_test.cpp +++ b/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.cpp @@ -17,7 +17,7 @@ #include <string> #include <vector> -#include "aconfig_storage/aconfig_storage.hpp" +#include "aconfig_storage/aconfig_storage_read_api.hpp" #include <gtest/gtest.h> #include <protos/aconfig_storage_metadata.pb.h> #include <android-base/file.h> diff --git a/tools/aconfig/aconfig_storage_file/tests/storage_lib_rust_test.rs b/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.rs index 991691550d..9b23ec487d 100644 --- a/tools/aconfig/aconfig_storage_file/tests/storage_lib_rust_test.rs +++ b/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.rs @@ -1,6 +1,6 @@ #[cfg(not(feature = "cargo"))] mod aconfig_storage_rust_test { - use aconfig_storage_file::{ + use aconfig_storage_read_api::{ get_boolean_flag_value_impl, get_flag_offset_impl, get_package_offset_impl, PackageOffset, ProtoStorageFiles, }; |