Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c505dbfa authored by Marybeth Fair's avatar Marybeth Fair
Browse files

Refactor fingerprint logic.

Breaking up my larger CL aosp/3322996, this is needed to make the
fingerprint work on the information we have.

Test: atest
Change-Id: I930241019ccdb9a8ea85049b493bbbde26434a95
parent cf2c4319
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -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: [
+62 −16
Original line number Diff line number Diff line
@@ -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;
@@ -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::*;
@@ -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
+18 −0
Original line number Diff line number Diff line
@@ -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());
+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"
}
+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
}