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

Commit d562ee0f authored by Omer Nebil Yaveroglu's avatar Omer Nebil Yaveroglu
Browse files

Modify the RuleBinarySerializer to identify the indexes per block.

This change required some changes in RuleIndexingDetailsIdentifier. Note
that this change caused some noop changes in RuleXmlSerializer.

A change to write the identified rules will be send separately as a
follow up.

Bug: 145493956
Test: atest FrameworksServicesTests:RuleIndexingDetailsIdentifierTest
Change-Id: Ia1ff123325b3a5226ece29f2377db900c17569ae
parent a39cf4f6
Loading
Loading
Loading
Loading
+48 −15
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import android.content.integrity.CompoundFormula;
import android.content.integrity.Formula;
import android.content.integrity.Rule;

import com.android.internal.util.Preconditions;
import com.android.server.integrity.IntegrityUtils;
import com.android.server.integrity.model.BitOutputStream;

@@ -46,10 +47,17 @@ import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;

/** A helper class to serialize rules from the {@link Rule} model to Binary representation. */
public class RuleBinarySerializer implements RuleSerializer {

    // The parsing time seems acceptable for 100 rules based on the tests in go/ic-rule-file-format.
    private static final int INDEXING_BLOCK_SIZE = 100;

    private static final String START_INDEXING_KEY = "START_KEY";
    private static final String END_INDEXING_KEY = "END_KEY";

