Loading tools/aconfig/aconfig/Android.bp +8 −0 Original line number Diff line number Diff line Loading @@ -68,6 +68,14 @@ aconfig_values { ], } aconfig_values { name: "aconfig.test.flag.second_values", package: "com.android.aconfig.test", srcs: [ "tests/third.values", ], } aconfig_value_set { name: "aconfig.test.flag.value_set", values: [ Loading tools/aconfig/aconfig/src/commands.rs +62 −16 Original line number Diff line number Diff line Loading @@ -17,7 +17,7 @@ use anyhow::{bail, ensure, Context, Result}; use itertools::Itertools; use protobuf::Message; use std::collections::{BTreeMap, HashMap}; use std::collections::HashMap; use std::hash::Hasher; use std::io::Read; use std::path::PathBuf; Loading Loading @@ -425,23 +425,34 @@ where #[allow(dead_code)] // TODO: b/316357686 - Use fingerprint in codegen to // protect hardcoded offset reads. pub fn compute_flag_offsets_fingerprint(flags_map: &HashMap<String, u16>) -> Result<u64> { let mut hasher = SipHasher13::new(); // Need to sort to ensure the data is added to the hasher in the same order // each run. let sorted_map: BTreeMap<&String, &u16> = flags_map.iter().collect(); // Creates a fingerprint of the flag names (which requires sorting the vector). // Fingerprint is used by both codegen and storage files. pub fn compute_flags_fingerprint(flag_names: &mut Vec<String>) -> Result<u64> { flag_names.sort(); for (flag, offset) in sorted_map { // See https://docs.rs/siphasher/latest/siphasher/#note for use of write // over write_i16. Similarly, use to_be_bytes rather than to_ne_bytes to // ensure consistency. let mut hasher = SipHasher13::new(); for flag in flag_names { hasher.write(flag.as_bytes()); hasher.write(&offset.to_be_bytes()); } Ok(hasher.finish()) } #[allow(dead_code)] // TODO: b/316357686 - Use fingerprint in codegen to // protect hardcoded offset reads. // Converts ProtoParsedFlags into a vector of strings containing all of the flag // names. Helper fn for creating fingerprint for codegen files. Flags must all // belong to the same package. fn extract_flag_names(flags: ProtoParsedFlags) -> Result<Vec<String>> { let separated_flags: Vec<ProtoParsedFlag> = flags.parsed_flag.into_iter().collect::<Vec<_>>(); // All flags must belong to the same package as the fingerprint is per-package. let Some(_package) = find_unique_package(&separated_flags) else { bail!("No parsed flags, or the parsed flags use different packages."); }; Ok(separated_flags.into_iter().map(|flag| flag.name.unwrap()).collect::<Vec<_>>()) } #[cfg(test)] mod tests { use super::*; Loading @@ -450,15 +461,50 @@ mod tests { #[test] fn test_offset_fingerprint() { let parsed_flags = crate::test::parse_test_flags(); let package = find_unique_package(&parsed_flags.parsed_flag).unwrap().to_string(); let flag_ids = assign_flag_ids(&package, parsed_flags.parsed_flag.iter()).unwrap(); let expected_fingerprint = 10709892481002252132u64; let expected_fingerprint: u64 = 5801144784618221668; let hash_result = compute_flag_offsets_fingerprint(&flag_ids); let mut extracted_flags = extract_flag_names(parsed_flags).unwrap(); let hash_result = compute_flags_fingerprint(&mut extracted_flags); assert_eq!(hash_result.unwrap(), expected_fingerprint); } #[test] fn test_offset_fingerprint_matches_from_package() { let parsed_flags: ProtoParsedFlags = crate::test::parse_test_flags(); // All test flags are in the same package, so fingerprint from all of them. let mut extracted_flags = extract_flag_names(parsed_flags.clone()).unwrap(); let result_from_parsed_flags = compute_flags_fingerprint(&mut extracted_flags); let mut flag_names_vec = parsed_flags .parsed_flag .clone() .into_iter() .map(|flag| flag.name.unwrap()) .map(String::from) .collect::<Vec<_>>(); let result_from_names = compute_flags_fingerprint(&mut flag_names_vec); // Assert the same hash is generated for each case. assert_eq!(result_from_parsed_flags.unwrap(), result_from_names.unwrap()); } #[test] fn test_offset_fingerprint_different_packages_does_not_match() { // Parse flags from two packages. let parsed_flags: ProtoParsedFlags = crate::test::parse_test_flags(); let second_parsed_flags = crate::test::parse_second_package_flags(); let mut extracted_flags = extract_flag_names(parsed_flags).unwrap(); let result_from_parsed_flags = compute_flags_fingerprint(&mut extracted_flags).unwrap(); let mut second_extracted_flags = extract_flag_names(second_parsed_flags).unwrap(); let second_result = compute_flags_fingerprint(&mut second_extracted_flags).unwrap(); // Different flags should have a different fingerprint. assert_ne!(result_from_parsed_flags, second_result); } #[test] fn test_parse_flags() { let parsed_flags = crate::test::parse_test_flags(); // calls parse_flags Loading tools/aconfig/aconfig/src/test.rs +18 −0 Original line number Diff line number Diff line Loading @@ -295,6 +295,24 @@ parsed_flag { aconfig_protos::parsed_flags::try_from_binary_proto(&bytes).unwrap() } pub fn parse_second_package_flags() -> ProtoParsedFlags { let bytes = crate::commands::parse_flags( "com.android.aconfig.second_test", Some("system"), vec![Input { source: "tests/test_second_package.aconfig".to_string(), reader: Box::new(include_bytes!("../tests/test_second_package.aconfig").as_slice()), }], vec![Input { source: "tests/third.values".to_string(), reader: Box::new(include_bytes!("../tests/third.values").as_slice()), }], crate::commands::DEFAULT_FLAG_PERMISSION, ) .unwrap(); aconfig_protos::parsed_flags::try_from_binary_proto(&bytes).unwrap() } pub fn first_significant_code_diff(a: &str, b: &str) -> Option<String> { let a = a.lines().map(|line| line.trim_start()).filter(|line| !line.is_empty()); let b = b.lines().map(|line| line.trim_start()).filter(|line| !line.is_empty()); Loading tools/aconfig/aconfig/tests/test_second_package.aconfig 0 → 100644 +10 −0 Original line number Diff line number Diff line package: "com.android.aconfig.second_test" container: "system" flag { name: "testing_flag" namespace: "another_namespace" description: "This is a flag for testing." bug: "123" } tools/aconfig/aconfig/tests/third.values 0 → 100644 +6 −0 Original line number Diff line number Diff line flag_value { package: "com.android.aconfig.second_test" name: "testing_flag" state: DISABLED permission: READ_WRITE } Loading
tools/aconfig/aconfig/Android.bp +8 −0 Original line number Diff line number Diff line Loading @@ -68,6 +68,14 @@ aconfig_values { ], } aconfig_values { name: "aconfig.test.flag.second_values", package: "com.android.aconfig.test", srcs: [ "tests/third.values", ], } aconfig_value_set { name: "aconfig.test.flag.value_set", values: [ Loading
tools/aconfig/aconfig/src/commands.rs +62 −16 Original line number Diff line number Diff line Loading @@ -17,7 +17,7 @@ use anyhow::{bail, ensure, Context, Result}; use itertools::Itertools; use protobuf::Message; use std::collections::{BTreeMap, HashMap}; use std::collections::HashMap; use std::hash::Hasher; use std::io::Read; use std::path::PathBuf; Loading Loading @@ -425,23 +425,34 @@ where #[allow(dead_code)] // TODO: b/316357686 - Use fingerprint in codegen to // protect hardcoded offset reads. pub fn compute_flag_offsets_fingerprint(flags_map: &HashMap<String, u16>) -> Result<u64> { let mut hasher = SipHasher13::new(); // Need to sort to ensure the data is added to the hasher in the same order // each run. let sorted_map: BTreeMap<&String, &u16> = flags_map.iter().collect(); // Creates a fingerprint of the flag names (which requires sorting the vector). // Fingerprint is used by both codegen and storage files. pub fn compute_flags_fingerprint(flag_names: &mut Vec<String>) -> Result<u64> { flag_names.sort(); for (flag, offset) in sorted_map { // See https://docs.rs/siphasher/latest/siphasher/#note for use of write // over write_i16. Similarly, use to_be_bytes rather than to_ne_bytes to // ensure consistency. let mut hasher = SipHasher13::new(); for flag in flag_names { hasher.write(flag.as_bytes()); hasher.write(&offset.to_be_bytes()); } Ok(hasher.finish()) } #[allow(dead_code)] // TODO: b/316357686 - Use fingerprint in codegen to // protect hardcoded offset reads. // Converts ProtoParsedFlags into a vector of strings containing all of the flag // names. Helper fn for creating fingerprint for codegen files. Flags must all // belong to the same package. fn extract_flag_names(flags: ProtoParsedFlags) -> Result<Vec<String>> { let separated_flags: Vec<ProtoParsedFlag> = flags.parsed_flag.into_iter().collect::<Vec<_>>(); // All flags must belong to the same package as the fingerprint is per-package. let Some(_package) = find_unique_package(&separated_flags) else { bail!("No parsed flags, or the parsed flags use different packages."); }; Ok(separated_flags.into_iter().map(|flag| flag.name.unwrap()).collect::<Vec<_>>()) } #[cfg(test)] mod tests { use super::*; Loading @@ -450,15 +461,50 @@ mod tests { #[test] fn test_offset_fingerprint() { let parsed_flags = crate::test::parse_test_flags(); let package = find_unique_package(&parsed_flags.parsed_flag).unwrap().to_string(); let flag_ids = assign_flag_ids(&package, parsed_flags.parsed_flag.iter()).unwrap(); let expected_fingerprint = 10709892481002252132u64; let expected_fingerprint: u64 = 5801144784618221668; let hash_result = compute_flag_offsets_fingerprint(&flag_ids); let mut extracted_flags = extract_flag_names(parsed_flags).unwrap(); let hash_result = compute_flags_fingerprint(&mut extracted_flags); assert_eq!(hash_result.unwrap(), expected_fingerprint); } #[test] fn test_offset_fingerprint_matches_from_package() { let parsed_flags: ProtoParsedFlags = crate::test::parse_test_flags(); // All test flags are in the same package, so fingerprint from all of them. let mut extracted_flags = extract_flag_names(parsed_flags.clone()).unwrap(); let result_from_parsed_flags = compute_flags_fingerprint(&mut extracted_flags); let mut flag_names_vec = parsed_flags .parsed_flag .clone() .into_iter() .map(|flag| flag.name.unwrap()) .map(String::from) .collect::<Vec<_>>(); let result_from_names = compute_flags_fingerprint(&mut flag_names_vec); // Assert the same hash is generated for each case. assert_eq!(result_from_parsed_flags.unwrap(), result_from_names.unwrap()); } #[test] fn test_offset_fingerprint_different_packages_does_not_match() { // Parse flags from two packages. let parsed_flags: ProtoParsedFlags = crate::test::parse_test_flags(); let second_parsed_flags = crate::test::parse_second_package_flags(); let mut extracted_flags = extract_flag_names(parsed_flags).unwrap(); let result_from_parsed_flags = compute_flags_fingerprint(&mut extracted_flags).unwrap(); let mut second_extracted_flags = extract_flag_names(second_parsed_flags).unwrap(); let second_result = compute_flags_fingerprint(&mut second_extracted_flags).unwrap(); // Different flags should have a different fingerprint. assert_ne!(result_from_parsed_flags, second_result); } #[test] fn test_parse_flags() { let parsed_flags = crate::test::parse_test_flags(); // calls parse_flags Loading
tools/aconfig/aconfig/src/test.rs +18 −0 Original line number Diff line number Diff line Loading @@ -295,6 +295,24 @@ parsed_flag { aconfig_protos::parsed_flags::try_from_binary_proto(&bytes).unwrap() } pub fn parse_second_package_flags() -> ProtoParsedFlags { let bytes = crate::commands::parse_flags( "com.android.aconfig.second_test", Some("system"), vec![Input { source: "tests/test_second_package.aconfig".to_string(), reader: Box::new(include_bytes!("../tests/test_second_package.aconfig").as_slice()), }], vec![Input { source: "tests/third.values".to_string(), reader: Box::new(include_bytes!("../tests/third.values").as_slice()), }], crate::commands::DEFAULT_FLAG_PERMISSION, ) .unwrap(); aconfig_protos::parsed_flags::try_from_binary_proto(&bytes).unwrap() } pub fn first_significant_code_diff(a: &str, b: &str) -> Option<String> { let a = a.lines().map(|line| line.trim_start()).filter(|line| !line.is_empty()); let b = b.lines().map(|line| line.trim_start()).filter(|line| !line.is_empty()); Loading
tools/aconfig/aconfig/tests/test_second_package.aconfig 0 → 100644 +10 −0 Original line number Diff line number Diff line package: "com.android.aconfig.second_test" container: "system" flag { name: "testing_flag" namespace: "another_namespace" description: "This is a flag for testing." bug: "123" }
tools/aconfig/aconfig/tests/third.values 0 → 100644 +6 −0 Original line number Diff line number Diff line flag_value { package: "com.android.aconfig.second_test" name: "testing_flag" state: DISABLED permission: READ_WRITE }