Implement rule binary serializer
Bug: 143697198
Test: atest FrameworksServicesTests:RuleBinarySerializerTest
Change-Id: Ia804137d8b127a3b2ef8a7b692b7903360fdbe8c
diff --git a/services/core/java/com/android/server/integrity/model/BitOutputStream.java b/services/core/java/com/android/server/integrity/model/BitOutputStream.java
new file mode 100644
index 0000000..ecb9189
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/model/BitOutputStream.java
@@ -0,0 +1,86 @@
+/*
+ * 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.model;
+
+import java.util.BitSet;
+
+/** A wrapper class for writing a stream of bits. */
+public class BitOutputStream {
+
+ private BitSet mBitSet;
+ private int mIndex;
+
+ public BitOutputStream() {
+ mBitSet = new BitSet();
+ mIndex = 0;
+ }
+
+ /**
+ * Set the next number of bits in the stream to value.
+ *
+ * @param numOfBits The number of bits used to represent the value.
+ * @param value The value to convert to bits.
+ */
+ public void setNext(int numOfBits, int value) {
+ if (numOfBits <= 0) {
+ return;
+ }
+ int offset = 1 << (numOfBits - 1);
+ while (numOfBits-- > 0) {
+ mBitSet.set(mIndex, (value & offset) != 0);
+ offset >>= 1;
+ mIndex++;
+ }
+ }
+
+ /**
+ * Set the next bit in the stream to value.
+ *
+ * @param value The value to set the bit to.
+ */
+ public void setNext(boolean value) {
+ mBitSet.set(mIndex, value);
+ mIndex++;
+ }
+
+ /** Set the next bit in the stream to true. */
+ public void setNext() {
+ setNext(/* value= */ true);
+ }
+
+ /** Convert BitSet in big-endian to ByteArray in big-endian. */
+ public byte[] toByteArray() {
+ int bitSetSize = mBitSet.length();
+ int numOfBytes = bitSetSize / 8;
+ if (bitSetSize % 8 != 0) {
+ numOfBytes++;
+ }
+ byte[] bytes = new byte[numOfBytes];
+ for (int i = 0; i < mBitSet.length(); i++) {
+ if (mBitSet.get(i)) {
+ bytes[i / 8] |= 1 << (7 - (i % 8));
+ }
+ }
+ return bytes;
+ }
+
+ /** Clear the stream. */
+ public void clear() {
+ mBitSet.clear();
+ mIndex = 0;
+ }
+}
diff --git a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
index aad177e..54c0bd8 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
@@ -25,13 +25,13 @@
public class RuleBinaryParser implements RuleParser {
@Override
- public List<Rule> parse(byte[] ruleBytes) {
+ public List<Rule> parse(byte[] ruleBytes) throws RuleParseException {
// TODO: Implement binary text parser.
return null;
}
@Override
- public List<Rule> parse(InputStream inputStream) {
+ public List<Rule> parse(InputStream inputStream) throws RuleParseException {
// TODO: Implement stream parser.
return null;
}
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 d4f41eb..f782f71 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
@@ -16,24 +16,157 @@
package com.android.server.integrity.serializer;
+import android.content.integrity.AtomicFormula;
+import android.content.integrity.CompoundFormula;
+import android.content.integrity.Formula;
import android.content.integrity.Rule;
+import com.android.server.integrity.model.BitOutputStream;
+
+import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;
-/** 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 Binary representation. */
public class RuleBinarySerializer implements RuleSerializer {
+ public static final int FORMAT_VERSION_BITS = 5;
+ public static final int EFFECT_BITS = 3;
+ public static final int KEY_BITS = 4;
+ public static final int OPERATOR_BITS = 3;
+ public static final int CONNECTOR_BITS = 2;
+ public static final int SEPARATOR_BITS = 2;
+ public static final int VALUE_SIZE_BITS = 5;
+
+ public static final int ATOMIC_FORMULA_START = 0;
+ public static final int COMPOUND_FORMULA_START = 1;
+ public static final int COMPOUND_FORMULA_END = 2;
+
+ public static final int DEFAULT_FORMAT_VERSION = 1;
+
+ // 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) {
- // TODO: Implement stream serializer.
+ List<Rule> rules, Optional<Integer> formatVersion, OutputStream outputStream)
+ throws RuleSerializeException {
+ try {
+ BitOutputStream bitOutputStream = new BitOutputStream();
+
+ int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION);
+ bitOutputStream.setNext(FORMAT_VERSION_BITS, formatVersionValue);
+ outputStream.write(bitOutputStream.toByteArray());
+
+ for (Rule rule : rules) {
+ bitOutputStream.clear();
+ serializeRule(rule, bitOutputStream);
+ outputStream.write(bitOutputStream.toByteArray());
+ }
+ } catch (Exception e) {
+ throw new RuleSerializeException(e.getMessage(), e);
+ }
}
+ // Get the byte representation for a list of rules.
@Override
- public byte[] serialize(List<Rule> rules, Optional<Integer> formatVersion) {
- // TODO: Implement text serializer.
- return null;
+ public byte[] serialize(List<Rule> rules, Optional<Integer> formatVersion)
+ throws RuleSerializeException {
+ try {
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ serialize(rules, formatVersion, byteArrayOutputStream);
+ return byteArrayOutputStream.toByteArray();
+ } catch (Exception e) {
+ throw new RuleSerializeException(e.getMessage(), e);
+ }
+ }
+
+ private void serializeRule(Rule rule, BitOutputStream bitOutputStream) {
+ if (rule == null) {
+ throw new IllegalArgumentException("Null rule can not be serialized");
+ }
+
+ // Start with a '1' bit to mark the start of a rule.
+ bitOutputStream.setNext();
+
+ serializeFormula(rule.getFormula(), bitOutputStream);
+ bitOutputStream.setNext(EFFECT_BITS, rule.getEffect());
+
+ // End with a '1' bit to mark the end of a rule.
+ bitOutputStream.setNext();
+ }
+
+ private void serializeFormula(Formula formula, BitOutputStream bitOutputStream) {
+ if (formula instanceof AtomicFormula) {
+ serializeAtomicFormula((AtomicFormula) formula, bitOutputStream);
+ } else if (formula instanceof CompoundFormula) {
+ serializeCompoundFormula((CompoundFormula) formula, bitOutputStream);
+ } else {
+ throw new IllegalArgumentException(
+ String.format("Invalid formula type: %s", formula.getClass()));
+ }
+ }
+
+ private void serializeCompoundFormula(
+ CompoundFormula compoundFormula, BitOutputStream bitOutputStream) {
+ if (compoundFormula == null) {
+ throw new IllegalArgumentException("Null compound formula can not be serialized");
+ }
+
+ bitOutputStream.setNext(SEPARATOR_BITS, COMPOUND_FORMULA_START);
+ bitOutputStream.setNext(CONNECTOR_BITS, compoundFormula.getConnector());
+ for (Formula formula : compoundFormula.getFormulas()) {
+ serializeFormula(formula, bitOutputStream);
+ }
+ bitOutputStream.setNext(SEPARATOR_BITS, COMPOUND_FORMULA_END);
+ }
+
+ private void serializeAtomicFormula(
+ AtomicFormula atomicFormula, BitOutputStream bitOutputStream) {
+ if (atomicFormula == null) {
+ throw new IllegalArgumentException("Null atomic formula can not be serialized");
+ }
+
+ bitOutputStream.setNext(SEPARATOR_BITS, ATOMIC_FORMULA_START);
+ bitOutputStream.setNext(KEY_BITS, atomicFormula.getKey());
+ if (atomicFormula instanceof AtomicFormula.StringAtomicFormula) {
+ AtomicFormula.StringAtomicFormula stringAtomicFormula =
+ (AtomicFormula.StringAtomicFormula) atomicFormula;
+ bitOutputStream.setNext(OPERATOR_BITS, AtomicFormula.EQ);
+ serializeValue(
+ stringAtomicFormula.getValue(),
+ stringAtomicFormula.getIsHashedValue(),
+ bitOutputStream);
+ } else if (atomicFormula instanceof AtomicFormula.IntAtomicFormula) {
+ AtomicFormula.IntAtomicFormula intAtomicFormula =
+ (AtomicFormula.IntAtomicFormula) atomicFormula;
+ bitOutputStream.setNext(OPERATOR_BITS, intAtomicFormula.getOperator());
+ serializeValue(
+ String.valueOf(intAtomicFormula.getValue()),
+ /* isHashedValue= */ false,
+ bitOutputStream);
+ } else if (atomicFormula instanceof AtomicFormula.BooleanAtomicFormula) {
+ AtomicFormula.BooleanAtomicFormula booleanAtomicFormula =
+ (AtomicFormula.BooleanAtomicFormula) atomicFormula;
+ bitOutputStream.setNext(OPERATOR_BITS, AtomicFormula.EQ);
+ serializeValue(
+ booleanAtomicFormula.getValue() ? "1" : "0",
+ /* isHashedValue= */ false,
+ bitOutputStream);
+ } else {
+ throw new IllegalArgumentException(
+ String.format("Invalid atomic formula type: %s", atomicFormula.getClass()));
+ }
+ }
+
+ private void serializeValue(
+ String value, boolean isHashedValue, BitOutputStream bitOutputStream) {
+ byte[] valueBytes = value.getBytes(StandardCharsets.UTF_8);
+
+ bitOutputStream.setNext(isHashedValue);
+ bitOutputStream.setNext(VALUE_SIZE_BITS, valueBytes.length);
+ for (byte valueByte : valueBytes) {
+ bitOutputStream.setNext(/* numOfBits= */ 8, valueByte);
+ }
}
}
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 3ec9cf2..72068ce 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java
@@ -39,7 +39,7 @@
private static final String RULE_LIST_TAG = "RL";
private static final String RULE_TAG = "R";
- private static final String OPEN_FORMULA_TAG = "OF";
+ private static final String COMPOUND_FORMULA_TAG = "OF";
private static final String ATOMIC_FORMULA_TAG = "AF";
private static final String EFFECT_ATTRIBUTE = "E";
private static final String KEY_ATTRIBUTE = "K";
@@ -78,13 +78,13 @@
private void serializeRules(List<Rule> rules, XmlSerializer xmlSerializer) throws IOException {
xmlSerializer.startTag(NAMESPACE, RULE_LIST_TAG);
for (Rule rule : rules) {
- serialize(rule, xmlSerializer);
+ serializeRule(rule, xmlSerializer);
}
xmlSerializer.endTag(NAMESPACE, RULE_LIST_TAG);
xmlSerializer.endDocument();
}
- private void serialize(Rule rule, XmlSerializer xmlSerializer) throws IOException {
+ private void serializeRule(Rule rule, XmlSerializer xmlSerializer) throws IOException {
if (rule == null) {
return;
}
@@ -98,25 +98,25 @@
if (formula instanceof AtomicFormula) {
serializeAtomicFormula((AtomicFormula) formula, xmlSerializer);
} else if (formula instanceof CompoundFormula) {
- serializeOpenFormula((CompoundFormula) formula, xmlSerializer);
+ serializeCompoundFormula((CompoundFormula) formula, xmlSerializer);
} else {
throw new IllegalArgumentException(
String.format("Invalid formula type: %s", formula.getClass()));
}
}
- private void serializeOpenFormula(CompoundFormula compoundFormula, XmlSerializer xmlSerializer)
- throws IOException {
+ private void serializeCompoundFormula(
+ CompoundFormula compoundFormula, XmlSerializer xmlSerializer) throws IOException {
if (compoundFormula == null) {
return;
}
- xmlSerializer.startTag(NAMESPACE, OPEN_FORMULA_TAG);
+ xmlSerializer.startTag(NAMESPACE, COMPOUND_FORMULA_TAG);
serializeAttributeValue(
CONNECTOR_ATTRIBUTE, String.valueOf(compoundFormula.getConnector()), xmlSerializer);
for (Formula formula : compoundFormula.getFormulas()) {
serializeFormula(formula, xmlSerializer);
}
- xmlSerializer.endTag(NAMESPACE, OPEN_FORMULA_TAG);
+ xmlSerializer.endTag(NAMESPACE, COMPOUND_FORMULA_TAG);
}
private void serializeAtomicFormula(AtomicFormula atomicFormula, XmlSerializer xmlSerializer)
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
new file mode 100644
index 0000000..a43dbd7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
@@ -0,0 +1,445 @@
+/*
+ * 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.integrity.utils.TestUtils.getBits;
+import static com.android.server.integrity.utils.TestUtils.getBytes;
+import static com.android.server.integrity.utils.TestUtils.getValueBits;
+import static com.android.server.testutils.TestUtils.assertExpectException;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.integrity.AppInstallMetadata;
+import android.content.integrity.AtomicFormula;
+import android.content.integrity.CompoundFormula;
+import android.content.integrity.Formula;
+import android.content.integrity.Rule;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.ByteArrayOutputStream;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Optional;
+
+@RunWith(JUnit4.class)
+public class RuleBinarySerializerTest {
+
+ private static final String COMPOUND_FORMULA_START =
+ getBits(
+ RuleBinarySerializer.COMPOUND_FORMULA_START,
+ RuleBinarySerializer.SEPARATOR_BITS);
+ private static final String COMPOUND_FORMULA_END =
+ getBits(RuleBinarySerializer.COMPOUND_FORMULA_END, RuleBinarySerializer.SEPARATOR_BITS);
+ private static final String ATOMIC_FORMULA_START =
+ getBits(RuleBinarySerializer.ATOMIC_FORMULA_START, RuleBinarySerializer.SEPARATOR_BITS);
+
+ private static final String NOT =
+ getBits(CompoundFormula.NOT, RuleBinarySerializer.CONNECTOR_BITS);
+ private static final String AND =
+ getBits(CompoundFormula.AND, RuleBinarySerializer.CONNECTOR_BITS);
+ private static final String OR =
+ getBits(CompoundFormula.OR, RuleBinarySerializer.CONNECTOR_BITS);
+
+ private static final String PACKAGE_NAME =
+ getBits(AtomicFormula.PACKAGE_NAME, RuleBinarySerializer.KEY_BITS);
+ private static final String APP_CERTIFICATE =
+ getBits(AtomicFormula.APP_CERTIFICATE, RuleBinarySerializer.KEY_BITS);
+ private static final String VERSION_CODE =
+ getBits(AtomicFormula.VERSION_CODE, RuleBinarySerializer.KEY_BITS);
+ private static final String PRE_INSTALLED =
+ getBits(AtomicFormula.PRE_INSTALLED, RuleBinarySerializer.KEY_BITS);
+
+ private static final String EQ = getBits(AtomicFormula.EQ, RuleBinarySerializer.OPERATOR_BITS);
+
+ private static final String IS_NOT_HASHED = "0";
+
+ private static final String DENY = getBits(Rule.DENY, RuleBinarySerializer.EFFECT_BITS);
+
+ private static final String START_BIT = "1";
+ private static final String END_BIT = "1";
+
+ private static final byte[] DEFAULT_FORMAT_VERSION =
+ getBytes(
+ getBits(
+ RuleBinarySerializer.DEFAULT_FORMAT_VERSION,
+ RuleBinarySerializer.FORMAT_VERSION_BITS));
+
+ @Test
+ public void testBinaryString_serializeEmptyRule() throws Exception {
+ Rule rule = null;
+ RuleSerializer binarySerializer = new RuleBinarySerializer();
+
+ assertExpectException(
+ RuleSerializeException.class,
+ /* expectedExceptionMessageRegex= */ "Null rule can not be serialized",
+ () ->
+ binarySerializer.serialize(
+ Collections.singletonList(rule),
+ /* formatVersion= */ Optional.empty()));
+ }
+
+ @Test
+ public void testBinaryStream_serializeValidCompoundFormula() throws Exception {
+ String packageName = "com.test.app";
+ Rule rule =
+ new Rule(
+ new CompoundFormula(
+ CompoundFormula.NOT,
+ Collections.singletonList(
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.PACKAGE_NAME,
+ packageName,
+ /* isHashedValue= */ false))),
+ Rule.DENY);
+ RuleSerializer binarySerializer = new RuleBinarySerializer();
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ String expectedBits =
+ START_BIT
+ + COMPOUND_FORMULA_START
+ + NOT
+ + ATOMIC_FORMULA_START
+ + PACKAGE_NAME
+ + EQ
+ + IS_NOT_HASHED
+ + getBits(packageName.length(), RuleBinarySerializer.VALUE_SIZE_BITS)
+ + getValueBits(packageName)
+ + COMPOUND_FORMULA_END
+ + DENY
+ + END_BIT;
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION);
+ byteArrayOutputStream.write(getBytes(expectedBits));
+ byte[] expectedRules = byteArrayOutputStream.toByteArray();
+
+ binarySerializer.serialize(
+ Collections.singletonList(rule),
+ /* formatVersion= */ Optional.empty(),
+ outputStream);
+
+ byte[] actualRules = outputStream.toByteArray();
+ assertThat(actualRules).isEqualTo(expectedRules);
+ }
+
+ @Test
+ public void testBinaryString_serializeValidCompoundFormula_notConnector() throws Exception {
+ String packageName = "com.test.app";
+ Rule rule =
+ new Rule(
+ new CompoundFormula(
+ CompoundFormula.NOT,
+ Collections.singletonList(
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.PACKAGE_NAME,
+ packageName,
+ /* isHashedValue= */ false))),
+ Rule.DENY);
+ RuleSerializer binarySerializer = new RuleBinarySerializer();
+ String expectedBits =
+ START_BIT
+ + COMPOUND_FORMULA_START
+ + NOT
+ + ATOMIC_FORMULA_START
+ + PACKAGE_NAME
+ + EQ
+ + IS_NOT_HASHED
+ + getBits(packageName.length(), RuleBinarySerializer.VALUE_SIZE_BITS)
+ + getValueBits(packageName)
+ + COMPOUND_FORMULA_END
+ + DENY
+ + END_BIT;
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION);
+ byteArrayOutputStream.write(getBytes(expectedBits));
+ byte[] expectedRules = byteArrayOutputStream.toByteArray();
+
+ byte[] actualRules =
+ binarySerializer.serialize(
+ Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
+
+ assertThat(actualRules).isEqualTo(expectedRules);
+ }
+
+ @Test
+ public void testBinaryString_serializeValidCompoundFormula_andConnector() throws Exception {
+ String packageName = "com.test.app";
+ String appCertificate = "test_cert";
+ Rule rule =
+ new Rule(
+ new CompoundFormula(
+ CompoundFormula.AND,
+ Arrays.asList(
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.PACKAGE_NAME,
+ packageName,
+ /* isHashedValue= */ false),
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.APP_CERTIFICATE,
+ appCertificate,
+ /* isHashedValue= */ false))),
+ Rule.DENY);
+ RuleSerializer binarySerializer = new RuleBinarySerializer();
+ String expectedBits =
+ START_BIT
+ + COMPOUND_FORMULA_START
+ + AND
+ + ATOMIC_FORMULA_START
+ + PACKAGE_NAME
+ + EQ
+ + IS_NOT_HASHED
+ + getBits(packageName.length(), RuleBinarySerializer.VALUE_SIZE_BITS)
+ + getValueBits(packageName)
+ + ATOMIC_FORMULA_START
+ + APP_CERTIFICATE
+ + EQ
+ + IS_NOT_HASHED
+ + getBits(appCertificate.length(), RuleBinarySerializer.VALUE_SIZE_BITS)
+ + getValueBits(appCertificate)
+ + COMPOUND_FORMULA_END
+ + DENY
+ + END_BIT;
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION);
+ byteArrayOutputStream.write(getBytes(expectedBits));
+ byte[] expectedRules = byteArrayOutputStream.toByteArray();
+
+ byte[] actualRules =
+ binarySerializer.serialize(
+ Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
+
+ assertThat(actualRules).isEqualTo(expectedRules);
+ }
+
+ @Test
+ public void testBinaryString_serializeValidCompoundFormula_orConnector() throws Exception {
+ String packageName = "com.test.app";
+ String appCertificate = "test_cert";
+ Rule rule =
+ new Rule(
+ new CompoundFormula(
+ CompoundFormula.OR,
+ Arrays.asList(
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.PACKAGE_NAME,
+ packageName,
+ /* isHashedValue= */ false),
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.APP_CERTIFICATE,
+ appCertificate,
+ /* isHashedValue= */ false))),
+ Rule.DENY);
+ RuleSerializer binarySerializer = new RuleBinarySerializer();
+ String expectedBits =
+ START_BIT
+ + COMPOUND_FORMULA_START
+ + OR
+ + ATOMIC_FORMULA_START
+ + PACKAGE_NAME
+ + EQ
+ + IS_NOT_HASHED
+ + getBits(packageName.length(), RuleBinarySerializer.VALUE_SIZE_BITS)
+ + getValueBits(packageName)
+ + ATOMIC_FORMULA_START
+ + APP_CERTIFICATE
+ + EQ
+ + IS_NOT_HASHED
+ + getBits(appCertificate.length(), RuleBinarySerializer.VALUE_SIZE_BITS)
+ + getValueBits(appCertificate)
+ + COMPOUND_FORMULA_END
+ + DENY
+ + END_BIT;
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION);
+ byteArrayOutputStream.write(getBytes(expectedBits));
+ byte[] expectedRules = byteArrayOutputStream.toByteArray();
+
+ byte[] actualRules =
+ binarySerializer.serialize(
+ Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
+
+ assertThat(actualRules).isEqualTo(expectedRules);
+ }
+
+ @Test
+ public void testBinaryString_serializeValidAtomicFormula_stringValue() throws Exception {
+ String packageName = "com.test.app";
+ Rule rule =
+ new Rule(
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.PACKAGE_NAME,
+ packageName,
+ /* isHashedValue= */ false),
+ Rule.DENY);
+ RuleSerializer binarySerializer = new RuleBinarySerializer();
+ String expectedBits =
+ START_BIT
+ + ATOMIC_FORMULA_START
+ + PACKAGE_NAME
+ + EQ
+ + IS_NOT_HASHED
+ + getBits(packageName.length(), RuleBinarySerializer.VALUE_SIZE_BITS)
+ + getValueBits(packageName)
+ + DENY
+ + END_BIT;
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION);
+ byteArrayOutputStream.write(getBytes(expectedBits));
+ byte[] expectedRules = byteArrayOutputStream.toByteArray();
+
+ byte[] actualRules =
+ binarySerializer.serialize(
+ Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
+
+ assertThat(actualRules).isEqualTo(expectedRules);
+ }
+
+ @Test
+ public void testBinaryString_serializeValidAtomicFormula_integerValue() throws Exception {
+ String versionCode = "1";
+ Rule rule =
+ new Rule(
+ new AtomicFormula.IntAtomicFormula(
+ AtomicFormula.VERSION_CODE,
+ AtomicFormula.EQ,
+ Integer.parseInt(versionCode)),
+ Rule.DENY);
+ RuleSerializer binarySerializer = new RuleBinarySerializer();
+ String expectedBits =
+ START_BIT
+ + ATOMIC_FORMULA_START
+ + VERSION_CODE
+ + EQ
+ + IS_NOT_HASHED
+ + getBits(versionCode.length(), RuleBinarySerializer.VALUE_SIZE_BITS)
+ + getValueBits(versionCode)
+ + DENY
+ + END_BIT;
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION);
+ byteArrayOutputStream.write(getBytes(expectedBits));
+ byte[] expectedRules = byteArrayOutputStream.toByteArray();
+
+ byte[] actualRules =
+ binarySerializer.serialize(
+ Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
+
+ assertThat(actualRules).isEqualTo(expectedRules);
+ }
+
+ @Test
+ public void testBinaryString_serializeValidAtomicFormula_booleanValue() throws Exception {
+ String preInstalled = "1";
+ Rule rule =
+ new Rule(
+ new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true),
+ Rule.DENY);
+ RuleSerializer binarySerializer = new RuleBinarySerializer();
+ String expectedBits =
+ START_BIT
+ + ATOMIC_FORMULA_START
+ + PRE_INSTALLED
+ + EQ
+ + IS_NOT_HASHED
+ + getBits(preInstalled.length(), RuleBinarySerializer.VALUE_SIZE_BITS)
+ + getValueBits(preInstalled)
+ + DENY
+ + END_BIT;
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION);
+ byteArrayOutputStream.write(getBytes(expectedBits));
+ byte[] expectedRules = byteArrayOutputStream.toByteArray();
+
+ byte[] actualRules =
+ binarySerializer.serialize(
+ Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
+
+ assertThat(actualRules).isEqualTo(expectedRules);
+ }
+
+ @Test
+ public void testBinaryString_serializeInvalidFormulaType() throws Exception {
+ Formula invalidFormula = getInvalidFormula();
+ Rule rule = new Rule(invalidFormula, Rule.DENY);
+ RuleSerializer binarySerializer = new RuleBinarySerializer();
+
+ assertExpectException(
+ RuleSerializeException.class,
+ /* expectedExceptionMessageRegex= */ "Invalid formula type",
+ () ->
+ binarySerializer.serialize(
+ Collections.singletonList(rule),
+ /* formatVersion= */ Optional.empty()));
+ }
+
+ @Test
+ public void testBinaryString_serializeFormatVersion() throws Exception {
+ int formatVersion = 1;
+ RuleSerializer binarySerializer = new RuleBinarySerializer();
+ String expectedBits = getBits(formatVersion, RuleBinarySerializer.FORMAT_VERSION_BITS);
+ byte[] expectedRules = getBytes(expectedBits);
+
+ byte[] actualRules =
+ binarySerializer.serialize(
+ Collections.emptyList(), /* formatVersion= */ Optional.of(formatVersion));
+
+ assertThat(actualRules).isEqualTo(expectedRules);
+ }
+
+ private static Formula getInvalidFormula() {
+ return new Formula() {
+ @Override
+ public boolean isSatisfied(AppInstallMetadata appInstallMetadata) {
+ return false;
+ }
+
+ @Override
+ public int getTag() {
+ return 0;
+ }
+
+ @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();
+ }
+ };
+ }
+}
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
index 5903b5a..ad74901 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleXmlSerializerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleXmlSerializerTest.java
@@ -91,7 +91,7 @@
}
@Test
- public void testXmlStream_serializeValidOpenFormula() throws Exception {
+ public void testXmlStream_serializeValidCompoundFormula() throws Exception {
Rule rule =
new Rule(
new CompoundFormula(
@@ -134,7 +134,7 @@
}
@Test
- public void testXmlString_serializeValidOpenFormula_notConnector() throws Exception {
+ public void testXmlString_serializeValidCompoundFormula_notConnector() throws Exception {
Rule rule =
new Rule(
new CompoundFormula(
@@ -174,7 +174,7 @@
}
@Test
- public void testXmlString_serializeValidOpenFormula_andConnector() throws Exception {
+ public void testXmlString_serializeValidCompoundFormula_andConnector() throws Exception {
Rule rule =
new Rule(
new CompoundFormula(
@@ -224,7 +224,7 @@
}
@Test
- public void testXmlString_serializeValidOpenFormula_orConnector() throws Exception {
+ public void testXmlString_serializeValidCompoundFormula_orConnector() throws Exception {
Rule rule =
new Rule(
new CompoundFormula(
diff --git a/services/tests/servicestests/src/com/android/server/integrity/utils/TestUtils.java b/services/tests/servicestests/src/com/android/server/integrity/utils/TestUtils.java
new file mode 100644
index 0000000..e54410b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/integrity/utils/TestUtils.java
@@ -0,0 +1,48 @@
+/*
+ * 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.utils;
+
+public class TestUtils {
+
+ public static String getBits(int component, int numOfBits) {
+ return String.format("%" + numOfBits + "s", Integer.toBinaryString(component))
+ .replace(' ', '0');
+ }
+
+ public static String getValueBits(String value) {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (byte valueByte : value.getBytes()) {
+ stringBuilder.append(getBits(valueByte, /* numOfBits= */ 8));
+ }
+ return stringBuilder.toString();
+ }
+
+ public static byte[] getBytes(String bits) {
+ int bitStringSize = bits.length();
+ int numOfBytes = bitStringSize / 8;
+ if (bitStringSize % 8 != 0) {
+ numOfBytes++;
+ }
+ byte[] bytes = new byte[numOfBytes];
+ for (int i = 0; i < bits.length(); i++) {
+ if (bits.charAt(i) == '1') {
+ bytes[i / 8] |= 1 << (7 - (i % 8));
+ }
+ }
+ return bytes;
+ }
+}