From 0efe7cbcbeea9a2d80a1065787877490585fe078 Mon Sep 17 00:00:00 2001 From: Khaled Abdelmohsen Date: Fri, 8 Nov 2019 22:18:54 +0000 Subject: [PATCH] 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 --- .../serializer/RuleBinarySerializer.java | 5 +- .../serializer/RuleSerializeException.java | 32 ++ .../integrity/serializer/RuleSerializer.java | 9 +- .../serializer/RuleXmlSerializer.java | 136 +++++++- .../serializer/RuleXmlSerializerTest.java | 298 ++++++++++++++++++ 5 files changed, 467 insertions(+), 13 deletions(-) create mode 100644 services/core/java/com/android/server/integrity/serializer/RuleSerializeException.java create mode 100644 services/tests/servicestests/src/com/android/server/integrity/serializer/RuleXmlSerializerTest.java diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java index ecb00a4b70254..ee95d2bb4d692 100644 --- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java +++ b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java @@ -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 rules, OutputStream outputStream) { // TODO: Implement stream serializer. } @Override - public String serialize(Rule rule) { + public String serialize(List rules) { // TODO: Implement text serializer. return null; } diff --git a/services/core/java/com/android/server/integrity/serializer/RuleSerializeException.java b/services/core/java/com/android/server/integrity/serializer/RuleSerializeException.java new file mode 100644 index 0000000000000..60cfc4876414c --- /dev/null +++ b/services/core/java/com/android/server/integrity/serializer/RuleSerializeException.java @@ -0,0 +1,32 @@ +/* + * 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); + } +} diff --git a/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java index 07a912f8aeab8..5c99c5a4c2dac 100644 --- a/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java +++ b/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java @@ -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 rules, OutputStream outputStream) throws RuleSerializeException; - /** Serialize a rule to a string. */ - String serialize(Rule rule); + /** Serialize rules to a string. */ + String serialize(List rule) throws RuleSerializeException; } diff --git a/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java index 62973e2b026f6..98042bba9206e 100644 --- a/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java +++ b/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java @@ -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 java.io.OutputStream; +import org.xmlpull.v1.XmlSerializer; -/** A helper class to serialize rules from the {@link Rule} model to Xml representation. */ +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. + */ 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 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 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 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); } } diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleXmlSerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleXmlSerializerTest.java new file mode 100644 index 0000000000000..9a4f6f94d72ce --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleXmlSerializerTest.java @@ -0,0 +1,298 @@ +/* + * 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 = ""; + + 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 = "" + + "" + + "" + + "" + AtomicFormula.PACKAGE_NAME + "" + + "com.app.test" + + "" + + "" + Rule.DENY + "" + + "" + + ""; + + 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 = "" + + "" + + "" + + "" + OpenFormula.NOT + "" + + "" + + "" + AtomicFormula.PACKAGE_NAME + "" + + "com.app.test" + + "" + + "" + + "" + Rule.DENY + "" + + "" + + ""; + + 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 = "" + + "" + + "" + + "" + OpenFormula.NOT + "" + + "" + + "" + AtomicFormula.PACKAGE_NAME + "" + + "com.app.test" + + "" + + "" + + "" + Rule.DENY + "" + + "" + + ""; + + 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 = "" + + "" + + "" + + "" + OpenFormula.AND + "" + + "" + + "" + AtomicFormula.PACKAGE_NAME + "" + + "com.app.test" + + "" + + "" + + "" + AtomicFormula.APP_CERTIFICATE + "" + + "test_cert" + + "" + + "" + + "" + Rule.DENY + "" + + "" + + ""; + + 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 = "" + + "" + + "" + + "" + OpenFormula.OR + "" + + "" + + "" + AtomicFormula.PACKAGE_NAME + "" + + "com.app.test" + + "" + + "" + + "" + AtomicFormula.APP_CERTIFICATE + "" + + "test_cert" + + "" + + "" + + "" + Rule.DENY + "" + + "" + + ""; + + 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 = "" + + "" + + "" + + "" + AtomicFormula.PACKAGE_NAME + "" + + "com.app.test" + + "" + + "" + Rule.DENY + "" + + "" + + ""; + + 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 = "" + + "" + + "" + + "" + AtomicFormula.VERSION_CODE + "" + + "" + AtomicFormula.EQ + "" + + "1" + + "" + + "" + Rule.DENY + "" + + "" + + ""; + + 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 = "" + + "" + + "" + + "" + AtomicFormula.PRE_INSTALLED + "" + + "true" + + "" + + "" + Rule.DENY + "" + + "" + + ""; + + 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(); + } + }; + } +}