Merge "aconfig: split aconfig_storage_file crate" into main
This commit is contained in:
@@ -4,6 +4,7 @@ members = [
|
|||||||
"aconfig",
|
"aconfig",
|
||||||
"aconfig_protos",
|
"aconfig_protos",
|
||||||
"aconfig_storage_file",
|
"aconfig_storage_file",
|
||||||
|
"aconfig_storage_read_api",
|
||||||
"aflags",
|
"aflags",
|
||||||
"printflags"
|
"printflags"
|
||||||
]
|
]
|
||||||
|
@@ -69,13 +69,17 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"postsubmit": [
|
"postsubmit": [
|
||||||
|
{
|
||||||
|
// aconfig_storage_read_api unit tests
|
||||||
|
"name": "aconfig_storage_read_api.test"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// aconfig_storage read api rust integration tests
|
// 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
|
// aconfig_storage read api cpp integration tests
|
||||||
"name": "aconfig_storage.test.cpp"
|
"name": "aconfig_storage_read_api.test.cpp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// aflags CLI unit tests
|
// aflags CLI unit tests
|
||||||
|
@@ -9,12 +9,6 @@ rust_defaults {
|
|||||||
srcs: ["src/lib.rs"],
|
srcs: ["src/lib.rs"],
|
||||||
rustlibs: [
|
rustlibs: [
|
||||||
"libanyhow",
|
"libanyhow",
|
||||||
"libaconfig_storage_protos",
|
|
||||||
"libonce_cell",
|
|
||||||
"libprotobuf",
|
|
||||||
"libtempfile",
|
|
||||||
"libmemmap2",
|
|
||||||
"libcxx",
|
|
||||||
"libthiserror",
|
"libthiserror",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
@@ -26,91 +20,8 @@ rust_library {
|
|||||||
defaults: ["aconfig_storage_file.defaults"],
|
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 {
|
rust_test_host {
|
||||||
name: "aconfig_storage_file.test",
|
name: "aconfig_storage_file.test",
|
||||||
test_suites: ["general-tests"],
|
test_suites: ["general-tests"],
|
||||||
defaults: ["aconfig_storage_file.defaults"],
|
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"],
|
|
||||||
}
|
}
|
||||||
|
@@ -17,10 +17,9 @@
|
|||||||
//! flag table module defines the flag table file format and methods for serialization
|
//! flag table module defines the flag table file format and methods for serialization
|
||||||
//! and deserialization
|
//! 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 crate::{get_bucket_index, read_str_from_bytes, read_u16_from_bytes, read_u32_from_bytes};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
pub type FlagOffset = u16;
|
|
||||||
|
|
||||||
/// Flag table header struct
|
/// Flag table header struct
|
||||||
#[derive(PartialEq, Debug)]
|
#[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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -270,52 +231,4 @@ mod tests {
|
|||||||
assert!(reinterpreted_table.is_ok());
|
assert!(reinterpreted_table.is_ok());
|
||||||
assert_eq!(&flag_table, &reinterpreted_table.unwrap());
|
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
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -17,9 +17,8 @@
|
|||||||
//! flag value module defines the flag value file format and methods for serialization
|
//! flag value module defines the flag value file format and methods for serialization
|
||||||
//! and deserialization
|
//! 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 crate::{read_str_from_bytes, read_u32_from_bytes, read_u8_from_bytes};
|
||||||
use anyhow::anyhow;
|
|
||||||
|
|
||||||
/// Flag value header struct
|
/// Flag value header struct
|
||||||
#[derive(PartialEq, Debug)]
|
#[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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -142,43 +116,4 @@ mod tests {
|
|||||||
assert!(reinterpreted_value_list.is_ok());
|
assert!(reinterpreted_value_list.is_ok());
|
||||||
assert_eq!(&flag_value_list, &reinterpreted_value_list.unwrap());
|
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
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -34,21 +34,15 @@
|
|||||||
|
|
||||||
pub mod flag_table;
|
pub mod flag_table;
|
||||||
pub mod flag_value;
|
pub mod flag_value;
|
||||||
pub mod mapped_file;
|
|
||||||
pub mod package_table;
|
pub mod package_table;
|
||||||
pub mod protos;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test_utils;
|
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use std::collections::hash_map::DefaultHasher;
|
use std::collections::hash_map::DefaultHasher;
|
||||||
use std::hash::{Hash, Hasher};
|
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::flag_value::{FlagValueHeader, FlagValueList};
|
||||||
pub use crate::package_table::{PackageOffset, PackageTable, PackageTableHeader, PackageTableNode};
|
pub use crate::package_table::{PackageTable, PackageTableHeader, PackageTableNode};
|
||||||
pub use crate::protos::ProtoStorageFiles;
|
|
||||||
|
|
||||||
use crate::AconfigStorageError::{BytesParseFail, HashTableSizeLimit};
|
use crate::AconfigStorageError::{BytesParseFail, HashTableSizeLimit};
|
||||||
|
|
||||||
@@ -62,9 +56,6 @@ pub(crate) const HASH_PRIMES: [u32; 29] = [
|
|||||||
402653189, 805306457, 1610612741,
|
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
|
/// Storage file type enum
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum StorageFileSelection {
|
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
|
/// 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 =
|
let val =
|
||||||
u8::from_le_bytes(buf[*head..*head + 1].try_into().map_err(|errmsg| {
|
u8::from_le_bytes(buf[*head..*head + 1].try_into().map_err(|errmsg| {
|
||||||
BytesParseFail(anyhow!("fail to parse u8 from bytes: {}", 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
|
/// Read and parse bytes as u32
|
||||||
pub(crate) fn read_u32_from_bytes(
|
pub fn read_u32_from_bytes(buf: &[u8], head: &mut usize) -> Result<u32, AconfigStorageError> {
|
||||||
buf: &[u8],
|
|
||||||
head: &mut usize,
|
|
||||||
) -> Result<u32, AconfigStorageError> {
|
|
||||||
let val =
|
let val =
|
||||||
u32::from_le_bytes(buf[*head..*head + 4].try_into().map_err(|errmsg| {
|
u32::from_le_bytes(buf[*head..*head + 4].try_into().map_err(|errmsg| {
|
||||||
BytesParseFail(anyhow!("fail to parse u32 from bytes: {}", errmsg))
|
BytesParseFail(anyhow!("fail to parse u32 from bytes: {}", errmsg))
|
||||||
@@ -179,392 +167,3 @@ pub enum AconfigStorageError {
|
|||||||
#[error("invalid storage file byte offset")]
|
#[error("invalid storage file byte offset")]
|
||||||
InvalidStorageFileOffset(#[source] anyhow::Error),
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -17,7 +17,7 @@
|
|||||||
//! package table module defines the package table file format and methods for serialization
|
//! package table module defines the package table file format and methods for serialization
|
||||||
//! and deserialization
|
//! 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 crate::{get_bucket_index, read_str_from_bytes, read_u32_from_bytes};
|
||||||
use anyhow::anyhow;
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -255,60 +208,4 @@ mod tests {
|
|||||||
assert!(reinterpreted_table.is_ok());
|
assert!(reinterpreted_table.is_ok());
|
||||||
assert_eq!(&package_table, &reinterpreted_table.unwrap());
|
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
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
121
tools/aconfig/aconfig_storage_read_api/Android.bp
Normal file
121
tools/aconfig/aconfig_storage_read_api/Android.bp
Normal file
@@ -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"],
|
||||||
|
}
|
22
tools/aconfig/aconfig_storage_read_api/Cargo.toml
Normal file
22
tools/aconfig/aconfig_storage_read_api/Cargo.toml
Normal file
@@ -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"
|
@@ -1,4 +1,4 @@
|
|||||||
#include "aconfig_storage/aconfig_storage.hpp"
|
#include "aconfig_storage/aconfig_storage_read_api.hpp"
|
||||||
|
|
||||||
#include "rust/cxx.h"
|
#include "rust/cxx.h"
|
||||||
#include "aconfig_storage/lib.rs.h"
|
#include "aconfig_storage/lib.rs.h"
|
175
tools/aconfig/aconfig_storage_read_api/src/flag_table_query.rs
Normal file
175
tools/aconfig/aconfig_storage_read_api/src/flag_table_query.rs
Normal file
@@ -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
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
103
tools/aconfig/aconfig_storage_read_api/src/flag_value_query.rs
Normal file
103
tools/aconfig/aconfig_storage_read_api/src/flag_value_query.rs
Normal file
@@ -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
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
441
tools/aconfig/aconfig_storage_read_api/src/lib.rs
Normal file
441
tools/aconfig/aconfig_storage_read_api/src/lib.rs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,11 +1,11 @@
|
|||||||
rust_test {
|
rust_test {
|
||||||
name: "aconfig_storage.test.rust",
|
name: "aconfig_storage_read_api.test.rust",
|
||||||
srcs: [
|
srcs: [
|
||||||
"storage_lib_rust_test.rs"
|
"storage_read_api_test.rs"
|
||||||
],
|
],
|
||||||
rustlibs: [
|
rustlibs: [
|
||||||
"libanyhow",
|
"libanyhow",
|
||||||
"libaconfig_storage_file",
|
"libaconfig_storage_read_api",
|
||||||
"libprotobuf",
|
"libprotobuf",
|
||||||
"libtempfile",
|
"libtempfile",
|
||||||
],
|
],
|
||||||
@@ -18,15 +18,15 @@ rust_test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cc_test {
|
cc_test {
|
||||||
name: "aconfig_storage.test.cpp",
|
name: "aconfig_storage_read_api.test.cpp",
|
||||||
srcs: [
|
srcs: [
|
||||||
"storage_lib_cc_test.cpp",
|
"storage_read_api_test.cpp",
|
||||||
],
|
],
|
||||||
static_libs: [
|
static_libs: [
|
||||||
"libgmock",
|
"libgmock",
|
||||||
"libaconfig_storage_protos_cc",
|
"libaconfig_storage_protos_cc",
|
||||||
"libprotobuf-cpp-lite",
|
"libprotobuf-cpp-lite",
|
||||||
"libaconfig_storage_cc",
|
"libaconfig_storage_read_api_cc",
|
||||||
"libbase",
|
"libbase",
|
||||||
"liblog",
|
"liblog",
|
||||||
],
|
],
|
@@ -17,7 +17,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "aconfig_storage/aconfig_storage.hpp"
|
#include "aconfig_storage/aconfig_storage_read_api.hpp"
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <protos/aconfig_storage_metadata.pb.h>
|
#include <protos/aconfig_storage_metadata.pb.h>
|
||||||
#include <android-base/file.h>
|
#include <android-base/file.h>
|
@@ -1,6 +1,6 @@
|
|||||||
#[cfg(not(feature = "cargo"))]
|
#[cfg(not(feature = "cargo"))]
|
||||||
mod aconfig_storage_rust_test {
|
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,
|
get_boolean_flag_value_impl, get_flag_offset_impl, get_package_offset_impl, PackageOffset,
|
||||||
ProtoStorageFiles,
|
ProtoStorageFiles,
|
||||||
};
|
};
|
Reference in New Issue
Block a user