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

Commit 0efe7cbc authored by Khaled Abdelmohsen's avatar Khaled Abdelmohsen
Browse files

Implement XML serializing for rule

Add rule serializers for XML format which accepts serializing into
strings or output streams.

Bug: 143697198
Test: atest FrameworksServicesTests:RuleXmlSerializerTest
Change-Id: I15a16bbbfb992f0b1242ad68ecc7c383d334f6f4
parent 21a96050
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -19,17 +19,18 @@ package com.android.server.integrity.serializer;
import com.android.server.integrity.model.Rule;

import java.io.OutputStream;
import java.util.List;

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

    @Override
    public void serialize(Rule rule, OutputStream outputStream) {
    public void serialize(List<Rule> rules, OutputStream outputStream) {
        // TODO: Implement stream serializer.
    }

    @Override
    public String serialize(Rule rule) {
    public String serialize(List<Rule> rules) {
        // TODO: Implement text serializer.
        return null;
    }
+32 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.integrity.serializer;

import android.annotation.NonNull;

/**
 * Thrown when rule serialization fails.
 */
public class RuleSerializeException extends Exception {
    public RuleSerializeException(@NonNull String message) {
        super(message);
    }

    public RuleSerializeException(@NonNull String message, @NonNull Throwable cause) {
        super(message, cause);
    }
}
+5 −4
Original line number Diff line number Diff line
@@ -19,13 +19,14 @@ package com.android.server.integrity.serializer;
import com.android.server.integrity.model.Rule;

import java.io.OutputStream;
import java.util.List;

/** A helper class to serialize rules from the {@link Rule} model. */
public interface RuleSerializer {

    /** Serialize a rule to an output stream */
    void serialize(Rule rule, OutputStream outputStream);
    /** Serialize rules to an output stream */
    void serialize(List<Rule> rules, OutputStream outputStream) throws RuleSerializeException;

    /** Serialize a rule to a string. */
    String serialize(Rule rule);
    /** Serialize rules to a string. */
    String serialize(List<Rule> rule) throws RuleSerializeException;
}
+128 −6
Original line number Diff line number Diff line
@@ -16,21 +16,143 @@

package com.android.server.integrity.serializer;

import android.util.Xml;

import com.android.server.integrity.model.AtomicFormula;
import com.android.server.integrity.model.Formula;
import com.android.server.integrity.model.OpenFormula;
import com.android.server.integrity.model.Rule;

import org.xmlpull.v1.XmlSerializer;

import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.List;

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

    public static final String TAG = "RuleXmlSerializer";
    private static final String NAMESPACE = "";

    // TODO: Use smaller tag names.
    private static final String RULE_LIST_TAG = "RuleList";
    private static final String RULE_TAG = "Rule";
    private static final String OPEN_FORMULA_TAG = "OpenFormula";
    private static final String ATOMIC_FORMULA_TAG = "AtomicFormula";
    private static final String EFFECT_TAG = "Effect";
    private static final String KEY_TAG = "Key";
    private static final String OPERATOR_TAG = "Operator";
    private static final String VALUE_TAG = "Value";
    private static final String CONNECTOR_TAG = "Connector";

    @Override
    public void serialize(Rule rule, OutputStream outputStream) {
        // TODO: Implement stream serializer.
    public void serialize(List<Rule> rules, OutputStream outputStream)
            throws RuleSerializeException {
        try {
            XmlSerializer xmlSerializer = Xml.newSerializer();
            xmlSerializer.setOutput(outputStream, StandardCharsets.UTF_8.name());
            serializeRules(rules, xmlSerializer);
        } catch (Exception e) {
            throw new RuleSerializeException(e.getMessage(), e);
        }
    }

    @Override
    public String serialize(Rule rule) {
        // TODO: Implement text serializer.
        return null;
    public String serialize(List<Rule> rules) throws RuleSerializeException {
        try {
            XmlSerializer xmlSerializer = Xml.newSerializer();
            StringWriter writer = new StringWriter();
            xmlSerializer.setOutput(writer);
            serializeRules(rules, xmlSerializer);
            return writer.toString();
        } catch (Exception e) {
            throw new RuleSerializeException(e.getMessage(), e);
        }
    }

    private void serializeRules(List<Rule> rules, XmlSerializer xmlSerializer) throws IOException {
        xmlSerializer.startTag(NAMESPACE, RULE_LIST_TAG);
        for (Rule rule : rules) {
            serialize(rule, xmlSerializer);
        }
        xmlSerializer.endTag(NAMESPACE, RULE_LIST_TAG);
        xmlSerializer.endDocument();
    }

    private void serialize(Rule rule, XmlSerializer xmlSerializer) throws IOException {
        if (rule == null) {
            return;
        }
        xmlSerializer.startTag(NAMESPACE, RULE_TAG);
        serializeFormula(rule.getFormula(), xmlSerializer);
        serializeValue(EFFECT_TAG, String.valueOf(rule.getEffect()), xmlSerializer);
        xmlSerializer.endTag(NAMESPACE, RULE_TAG);
    }

    private void serializeFormula(Formula formula, XmlSerializer xmlSerializer) throws IOException {
        if (formula instanceof AtomicFormula) {
            serializeAtomicFormula((AtomicFormula) formula, xmlSerializer);
        } else if (formula instanceof OpenFormula) {
            serializeOpenFormula((OpenFormula) formula, xmlSerializer);
        } else {
            throw new IllegalArgumentException(
                    String.format("Invalid formula type: %s", formula.getClass()));
        }
    }

    private void serializeOpenFormula(OpenFormula openFormula, XmlSerializer xmlSerializer)
            throws IOException {
        if (openFormula == null) {
            return;
        }
        xmlSerializer.startTag(NAMESPACE, OPEN_FORMULA_TAG);
        serializeValue(CONNECTOR_TAG, String.valueOf(openFormula.getConnector()), xmlSerializer);
        for (Formula formula : openFormula.getFormulas()) {
            serializeFormula(formula, xmlSerializer);
        }
        xmlSerializer.endTag(NAMESPACE, OPEN_FORMULA_TAG);
    }

    private void serializeAtomicFormula(AtomicFormula atomicFormula, XmlSerializer xmlSerializer)
            throws IOException {
        if (atomicFormula == null) {
            return;
        }
        xmlSerializer.startTag(NAMESPACE, ATOMIC_FORMULA_TAG);
        serializeValue(KEY_TAG, String.valueOf(atomicFormula.getKey()), xmlSerializer);
        if (atomicFormula instanceof AtomicFormula.StringAtomicFormula) {
            serializeValue(VALUE_TAG,
                    ((AtomicFormula.StringAtomicFormula) atomicFormula).getValue(), xmlSerializer);
        } else if (atomicFormula instanceof AtomicFormula.IntAtomicFormula) {
            serializeValue(OPERATOR_TAG,
                    String.valueOf(((AtomicFormula.IntAtomicFormula) atomicFormula).getOperator()),
                    xmlSerializer);
            serializeValue(VALUE_TAG,
                    String.valueOf(((AtomicFormula.IntAtomicFormula) atomicFormula).getValue()),
                    xmlSerializer);
        } else if (atomicFormula instanceof AtomicFormula.BooleanAtomicFormula) {
            serializeValue(VALUE_TAG,
                    String.valueOf(((AtomicFormula.BooleanAtomicFormula) atomicFormula).getValue()),
                    xmlSerializer);
        } else {
            throw new IllegalArgumentException(
                    String.format("Invalid atomic formula type: %s", atomicFormula.getClass()));
        }
        xmlSerializer.endTag(NAMESPACE, ATOMIC_FORMULA_TAG);
    }

    private void serializeValue(String tag, String value, XmlSerializer xmlSerializer)
            throws IOException {
        if (value == null) {
            return;
        }
        xmlSerializer.startTag(NAMESPACE, tag);
        xmlSerializer.text(value);
        xmlSerializer.endTag(NAMESPACE, tag);
    }
}
+298 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.integrity.serializer;

import static com.android.server.testutils.TestUtils.assertExpectException;

import static org.junit.Assert.assertEquals;

import androidx.annotation.NonNull;

import com.android.server.integrity.model.AppInstallMetadata;
import com.android.server.integrity.model.AtomicFormula;
import com.android.server.integrity.model.Formula;
import com.android.server.integrity.model.OpenFormula;
import com.android.server.integrity.model.Rule;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collections;

@RunWith(JUnit4.class)
public class RuleXmlSerializerTest {

    @Test
    public void testXmlString_serializeEmptyRule() throws Exception {
        Rule rule = null;
        RuleSerializer xmlSerializer = new RuleXmlSerializer();
        String expectedRules = "<RuleList />";

        String actualRules = xmlSerializer.serialize(Collections.singletonList(rule));

        assertEquals(expectedRules, actualRules);
    }

    @Test
    public void testXmlString_serializeMultipleRules_oneEmpty() throws Exception {
        Rule rule1 = null;
        Rule rule2 = new Rule(
                new AtomicFormula.StringAtomicFormula(AtomicFormula.PACKAGE_NAME, "com.app.test"),
                Rule.DENY);
        RuleSerializer xmlSerializer = new RuleXmlSerializer();
        String expectedRules = "<RuleList>"
                + "<Rule>"
                + "<AtomicFormula>"
                + "<Key>" + AtomicFormula.PACKAGE_NAME + "</Key>"
                + "<Value>com.app.test</Value>"
                + "</AtomicFormula>"
                + "<Effect>" + Rule.DENY + "</Effect>"
                + "</Rule>"
                + "</RuleList>";

        String actualRules = xmlSerializer.serialize(Arrays.asList(rule1, rule2));

        assertEquals(expectedRules, actualRules);
    }

    @Test
    public void testXmlStream_serializeValidOpenFormula() throws Exception {
        Rule rule = new Rule(new OpenFormula(OpenFormula.NOT,
                Collections.singletonList(
                        new AtomicFormula.StringAtomicFormula(AtomicFormula.PACKAGE_NAME,
                                "com.app.test"))), Rule.DENY);
        RuleSerializer xmlSerializer = new RuleXmlSerializer();
        OutputStream outputStream = new ByteArrayOutputStream();
        String expectedRules = "<RuleList>"
                + "<Rule>"
                + "<OpenFormula>"
                + "<Connector>" + OpenFormula.NOT + "</Connector>"
                + "<AtomicFormula>"
                + "<Key>" + AtomicFormula.PACKAGE_NAME + "</Key>"
                + "<Value>com.app.test</Value>"
                + "</AtomicFormula>"
                + "</OpenFormula>"
                + "<Effect>" + Rule.DENY + "</Effect>"
                + "</Rule>"
                + "</RuleList>";

        xmlSerializer.serialize(Collections.singletonList(rule), outputStream);

        String actualRules = outputStream.toString();
        assertEquals(expectedRules, actualRules);
    }

    @Test
    public void testXmlString_serializeValidOpenFormula_notConnector() throws Exception {
        Rule rule = new Rule(new OpenFormula(OpenFormula.NOT,
                Collections.singletonList(
                        new AtomicFormula.StringAtomicFormula(AtomicFormula.PACKAGE_NAME,
                                "com.app.test"))), Rule.DENY);
        RuleSerializer xmlSerializer = new RuleXmlSerializer();
        String expectedRules = "<RuleList>"
                + "<Rule>"
                + "<OpenFormula>"
                + "<Connector>" + OpenFormula.NOT + "</Connector>"
                + "<AtomicFormula>"
                + "<Key>" + AtomicFormula.PACKAGE_NAME + "</Key>"
                + "<Value>com.app.test</Value>"
                + "</AtomicFormula>"
                + "</OpenFormula>"
                + "<Effect>" + Rule.DENY + "</Effect>"
                + "</Rule>"
                + "</RuleList>";

        String actualRules = xmlSerializer.serialize(Collections.singletonList(rule));

        assertEquals(expectedRules, actualRules);
    }

    @Test
    public void testXmlString_serializeValidOpenFormula_andConnector() throws Exception {
        Rule rule = new Rule(new OpenFormula(OpenFormula.AND,
                Arrays.asList(new AtomicFormula.StringAtomicFormula(AtomicFormula.PACKAGE_NAME,
                                "com.app.test"),
                        new AtomicFormula.StringAtomicFormula(AtomicFormula.APP_CERTIFICATE,
                                "test_cert"))), Rule.DENY);
        RuleSerializer xmlSerializer = new RuleXmlSerializer();
        String expectedRules = "<RuleList>"
                + "<Rule>"
                + "<OpenFormula>"
                + "<Connector>" + OpenFormula.AND + "</Connector>"
                + "<AtomicFormula>"
                + "<Key>" + AtomicFormula.PACKAGE_NAME + "</Key>"
                + "<Value>com.app.test</Value>"
                + "</AtomicFormula>"
                + "<AtomicFormula>"
                + "<Key>" + AtomicFormula.APP_CERTIFICATE + "</Key>"
                + "<Value>test_cert</Value>"
                + "</AtomicFormula>"
                + "</OpenFormula>"
                + "<Effect>" + Rule.DENY + "</Effect>"
                + "</Rule>"
                + "</RuleList>";

        String actualRules = xmlSerializer.serialize(Collections.singletonList(rule));

        assertEquals(expectedRules, actualRules);
    }

    @Test
    public void testXmlString_serializeValidOpenFormula_orConnector() throws Exception {
        Rule rule = new Rule(new OpenFormula(OpenFormula.OR,
                Arrays.asList(new AtomicFormula.StringAtomicFormula(AtomicFormula.PACKAGE_NAME,
                                "com.app.test"),
                        new AtomicFormula.StringAtomicFormula(AtomicFormula.APP_CERTIFICATE,
                                "test_cert"))), Rule.DENY);
        RuleSerializer xmlSerializer = new RuleXmlSerializer();
        String expectedRules = "<RuleList>"
                + "<Rule>"
                + "<OpenFormula>"
                + "<Connector>" + OpenFormula.OR + "</Connector>"
                + "<AtomicFormula>"
                + "<Key>" + AtomicFormula.PACKAGE_NAME + "</Key>"
                + "<Value>com.app.test</Value>"
                + "</AtomicFormula>"
                + "<AtomicFormula>"
                + "<Key>" + AtomicFormula.APP_CERTIFICATE + "</Key>"
                + "<Value>test_cert</Value>"
                + "</AtomicFormula>"
                + "</OpenFormula>"
                + "<Effect>" + Rule.DENY + "</Effect>"
                + "</Rule>"
                + "</RuleList>";

        String actualRules = xmlSerializer.serialize(Collections.singletonList(rule));

        assertEquals(expectedRules, actualRules);
    }

    @Test
    public void testXmlString_serializeValidAtomicFormula_stringValue() throws Exception {
        Rule rule = new Rule(
                new AtomicFormula.StringAtomicFormula(AtomicFormula.PACKAGE_NAME, "com.app.test"),
                Rule.DENY);
        RuleSerializer xmlSerializer = new RuleXmlSerializer();
        String expectedRules = "<RuleList>"
                + "<Rule>"
                + "<AtomicFormula>"
                + "<Key>" + AtomicFormula.PACKAGE_NAME + "</Key>"
                + "<Value>com.app.test</Value>"
                + "</AtomicFormula>"
                + "<Effect>" + Rule.DENY + "</Effect>"
                + "</Rule>"
                + "</RuleList>";

        String actualRules = xmlSerializer.serialize(Collections.singletonList(rule));

        assertEquals(expectedRules, actualRules);
    }

    @Test
    public void testXmlString_serializeValidAtomicFormula_integerValue() throws Exception {
        Rule rule = new Rule(
                new AtomicFormula.IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.EQ, 1),
                Rule.DENY);
        RuleSerializer xmlSerializer = new RuleXmlSerializer();
        String expectedRules = "<RuleList>"
                + "<Rule>"
                + "<AtomicFormula>"
                + "<Key>" + AtomicFormula.VERSION_CODE + "</Key>"
                + "<Operator>" + AtomicFormula.EQ + "</Operator>"
                + "<Value>1</Value>"
                + "</AtomicFormula>"
                + "<Effect>" + Rule.DENY + "</Effect>"
                + "</Rule>"
                + "</RuleList>";

        String actualRules = xmlSerializer.serialize(Collections.singletonList(rule));

        assertEquals(expectedRules, actualRules);
    }

    @Test
    public void testXmlString_serializeValidAtomicFormula_booleanValue() throws Exception {
        Rule rule = new Rule(
                new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true),
                Rule.DENY);
        RuleSerializer xmlSerializer = new RuleXmlSerializer();
        String expectedRules = "<RuleList>"
                + "<Rule>"
                + "<AtomicFormula>"
                + "<Key>" + AtomicFormula.PRE_INSTALLED + "</Key>"
                + "<Value>true</Value>"
                + "</AtomicFormula>"
                + "<Effect>" + Rule.DENY + "</Effect>"
                + "</Rule>"
                + "</RuleList>";

        String actualRules = xmlSerializer.serialize(Collections.singletonList(rule));

        assertEquals(expectedRules, actualRules);
    }

    @Test
    public void testXmlString_serializeInvalidFormulaType() throws Exception {
        Formula invalidFormula = getInvalidFormula();
        Rule rule = new Rule(invalidFormula, Rule.DENY);
        RuleSerializer xmlSerializer = new RuleXmlSerializer();

        assertExpectException(
                RuleSerializeException.class,
                /* expectedExceptionMessageRegex */ "Invalid formula type",
                () -> xmlSerializer.serialize(Collections.singletonList(rule)));
    }

    private Formula getInvalidFormula() {
        return new Formula() {
            @Override
            public boolean isSatisfied(AppInstallMetadata appInstallMetadata) {
                return false;
            }

            @Override
            public int hashCode() {
                return super.hashCode();
            }

            @Override
            public boolean equals(Object obj) {
                return super.equals(obj);
            }

            @NonNull
            @Override
            protected Object clone() throws CloneNotSupportedException {
                return super.clone();
            }

            @Override
            public String toString() {
                return super.toString();
            }

            @Override
            protected void finalize() throws Throwable {
                super.finalize();
            }
        };
    }
}