    // Get the byte representation for a list of rules.
    @Override
    public byte[] serialize(List<Rule> rules, Optional<Integer> formatVersion)
@@ -66,25 +74,34 @@ public class RuleBinarySerializer implements RuleSerializer {
    // Get the byte representation for a list of rules, and write them to an output stream.
    @Override
    public void serialize(
            List<Rule> rules, Optional<Integer> formatVersion, OutputStream outputStream)
            List<Rule> rules, Optional<Integer> formatVersion, OutputStream originalOutputStream)
            throws RuleSerializeException {
        try {
            // Determine the indexing groups and the order of the rules within each indexed group.
            Map<Integer, List<Rule>> indexedRules =
            Map<Integer, TreeMap<String, List<Rule>>> indexedRules =
                    RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules);

            ByteTrackedOutputStream outputStream =
                    new ByteTrackedOutputStream(originalOutputStream);

            serializeRuleFileMetadata(formatVersion, outputStream);

            serializeIndexedRules(indexedRules.get(PACKAGE_NAME_INDEXED), outputStream);
            serializeIndexedRules(indexedRules.get(APP_CERTIFICATE_INDEXED), outputStream);
            serializeIndexedRules(indexedRules.get(NOT_INDEXED), outputStream);
            Map<String, Long> packageNameIndexes =
                    serializeRuleList(indexedRules.get(PACKAGE_NAME_INDEXED), outputStream);
            Map<String, Long> appCertificateIndexes =
                    serializeRuleList(indexedRules.get(APP_CERTIFICATE_INDEXED), outputStream);
            Map<String, Long> unindexedRulesIndex =
                    serializeRuleList(indexedRules.get(NOT_INDEXED), outputStream);

            // TODO(b/145493956): Write these indexes into a index file provided by integrity file
            //  manager.
        } catch (Exception e) {
            throw new RuleSerializeException(e.getMessage(), e);
        }
    }

    private void serializeRuleFileMetadata(
            Optional<Integer> formatVersion, OutputStream outputStream) throws IOException {
    private void serializeRuleFileMetadata(Optional<Integer> formatVersion,
            ByteTrackedOutputStream outputStream) throws IOException {
        int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION);

        BitOutputStream bitOutputStream = new BitOutputStream();
@@ -92,18 +109,34 @@ public class RuleBinarySerializer implements RuleSerializer {
        outputStream.write(bitOutputStream.toByteArray());
    }

    private void serializeIndexedRules(List<Rule> rules, OutputStream outputStream)
    private Map<String, Long> serializeRuleList(TreeMap<String, List<Rule>> rulesMap,
            ByteTrackedOutputStream outputStream)
            throws IOException {
        if (rules == null) {
            return;
        }
        Preconditions.checkArgument(rulesMap != null,
                "serializeRuleList should never be called with null rule list.");

        BitOutputStream bitOutputStream = new BitOutputStream();
        for (Rule rule : rules) {
        Map<String, Long> indexMapping = new TreeMap();
        long indexTracker = 0;

        indexMapping.put(START_INDEXING_KEY, outputStream.getWrittenBytesCount());
        for (Map.Entry<String, List<Rule>> entry : rulesMap.entrySet()) {
            if (indexTracker >= INDEXING_BLOCK_SIZE) {
                indexMapping.put(entry.getKey(), outputStream.getWrittenBytesCount());
                indexTracker = 0;
            }

            for (Rule rule : entry.getValue()) {
                bitOutputStream.clear();
                serializeRule(rule, bitOutputStream);
                outputStream.write(bitOutputStream.toByteArray());
                indexTracker++;
            }
        }
        indexMapping.put(END_INDEXING_KEY, outputStream.getWrittenBytesCount());

        return indexMapping;
    }

    private void serializeRule(Rule rule, BitOutputStream bitOutputStream) {
        if (rule == null) {
+8 −24
Original line number Diff line number Diff line
@@ -38,23 +38,19 @@ class RuleIndexingDetailsIdentifier {
    private static final String DEFAULT_RULE_KEY = "N/A";

    /**
     * Splits a given rule list into three indexing categories and returns a sorted list of rules
     * per each index.
     *
     * The sorting guarantees an order based on the key but the rules that have the same key
     * can be in arbitrary order. For example, given the rules of [package_name_a_rule_1,
     * package_name_a_rule_2, package_name_b_rule_3, package_name_b_rule_4], the  method will
     * guarantee that package_name_b rules (i.e., 3 and 4) will never come before package_name_a
     * rules (i.e., 1 and 2). However, we do not care about the ordering between rule 1 and 2.
     * We also do not care about the ordering between rule 3 and 4.
     * Splits a given rule list into three indexing categories. Each rule category is returned as a
     * TreeMap that is sorted by their indexing keys -- where keys correspond to package name for
     * PACKAGE_NAME_INDEXED rules, app certificate for APP_CERTIFICATE_INDEXED rules and N/A for
     * NOT_INDEXED rules.
     */
    public static Map<Integer, List<Rule>> splitRulesIntoIndexBuckets(List<Rule> rules) {
    public static Map<Integer, TreeMap<String, List<Rule>>> splitRulesIntoIndexBuckets(
            List<Rule> rules) {
        if (rules == null) {
            throw new IllegalArgumentException(
                    "Index buckets cannot be created for null rule list.");
        }

        Map<Integer, Map<String, List<Rule>>> typeOrganizedRuleMap = new HashMap();
        Map<Integer, TreeMap<String, List<Rule>>> typeOrganizedRuleMap = new HashMap();
        typeOrganizedRuleMap.put(NOT_INDEXED, new TreeMap());
        typeOrganizedRuleMap.put(PACKAGE_NAME_INDEXED, new TreeMap());
        typeOrganizedRuleMap.put(APP_CERTIFICATE_INDEXED, new TreeMap());
@@ -87,19 +83,7 @@ class RuleIndexingDetailsIdentifier {
                    .add(rule);
        }

        // Per indexing type, create the sorted rule set based on their key.
        Map<Integer, List<Rule>> orderedListPerIndexingType = new HashMap<>();

        for (Integer indexingKey : typeOrganizedRuleMap.keySet()) {
            List<Rule> sortedRules = new ArrayList();
            for (Map.Entry<String, List<Rule>> entry :
                    typeOrganizedRuleMap.get(indexingKey).entrySet()) {
                sortedRules.addAll(entry.getValue());
            }
            orderedListPerIndexingType.put(indexingKey, sortedRules);
        }

        return orderedListPerIndexingType;
        return typeOrganizedRuleMap;
    }

    private static RuleIndexingDetails getIndexingDetails(Formula formula) {
+8 −4
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;

/** A helper class to serialize rules from the {@link Rule} model to Xml representation. */
public class RuleXmlSerializer implements RuleSerializer {
@@ -84,7 +85,7 @@ public class RuleXmlSerializer implements RuleSerializer {
            throws RuleSerializeException {
        try {
            // Determine the indexing groups and the order of the rules within each indexed group.
            Map<Integer, List<Rule>> indexedRules =
            Map<Integer, TreeMap<String, List<Rule>>> indexedRules =
                    RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules);

            // Write the XML formatted rules in order.
@@ -101,12 +102,15 @@ public class RuleXmlSerializer implements RuleSerializer {
        }
    }

    private void serializeRuleList(List<Rule> rules, XmlSerializer xmlSerializer)
    private void serializeRuleList(TreeMap<String, List<Rule>> rulesMap,
            XmlSerializer xmlSerializer)
            throws IOException {
        for (Rule rule : rules) {
        for (Map.Entry<String, List<Rule>> entry : rulesMap.entrySet()) {
            for (Rule rule : entry.getValue()) {
                serializeRule(rule, xmlSerializer);
            }
        }
    }

    private void serializeRule(Rule rule, XmlSerializer xmlSerializer) throws IOException {
        if (rule == null) {
+42 −19
Original line number Diff line number Diff line
@@ -38,8 +38,10 @@ import org.junit.runners.JUnit4;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/** Unit tests for {@link RuleIndexingDetailsIdentifier}. */
@RunWith(JUnit4.class)
@@ -138,14 +140,16 @@ public class RuleIndexingDetailsIdentifierTest {
        List<Rule> ruleList = new ArrayList();
        ruleList.add(RULE_WITH_PACKAGE_NAME);

        Map<Integer, List<Rule>> result = splitRulesIntoIndexBuckets(ruleList);
        Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);

        // Verify the resulting map content.
        assertThat(result.keySet())
                .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
        assertThat(result.get(NOT_INDEXED)).isEmpty();
        assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
        assertThat(result.get(PACKAGE_NAME_INDEXED)).containsExactly(RULE_WITH_PACKAGE_NAME);
        assertThat(result.get(PACKAGE_NAME_INDEXED).keySet()).containsExactly(SAMPLE_PACKAGE_NAME);
        assertThat(result.get(PACKAGE_NAME_INDEXED).get(SAMPLE_PACKAGE_NAME))
                .containsExactly(RULE_WITH_PACKAGE_NAME);
    }

    @Test
@@ -153,13 +157,16 @@ public class RuleIndexingDetailsIdentifierTest {
        List<Rule> ruleList = new ArrayList();
        ruleList.add(RULE_WITH_APP_CERTIFICATE);

        Map<Integer, List<Rule>> result = splitRulesIntoIndexBuckets(ruleList);
        Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);

        assertThat(result.keySet())
                .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
        assertThat(result.get(NOT_INDEXED)).isEmpty();
        assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
        assertThat(result.get(APP_CERTIFICATE_INDEXED)).containsExactly(RULE_WITH_APP_CERTIFICATE);
        assertThat(result.get(APP_CERTIFICATE_INDEXED).keySet())
                .containsExactly(SAMPLE_APP_CERTIFICATE);
        assertThat(result.get(APP_CERTIFICATE_INDEXED).get(SAMPLE_APP_CERTIFICATE))
                .containsExactly(RULE_WITH_APP_CERTIFICATE);
    }

    @Test
@@ -167,13 +174,14 @@ public class RuleIndexingDetailsIdentifierTest {
        List<Rule> ruleList = new ArrayList();
        ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS);

        Map<Integer, List<Rule>> result = splitRulesIntoIndexBuckets(ruleList);
        Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);

        assertThat(result.keySet())
                .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
        assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
        assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
        assertThat(result.get(NOT_INDEXED)).containsExactly(RULE_WITH_INSTALLER_RESTRICTIONS);
        assertThat(result.get(NOT_INDEXED).get("N/A"))
                .containsExactly(RULE_WITH_INSTALLER_RESTRICTIONS);
    }

    @Test
@@ -181,13 +189,14 @@ public class RuleIndexingDetailsIdentifierTest {
        List<Rule> ruleList = new ArrayList();
        ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS);

        Map<Integer, List<Rule>> result = splitRulesIntoIndexBuckets(ruleList);
        Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);

        assertThat(result.keySet())
                .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
        assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
        assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
        assertThat(result.get(NOT_INDEXED)).containsExactly(RULE_WITH_NONSTRING_RESTRICTIONS);
        assertThat(result.get(NOT_INDEXED).get("N/A"))
                .containsExactly(RULE_WITH_NONSTRING_RESTRICTIONS);
    }

    @Test
@@ -206,13 +215,13 @@ public class RuleIndexingDetailsIdentifierTest {
        List<Rule> ruleList = new ArrayList();
        ruleList.add(negatedRule);

        Map<Integer, List<Rule>> result = splitRulesIntoIndexBuckets(ruleList);
        Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);

        assertThat(result.keySet())
                .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
        assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
        assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
        assertThat(result.get(NOT_INDEXED)).containsExactly(negatedRule);
        assertThat(result.get(NOT_INDEXED).get("N/A")).containsExactly(negatedRule);
    }

    @Test
@@ -234,22 +243,36 @@ public class RuleIndexingDetailsIdentifierTest {
        ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS);
        ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS);

        Map<Integer, List<Rule>> result = splitRulesIntoIndexBuckets(ruleList);
        Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);

        assertThat(result.keySet())
                .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);

        // We check asserts this way to ensure ordering based on package name.
        assertThat(result.get(PACKAGE_NAME_INDEXED).get(0)).isEqualTo(packageNameRuleA);
        assertThat(result.get(PACKAGE_NAME_INDEXED).get(1)).isEqualTo(packageNameRuleB);
        assertThat(result.get(PACKAGE_NAME_INDEXED).get(2)).isEqualTo(packageNameRuleC);
        assertThat(result.get(PACKAGE_NAME_INDEXED).keySet()).containsExactly("aaa", "bbb", "ccc");
        Iterator<String> keySetIterator = result.get(PACKAGE_NAME_INDEXED).keySet().iterator();
        assertThat(keySetIterator.next()).isEqualTo("aaa");
        assertThat(keySetIterator.next()).isEqualTo("bbb");
        assertThat(keySetIterator.next()).isEqualTo("ccc");
        assertThat(result.get(PACKAGE_NAME_INDEXED).get("aaa")).containsExactly(packageNameRuleA);
        assertThat(result.get(PACKAGE_NAME_INDEXED).get("bbb")).containsExactly(packageNameRuleB);
        assertThat(result.get(PACKAGE_NAME_INDEXED).get("ccc")).containsExactly(packageNameRuleC);

        // We check asserts this way to ensure ordering based on app certificate.
        assertThat(result.get(APP_CERTIFICATE_INDEXED).get(0)).isEqualTo(certificateRule1);
        assertThat(result.get(APP_CERTIFICATE_INDEXED).get(1)).isEqualTo(certificateRule2);
        assertThat(result.get(APP_CERTIFICATE_INDEXED).get(2)).isEqualTo(certificateRule3);

        assertThat(result.get(NOT_INDEXED))
        assertThat(result.get(APP_CERTIFICATE_INDEXED).keySet()).containsExactly("cert1", "cert2",
                "cert3");
        keySetIterator = result.get(APP_CERTIFICATE_INDEXED).keySet().iterator();
        assertThat(keySetIterator.next()).isEqualTo("cert1");
        assertThat(keySetIterator.next()).isEqualTo("cert2");
        assertThat(keySetIterator.next()).isEqualTo("cert3");
        assertThat(result.get(APP_CERTIFICATE_INDEXED).get("cert1")).containsExactly(
                certificateRule1);
        assertThat(result.get(APP_CERTIFICATE_INDEXED).get("cert2")).containsExactly(
                certificateRule2);
        assertThat(result.get(APP_CERTIFICATE_INDEXED).get("cert3")).containsExactly(
                certificateRule3);

        assertThat(result.get(NOT_INDEXED).get("N/A"))
                .containsExactly(
                        RULE_WITH_INSTALLER_RESTRICTIONS,
                        RULE_WITH_NONSTRING_RESTRICTIONS);