Merge "aflags: show staged values for each flag." into main
diff --git a/core/config.mk b/core/config.mk
index 546858a..c6eeffa 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -885,6 +885,7 @@
32.0 \
33.0 \
34.0 \
+ 202404 \
)
.KATI_READONLY := \
diff --git a/tools/aconfig/Cargo.toml b/tools/aconfig/Cargo.toml
index 7112fd4..6bd0d06 100644
--- a/tools/aconfig/Cargo.toml
+++ b/tools/aconfig/Cargo.toml
@@ -5,6 +5,7 @@
"aconfig_protos",
"aconfig_storage_file",
"aconfig_storage_read_api",
+ "aconfig_storage_write_api",
"aflags",
"printflags"
]
diff --git a/tools/aconfig/TEST_MAPPING b/tools/aconfig/TEST_MAPPING
index 26d6172..2f1694b 100644
--- a/tools/aconfig/TEST_MAPPING
+++ b/tools/aconfig/TEST_MAPPING
@@ -70,10 +70,18 @@
],
"postsubmit": [
{
+ // aconfig_storage_write_api unit tests
+ "name": "aconfig_storage_write_api.test"
+ },
+ {
// aconfig_storage_read_api unit tests
"name": "aconfig_storage_read_api.test"
},
{
+ // aconfig_storage write api rust integration tests
+ "name": "aconfig_storage_write_api.test.rust"
+ },
+ {
// aconfig_storage read api rust integration tests
"name": "aconfig_storage_read_api.test.rust"
},
diff --git a/tools/aconfig/aconfig/src/commands.rs b/tools/aconfig/aconfig/src/commands.rs
index c1df16b..98dde44 100644
--- a/tools/aconfig/aconfig/src/commands.rs
+++ b/tools/aconfig/aconfig/src/commands.rs
@@ -31,7 +31,7 @@
ParsedFlagExt, ProtoFlagMetadata, ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag,
ProtoParsedFlags, ProtoTracepoint,
};
-use aconfig_storage_file::StorageFileSelection;
+use aconfig_storage_file::StorageFileType;
pub struct Input {
pub source: String,
@@ -237,7 +237,7 @@
pub fn create_storage(
caches: Vec<Input>,
container: &str,
- file: &StorageFileSelection,
+ file: &StorageFileType,
) -> Result<Vec<u8>> {
let parsed_flags_vec: Vec<ProtoParsedFlags> = caches
.into_iter()
diff --git a/tools/aconfig/aconfig/src/main.rs b/tools/aconfig/aconfig/src/main.rs
index 5a4f23c..69f5458 100644
--- a/tools/aconfig/aconfig/src/main.rs
+++ b/tools/aconfig/aconfig/src/main.rs
@@ -29,7 +29,7 @@
mod dump;
mod storage;
-use aconfig_storage_file::StorageFileSelection;
+use aconfig_storage_file::StorageFileType;
use codegen::CodegenMode;
use dump::DumpFormat;
@@ -138,7 +138,7 @@
.arg(
Arg::new("file")
.long("file")
- .value_parser(|s: &str| StorageFileSelection::try_from(s)),
+ .value_parser(|s: &str| StorageFileType::try_from(s)),
)
.arg(Arg::new("cache").long("cache").action(ArgAction::Append).required(true))
.arg(Arg::new("out").long("out").required(true)),
@@ -285,7 +285,7 @@
write_output_to_file_or_stdout(path, &output)?;
}
Some(("create-storage", sub_matches)) => {
- let file = get_required_arg::<StorageFileSelection>(sub_matches, "file")
+ let file = get_required_arg::<StorageFileType>(sub_matches, "file")
.context("Invalid storage file selection")?;
let cache = open_zero_or_more_files(sub_matches, "cache")?;
let container = get_required_arg::<String>(sub_matches, "container")?;
diff --git a/tools/aconfig/aconfig/src/storage/flag_table.rs b/tools/aconfig/aconfig/src/storage/flag_table.rs
index 1381e89..b861c1f 100644
--- a/tools/aconfig/aconfig/src/storage/flag_table.rs
+++ b/tools/aconfig/aconfig/src/storage/flag_table.rs
@@ -17,7 +17,7 @@
use crate::commands::assign_flag_ids;
use crate::storage::FlagPackage;
use aconfig_storage_file::{
- get_table_size, FlagTable, FlagTableHeader, FlagTableNode, FILE_VERSION,
+ get_table_size, FlagTable, FlagTableHeader, FlagTableNode, FILE_VERSION, StorageFileType
};
use anyhow::{anyhow, Result};
@@ -25,6 +25,7 @@
FlagTableHeader {
version: FILE_VERSION,
container: String::from(container),
+ file_type: StorageFileType::FlagMap as u8,
file_size: 0,
num_flags,
bucket_offset: 0,
@@ -168,31 +169,32 @@
let expected_header = FlagTableHeader {
version: FILE_VERSION,
container: String::from("system"),
- file_size: 320,
+ file_type: StorageFileType::FlagMap as u8,
+ file_size: 321,
num_flags: 8,
- bucket_offset: 30,
- node_offset: 98,
+ bucket_offset: 31,
+ node_offset: 99,
};
assert_eq!(header, &expected_header);
let buckets: &Vec<Option<u32>> = &flag_table.as_ref().unwrap().buckets;
let expected_bucket: Vec<Option<u32>> = vec![
- Some(98),
- Some(124),
+ Some(99),
+ Some(125),
None,
None,
None,
- Some(177),
+ Some(178),
None,
- Some(203),
+ Some(204),
None,
- Some(261),
+ Some(262),
None,
None,
None,
None,
None,
- Some(293),
+ Some(294),
None,
];
assert_eq!(buckets, &expected_bucket);
@@ -201,10 +203,10 @@
assert_eq!(nodes.len(), 8);
assert_eq!(nodes[0], new_expected_node(0, "enabled_ro", 1, 1, None));
- assert_eq!(nodes[1], new_expected_node(0, "enabled_rw", 1, 2, Some(150)));
+ assert_eq!(nodes[1], new_expected_node(0, "enabled_rw", 1, 2, Some(151)));
assert_eq!(nodes[2], new_expected_node(1, "disabled_ro", 1, 0, None));
assert_eq!(nodes[3], new_expected_node(2, "enabled_ro", 1, 1, None));
- assert_eq!(nodes[4], new_expected_node(1, "enabled_fixed_ro", 1, 1, Some(235)));
+ assert_eq!(nodes[4], new_expected_node(1, "enabled_fixed_ro", 1, 1, Some(236)));
assert_eq!(nodes[5], new_expected_node(1, "enabled_ro", 1, 2, None));
assert_eq!(nodes[6], new_expected_node(2, "enabled_fixed_ro", 1, 0, None));
assert_eq!(nodes[7], new_expected_node(0, "disabled_rw", 1, 0, None));
diff --git a/tools/aconfig/aconfig/src/storage/flag_value.rs b/tools/aconfig/aconfig/src/storage/flag_value.rs
index 0d4b5b4..e40bbc1 100644
--- a/tools/aconfig/aconfig/src/storage/flag_value.rs
+++ b/tools/aconfig/aconfig/src/storage/flag_value.rs
@@ -17,13 +17,14 @@
use crate::commands::assign_flag_ids;
use crate::storage::FlagPackage;
use aconfig_protos::ProtoFlagState;
-use aconfig_storage_file::{FlagValueHeader, FlagValueList, FILE_VERSION};
+use aconfig_storage_file::{FlagValueHeader, FlagValueList, FILE_VERSION, StorageFileType};
use anyhow::{anyhow, Result};
fn new_header(container: &str, num_flags: u32) -> FlagValueHeader {
FlagValueHeader {
version: FILE_VERSION,
container: String::from(container),
+ file_type: StorageFileType::FlagVal as u8,
file_size: 0,
num_flags,
boolean_value_offset: 0,
@@ -79,9 +80,10 @@
let expected_header = FlagValueHeader {
version: FILE_VERSION,
container: String::from("system"),
- file_size: 34,
+ file_type: StorageFileType::FlagVal as u8,
+ file_size: 35,
num_flags: 8,
- boolean_value_offset: 26,
+ boolean_value_offset: 27,
};
assert_eq!(header, &expected_header);
diff --git a/tools/aconfig/aconfig/src/storage/mod.rs b/tools/aconfig/aconfig/src/storage/mod.rs
index 29eb9c8..c818d79 100644
--- a/tools/aconfig/aconfig/src/storage/mod.rs
+++ b/tools/aconfig/aconfig/src/storage/mod.rs
@@ -26,7 +26,7 @@
package_table::create_package_table,
};
use aconfig_protos::{ProtoParsedFlag, ProtoParsedFlags};
-use aconfig_storage_file::StorageFileSelection;
+use aconfig_storage_file::StorageFileType;
pub struct FlagPackage<'a> {
pub package_name: &'a str,
@@ -87,7 +87,7 @@
pub fn generate_storage_file<'a, I>(
container: &str,
parsed_flags_vec_iter: I,
- file: &StorageFileSelection,
+ file: &StorageFileType,
) -> Result<Vec<u8>>
where
I: Iterator<Item = &'a ProtoParsedFlags>,
@@ -95,15 +95,15 @@
let packages = group_flags_by_package(parsed_flags_vec_iter);
match file {
- StorageFileSelection::PackageMap => {
+ StorageFileType::PackageMap => {
let package_table = create_package_table(container, &packages)?;
Ok(package_table.as_bytes())
}
- StorageFileSelection::FlagMap => {
+ StorageFileType::FlagMap => {
let flag_table = create_flag_table(container, &packages)?;
Ok(flag_table.as_bytes())
}
- StorageFileSelection::FlagVal => {
+ StorageFileType::FlagVal => {
let flag_value = create_flag_value(container, &packages)?;
Ok(flag_value.as_bytes())
}
diff --git a/tools/aconfig/aconfig/src/storage/package_table.rs b/tools/aconfig/aconfig/src/storage/package_table.rs
index 4c08129..bc2da4d 100644
--- a/tools/aconfig/aconfig/src/storage/package_table.rs
+++ b/tools/aconfig/aconfig/src/storage/package_table.rs
@@ -17,7 +17,7 @@
use anyhow::Result;
use aconfig_storage_file::{
- get_table_size, PackageTable, PackageTableHeader, PackageTableNode, FILE_VERSION,
+ get_table_size, PackageTable, PackageTableHeader, PackageTableNode, FILE_VERSION, StorageFileType
};
use crate::storage::FlagPackage;
@@ -26,6 +26,7 @@
PackageTableHeader {
version: FILE_VERSION,
container: String::from(container),
+ file_type: StorageFileType::PackageMap as u8,
file_size: 0,
num_packages,
bucket_offset: 0,
@@ -123,15 +124,16 @@
let expected_header = PackageTableHeader {
version: FILE_VERSION,
container: String::from("system"),
- file_size: 208,
+ file_type: StorageFileType::PackageMap as u8,
+ file_size: 209,
num_packages: 3,
- bucket_offset: 30,
- node_offset: 58,
+ bucket_offset: 31,
+ node_offset: 59,
};
assert_eq!(header, &expected_header);
let buckets: &Vec<Option<u32>> = &package_table.as_ref().unwrap().buckets;
- let expected: Vec<Option<u32>> = vec![Some(58), None, None, Some(108), None, None, None];
+ let expected: Vec<Option<u32>> = vec![Some(59), None, None, Some(109), None, None, None];
assert_eq!(buckets, &expected);
let nodes: &Vec<PackageTableNode> = &package_table.as_ref().unwrap().nodes;
@@ -147,7 +149,7 @@
package_name: String::from("com.android.aconfig.storage.test_1"),
package_id: 0,
boolean_offset: 0,
- next_offset: Some(158),
+ next_offset: Some(159),
};
assert_eq!(nodes[1], second_node_expected);
let third_node_expected = PackageTableNode {
diff --git a/tools/aconfig/aconfig_storage_file/src/flag_table.rs b/tools/aconfig/aconfig_storage_file/src/flag_table.rs
index d6e5c62..f9b3158 100644
--- a/tools/aconfig/aconfig_storage_file/src/flag_table.rs
+++ b/tools/aconfig/aconfig_storage_file/src/flag_table.rs
@@ -17,8 +17,11 @@
//! flag table module defines the flag table file format and methods for serialization
//! and deserialization
-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,
+ read_u8_from_bytes,
+};
+use crate::{AconfigStorageError, StorageFileType};
use anyhow::anyhow;
use std::fmt;
@@ -27,6 +30,7 @@
pub struct FlagTableHeader {
pub version: u32,
pub container: String,
+ pub file_type: u8,
pub file_size: u32,
pub num_flags: u32,
pub bucket_offset: u32,
@@ -38,8 +42,11 @@
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(
f,
- "Version: {}, Container: {}, File Size: {}",
- self.version, self.container, self.file_size
+ "Version: {}, Container: {}, File Type: {:?}, File Size: {}",
+ self.version,
+ self.container,
+ StorageFileType::try_from(self.file_type),
+ self.file_size
)?;
writeln!(
f,
@@ -58,6 +65,7 @@
let container_bytes = self.container.as_bytes();
result.extend_from_slice(&(container_bytes.len() as u32).to_le_bytes());
result.extend_from_slice(container_bytes);
+ result.extend_from_slice(&self.file_type.to_le_bytes());
result.extend_from_slice(&self.file_size.to_le_bytes());
result.extend_from_slice(&self.num_flags.to_le_bytes());
result.extend_from_slice(&self.bucket_offset.to_le_bytes());
@@ -68,14 +76,21 @@
/// Deserialize from bytes
pub fn from_bytes(bytes: &[u8]) -> Result<Self, AconfigStorageError> {
let mut head = 0;
- Ok(Self {
+ let table = Self {
version: read_u32_from_bytes(bytes, &mut head)?,
container: read_str_from_bytes(bytes, &mut head)?,
+ file_type: read_u8_from_bytes(bytes, &mut head)?,
file_size: read_u32_from_bytes(bytes, &mut head)?,
num_flags: read_u32_from_bytes(bytes, &mut head)?,
bucket_offset: read_u32_from_bytes(bytes, &mut head)?,
node_offset: read_u32_from_bytes(bytes, &mut head)?,
- })
+ };
+ if table.file_type != StorageFileType::FlagMap as u8 {
+ return Err(AconfigStorageError::BytesParseFail(anyhow!(
+ "binary file is not a flag map"
+ )));
+ }
+ Ok(table)
}
}
@@ -191,7 +206,9 @@
Ok(node)
})
.collect::<Result<Vec<_>, AconfigStorageError>>()
- .map_err(|errmsg| BytesParseFail(anyhow!("fail to parse flag table: {}", errmsg)))?;
+ .map_err(|errmsg| {
+ AconfigStorageError::BytesParseFail(anyhow!("fail to parse flag table: {}", errmsg))
+ })?;
let table = Self { header, buckets, nodes };
Ok(table)
@@ -219,9 +236,11 @@
assert_eq!(node, &reinterpreted_node);
}
- let reinterpreted_table = FlagTable::from_bytes(&flag_table.as_bytes());
+ let flag_table_bytes = flag_table.as_bytes();
+ let reinterpreted_table = FlagTable::from_bytes(&flag_table_bytes);
assert!(reinterpreted_table.is_ok());
assert_eq!(&flag_table, &reinterpreted_table.unwrap());
+ assert_eq!(flag_table_bytes.len() as u32, header.file_size);
}
#[test]
@@ -234,4 +253,16 @@
let version = read_u32_from_bytes(bytes, &mut head).unwrap();
assert_eq!(version, 1234)
}
+
+ #[test]
+ // this test point locks down file type check
+ fn test_file_type_check() {
+ let mut flag_table = create_test_flag_table();
+ flag_table.header.file_type = 123u8;
+ let error = FlagTable::from_bytes(&flag_table.as_bytes()).unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ format!("BytesParseFail(binary file is not a flag map)")
+ );
+ }
}
diff --git a/tools/aconfig/aconfig_storage_file/src/flag_value.rs b/tools/aconfig/aconfig_storage_file/src/flag_value.rs
index 021546c..c9d09a1 100644
--- a/tools/aconfig/aconfig_storage_file/src/flag_value.rs
+++ b/tools/aconfig/aconfig_storage_file/src/flag_value.rs
@@ -17,8 +17,9 @@
//! flag value module defines the flag value file format and methods for serialization
//! and deserialization
-use crate::AconfigStorageError;
use crate::{read_str_from_bytes, read_u32_from_bytes, read_u8_from_bytes};
+use crate::{AconfigStorageError, StorageFileType};
+use anyhow::anyhow;
use std::fmt;
/// Flag value header struct
@@ -26,6 +27,7 @@
pub struct FlagValueHeader {
pub version: u32,
pub container: String,
+ pub file_type: u8,
pub file_size: u32,
pub num_flags: u32,
pub boolean_value_offset: u32,
@@ -36,8 +38,11 @@
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(
f,
- "Version: {}, Container: {}, File Size: {}",
- self.version, self.container, self.file_size
+ "Version: {}, Container: {}, File Type: {:?}, File Size: {}",
+ self.version,
+ self.container,
+ StorageFileType::try_from(self.file_type),
+ self.file_size
)?;
writeln!(
f,
@@ -56,6 +61,7 @@
let container_bytes = self.container.as_bytes();
result.extend_from_slice(&(container_bytes.len() as u32).to_le_bytes());
result.extend_from_slice(container_bytes);
+ result.extend_from_slice(&self.file_type.to_le_bytes());
result.extend_from_slice(&self.file_size.to_le_bytes());
result.extend_from_slice(&self.num_flags.to_le_bytes());
result.extend_from_slice(&self.boolean_value_offset.to_le_bytes());
@@ -65,13 +71,20 @@
/// Deserialize from bytes
pub fn from_bytes(bytes: &[u8]) -> Result<Self, AconfigStorageError> {
let mut head = 0;
- Ok(Self {
+ let list = Self {
version: read_u32_from_bytes(bytes, &mut head)?,
container: read_str_from_bytes(bytes, &mut head)?,
+ file_type: read_u8_from_bytes(bytes, &mut head)?,
file_size: read_u32_from_bytes(bytes, &mut head)?,
num_flags: read_u32_from_bytes(bytes, &mut head)?,
boolean_value_offset: read_u32_from_bytes(bytes, &mut head)?,
- })
+ };
+ if list.file_type != StorageFileType::FlagVal as u8 {
+ return Err(AconfigStorageError::BytesParseFail(anyhow!(
+ "binary file is not a flag value file"
+ )));
+ }
+ Ok(list)
}
}
@@ -130,9 +143,11 @@
assert!(reinterpreted_header.is_ok());
assert_eq!(header, &reinterpreted_header.unwrap());
- let reinterpreted_value_list = FlagValueList::from_bytes(&flag_value_list.as_bytes());
+ let flag_value_bytes = flag_value_list.as_bytes();
+ let reinterpreted_value_list = FlagValueList::from_bytes(&flag_value_bytes);
assert!(reinterpreted_value_list.is_ok());
assert_eq!(&flag_value_list, &reinterpreted_value_list.unwrap());
+ assert_eq!(flag_value_bytes.len() as u32, header.file_size);
}
#[test]
@@ -145,4 +160,16 @@
let version = read_u32_from_bytes(bytes, &mut head).unwrap();
assert_eq!(version, 1234)
}
+
+ #[test]
+ // this test point locks down file type check
+ fn test_file_type_check() {
+ let mut flag_value_list = create_test_flag_value_list();
+ flag_value_list.header.file_type = 123u8;
+ let error = FlagValueList::from_bytes(&flag_value_list.as_bytes()).unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ format!("BytesParseFail(binary file is not a flag value file)")
+ );
+ }
}
diff --git a/tools/aconfig/aconfig_storage_file/src/lib.rs b/tools/aconfig/aconfig_storage_file/src/lib.rs
index 202f6a4..24b16a1 100644
--- a/tools/aconfig/aconfig_storage_file/src/lib.rs
+++ b/tools/aconfig/aconfig_storage_file/src/lib.rs
@@ -64,13 +64,13 @@
/// Storage file type enum
#[derive(Clone, Debug, PartialEq, Eq)]
-pub enum StorageFileSelection {
- PackageMap,
- FlagMap,
- FlagVal,
+pub enum StorageFileType {
+ PackageMap = 0,
+ FlagMap = 1,
+ FlagVal = 2,
}
-impl TryFrom<&str> for StorageFileSelection {
+impl TryFrom<&str> for StorageFileType {
type Error = anyhow::Error;
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
@@ -79,12 +79,25 @@
"flag_map" => Ok(Self::FlagMap),
"flag_val" => Ok(Self::FlagVal),
_ => Err(anyhow!(
- "Invalid storage file type, valid types are package_map|flag_map|flag_val]"
+ "Invalid storage file type, valid types are package_map|flag_map|flag_val"
)),
}
}
}
+impl TryFrom<u8> for StorageFileType {
+ type Error = anyhow::Error;
+
+ fn try_from(value: u8) -> Result<Self, Self::Error> {
+ match value {
+ x if x == Self::PackageMap as u8 => Ok(Self::PackageMap),
+ x if x == Self::FlagMap as u8 => Ok(Self::FlagMap),
+ x if x == Self::FlagVal as u8 => Ok(Self::FlagVal),
+ _ => Err(anyhow!("Invalid storage file type")),
+ }
+ }
+}
+
/// Get the right hash table size given number of entries in the table. Use a
/// load factor of 0.5 for performance.
pub fn get_table_size(entries: u32) -> Result<u32, AconfigStorageError> {
@@ -163,6 +176,12 @@
#[error("fail to map storage file")]
MapFileFail(#[source] anyhow::Error),
+ #[error("fail to get mapped file")]
+ ObtainMappedFileFail(#[source] anyhow::Error),
+
+ #[error("fail to flush mapped storage file")]
+ MapFlushFail(#[source] anyhow::Error),
+
#[error("number of items in hash table exceed limit")]
HashTableSizeLimit(#[source] anyhow::Error),
diff --git a/tools/aconfig/aconfig_storage_file/src/main.rs b/tools/aconfig/aconfig_storage_file/src/main.rs
index b5e6158..293d018 100644
--- a/tools/aconfig/aconfig_storage_file/src/main.rs
+++ b/tools/aconfig/aconfig_storage_file/src/main.rs
@@ -18,7 +18,7 @@
use aconfig_storage_file::{
list_flags, read_file_to_bytes, AconfigStorageError, FlagTable, FlagValueList, PackageTable,
- StorageFileSelection,
+ StorageFileType,
};
use clap::{builder::ArgAction, Arg, Command};
@@ -33,7 +33,7 @@
Arg::new("type")
.long("type")
.required(true)
- .value_parser(|s: &str| StorageFileSelection::try_from(s)),
+ .value_parser(|s: &str| StorageFileType::try_from(s)),
),
)
.subcommand(
@@ -51,19 +51,19 @@
fn print_storage_file(
file_path: &str,
- file_type: &StorageFileSelection,
+ file_type: &StorageFileType,
) -> Result<(), AconfigStorageError> {
let bytes = read_file_to_bytes(file_path)?;
match file_type {
- StorageFileSelection::PackageMap => {
+ StorageFileType::PackageMap => {
let package_table = PackageTable::from_bytes(&bytes)?;
println!("{:?}", package_table);
}
- StorageFileSelection::FlagMap => {
+ StorageFileType::FlagMap => {
let flag_table = FlagTable::from_bytes(&bytes)?;
println!("{:?}", flag_table);
}
- StorageFileSelection::FlagVal => {
+ StorageFileType::FlagVal => {
let flag_value = FlagValueList::from_bytes(&bytes)?;
println!("{:?}", flag_value);
}
@@ -76,7 +76,7 @@
match matches.subcommand() {
Some(("print", sub_matches)) => {
let file_path = sub_matches.get_one::<String>("file").unwrap();
- let file_type = sub_matches.get_one::<StorageFileSelection>("type").unwrap();
+ let file_type = sub_matches.get_one::<StorageFileType>("type").unwrap();
print_storage_file(file_path, file_type)?
}
Some(("list", sub_matches)) => {
diff --git a/tools/aconfig/aconfig_storage_file/src/package_table.rs b/tools/aconfig/aconfig_storage_file/src/package_table.rs
index f7435b0..7cb60eb 100644
--- a/tools/aconfig/aconfig_storage_file/src/package_table.rs
+++ b/tools/aconfig/aconfig_storage_file/src/package_table.rs
@@ -17,8 +17,8 @@
//! package table module defines the package table file format and methods for serialization
//! and deserialization
-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, read_u8_from_bytes};
+use crate::{AconfigStorageError, StorageFileType};
use anyhow::anyhow;
use std::fmt;
@@ -27,6 +27,7 @@
pub struct PackageTableHeader {
pub version: u32,
pub container: String,
+ pub file_type: u8,
pub file_size: u32,
pub num_packages: u32,
pub bucket_offset: u32,
@@ -38,8 +39,11 @@
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(
f,
- "Version: {}, Container: {}, File Size: {}",
- self.version, self.container, self.file_size
+ "Version: {}, Container: {}, File Type: {:?}, File Size: {}",
+ self.version,
+ self.container,
+ StorageFileType::try_from(self.file_type),
+ self.file_size
)?;
writeln!(
f,
@@ -58,6 +62,7 @@
let container_bytes = self.container.as_bytes();
result.extend_from_slice(&(container_bytes.len() as u32).to_le_bytes());
result.extend_from_slice(container_bytes);
+ result.extend_from_slice(&self.file_type.to_le_bytes());
result.extend_from_slice(&self.file_size.to_le_bytes());
result.extend_from_slice(&self.num_packages.to_le_bytes());
result.extend_from_slice(&self.bucket_offset.to_le_bytes());
@@ -68,14 +73,21 @@
/// Deserialize from bytes
pub fn from_bytes(bytes: &[u8]) -> Result<Self, AconfigStorageError> {
let mut head = 0;
- Ok(Self {
+ let table = Self {
version: read_u32_from_bytes(bytes, &mut head)?,
container: read_str_from_bytes(bytes, &mut head)?,
+ file_type: read_u8_from_bytes(bytes, &mut head)?,
file_size: read_u32_from_bytes(bytes, &mut head)?,
num_packages: read_u32_from_bytes(bytes, &mut head)?,
bucket_offset: read_u32_from_bytes(bytes, &mut head)?,
node_offset: read_u32_from_bytes(bytes, &mut head)?,
- })
+ };
+ if table.file_type != StorageFileType::PackageMap as u8 {
+ return Err(AconfigStorageError::BytesParseFail(anyhow!(
+ "binary file is not a package map"
+ )));
+ }
+ Ok(table)
}
}
@@ -191,7 +203,12 @@
Ok(node)
})
.collect::<Result<Vec<_>, AconfigStorageError>>()
- .map_err(|errmsg| BytesParseFail(anyhow!("fail to parse package table: {}", errmsg)))?;
+ .map_err(|errmsg| {
+ AconfigStorageError::BytesParseFail(anyhow!(
+ "fail to parse package table: {}",
+ errmsg
+ ))
+ })?;
let table = Self { header, buckets, nodes };
Ok(table)
@@ -218,9 +235,11 @@
assert_eq!(node, &reinterpreted_node);
}
- let reinterpreted_table = PackageTable::from_bytes(&package_table.as_bytes());
+ let package_table_bytes = package_table.as_bytes();
+ let reinterpreted_table = PackageTable::from_bytes(&package_table_bytes);
assert!(reinterpreted_table.is_ok());
assert_eq!(&package_table, &reinterpreted_table.unwrap());
+ assert_eq!(package_table_bytes.len() as u32, header.file_size);
}
#[test]
@@ -233,4 +252,16 @@
let version = read_u32_from_bytes(bytes, &mut head).unwrap();
assert_eq!(version, 1234)
}
+
+ #[test]
+ // this test point locks down file type check
+ fn test_file_type_check() {
+ let mut package_table = create_test_package_table();
+ package_table.header.file_type = 123u8;
+ let error = PackageTable::from_bytes(&package_table.as_bytes()).unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ format!("BytesParseFail(binary file is not a package map)")
+ );
+ }
}
diff --git a/tools/aconfig/aconfig_storage_file/src/test_utils.rs b/tools/aconfig/aconfig_storage_file/src/test_utils.rs
index 7780044..586bb4c 100644
--- a/tools/aconfig/aconfig_storage_file/src/test_utils.rs
+++ b/tools/aconfig/aconfig_storage_file/src/test_utils.rs
@@ -17,7 +17,7 @@
use crate::flag_table::{FlagTable, FlagTableHeader, FlagTableNode};
use crate::flag_value::{FlagValueHeader, FlagValueList};
use crate::package_table::{PackageTable, PackageTableHeader, PackageTableNode};
-use crate::AconfigStorageError;
+use crate::{AconfigStorageError, StorageFileType};
use anyhow::anyhow;
use std::io::Write;
@@ -27,12 +27,13 @@
let header = PackageTableHeader {
version: 1234,
container: String::from("system"),
- file_size: 208,
+ file_type: StorageFileType::PackageMap as u8,
+ file_size: 209,
num_packages: 3,
- bucket_offset: 30,
- node_offset: 58,
+ bucket_offset: 31,
+ node_offset: 59,
};
- let buckets: Vec<Option<u32>> = vec![Some(58), None, None, Some(108), None, None, None];
+ let buckets: Vec<Option<u32>> = vec![Some(59), None, None, Some(109), None, None, None];
let first_node = PackageTableNode {
package_name: String::from("com.android.aconfig.storage.test_2"),
package_id: 1,
@@ -43,7 +44,7 @@
package_name: String::from("com.android.aconfig.storage.test_1"),
package_id: 0,
boolean_offset: 0,
- next_offset: Some(158),
+ next_offset: Some(159),
};
let third_node = PackageTableNode {
package_name: String::from("com.android.aconfig.storage.test_4"),
@@ -72,36 +73,37 @@
let header = FlagTableHeader {
version: 1234,
container: String::from("system"),
- file_size: 320,
+ file_type: StorageFileType::FlagMap as u8,
+ file_size: 321,
num_flags: 8,
- bucket_offset: 30,
- node_offset: 98,
+ bucket_offset: 31,
+ node_offset: 99,
};
let buckets: Vec<Option<u32>> = vec![
- Some(98),
- Some(124),
+ Some(99),
+ Some(125),
None,
None,
None,
- Some(177),
+ Some(178),
None,
- Some(203),
+ Some(204),
None,
- Some(261),
+ Some(262),
None,
None,
None,
None,
None,
- Some(293),
+ Some(294),
None,
];
let nodes = vec![
FlagTableNode::new_expected(0, "enabled_ro", 1, 1, None),
- FlagTableNode::new_expected(0, "enabled_rw", 1, 2, Some(150)),
+ FlagTableNode::new_expected(0, "enabled_rw", 1, 2, Some(151)),
FlagTableNode::new_expected(1, "disabled_ro", 1, 0, None),
FlagTableNode::new_expected(2, "enabled_ro", 1, 1, None),
- FlagTableNode::new_expected(1, "enabled_fixed_ro", 1, 1, Some(235)),
+ FlagTableNode::new_expected(1, "enabled_fixed_ro", 1, 1, Some(236)),
FlagTableNode::new_expected(1, "enabled_ro", 1, 2, None),
FlagTableNode::new_expected(2, "enabled_fixed_ro", 1, 0, None),
FlagTableNode::new_expected(0, "disabled_rw", 1, 0, None),
@@ -113,9 +115,10 @@
let header = FlagValueHeader {
version: 1234,
container: String::from("system"),
- file_size: 34,
+ file_type: StorageFileType::FlagVal as u8,
+ file_size: 35,
num_flags: 8,
- boolean_value_offset: 26,
+ boolean_value_offset: 27,
};
let booleans: Vec<bool> = vec![false, true, false, false, true, true, false, true];
FlagValueList { header, booleans }
diff --git a/tools/aconfig/aconfig_storage_read_api/Android.bp b/tools/aconfig/aconfig_storage_read_api/Android.bp
index 721f9a5..a8fde53 100644
--- a/tools/aconfig/aconfig_storage_read_api/Android.bp
+++ b/tools/aconfig/aconfig_storage_read_api/Android.bp
@@ -74,24 +74,3 @@
whole_static_libs: ["libaconfig_storage_read_api_cxx_bridge"],
export_include_dirs: ["include"],
}
-
-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)",
-}
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
index cc05557..403badb 100644
--- a/tools/aconfig/aconfig_storage_read_api/src/flag_table_query.rs
+++ b/tools/aconfig/aconfig_storage_read_api/src/flag_table_query.rs
@@ -65,7 +65,7 @@
#[cfg(test)]
mod tests {
use super::*;
- use aconfig_storage_file::FlagTable;
+ use aconfig_storage_file::{StorageFileType, FlagTable};
// create test baseline, syntactic sugar
fn new_expected_node(
@@ -88,36 +88,37 @@
let header = FlagTableHeader {
version: crate::FILE_VERSION,
container: String::from("system"),
- file_size: 320,
+ file_type: StorageFileType::FlagMap as u8,
+ file_size: 321,
num_flags: 8,
- bucket_offset: 30,
- node_offset: 98,
+ bucket_offset: 31,
+ node_offset: 99,
};
let buckets: Vec<Option<u32>> = vec![
- Some(98),
- Some(124),
+ Some(99),
+ Some(125),
None,
None,
None,
- Some(177),
+ Some(178),
None,
- Some(203),
+ Some(204),
None,
- Some(261),
+ Some(262),
None,
None,
None,
None,
None,
- Some(293),
+ Some(294),
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(0, "enabled_rw", 1, 2, Some(151)),
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_fixed_ro", 1, 1, Some(236)),
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),
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
index 6ff6b05..4b7a65e 100644
--- a/tools/aconfig/aconfig_storage_read_api/src/flag_value_query.rs
+++ b/tools/aconfig/aconfig_storage_read_api/src/flag_value_query.rs
@@ -48,15 +48,16 @@
#[cfg(test)]
mod tests {
use super::*;
- use aconfig_storage_file::FlagValueList;
+ use aconfig_storage_file::{StorageFileType, FlagValueList};
pub fn create_test_flag_value_list() -> FlagValueList {
let header = FlagValueHeader {
version: crate::FILE_VERSION,
container: String::from("system"),
- file_size: 34,
+ file_type: StorageFileType::FlagVal as u8,
+ file_size: 35,
num_flags: 8,
- boolean_value_offset: 26,
+ boolean_value_offset: 27,
};
let booleans: Vec<bool> = vec![false, true, false, false, true, true, false, true];
FlagValueList { header, booleans }
diff --git a/tools/aconfig/aconfig_storage_read_api/src/lib.rs b/tools/aconfig/aconfig_storage_read_api/src/lib.rs
index 3958b2e..cb6a74a 100644
--- a/tools/aconfig/aconfig_storage_read_api/src/lib.rs
+++ b/tools/aconfig/aconfig_storage_read_api/src/lib.rs
@@ -43,7 +43,7 @@
mod test_utils;
pub use aconfig_storage_file::{
- protos::ProtoStorageFiles, read_u32_from_bytes, AconfigStorageError, StorageFileSelection,
+ protos::ProtoStorageFiles, read_u32_from_bytes, AconfigStorageError, StorageFileType,
FILE_VERSION,
};
pub use flag_table_query::FlagOffset;
@@ -67,7 +67,7 @@
container: &str,
package: &str,
) -> Result<Option<PackageOffset>, AconfigStorageError> {
- let mapped_file = get_mapped_file(pb_file, container, StorageFileSelection::PackageMap)?;
+ let mapped_file = get_mapped_file(pb_file, container, StorageFileType::PackageMap)?;
find_package_offset(&mapped_file, package)
}
@@ -78,7 +78,7 @@
package_id: u32,
flag: &str,
) -> Result<Option<FlagOffset>, AconfigStorageError> {
- let mapped_file = get_mapped_file(pb_file, container, StorageFileSelection::FlagMap)?;
+ let mapped_file = get_mapped_file(pb_file, container, StorageFileType::FlagMap)?;
find_flag_offset(&mapped_file, package_id, flag)
}
@@ -88,7 +88,7 @@
container: &str,
offset: u32,
) -> Result<bool, AconfigStorageError> {
- let mapped_file = get_mapped_file(pb_file, container, StorageFileSelection::FlagVal)?;
+ let mapped_file = get_mapped_file(pb_file, container, StorageFileType::FlagVal)?;
find_boolean_flag_value(&mapped_file, offset)
}
diff --git a/tools/aconfig/aconfig_storage_read_api/src/mapped_file.rs b/tools/aconfig/aconfig_storage_read_api/src/mapped_file.rs
index e94c56f..67c293c 100644
--- a/tools/aconfig/aconfig_storage_read_api/src/mapped_file.rs
+++ b/tools/aconfig/aconfig_storage_read_api/src/mapped_file.rs
@@ -15,7 +15,7 @@
*/
use std::collections::HashMap;
-use std::fs::File;
+use std::fs::{self, File};
use std::io::{BufReader, Read};
use std::sync::{Arc, Mutex};
@@ -26,7 +26,7 @@
use crate::AconfigStorageError::{
self, FileReadFail, MapFileFail, ProtobufParseFail, StorageFileNotFound,
};
-use crate::StorageFileSelection;
+use crate::StorageFileType;
use aconfig_storage_file::protos::{
storage_record_pb::try_from_binary_proto, ProtoStorageFileInfo, ProtoStorageFiles,
};
@@ -75,17 +75,15 @@
/// Verify the file is read only and then map it
fn verify_read_only_and_map(file_path: &str) -> Result<Mmap, AconfigStorageError> {
- let file = File::open(file_path)
- .map_err(|errmsg| FileReadFail(anyhow!("Failed to open file {}: {}", file_path, errmsg)))?;
- let metadata = file.metadata().map_err(|errmsg| {
- FileReadFail(anyhow!("Failed to find metadata for {}: {}", file_path, errmsg))
- })?;
-
- // ensure storage file is read only
- if !metadata.permissions().readonly() {
+ // ensure file has read only permission
+ let perms = fs::metadata(file_path).unwrap().permissions();
+ if !perms.readonly() {
return Err(MapFileFail(anyhow!("fail to map non read only storage file {}", file_path)));
}
+ let file = File::open(file_path)
+ .map_err(|errmsg| FileReadFail(anyhow!("Failed to open file {}: {}", file_path, errmsg)))?;
+
// SAFETY:
//
// Mmap constructors are unsafe as it would have undefined behaviors if the file
@@ -95,10 +93,6 @@
// which means it is read only. Here in the code, we check explicitly that the file
// being mapped must only have read permission, otherwise, error out, thus making sure
// it is safe.
- //
- // We should remove this restriction if we need to support mmap non read only file in
- // the future (by making this api unsafe). But for now, all flags are boot stable, so
- // the boot flag file copy should be readonly.
unsafe {
let mapped_file = Mmap::map(&file).map_err(|errmsg| {
MapFileFail(anyhow!("fail to map storage file {}: {}", file_path, errmsg))
@@ -123,21 +117,21 @@
pub(crate) fn get_mapped_file(
location_pb_file: &str,
container: &str,
- file_selection: StorageFileSelection,
+ file_selection: StorageFileType,
) -> Result<Arc<Mmap>, AconfigStorageError> {
let mut all_mapped_files = ALL_MAPPED_FILES.lock().unwrap();
match all_mapped_files.get(container) {
Some(mapped_files) => Ok(match file_selection {
- StorageFileSelection::PackageMap => Arc::clone(&mapped_files.package_map),
- StorageFileSelection::FlagMap => Arc::clone(&mapped_files.flag_map),
- StorageFileSelection::FlagVal => Arc::clone(&mapped_files.flag_val),
+ StorageFileType::PackageMap => Arc::clone(&mapped_files.package_map),
+ StorageFileType::FlagMap => Arc::clone(&mapped_files.flag_map),
+ StorageFileType::FlagVal => Arc::clone(&mapped_files.flag_val),
}),
None => {
let mapped_files = map_container_storage_files(location_pb_file, container)?;
let file_ptr = match file_selection {
- StorageFileSelection::PackageMap => Arc::clone(&mapped_files.package_map),
- StorageFileSelection::FlagMap => Arc::clone(&mapped_files.flag_map),
- StorageFileSelection::FlagVal => Arc::clone(&mapped_files.flag_val),
+ StorageFileType::PackageMap => Arc::clone(&mapped_files.package_map),
+ StorageFileType::FlagMap => Arc::clone(&mapped_files.flag_map),
+ StorageFileType::FlagVal => Arc::clone(&mapped_files.flag_val),
};
all_mapped_files.insert(container.to_string(), mapped_files);
Ok(file_ptr)
@@ -196,11 +190,7 @@
);
}
- fn map_and_verify(
- location_pb_file: &str,
- file_selection: StorageFileSelection,
- actual_file: &str,
- ) {
+ fn map_and_verify(location_pb_file: &str, file_selection: StorageFileType, actual_file: &str) {
let mut opened_file = File::open(actual_file).unwrap();
let mut content = Vec::new();
opened_file.read_to_end(&mut content).unwrap();
@@ -238,13 +228,9 @@
let file = write_proto_to_temp_file(&text_proto).unwrap();
let file_full_path = file.path().display().to_string();
- map_and_verify(
- &file_full_path,
- StorageFileSelection::PackageMap,
- &ro_files.package_map.name,
- );
- map_and_verify(&file_full_path, StorageFileSelection::FlagMap, &ro_files.flag_map.name);
- map_and_verify(&file_full_path, StorageFileSelection::FlagVal, &ro_files.flag_val.name);
+ map_and_verify(&file_full_path, StorageFileType::PackageMap, &ro_files.package_map.name);
+ map_and_verify(&file_full_path, StorageFileType::FlagMap, &ro_files.flag_map.name);
+ map_and_verify(&file_full_path, StorageFileType::FlagVal, &ro_files.flag_val.name);
}
#[test]
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
index 6d2ed5f7..81feec6 100644
--- a/tools/aconfig/aconfig_storage_read_api/src/package_table_query.rs
+++ b/tools/aconfig/aconfig_storage_read_api/src/package_table_query.rs
@@ -72,18 +72,19 @@
#[cfg(test)]
mod tests {
use super::*;
- use aconfig_storage_file::PackageTable;
+ use aconfig_storage_file::{StorageFileType, PackageTable};
pub fn create_test_package_table() -> PackageTable {
let header = PackageTableHeader {
version: crate::FILE_VERSION,
container: String::from("system"),
- file_size: 208,
+ file_type: StorageFileType::PackageMap as u8,
+ file_size: 209,
num_packages: 3,
- bucket_offset: 30,
- node_offset: 58,
+ bucket_offset: 31,
+ node_offset: 59,
};
- let buckets: Vec<Option<u32>> = vec![Some(58), None, None, Some(108), None, None, None];
+ let buckets: Vec<Option<u32>> = vec![Some(59), None, None, Some(109), None, None, None];
let first_node = PackageTableNode {
package_name: String::from("com.android.aconfig.storage.test_2"),
package_id: 1,
@@ -94,7 +95,7 @@
package_name: String::from("com.android.aconfig.storage.test_1"),
package_id: 0,
boolean_offset: 0,
- next_offset: Some(158),
+ next_offset: Some(159),
};
let third_node = PackageTableNode {
package_name: String::from("com.android.aconfig.storage.test_4"),
diff --git a/tools/aconfig/aconfig_storage_read_api/src/test_utils.rs b/tools/aconfig/aconfig_storage_read_api/src/test_utils.rs
index cc5938d..22f367f 100644
--- a/tools/aconfig/aconfig_storage_read_api/src/test_utils.rs
+++ b/tools/aconfig/aconfig_storage_read_api/src/test_utils.rs
@@ -26,14 +26,6 @@
}
}
-fn set_file_read_write(file: &NamedTempFile) {
- let mut perms = fs::metadata(file.path()).unwrap().permissions();
- if perms.readonly() {
- perms.set_readonly(false);
- fs::set_permissions(file.path(), perms).unwrap();
- }
-}
-
#[allow(dead_code)]
pub(crate) struct TestStorageFile {
pub file: NamedTempFile,
@@ -46,8 +38,6 @@
fs::copy(source_file, file.path())?;
if read_only {
set_file_read_only(&file);
- } else {
- set_file_read_write(&file);
}
let name = file.path().display().to_string();
Ok(Self { file, name })
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/Android.bp b/tools/aconfig/aconfig_storage_read_api/tests/Android.bp
index 0bfc7bf..ab5ca44 100644
--- a/tools/aconfig/aconfig_storage_read_api/tests/Android.bp
+++ b/tools/aconfig/aconfig_storage_read_api/tests/Android.bp
@@ -10,9 +10,9 @@
"libtempfile",
],
data: [
- ":ro.package.map",
- ":ro.flag.map",
- ":ro.flag.val",
+ "package.map",
+ "flag.map",
+ "flag.val",
],
test_suites: ["general-tests"],
}
@@ -31,9 +31,9 @@
"liblog",
],
data: [
- ":ro.package.map",
- ":ro.flag.map",
- ":ro.flag.val",
+ "package.map",
+ "flag.map",
+ "flag.val",
],
test_suites: [
"device-tests",
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/flag.map b/tools/aconfig/aconfig_storage_read_api/tests/flag.map
index 43b6f9a..5507894 100644
--- a/tools/aconfig/aconfig_storage_read_api/tests/flag.map
+++ b/tools/aconfig/aconfig_storage_read_api/tests/flag.map
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/flag.val b/tools/aconfig/aconfig_storage_read_api/tests/flag.val
index f39f8d3..75b8564 100644
--- a/tools/aconfig/aconfig_storage_read_api/tests/flag.val
+++ b/tools/aconfig/aconfig_storage_read_api/tests/flag.val
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/package.map b/tools/aconfig/aconfig_storage_read_api/tests/package.map
index 8ed4767..02267e5 100644
--- a/tools/aconfig/aconfig_storage_read_api/tests/package.map
+++ b/tools/aconfig/aconfig_storage_read_api/tests/package.map
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.cpp b/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.cpp
index 6fecd08..1fd7770 100644
--- a/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.cpp
+++ b/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.cpp
@@ -16,58 +16,104 @@
#include <string>
#include <vector>
+#include <cstdio>
+#include <sys/stat.h>
#include "aconfig_storage/aconfig_storage_read_api.hpp"
#include <gtest/gtest.h>
#include <protos/aconfig_storage_metadata.pb.h>
#include <android-base/file.h>
+#include <android-base/result.h>
using android::aconfig_storage_metadata::storage_files;
+using ::android::base::ReadFileToString;
using ::android::base::WriteStringToFile;
+using ::android::base::Result;
+using ::android::base::Error;
using ::aconfig_storage::test_only_api::get_package_offset_impl;
using ::aconfig_storage::test_only_api::get_flag_offset_impl;
using ::aconfig_storage::test_only_api::get_boolean_flag_value_impl;
using ::aconfig_storage::get_storage_file_version;
-void write_storage_location_pb_to_file(std::string const& file_path) {
- auto const test_dir = android::base::GetExecutableDirectory();
- auto proto = storage_files();
- auto* info = proto.add_files();
- info->set_version(0);
- info->set_container("system");
- info->set_package_map(test_dir + "/tests/tmp.ro.package.map");
- info->set_flag_map(test_dir + "/tests/tmp.ro.flag.map");
- info->set_flag_val(test_dir + "/tests/tmp.ro.flag.val");
- info->set_timestamp(12345);
+class AconfigStorageTest : public ::testing::Test {
+ protected:
+ Result<std::string> copy_to_ro_temp_file(std::string const& source_file) {
+ auto temp_file = std::string(std::tmpnam(nullptr));
+ auto content = std::string();
+ if (!ReadFileToString(source_file, &content)) {
+ return Error() << "failed to read file: " << source_file;
+ }
+ if (!WriteStringToFile(content, temp_file)) {
+ return Error() << "failed to copy file: " << source_file;
+ }
+ if (chmod(temp_file.c_str(), S_IRUSR | S_IRGRP) == -1) {
+ return Error() << "failed to make file read only";
+ }
+ return temp_file;
+ }
- auto content = std::string();
- proto.SerializeToString(&content);
- ASSERT_TRUE(WriteStringToFile(content, file_path))
- << "Failed to write a file: " << file_path;
-}
+ Result<std::string> write_storage_location_pb_file(std::string const& package_map,
+ std::string const& flag_map,
+ std::string const& flag_val) {
+ auto temp_file = std::tmpnam(nullptr);
+ auto proto = storage_files();
+ auto* info = proto.add_files();
+ info->set_version(0);
+ info->set_container("system");
+ info->set_package_map(package_map);
+ info->set_flag_map(flag_map);
+ info->set_flag_val(flag_val);
+ info->set_timestamp(12345);
-TEST(AconfigStorageTest, test_storage_version_query) {
- auto const test_dir = android::base::GetExecutableDirectory();
- auto query = get_storage_file_version(test_dir + "/tests/tmp.ro.package.map");
+ auto content = std::string();
+ proto.SerializeToString(&content);
+ if (!WriteStringToFile(content, temp_file)) {
+ return Error() << "failed to write storage records pb file";
+ }
+ return temp_file;
+ }
+
+ void SetUp() override {
+ auto const test_dir = android::base::GetExecutableDirectory();
+ package_map = *copy_to_ro_temp_file(test_dir + "/package.map");
+ flag_map = *copy_to_ro_temp_file(test_dir + "/flag.map");
+ flag_val = *copy_to_ro_temp_file(test_dir + "/flag.val");
+ storage_record_pb = *write_storage_location_pb_file(
+ package_map, flag_map, flag_val);
+ }
+
+ void TearDown() override {
+ std::remove(package_map.c_str());
+ std::remove(flag_map.c_str());
+ std::remove(flag_val.c_str());
+ std::remove(storage_record_pb.c_str());
+ }
+
+ std::string package_map;
+ std::string flag_map;
+ std::string flag_val;
+ std::string storage_record_pb;
+};
+
+
+TEST_F(AconfigStorageTest, test_storage_version_query) {
+ auto query = get_storage_file_version(package_map);
ASSERT_EQ(query.error_message, std::string());
ASSERT_TRUE(query.query_success);
ASSERT_EQ(query.version_number, 1);
- query = get_storage_file_version(test_dir + "/tests/tmp.ro.flag.map");
+ query = get_storage_file_version(flag_map);
ASSERT_EQ(query.error_message, std::string());
ASSERT_TRUE(query.query_success);
ASSERT_EQ(query.version_number, 1);
- query = get_storage_file_version(test_dir + "/tests/tmp.ro.flag.val");
+ query = get_storage_file_version(flag_val);
ASSERT_EQ(query.error_message, std::string());
ASSERT_TRUE(query.query_success);
ASSERT_EQ(query.version_number, 1);
}
-TEST(AconfigStorageTest, test_package_offset_query) {
- auto pb_file = std::string("/tmp/test_package_offset_query.pb");
- write_storage_location_pb_to_file(pb_file);
-
+TEST_F(AconfigStorageTest, test_package_offset_query) {
auto query = get_package_offset_impl(
- pb_file, "system", "com.android.aconfig.storage.test_1");
+ storage_record_pb, "system", "com.android.aconfig.storage.test_1");
ASSERT_EQ(query.error_message, std::string());
ASSERT_TRUE(query.query_success);
ASSERT_TRUE(query.package_exists);
@@ -75,7 +121,7 @@
ASSERT_EQ(query.boolean_offset, 0);
query = get_package_offset_impl(
- pb_file, "system", "com.android.aconfig.storage.test_2");
+ storage_record_pb, "system", "com.android.aconfig.storage.test_2");
ASSERT_EQ(query.error_message, std::string());
ASSERT_TRUE(query.query_success);
ASSERT_TRUE(query.package_exists);
@@ -83,7 +129,7 @@
ASSERT_EQ(query.boolean_offset, 3);
query = get_package_offset_impl(
- pb_file, "system", "com.android.aconfig.storage.test_4");
+ storage_record_pb, "system", "com.android.aconfig.storage.test_4");
ASSERT_EQ(query.error_message, std::string());
ASSERT_TRUE(query.query_success);
ASSERT_TRUE(query.package_exists);
@@ -91,27 +137,21 @@
ASSERT_EQ(query.boolean_offset, 6);
}
-TEST(AconfigStorageTest, test_invalid_package_offset_query) {
- auto pb_file = std::string("/tmp/test_package_offset_query.pb");
- write_storage_location_pb_to_file(pb_file);
-
+TEST_F(AconfigStorageTest, test_invalid_package_offset_query) {
auto query = get_package_offset_impl(
- pb_file, "system", "com.android.aconfig.storage.test_3");
+ storage_record_pb, "system", "com.android.aconfig.storage.test_3");
ASSERT_EQ(query.error_message, std::string());
ASSERT_TRUE(query.query_success);
ASSERT_FALSE(query.package_exists);
query = get_package_offset_impl(
- pb_file, "vendor", "com.android.aconfig.storage.test_1");
+ storage_record_pb, "vendor", "com.android.aconfig.storage.test_1");
ASSERT_EQ(query.error_message,
std::string("StorageFileNotFound(Storage file does not exist for vendor)"));
ASSERT_FALSE(query.query_success);
}
-TEST(AconfigStorageTest, test_flag_offset_query) {
- auto pb_file = std::string("/tmp/test_package_offset_query.pb");
- write_storage_location_pb_to_file(pb_file);
-
+TEST_F(AconfigStorageTest, test_flag_offset_query) {
auto baseline = std::vector<std::tuple<int, std::string, int>>{
{0, "enabled_ro", 1},
{0, "enabled_rw", 2},
@@ -123,7 +163,7 @@
{0, "disabled_rw", 0},
};
for (auto const&[package_id, flag_name, expected_offset] : baseline) {
- auto query = get_flag_offset_impl(pb_file, "system", package_id, flag_name);
+ auto query = get_flag_offset_impl(storage_record_pb, "system", package_id, flag_name);
ASSERT_EQ(query.error_message, std::string());
ASSERT_TRUE(query.query_success);
ASSERT_TRUE(query.flag_exists);
@@ -131,47 +171,39 @@
}
}
-TEST(AconfigStorageTest, test_invalid_flag_offset_query) {
- auto pb_file = std::string("/tmp/test_invalid_package_offset_query.pb");
- write_storage_location_pb_to_file(pb_file);
-
- auto query = get_flag_offset_impl(pb_file, "system", 0, "none_exist");
+TEST_F(AconfigStorageTest, test_invalid_flag_offset_query) {
+ auto query = get_flag_offset_impl(storage_record_pb, "system", 0, "none_exist");
ASSERT_EQ(query.error_message, std::string());
ASSERT_TRUE(query.query_success);
ASSERT_FALSE(query.flag_exists);
- query = get_flag_offset_impl(pb_file, "system", 3, "enabled_ro");
+ query = get_flag_offset_impl(storage_record_pb, "system", 3, "enabled_ro");
ASSERT_EQ(query.error_message, std::string());
ASSERT_TRUE(query.query_success);
ASSERT_FALSE(query.flag_exists);
- query = get_flag_offset_impl(pb_file, "vendor", 0, "enabled_ro");
+ query = get_flag_offset_impl(storage_record_pb, "vendor", 0, "enabled_ro");
ASSERT_EQ(query.error_message,
std::string("StorageFileNotFound(Storage file does not exist for vendor)"));
ASSERT_FALSE(query.query_success);
}
-TEST(AconfigStorageTest, test_boolean_flag_value_query) {
- auto pb_file = std::string("/tmp/test_boolean_flag_value_query.pb");
- write_storage_location_pb_to_file(pb_file);
+TEST_F(AconfigStorageTest, test_boolean_flag_value_query) {
for (int offset = 0; offset < 8; ++offset) {
- auto query = get_boolean_flag_value_impl(pb_file, "system", offset);
+ auto query = get_boolean_flag_value_impl(storage_record_pb, "system", offset);
ASSERT_EQ(query.error_message, std::string());
ASSERT_TRUE(query.query_success);
ASSERT_FALSE(query.flag_value);
}
}
-TEST(AconfigStorageTest, test_invalid_boolean_flag_value_query) {
- auto pb_file = std::string("/tmp/test_invalid_boolean_flag_value_query.pb");
- write_storage_location_pb_to_file(pb_file);
-
- auto query = get_boolean_flag_value_impl(pb_file, "vendor", 0);
+TEST_F(AconfigStorageTest, test_invalid_boolean_flag_value_query) {
+ auto query = get_boolean_flag_value_impl(storage_record_pb, "vendor", 0);
ASSERT_EQ(query.error_message,
std::string("StorageFileNotFound(Storage file does not exist for vendor)"));
ASSERT_FALSE(query.query_success);
- query = get_boolean_flag_value_impl(pb_file, "system", 8);
+ query = get_boolean_flag_value_impl(storage_record_pb, "system", 8);
ASSERT_EQ(query.error_message,
std::string("InvalidStorageFileOffset(Flag value offset goes beyond the end of the file.)"));
ASSERT_FALSE(query.query_success);
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.rs b/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.rs
index 4a65876..b178d83 100644
--- a/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.rs
+++ b/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.rs
@@ -5,22 +5,43 @@
get_storage_file_version, PackageOffset, ProtoStorageFiles,
};
use protobuf::Message;
+ use std::fs;
use std::io::Write;
use tempfile::NamedTempFile;
- fn write_storage_location_file() -> NamedTempFile {
- let text_proto = r#"
-files {
+ pub fn copy_to_ro_temp_file(source_file: &str) -> NamedTempFile {
+ let file = NamedTempFile::new().unwrap();
+ fs::copy(source_file, file.path()).unwrap();
+ let file_name = file.path().display().to_string();
+ let mut perms = fs::metadata(file_name).unwrap().permissions();
+ perms.set_readonly(true);
+ fs::set_permissions(file.path(), perms.clone()).unwrap();
+ file
+ }
+
+ fn write_storage_location_file(
+ package_table: &NamedTempFile,
+ flag_table: &NamedTempFile,
+ flag_value: &NamedTempFile,
+ ) -> NamedTempFile {
+ let text_proto = format!(
+ r#"
+files {{
version: 0
container: "system"
- package_map: "./tests/tmp.ro.package.map"
- flag_map: "./tests/tmp.ro.flag.map"
- flag_val: "./tests/tmp.ro.flag.val"
+ package_map: "{}"
+ flag_map: "{}"
+ flag_val: "{}"
timestamp: 12345
-}
-"#;
+}}
+"#,
+ package_table.path().display(),
+ flag_table.path().display(),
+ flag_value.path().display(),
+ );
+
let storage_files: ProtoStorageFiles =
- protobuf::text_format::parse_from_str(text_proto).unwrap();
+ protobuf::text_format::parse_from_str(&text_proto).unwrap();
let mut binary_proto_bytes = Vec::new();
storage_files.write_to_vec(&mut binary_proto_bytes).unwrap();
let mut file = NamedTempFile::new().unwrap();
@@ -30,7 +51,11 @@
#[test]
fn test_package_offset_query() {
- let file = write_storage_location_file();
+ let package_table = copy_to_ro_temp_file("./package.map");
+ let flag_table = copy_to_ro_temp_file("./flag.map");
+ let flag_value = copy_to_ro_temp_file("./flag.val");
+
+ let file = write_storage_location_file(&package_table, &flag_table, &flag_value);
let file_full_path = file.path().display().to_string();
let package_offset = get_package_offset_impl(
@@ -74,7 +99,11 @@
#[test]
fn test_invalid_package_offset_query() {
- let file = write_storage_location_file();
+ let package_table = copy_to_ro_temp_file("./package.map");
+ let flag_table = copy_to_ro_temp_file("./flag.map");
+ let flag_value = copy_to_ro_temp_file("./flag.val");
+
+ let file = write_storage_location_file(&package_table, &flag_table, &flag_value);
let file_full_path = file.path().display().to_string();
let package_offset_option = get_package_offset_impl(
@@ -99,7 +128,11 @@
#[test]
fn test_flag_offset_query() {
- let file = write_storage_location_file();
+ let package_table = copy_to_ro_temp_file("./package.map");
+ let flag_table = copy_to_ro_temp_file("./flag.map");
+ let flag_value = copy_to_ro_temp_file("./flag.val");
+
+ let file = write_storage_location_file(&package_table, &flag_table, &flag_value);
let file_full_path = file.path().display().to_string();
let baseline = vec![
@@ -123,7 +156,11 @@
#[test]
fn test_invalid_flag_offset_query() {
- let file = write_storage_location_file();
+ let package_table = copy_to_ro_temp_file("./package.map");
+ let flag_table = copy_to_ro_temp_file("./flag.map");
+ let flag_value = copy_to_ro_temp_file("./flag.val");
+
+ let file = write_storage_location_file(&package_table, &flag_table, &flag_value);
let file_full_path = file.path().display().to_string();
let flag_offset_option =
@@ -143,7 +180,11 @@
#[test]
fn test_boolean_flag_value_query() {
- let file = write_storage_location_file();
+ let package_table = copy_to_ro_temp_file("./package.map");
+ let flag_table = copy_to_ro_temp_file("./flag.map");
+ let flag_value = copy_to_ro_temp_file("./flag.val");
+
+ let file = write_storage_location_file(&package_table, &flag_table, &flag_value);
let file_full_path = file.path().display().to_string();
let baseline: Vec<bool> = vec![false; 8];
@@ -156,7 +197,11 @@
#[test]
fn test_invalid_boolean_flag_value_query() {
- let file = write_storage_location_file();
+ let package_table = copy_to_ro_temp_file("./package.map");
+ let flag_table = copy_to_ro_temp_file("./flag.map");
+ let flag_value = copy_to_ro_temp_file("./flag.val");
+
+ let file = write_storage_location_file(&package_table, &flag_table, &flag_value);
let file_full_path = file.path().display().to_string();
let err = get_boolean_flag_value_impl(&file_full_path, "vendor", 0u32).unwrap_err();
@@ -174,8 +219,8 @@
#[test]
fn test_storage_version_query() {
- assert_eq!(get_storage_file_version("./tests/tmp.ro.package.map").unwrap(), 1);
- assert_eq!(get_storage_file_version("./tests/tmp.ro.flag.map").unwrap(), 1);
- assert_eq!(get_storage_file_version("./tests/tmp.ro.flag.val").unwrap(), 1);
+ assert_eq!(get_storage_file_version("./package.map").unwrap(), 1);
+ assert_eq!(get_storage_file_version("./flag.map").unwrap(), 1);
+ assert_eq!(get_storage_file_version("./flag.val").unwrap(), 1);
}
}
diff --git a/tools/aconfig/aconfig_storage_write_api/Android.bp b/tools/aconfig/aconfig_storage_write_api/Android.bp
new file mode 100644
index 0000000..1382aba
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/Android.bp
@@ -0,0 +1,37 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+ name: "aconfig_storage_write_api.defaults",
+ edition: "2021",
+ lints: "none",
+ srcs: ["src/lib.rs"],
+ rustlibs: [
+ "libanyhow",
+ "libtempfile",
+ "libmemmap2",
+ "libcxx",
+ "libthiserror",
+ "libaconfig_storage_file",
+ ],
+}
+
+rust_library {
+ name: "libaconfig_storage_write_api",
+ crate_name: "aconfig_storage_write_api",
+ host_supported: true,
+ defaults: ["aconfig_storage_write_api.defaults"],
+}
+
+rust_test_host {
+ name: "aconfig_storage_write_api.test",
+ test_suites: ["general-tests"],
+ defaults: ["aconfig_storage_write_api.defaults"],
+ data: [
+ "tests/flag.val",
+ ],
+ rustlibs: [
+ "libaconfig_storage_read_api",
+ ],
+}
diff --git a/tools/aconfig/aconfig_storage_write_api/Cargo.toml b/tools/aconfig/aconfig_storage_write_api/Cargo.toml
new file mode 100644
index 0000000..494c19c
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "aconfig_storage_write_api"
+version = "0.1.0"
+edition = "2021"
+
+[features]
+default = ["cargo"]
+cargo = []
+
+[dependencies]
+anyhow = "1.0.69"
+memmap2 = "0.8.0"
+tempfile = "3.9.0"
+thiserror = "1.0.56"
+protobuf = "3.2.0"
+once_cell = "1.19.0"
+aconfig_storage_file = { path = "../aconfig_storage_file" }
+aconfig_storage_read_api = { path = "../aconfig_storage_read_api" }
+
+[build-dependencies]
+cxx-build = "1.0"
diff --git a/tools/aconfig/aconfig_storage_write_api/src/flag_value_update.rs b/tools/aconfig/aconfig_storage_write_api/src/flag_value_update.rs
new file mode 100644
index 0000000..c2375dd
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/src/flag_value_update.rs
@@ -0,0 +1,114 @@
+/*
+ * 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 update module defines the flag value file write to mapped bytes
+
+use aconfig_storage_file::{AconfigStorageError, FlagValueHeader, FILE_VERSION};
+use anyhow::anyhow;
+
+/// Set flag value
+pub fn update_boolean_flag_value(
+ buf: &mut [u8],
+ flag_offset: u32,
+ flag_value: bool,
+) -> Result<(), AconfigStorageError> {
+ let interpreted_header = FlagValueHeader::from_bytes(buf)?;
+ if interpreted_header.version > FILE_VERSION {
+ return Err(AconfigStorageError::HigherStorageFileVersion(anyhow!(
+ "Cannot write to storage file with a higher version of {} with lib version {}",
+ interpreted_header.version,
+ FILE_VERSION
+ )));
+ }
+
+ let 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."
+ )));
+ }
+
+ buf[head] = u8::from(flag_value).to_le_bytes()[0];
+ Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use aconfig_storage_file::{FlagValueList, StorageFileType};
+
+ pub fn create_test_flag_value_list() -> FlagValueList {
+ let header = FlagValueHeader {
+ version: FILE_VERSION,
+ container: String::from("system"),
+ file_type: StorageFileType::FlagVal as u8,
+ file_size: 35,
+ num_flags: 8,
+ boolean_value_offset: 27,
+ };
+ let booleans: Vec<bool> = vec![false; 8];
+ FlagValueList { header, booleans }
+ }
+
+ #[test]
+ // this test point locks down flag value update
+ fn test_boolean_flag_value_update() {
+ let flag_value_list = create_test_flag_value_list();
+ let value_offset = flag_value_list.header.boolean_value_offset;
+ let mut content = flag_value_list.as_bytes();
+ let true_byte = u8::from(true).to_le_bytes()[0];
+ let false_byte = u8::from(false).to_le_bytes()[0];
+
+ for i in 0..flag_value_list.header.num_flags {
+ let offset = (value_offset + i) as usize;
+ update_boolean_flag_value(&mut content, i, true).unwrap();
+ assert_eq!(content[offset], true_byte);
+ update_boolean_flag_value(&mut content, i, false).unwrap();
+ assert_eq!(content[offset], false_byte);
+ }
+ }
+
+ #[test]
+ // this test point locks down update beyond the end of boolean section
+ fn test_boolean_out_of_range() {
+ let mut flag_value_list = create_test_flag_value_list().as_bytes();
+ let error = update_boolean_flag_value(&mut flag_value_list[..], 8, true).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 = FILE_VERSION + 1;
+ let mut flag_value = value_list.as_bytes();
+ let error = update_boolean_flag_value(&mut flag_value[..], 4, true).unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ format!(
+ "HigherStorageFileVersion(Cannot write to storage file with a higher version of {} with lib version {})",
+ FILE_VERSION + 1,
+ FILE_VERSION
+ )
+ );
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_write_api/src/lib.rs b/tools/aconfig/aconfig_storage_write_api/src/lib.rs
new file mode 100644
index 0000000..17a6538
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/src/lib.rs
@@ -0,0 +1,120 @@
+/*
+ * 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.
+ */
+
+//! `aconfig_storage_write_api` is a crate that defines write apis to update flag value
+//! in storage file. It provides one api to interface with storage files.
+
+pub mod flag_value_update;
+pub mod mapped_file;
+
+#[cfg(test)]
+mod test_utils;
+
+use aconfig_storage_file::AconfigStorageError;
+
+use anyhow::anyhow;
+use memmap2::MmapMut;
+
+/// Storage file location pb file
+pub const STORAGE_LOCATION_FILE: &str = "/metadata/aconfig/persistent_storage_file_records.pb";
+
+/// Get mmaped flag value file given the container name
+///
+/// \input container: the flag package container
+/// \return a result of mapped file
+///
+///
+/// # Safety
+///
+/// The memory mapped file may have undefined behavior if there are writes to this
+/// file not thru this memory mapped file or there are concurrent writes to this
+/// memory mapped file. Ensure all writes to the underlying file are thru this memory
+/// mapped file and there are no concurrent writes.
+pub unsafe fn get_mapped_flag_value_file(container: &str) -> Result<MmapMut, AconfigStorageError> {
+ unsafe { crate::mapped_file::get_mapped_file(STORAGE_LOCATION_FILE, container) }
+}
+
+/// Set boolean flag value thru mapped file and flush the change to file
+///
+/// \input mapped_file: the mapped flag value file
+/// \input offset: flag value offset
+/// \input value: updated flag value
+/// \return a result of ()
+///
+pub fn set_boolean_flag_value(
+ file: &mut MmapMut,
+ offset: u32,
+ value: bool,
+) -> Result<(), AconfigStorageError> {
+ crate::flag_value_update::update_boolean_flag_value(file, offset, value)?;
+ file.flush().map_err(|errmsg| {
+ AconfigStorageError::MapFlushFail(anyhow!("fail to flush storage file: {}", errmsg))
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::test_utils::copy_to_temp_file;
+ use aconfig_storage_file::protos::storage_record_pb::write_proto_to_temp_file;
+ use aconfig_storage_read_api::flag_value_query::find_boolean_flag_value;
+ use std::fs::File;
+ use std::io::Read;
+
+ fn get_boolean_flag_value_at_offset(file: &str, offset: u32) -> bool {
+ let mut f = File::open(&file).unwrap();
+ let mut bytes = Vec::new();
+ f.read_to_end(&mut bytes).unwrap();
+ find_boolean_flag_value(&bytes, offset).unwrap()
+ }
+
+ #[test]
+ fn test_set_boolean_flag_value() {
+ let flag_value_file = copy_to_temp_file("./tests/flag.val", false).unwrap();
+ let flag_value_path = flag_value_file.path().display().to_string();
+ let text_proto = format!(
+ r#"
+files {{
+ version: 0
+ container: "system"
+ package_map: "some_package.map"
+ flag_map: "some_flag.map"
+ flag_val: "{}"
+ timestamp: 12345
+}}
+"#,
+ flag_value_path
+ );
+ let record_pb_file = write_proto_to_temp_file(&text_proto).unwrap();
+ let record_pb_path = record_pb_file.path().display().to_string();
+
+ // SAFETY:
+ // The safety here is guaranteed as only this single threaded test process will
+ // write to this file
+ unsafe {
+ let mut file = crate::mapped_file::get_mapped_file(&record_pb_path, "system").unwrap();
+ for i in 0..8 {
+ set_boolean_flag_value(&mut file, i, true).unwrap();
+ let value = get_boolean_flag_value_at_offset(&flag_value_path, i);
+ assert_eq!(value, true);
+
+ set_boolean_flag_value(&mut file, i, false).unwrap();
+ let value = get_boolean_flag_value_at_offset(&flag_value_path, i);
+ assert_eq!(value, false);
+ }
+ }
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_write_api/src/mapped_file.rs b/tools/aconfig/aconfig_storage_write_api/src/mapped_file.rs
new file mode 100644
index 0000000..4c98be4
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/src/mapped_file.rs
@@ -0,0 +1,189 @@
+/*
+ * 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.
+ */
+
+use std::fs::{self, File, OpenOptions};
+use std::io::{BufReader, Read};
+
+use anyhow::anyhow;
+use memmap2::MmapMut;
+
+use aconfig_storage_file::protos::{storage_record_pb::try_from_binary_proto, ProtoStorageFiles};
+use aconfig_storage_file::AconfigStorageError::{
+ self, FileReadFail, MapFileFail, ProtobufParseFail, StorageFileNotFound,
+};
+
+/// Find where persistent storage value file is for a particular container
+fn find_persist_flag_value_file(
+ location_pb_file: &str,
+ container: &str,
+) -> Result<String, AconfigStorageError> {
+ let file = File::open(location_pb_file).map_err(|errmsg| {
+ FileReadFail(anyhow!("Failed to open file {}: {}", location_pb_file, errmsg))
+ })?;
+ let mut reader = BufReader::new(file);
+ let mut bytes = Vec::new();
+ reader.read_to_end(&mut bytes).map_err(|errmsg| {
+ FileReadFail(anyhow!("Failed to read file {}: {}", location_pb_file, errmsg))
+ })?;
+ let storage_locations: ProtoStorageFiles = try_from_binary_proto(&bytes).map_err(|errmsg| {
+ ProtobufParseFail(anyhow!(
+ "Failed to parse storage location pb file {}: {}",
+ location_pb_file,
+ errmsg
+ ))
+ })?;
+ for location_info in storage_locations.files.iter() {
+ if location_info.container() == container {
+ return Ok(location_info.flag_val().to_string());
+ }
+ }
+ Err(StorageFileNotFound(anyhow!("Persistent flag value file does not exist for {}", container)))
+}
+
+/// Get a mapped storage file given the container and file type
+///
+/// # Safety
+///
+/// The memory mapped file may have undefined behavior if there are writes to this
+/// file not thru this memory mapped file or there are concurrent writes to this
+/// memory mapped file. Ensure all writes to the underlying file are thru this memory
+/// mapped file and there are no concurrent writes.
+pub unsafe fn get_mapped_file(
+ location_pb_file: &str,
+ container: &str,
+) -> Result<MmapMut, AconfigStorageError> {
+ let file_path = find_persist_flag_value_file(location_pb_file, container)?;
+
+ // make sure file has read write permission
+ let perms = fs::metadata(&file_path).unwrap().permissions();
+ if perms.readonly() {
+ return Err(MapFileFail(anyhow!("fail to map non read write storage file {}", file_path)));
+ }
+
+ let file =
+ OpenOptions::new().read(true).write(true).open(&file_path).map_err(|errmsg| {
+ FileReadFail(anyhow!("Failed to open file {}: {}", file_path, errmsg))
+ })?;
+
+ unsafe {
+ MmapMut::map_mut(&file).map_err(|errmsg| {
+ MapFileFail(anyhow!("fail to map storage file {}: {}", file_path, errmsg))
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::test_utils::copy_to_temp_file;
+ use aconfig_storage_file::protos::storage_record_pb::write_proto_to_temp_file;
+
+ #[test]
+ fn test_find_persist_flag_value_file_location() {
+ let text_proto = r#"
+files {
+ version: 0
+ container: "system"
+ package_map: "/system/etc/package.map"
+ flag_map: "/system/etc/flag.map"
+ flag_val: "/metadata/aconfig/system.val"
+ timestamp: 12345
+}
+files {
+ version: 1
+ container: "product"
+ package_map: "/product/etc/package.map"
+ flag_map: "/product/etc/flag.map"
+ flag_val: "/metadata/aconfig/product.val"
+ timestamp: 54321
+}
+"#;
+ let file = write_proto_to_temp_file(&text_proto).unwrap();
+ let file_full_path = file.path().display().to_string();
+ let flag_value_file = find_persist_flag_value_file(&file_full_path, "system").unwrap();
+ assert_eq!(flag_value_file, "/metadata/aconfig/system.val");
+ let flag_value_file = find_persist_flag_value_file(&file_full_path, "product").unwrap();
+ assert_eq!(flag_value_file, "/metadata/aconfig/product.val");
+ let err = find_persist_flag_value_file(&file_full_path, "vendor").unwrap_err();
+ assert_eq!(
+ format!("{:?}", err),
+ "StorageFileNotFound(Persistent flag value file does not exist for vendor)"
+ );
+ }
+
+ #[test]
+ fn test_mapped_file_contents() {
+ let mut rw_file = copy_to_temp_file("./tests/flag.val", false).unwrap();
+ let text_proto = format!(
+ r#"
+files {{
+ version: 0
+ container: "system"
+ package_map: "some_package.map"
+ flag_map: "some_flag.map"
+ flag_val: "{}"
+ timestamp: 12345
+}}
+"#,
+ rw_file.path().display().to_string()
+ );
+ let storage_record_file = write_proto_to_temp_file(&text_proto).unwrap();
+ let storage_record_file_path = storage_record_file.path().display().to_string();
+
+ let mut content = Vec::new();
+ rw_file.read_to_end(&mut content).unwrap();
+
+ // SAFETY:
+ // The safety here is guaranteed here as no writes happens to this temp file
+ unsafe {
+ let mmaped_file = get_mapped_file(&storage_record_file_path, "system").unwrap();
+ assert_eq!(mmaped_file[..], content[..]);
+ }
+ }
+
+ #[test]
+ fn test_mapped_read_only_file() {
+ let ro_file = copy_to_temp_file("./tests/flag.val", true).unwrap();
+ let text_proto = format!(
+ r#"
+files {{
+ version: 0
+ container: "system"
+ package_map: "some_package.map"
+ flag_map: "some_flag.map"
+ flag_val: "{}"
+ timestamp: 12345
+}}
+"#,
+ ro_file.path().display().to_string()
+ );
+ let storage_record_file = write_proto_to_temp_file(&text_proto).unwrap();
+ let storage_record_file_path = storage_record_file.path().display().to_string();
+
+ // SAFETY:
+ // The safety here is guaranteed here as no writes happens to this temp file
+ unsafe {
+ let error = get_mapped_file(&storage_record_file_path, "system").unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ format!(
+ "MapFileFail(fail to map non read write storage file {})",
+ ro_file.path().display().to_string()
+ )
+ );
+ }
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_write_api/src/test_utils.rs b/tools/aconfig/aconfig_storage_write_api/src/test_utils.rs
new file mode 100644
index 0000000..06e2e22
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/src/test_utils.rs
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+use anyhow::Result;
+use std::fs;
+use tempfile::NamedTempFile;
+
+/// Create temp file copy
+pub(crate) fn copy_to_temp_file(source_file: &str, read_only: bool) -> Result<NamedTempFile> {
+ let file = NamedTempFile::new()?;
+ fs::copy(source_file, file.path())?;
+ if read_only {
+ let file_name = file.path().display().to_string();
+ let mut perms = fs::metadata(file_name).unwrap().permissions();
+ perms.set_readonly(true);
+ fs::set_permissions(file.path(), perms.clone()).unwrap();
+ }
+ Ok(file)
+}
diff --git a/tools/aconfig/aconfig_storage_write_api/tests/Android.bp b/tools/aconfig/aconfig_storage_write_api/tests/Android.bp
new file mode 100644
index 0000000..bb8c6df
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/tests/Android.bp
@@ -0,0 +1,19 @@
+
+rust_test {
+ name: "aconfig_storage_write_api.test.rust",
+ srcs: [
+ "storage_write_api_test.rs"
+ ],
+ rustlibs: [
+ "libanyhow",
+ "libaconfig_storage_file",
+ "libaconfig_storage_read_api",
+ "libaconfig_storage_write_api",
+ "libprotobuf",
+ "libtempfile",
+ ],
+ data: [
+ "flag.val",
+ ],
+ test_suites: ["general-tests"],
+}
diff --git a/tools/aconfig/aconfig_storage_write_api/tests/flag.val b/tools/aconfig/aconfig_storage_write_api/tests/flag.val
new file mode 100644
index 0000000..75b8564
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/tests/flag.val
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_write_api/tests/storage_write_api_test.rs b/tools/aconfig/aconfig_storage_write_api/tests/storage_write_api_test.rs
new file mode 100644
index 0000000..f6c1bbc
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/tests/storage_write_api_test.rs
@@ -0,0 +1,73 @@
+#[cfg(not(feature = "cargo"))]
+mod aconfig_storage_write_api_test {
+ use aconfig_storage_file::protos::ProtoStorageFiles;
+ use aconfig_storage_read_api::flag_value_query::find_boolean_flag_value;
+ use aconfig_storage_write_api::{mapped_file::get_mapped_file, set_boolean_flag_value};
+
+ use protobuf::Message;
+ use std::fs::{self, File};
+ use std::io::{Read, Write};
+ use tempfile::NamedTempFile;
+
+ /// Write storage location record pb to a temp file
+ fn write_storage_record_file(flag_val: &str) -> NamedTempFile {
+ let text_proto = format!(
+ r#"
+files {{
+ version: 0
+ container: "system"
+ package_map: "some_package_map"
+ flag_map: "some_flag_map"
+ flag_val: "{}"
+ timestamp: 12345
+}}
+"#,
+ flag_val
+ );
+ let storage_files: ProtoStorageFiles =
+ protobuf::text_format::parse_from_str(&text_proto).unwrap();
+ let mut binary_proto_bytes = Vec::new();
+ storage_files.write_to_vec(&mut binary_proto_bytes).unwrap();
+ let mut file = NamedTempFile::new().unwrap();
+ file.write_all(&binary_proto_bytes).unwrap();
+ file
+ }
+
+ /// Create temp file copy
+ fn copy_to_temp_rw_file(source_file: &str) -> NamedTempFile {
+ let file = NamedTempFile::new().unwrap();
+ fs::copy(source_file, file.path()).unwrap();
+ file
+ }
+
+ /// Get boolean flag value from offset
+ fn get_boolean_flag_value_at_offset(file: &str, offset: u32) -> bool {
+ let mut f = File::open(file).unwrap();
+ let mut bytes = Vec::new();
+ f.read_to_end(&mut bytes).unwrap();
+ find_boolean_flag_value(&bytes, offset).unwrap()
+ }
+
+ #[test]
+ /// Test to lock down flag value update api
+ fn test_boolean_flag_value_update() {
+ let flag_value_file = copy_to_temp_rw_file("./flag.val");
+ let flag_value_path = flag_value_file.path().display().to_string();
+ let record_pb_file = write_storage_record_file(&flag_value_path);
+ let record_pb_path = record_pb_file.path().display().to_string();
+
+ // SAFETY:
+ // The safety here is ensured as only this single threaded test process will
+ // write to this file
+ let mut file = unsafe { get_mapped_file(&record_pb_path, "system").unwrap() };
+ for i in 0..8 {
+ set_boolean_flag_value(&mut file, i, true).unwrap();
+ let value = get_boolean_flag_value_at_offset(&flag_value_path, i);
+ assert!(value);
+
+ set_boolean_flag_value(&mut file, i, false).unwrap();
+ let value = get_boolean_flag_value_at_offset(&flag_value_path, i);
+ assert!(!value);
+ }
+ }
+}
diff --git a/tools/aconfig/aflags/src/device_config_source.rs b/tools/aconfig/aflags/src/device_config_source.rs
index 882120f..089f33d 100644
--- a/tools/aconfig/aflags/src/device_config_source.rs
+++ b/tools/aconfig/aflags/src/device_config_source.rs
@@ -105,11 +105,16 @@
if !output.status.success() {
let reason = match output.status.code() {
Some(code) => {
- format!("exit code {}, output was {}", code, str::from_utf8(&output.stdout)?)
+ let output = str::from_utf8(&output.stdout)?;
+ if !output.is_empty() {
+ format!("exit code {code}, output was {output}")
+ } else {
+ format!("exit code {code}")
+ }
}
None => "terminated by signal".to_string(),
};
- bail!("failed to execute device_config: {}", reason);
+ bail!("failed to access flag storage: {}", reason);
}
Ok(str::from_utf8(&output.stdout)?.to_string())
}
diff --git a/tools/releasetools/sign_target_files_apks.py b/tools/releasetools/sign_target_files_apks.py
index 52d95d8..5d92ede 100755
--- a/tools/releasetools/sign_target_files_apks.py
+++ b/tools/releasetools/sign_target_files_apks.py
@@ -822,6 +822,52 @@
# Write back misc_info with the latest values.
ReplaceMiscInfoTxt(input_tf_zip, output_tf_zip, misc_info)
+# Parse string output of `avbtool info_image`.
+def ParseAvbInfo(info_raw):
+ # line_matcher is for parsing each output line of `avbtool info_image`.
+ # example string input: " Hash Algorithm: sha1"
+ # example matched input: (" ", "Hash Algorithm", "sha1")
+ line_matcher = re.compile(r'^(\s*)([^:]+):\s*(.*)$')
+ # prop_matcher is for parsing value part of 'Prop' in `avbtool info_image`.
+ # example string input: "example_prop_key -> 'example_prop_value'"
+ # example matched output: ("example_prop_key", "example_prop_value")
+ prop_matcher = re.compile(r"(.+)\s->\s'(.+)'")
+ info = {}
+ indent_stack = [[-1, info]]
+ for line_info_raw in info_raw.split('\n'):
+ # Parse the line
+ line_info_parsed = line_matcher.match(line_info_raw)
+ if not line_info_parsed:
+ continue
+ indent = len(line_info_parsed.group(1))
+ key = line_info_parsed.group(2).strip()
+ value = line_info_parsed.group(3).strip()
+
+ # Pop indentation stack
+ while indent <= indent_stack[-1][0]:
+ del indent_stack[-1]
+
+ # Insert information into 'info'.
+ cur_info = indent_stack[-1][1]
+ if value == "":
+ if key == "Descriptors":
+ empty_list = []
+ cur_info[key] = empty_list
+ indent_stack.append([indent, empty_list])
+ else:
+ empty_dict = {}
+ cur_info.append({key:empty_dict})
+ indent_stack.append([indent, empty_dict])
+ elif key == "Prop":
+ prop_parsed = prop_matcher.match(value)
+ if not prop_parsed:
+ raise ValueError(
+ "Failed to parse prop while getting avb information.")
+ cur_info.append({key:{prop_parsed.group(1):prop_parsed.group(2)}})
+ else:
+ cur_info[key] = value
+ return info
+
def ReplaceKeyInAvbHashtreeFooter(image, new_key, new_algorithm, misc_info):
# Get avb information about the image by parsing avbtool info_image.
def GetAvbInfo(avbtool, image_name):
@@ -830,51 +876,7 @@
avbtool, 'info_image',
'--image', image_name
])
-
- # line_matcher is for parsing each output line of `avbtool info_image`.
- # example string input: " Hash Algorithm: sha1"
- # example matched input: (" ", "Hash Algorithm", "sha1")
- line_matcher = re.compile(r'^(\s*)([^:]+):\s*(.*)$')
- # prop_matcher is for parsing value part of 'Prop' in `avbtool info_image`.
- # example string input: "example_prop_key -> 'example_prop_value'"
- # example matched output: ("example_prop_key", "example_prop_value")
- prop_matcher = re.compile(r"(.+)\s->\s'(.+)'")
- info = {}
- indent_stack = [[-1, info]]
- for line_info_raw in info_raw.split('\n'):
- # Parse the line
- line_info_parsed = line_matcher.match(line_info_raw)
- if not line_info_parsed:
- continue
- indent = len(line_info_parsed.group(1))
- key = line_info_parsed.group(2).strip()
- value = line_info_parsed.group(3).strip()
-
- # Pop indentation stack
- while indent <= indent_stack[-1][0]:
- del indent_stack[-1]
-
- # Insert information into 'info'.
- cur_info = indent_stack[-1][1]
- if value == "":
- if key == "Descriptors":
- empty_list = []
- cur_info[key] = empty_list
- indent_stack.append([indent, empty_list])
- else:
- empty_dict = {}
- cur_info.append({key:empty_dict})
- indent_stack.append([indent, empty_dict])
- elif key == "Prop":
- prop_parsed = prop_matcher.match(value)
- if not prop_parsed:
- raise ValueError(
- "Failed to parse prop while getting avb information.")
- cur_info.append({key:{prop_parsed.group(1):prop_parsed.group(2)}})
- else:
- cur_info[key] = value
-
- return info
+ return ParseAvbInfo(info_raw)
# Get hashtree descriptor from info
def GetAvbHashtreeDescriptor(avb_info):
diff --git a/tools/releasetools/test_sign_target_files_apks.py b/tools/releasetools/test_sign_target_files_apks.py
index 9cc6df4..7ac1cff 100644
--- a/tools/releasetools/test_sign_target_files_apks.py
+++ b/tools/releasetools/test_sign_target_files_apks.py
@@ -22,8 +22,9 @@
import common
import test_utils
from sign_target_files_apks import (
- CheckApkAndApexKeysAvailable, EditTags, GetApkFileInfo, ReadApexKeysInfo,
- ReplaceCerts, RewriteAvbProps, RewriteProps, WriteOtacerts)
+ CheckApkAndApexKeysAvailable, EditTags, GetApkFileInfo, ParseAvbInfo,
+ ReadApexKeysInfo, ReplaceCerts, RewriteAvbProps, RewriteProps,
+ WriteOtacerts)
class SignTargetFilesApksTest(test_utils.ReleaseToolsTestCase):
@@ -535,3 +536,86 @@
'system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.pem',
'build/make/target/product/security/testkey', None),
}, keys_info)
+
+ def test_ParseAvbInfo(self):
+ avb_info_string = """
+ Footer version: 1.0
+ Image size: 9999999 bytes
+ Original image size: 8888888 bytes
+ VBMeta offset: 7777777
+ VBMeta size: 1111 bytes
+ --
+ Minimum libavb version: 1.0
+ Header Block: 222 bytes
+ Authentication Block: 333 bytes
+ Auxiliary Block: 888 bytes
+ Public key (sha1): abababababababababababababababababababab
+ Algorithm: SHA256_RSA2048
+ Rollback Index: 0
+ Flags: 0
+ Rollback Index Location: 0
+ Release String: 'avbtool 1.3.0'
+ Descriptors:
+ Hashtree descriptor:
+ Version of dm-verity: 1
+ Image Size: 8888888 bytes
+ Tree Offset: 8888888
+ Tree Size: 44444 bytes
+ Data Block Size: 4444 bytes
+ Hash Block Size: 4444 bytes
+ FEC num roots: 0
+ FEC offset: 0
+ FEC size: 0 bytes
+ Hash Algorithm: sha1
+ Partition Name: partition-name
+ Salt: cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd
+ Root Digest: efefefefefefefefefefefefefefefefefef
+ Flags: 0
+ Prop: prop.key -> 'prop.value'
+ """
+
+ self.assertEqual(
+ {
+ 'Footer version': '1.0',
+ 'Image size': '9999999 bytes',
+ 'Original image size': '8888888 bytes',
+ 'VBMeta offset': '7777777',
+ 'VBMeta size': '1111 bytes',
+ 'Minimum libavb version': '1.0',
+ 'Header Block': '222 bytes',
+ 'Authentication Block': '333 bytes',
+ 'Auxiliary Block': '888 bytes',
+ 'Public key (sha1)': 'abababababababababababababababababababab',
+ 'Algorithm': 'SHA256_RSA2048',
+ 'Rollback Index': '0',
+ 'Flags': '0',
+ 'Rollback Index Location': '0',
+ 'Release String': "'avbtool 1.3.0'",
+ 'Descriptors': [
+ {
+ 'Hashtree descriptor': {
+ 'Version of dm-verity': '1',
+ 'Image Size': '8888888 bytes',
+ 'Tree Offset': '8888888',
+ 'Tree Size': '44444 bytes',
+ 'Data Block Size': '4444 bytes',
+ 'Hash Block Size': '4444 bytes',
+ 'FEC num roots': '0',
+ 'FEC offset': '0',
+ 'FEC size': '0 bytes',
+ 'Hash Algorithm': 'sha1',
+ 'Partition Name': 'partition-name',
+ 'Salt': 'cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd',
+ 'Root Digest': 'efefefefefefefefefefefefefefefefefef',
+ 'Flags': '0',
+ }
+ },
+ {
+ 'Prop': {
+ 'prop.key': 'prop.value',
+ }
+ },
+ ],
+ },
+ ParseAvbInfo(avb_info_string),
+ )
\ No newline at end of file