From 1fa4de584cff58261ee2c6378d700d3c549b8c86 Mon Sep 17 00:00:00 2001 From: Khaled Abdelmohsen Date: Wed, 13 Nov 2019 15:13:19 +0000 Subject: [PATCH 1/2] Implement rule binary serializer Bug: 143697198 Test: atest FrameworksServicesTests:RuleBinarySerializerTest Change-Id: Ia804137d8b127a3b2ef8a7b692b7903360fdbe8c --- .../integrity/model/BitOutputStream.java | 86 ++++ .../integrity/parser/RuleBinaryParser.java | 4 +- .../serializer/RuleBinarySerializer.java | 145 +++++- .../serializer/RuleXmlSerializer.java | 16 +- .../serializer/RuleBinarySerializerTest.java | 445 ++++++++++++++++++ .../serializer/RuleXmlSerializerTest.java | 8 +- .../server/integrity/utils/TestUtils.java | 48 ++ 7 files changed, 732 insertions(+), 20 deletions(-) create mode 100644 services/core/java/com/android/server/integrity/model/BitOutputStream.java create mode 100644 services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java create mode 100644 services/tests/servicestests/src/com/android/server/integrity/utils/TestUtils.java 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 0000000000000..ecb9189b2a0df --- /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 aad177e81c770..54c0bd85d897e 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 @@ import java.util.List; public class RuleBinaryParser implements RuleParser { @Override - public List parse(byte[] ruleBytes) { + public List parse(byte[] ruleBytes) throws RuleParseException { // TODO: Implement binary text parser. return null; } @Override - public List parse(InputStream inputStream) { + public List 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 d4f41eb5c9e04..f782f7173da24 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 rules, Optional formatVersion, OutputStream outputStream) { - // TODO: Implement stream serializer. + List rules, Optional 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 rules, Optional formatVersion) { - // TODO: Implement text serializer. - return null; + public byte[] serialize(List rules, Optional 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 3ec9cf2941362..72068ceeb4f03 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 @@ public class RuleXmlSerializer implements RuleSerializer { 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 @@ public class RuleXmlSerializer implements RuleSerializer { private void serializeRules(List 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 @@ public class RuleXmlSerializer implements RuleSerializer { 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 0000000000000..a43dbd77031a9 --- /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 5903b5a11d222..ad74901ece2c6 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 @@ public class RuleXmlSerializerTest { } @Test - public void testXmlStream_serializeValidOpenFormula() throws Exception { + public void testXmlStream_serializeValidCompoundFormula() throws Exception { Rule rule = new Rule( new CompoundFormula( @@ -134,7 +134,7 @@ public class RuleXmlSerializerTest { } @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 @@ public class RuleXmlSerializerTest { } @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 @@ public class RuleXmlSerializerTest { } @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 0000000000000..e54410b04b79d --- /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; + } +} From 4524c4228b86ddc7602f2cfeced20de61452c20b Mon Sep 17 00:00:00 2001 From: Khaled Abdelmohsen Date: Mon, 2 Dec 2019 17:43:31 +0000 Subject: [PATCH 2/2] Implement rule binary parser Bug: 143697198 Test: atest FrameworksServicesTests:RuleBinaryParserTest Change-Id: I11fbf84d54b2193126b6b8b854649517f4072972 --- .../integrity/model/BitInputStream.java | 66 ++ .../integrity/model/ComponentBitSize.java | 41 ++ .../integrity/parser/RuleBinaryParser.java | 119 +++- .../integrity/parser/RuleXmlParser.java | 15 +- .../serializer/RuleBinarySerializer.java | 26 +- .../parser/RuleBinaryParserTest.java | 628 ++++++++++++++++++ .../serializer/RuleBinarySerializerTest.java | 125 ++-- 7 files changed, 931 insertions(+), 89 deletions(-) create mode 100644 services/core/java/com/android/server/integrity/model/BitInputStream.java create mode 100644 services/core/java/com/android/server/integrity/model/ComponentBitSize.java create mode 100644 services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java diff --git a/services/core/java/com/android/server/integrity/model/BitInputStream.java b/services/core/java/com/android/server/integrity/model/BitInputStream.java new file mode 100644 index 0000000000000..09bc7e8b98610 --- /dev/null +++ b/services/core/java/com/android/server/integrity/model/BitInputStream.java @@ -0,0 +1,66 @@ +/* + * 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; + +/** A wrapper class for reading a stream of bits. */ +public class BitInputStream { + + private byte[] mRuleBytes; + private long mBitPointer; + + public BitInputStream(byte[] ruleBytes) { + this.mRuleBytes = ruleBytes; + this.mBitPointer = 0; + } + + /** + * Read the next number of bits from the stream. + * + * @param numOfBits The number of bits to read. + * @return The value read from the stream. + */ + public int getNext(int numOfBits) { + int component = 0; + int count = 0; + + int idx = (int) (mBitPointer / 8); + int offset = 7 - (int) (mBitPointer % 8); + + while (count++ < numOfBits) { + if (idx >= mRuleBytes.length) { + throw new IllegalArgumentException(String.format("Invalid byte index: %d", idx)); + } + + component <<= 1; + component |= (mRuleBytes[idx] >>> offset) & 1; + + offset--; + if (offset == -1) { + idx++; + offset = 7; + } + } + + mBitPointer += numOfBits; + return component; + } + + /** Check if there are bits left in the stream. */ + public boolean hasNext() { + return mBitPointer / 8 < mRuleBytes.length; + } +} diff --git a/services/core/java/com/android/server/integrity/model/ComponentBitSize.java b/services/core/java/com/android/server/integrity/model/ComponentBitSize.java new file mode 100644 index 0000000000000..d47ce2df45e12 --- /dev/null +++ b/services/core/java/com/android/server/integrity/model/ComponentBitSize.java @@ -0,0 +1,41 @@ +/* + * 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 android.content.integrity.Rule; + +/** + * A helper class containing information about the binary representation of different {@link Rule} + * components. + */ +public final class ComponentBitSize { + 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 IS_HASHED_BITS = 1; + + 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; + public static final int SIGNAL_BIT = 1; +} 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 54c0bd85d897e..8aa0751af11ca 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java +++ b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java @@ -16,23 +16,132 @@ package com.android.server.integrity.parser; +import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START; +import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END; +import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START; +import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS; +import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS; +import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS; +import static com.android.server.integrity.model.ComponentBitSize.IS_HASHED_BITS; +import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS; +import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS; +import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS; +import static com.android.server.integrity.model.ComponentBitSize.SIGNAL_BIT; +import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS; + +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.BitInputStream; + import java.io.InputStream; +import java.util.ArrayList; import java.util.List; /** A helper class to parse rules into the {@link Rule} model from Binary representation. */ public class RuleBinaryParser implements RuleParser { @Override - public List parse(byte[] ruleBytes) throws RuleParseException { - // TODO: Implement binary text parser. - return null; + public List parse(byte[] ruleBytes) throws RuleParseException { + try { + BitInputStream bitInputStream = new BitInputStream(ruleBytes); + return parseRules(bitInputStream); + } catch (Exception e) { + throw new RuleParseException(e.getMessage(), e); + } } @Override public List parse(InputStream inputStream) throws RuleParseException { - // TODO: Implement stream parser. - return null; + try { + byte[] ruleBytes = new byte[inputStream.available()]; + inputStream.read(ruleBytes); + return parse(ruleBytes); + } catch (Exception e) { + throw new RuleParseException(e.getMessage(), e); + } + } + + private List parseRules(BitInputStream bitInputStream) { + List parsedRules = new ArrayList<>(); + + // Read the rule binary file format version. + bitInputStream.getNext(FORMAT_VERSION_BITS); + + while (bitInputStream.hasNext()) { + if (bitInputStream.getNext(SIGNAL_BIT) == 1) { + parsedRules.add(parseRule(bitInputStream)); + } + } + + return parsedRules; + } + + private Rule parseRule(BitInputStream bitInputStream) { + Formula formula = parseFormula(bitInputStream); + int effect = bitInputStream.getNext(EFFECT_BITS); + + if (bitInputStream.getNext(SIGNAL_BIT) != 1) { + throw new IllegalArgumentException("A rule must end with a '1' bit."); + } + + return new Rule(formula, effect); + } + + private Formula parseFormula(BitInputStream bitInputStream) { + int separator = bitInputStream.getNext(SEPARATOR_BITS); + switch (separator) { + case ATOMIC_FORMULA_START: + return parseAtomicFormula(bitInputStream); + case COMPOUND_FORMULA_START: + return parseCompoundFormula(bitInputStream); + case COMPOUND_FORMULA_END: + return null; + default: + throw new IllegalArgumentException( + String.format("Unknown formula separator: %s", separator)); + } + } + + private CompoundFormula parseCompoundFormula(BitInputStream bitInputStream) { + int connector = bitInputStream.getNext(CONNECTOR_BITS); + List formulas = new ArrayList<>(); + + Formula parsedFormula = parseFormula(bitInputStream); + while (parsedFormula != null) { + formulas.add(parsedFormula); + parsedFormula = parseFormula(bitInputStream); + } + + return new CompoundFormula(connector, formulas); + } + + private AtomicFormula parseAtomicFormula(BitInputStream bitInputStream) { + int key = bitInputStream.getNext(KEY_BITS); + int operator = bitInputStream.getNext(OPERATOR_BITS); + + boolean isHashedValue = bitInputStream.getNext(IS_HASHED_BITS) == 1; + int valueSize = bitInputStream.getNext(VALUE_SIZE_BITS); + StringBuilder value = new StringBuilder(); + while (valueSize-- > 0) { + value.append((char) bitInputStream.getNext(/* numOfBits= */ 8)); + } + + switch (key) { + case AtomicFormula.PACKAGE_NAME: + case AtomicFormula.APP_CERTIFICATE: + case AtomicFormula.INSTALLER_NAME: + case AtomicFormula.INSTALLER_CERTIFICATE: + return new AtomicFormula.StringAtomicFormula(key, value.toString(), isHashedValue); + case AtomicFormula.VERSION_CODE: + return new AtomicFormula.IntAtomicFormula( + key, operator, Integer.parseInt(value.toString())); + case AtomicFormula.PRE_INSTALLED: + return new AtomicFormula.BooleanAtomicFormula(key, value.toString().equals("1")); + default: + throw new IllegalArgumentException(String.format("Unknown key: %d", key)); + } } } diff --git a/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java b/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java index 2e99d0f921093..d405583442bd5 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java +++ b/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java @@ -41,7 +41,7 @@ public final class RuleXmlParser implements RuleParser { private static final String NAMESPACE = ""; 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"; @@ -118,8 +118,8 @@ public final class RuleXmlParser implements RuleParser { if (eventType == XmlPullParser.START_TAG) { switch (nodeName) { - case OPEN_FORMULA_TAG: - formula = parseOpenFormula(parser); + case COMPOUND_FORMULA_TAG: + formula = parseCompoundFormula(parser); break; case ATOMIC_FORMULA_TAG: formula = parseAtomicFormula(parser); @@ -137,7 +137,7 @@ public final class RuleXmlParser implements RuleParser { return new Rule(formula, effect); } - private static Formula parseOpenFormula(XmlPullParser parser) + private static Formula parseCompoundFormula(XmlPullParser parser) throws IOException, XmlPullParserException { int connector = Integer.parseInt(extractAttributeValue(parser, CONNECTOR_ATTRIBUTE).orElse("-1")); @@ -147,7 +147,8 @@ public final class RuleXmlParser implements RuleParser { while ((eventType = parser.next()) != XmlPullParser.END_DOCUMENT) { String nodeName = parser.getName(); - if (eventType == XmlPullParser.END_TAG && parser.getName().equals(OPEN_FORMULA_TAG)) { + if (eventType == XmlPullParser.END_TAG + && parser.getName().equals(COMPOUND_FORMULA_TAG)) { break; } @@ -156,8 +157,8 @@ public final class RuleXmlParser implements RuleParser { case ATOMIC_FORMULA_TAG: formulas.add(parseAtomicFormula(parser)); break; - case OPEN_FORMULA_TAG: - formulas.add(parseOpenFormula(parser)); + case COMPOUND_FORMULA_TAG: + formulas.add(parseCompoundFormula(parser)); break; default: throw new RuntimeException( 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 f782f7173da24..b988fd4c40f1a 100644 --- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java +++ b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java @@ -16,6 +16,18 @@ package com.android.server.integrity.serializer; +import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START; +import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END; +import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START; +import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS; +import static com.android.server.integrity.model.ComponentBitSize.DEFAULT_FORMAT_VERSION; +import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS; +import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS; +import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS; +import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS; +import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS; +import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS; + import android.content.integrity.AtomicFormula; import android.content.integrity.CompoundFormula; import android.content.integrity.Formula; @@ -32,20 +44,6 @@ import java.util.Optional; /** 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( diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java new file mode 100644 index 0000000000000..88b6d70688ad7 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java @@ -0,0 +1,628 @@ +/* + * 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.parser; + +import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START; +import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END; +import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START; +import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS; +import static com.android.server.integrity.model.ComponentBitSize.DEFAULT_FORMAT_VERSION; +import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS; +import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS; +import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS; +import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS; +import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS; +import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS; +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.AtomicFormula; +import android.content.integrity.CompoundFormula; +import android.content.integrity.Rule; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +@RunWith(JUnit4.class) +public class RuleBinaryParserTest { + + private static final String COMPOUND_FORMULA_START_BITS = + getBits(COMPOUND_FORMULA_START, SEPARATOR_BITS); + private static final String COMPOUND_FORMULA_END_BITS = + getBits(COMPOUND_FORMULA_END, SEPARATOR_BITS); + private static final String ATOMIC_FORMULA_START_BITS = + getBits(ATOMIC_FORMULA_START, SEPARATOR_BITS); + private static final int INVALID_FORMULA_SEPARATOR_VALUE = 3; + private static final String INVALID_FORMULA_SEPARATOR_BITS = + getBits(INVALID_FORMULA_SEPARATOR_VALUE, SEPARATOR_BITS); + + private static final String NOT = getBits(CompoundFormula.NOT, CONNECTOR_BITS); + private static final String AND = getBits(CompoundFormula.AND, CONNECTOR_BITS); + private static final String OR = getBits(CompoundFormula.OR, CONNECTOR_BITS); + private static final int INVALID_CONNECTOR_VALUE = 3; + private static final String INVALID_CONNECTOR = + getBits(INVALID_CONNECTOR_VALUE, CONNECTOR_BITS); + + private static final String PACKAGE_NAME = getBits(AtomicFormula.PACKAGE_NAME, KEY_BITS); + private static final String APP_CERTIFICATE = getBits(AtomicFormula.APP_CERTIFICATE, KEY_BITS); + private static final String VERSION_CODE = getBits(AtomicFormula.VERSION_CODE, KEY_BITS); + private static final String PRE_INSTALLED = getBits(AtomicFormula.PRE_INSTALLED, KEY_BITS); + private static final int INVALID_KEY_VALUE = 6; + private static final String INVALID_KEY = getBits(INVALID_KEY_VALUE, KEY_BITS); + + private static final String EQ = getBits(AtomicFormula.EQ, OPERATOR_BITS); + private static final int INVALID_OPERATOR_VALUE = 5; + private static final String INVALID_OPERATOR = getBits(INVALID_OPERATOR_VALUE, OPERATOR_BITS); + + private static final String IS_NOT_HASHED = "0"; + + private static final String DENY = getBits(Rule.DENY, EFFECT_BITS); + private static final int INVALID_EFFECT_VALUE = 5; + private static final String INVALID_EFFECT = getBits(INVALID_EFFECT_VALUE, EFFECT_BITS); + + private static final String START_BIT = "1"; + private static final String END_BIT = "1"; + private static final String INVALID_MARKER_BIT = "0"; + + private static final byte[] DEFAULT_FORMAT_VERSION_BYTES = + getBytes(getBits(DEFAULT_FORMAT_VERSION, FORMAT_VERSION_BITS)); + + @Test + public void testBinaryStream_validCompoundFormula() throws Exception { + String packageName = "com.test.app"; + String ruleBits = + START_BIT + + COMPOUND_FORMULA_START_BITS + + NOT + + ATOMIC_FORMULA_START_BITS + + PACKAGE_NAME + + EQ + + IS_NOT_HASHED + + getBits(packageName.length(), VALUE_SIZE_BITS) + + getValueBits(packageName) + + COMPOUND_FORMULA_END_BITS + + DENY + + END_BIT; + byte[] ruleBytes = getBytes(ruleBits); + ByteBuffer rule = + ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); + rule.put(DEFAULT_FORMAT_VERSION_BYTES); + rule.put(ruleBytes); + RuleParser binaryParser = new RuleBinaryParser(); + InputStream inputStream = new ByteArrayInputStream(rule.array()); + Rule expectedRule = + new Rule( + new CompoundFormula( + CompoundFormula.NOT, + Collections.singletonList( + new AtomicFormula.StringAtomicFormula( + AtomicFormula.PACKAGE_NAME, + packageName, + /* isHashedValue= */ false))), + Rule.DENY); + + List rules = binaryParser.parse(inputStream); + + assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); + } + + @Test + public void testBinaryString_validCompoundFormula_notConnector() throws Exception { + String packageName = "com.test.app"; + String ruleBits = + START_BIT + + COMPOUND_FORMULA_START_BITS + + NOT + + ATOMIC_FORMULA_START_BITS + + PACKAGE_NAME + + EQ + + IS_NOT_HASHED + + getBits(packageName.length(), VALUE_SIZE_BITS) + + getValueBits(packageName) + + COMPOUND_FORMULA_END_BITS + + DENY + + END_BIT; + byte[] ruleBytes = getBytes(ruleBits); + ByteBuffer rule = + ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); + rule.put(DEFAULT_FORMAT_VERSION_BYTES); + rule.put(ruleBytes); + RuleParser binaryParser = new RuleBinaryParser(); + Rule expectedRule = + new Rule( + new CompoundFormula( + CompoundFormula.NOT, + Collections.singletonList( + new AtomicFormula.StringAtomicFormula( + AtomicFormula.PACKAGE_NAME, + packageName, + /* isHashedValue= */ false))), + Rule.DENY); + + List rules = binaryParser.parse(rule.array()); + + assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); + } + + @Test + public void testBinaryString_validCompoundFormula_andConnector() throws Exception { + String packageName = "com.test.app"; + String appCertificate = "test_cert"; + String ruleBits = + START_BIT + + COMPOUND_FORMULA_START_BITS + + AND + + ATOMIC_FORMULA_START_BITS + + PACKAGE_NAME + + EQ + + IS_NOT_HASHED + + getBits(packageName.length(), VALUE_SIZE_BITS) + + getValueBits(packageName) + + ATOMIC_FORMULA_START_BITS + + APP_CERTIFICATE + + EQ + + IS_NOT_HASHED + + getBits(appCertificate.length(), VALUE_SIZE_BITS) + + getValueBits(appCertificate) + + COMPOUND_FORMULA_END_BITS + + DENY + + END_BIT; + byte[] ruleBytes = getBytes(ruleBits); + ByteBuffer rule = + ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); + rule.put(DEFAULT_FORMAT_VERSION_BYTES); + rule.put(ruleBytes); + RuleParser binaryParser = new RuleBinaryParser(); + Rule expectedRule = + 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); + List rules = binaryParser.parse(rule.array()); + + assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); + } + + @Test + public void testBinaryString_validCompoundFormula_orConnector() throws Exception { + String packageName = "com.test.app"; + String appCertificate = "test_cert"; + String ruleBits = + START_BIT + + COMPOUND_FORMULA_START_BITS + + OR + + ATOMIC_FORMULA_START_BITS + + PACKAGE_NAME + + EQ + + IS_NOT_HASHED + + getBits(packageName.length(), VALUE_SIZE_BITS) + + getValueBits(packageName) + + ATOMIC_FORMULA_START_BITS + + APP_CERTIFICATE + + EQ + + IS_NOT_HASHED + + getBits(appCertificate.length(), VALUE_SIZE_BITS) + + getValueBits(appCertificate) + + COMPOUND_FORMULA_END_BITS + + DENY + + END_BIT; + byte[] ruleBytes = getBytes(ruleBits); + ByteBuffer rule = + ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); + rule.put(DEFAULT_FORMAT_VERSION_BYTES); + rule.put(ruleBytes); + RuleParser binaryParser = new RuleBinaryParser(); + Rule expectedRule = + 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); + + List rules = binaryParser.parse(rule.array()); + + assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); + } + + @Test + public void testBinaryString_validAtomicFormula_stringValue() throws Exception { + String packageName = "com.test.app"; + String ruleBits = + START_BIT + + ATOMIC_FORMULA_START_BITS + + PACKAGE_NAME + + EQ + + IS_NOT_HASHED + + getBits(packageName.length(), VALUE_SIZE_BITS) + + getValueBits(packageName) + + DENY + + END_BIT; + byte[] ruleBytes = getBytes(ruleBits); + ByteBuffer rule = + ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); + rule.put(DEFAULT_FORMAT_VERSION_BYTES); + rule.put(ruleBytes); + RuleParser binaryParser = new RuleBinaryParser(); + Rule expectedRule = + new Rule( + new AtomicFormula.StringAtomicFormula( + AtomicFormula.PACKAGE_NAME, + packageName, + /* isHashedValue= */ false), + Rule.DENY); + + List rules = binaryParser.parse(rule.array()); + + assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); + } + + @Test + public void testBinaryString_validAtomicFormula_integerValue() throws Exception { + String versionCode = "1"; + String ruleBits = + START_BIT + + ATOMIC_FORMULA_START_BITS + + VERSION_CODE + + EQ + + IS_NOT_HASHED + + getBits(versionCode.length(), VALUE_SIZE_BITS) + + getValueBits(versionCode) + + DENY + + END_BIT; + byte[] ruleBytes = getBytes(ruleBits); + ByteBuffer rule = + ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); + rule.put(DEFAULT_FORMAT_VERSION_BYTES); + rule.put(ruleBytes); + RuleParser binaryParser = new RuleBinaryParser(); + Rule expectedRule = + new Rule( + new AtomicFormula.IntAtomicFormula( + AtomicFormula.VERSION_CODE, AtomicFormula.EQ, 1), + Rule.DENY); + + List rules = binaryParser.parse(rule.array()); + + assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); + } + + @Test + public void testBinaryString_validAtomicFormula_booleanValue() throws Exception { + String isPreInstalled = "1"; + String ruleBits = + START_BIT + + ATOMIC_FORMULA_START_BITS + + PRE_INSTALLED + + EQ + + IS_NOT_HASHED + + getBits(isPreInstalled.length(), VALUE_SIZE_BITS) + + getValueBits(isPreInstalled) + + DENY + + END_BIT; + byte[] ruleBytes = getBytes(ruleBits); + ByteBuffer rule = + ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); + rule.put(DEFAULT_FORMAT_VERSION_BYTES); + rule.put(ruleBytes); + RuleParser binaryParser = new RuleBinaryParser(); + Rule expectedRule = + new Rule( + new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true), + Rule.DENY); + + List rules = binaryParser.parse(rule.array()); + + assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); + } + + @Test + public void testBinaryString_invalidAtomicFormula() throws Exception { + String versionCode = "test"; + String ruleBits = + START_BIT + + ATOMIC_FORMULA_START_BITS + + VERSION_CODE + + EQ + + IS_NOT_HASHED + + getBits(versionCode.length(), VALUE_SIZE_BITS) + + getValueBits(versionCode) + + DENY + + END_BIT; + byte[] ruleBytes = getBytes(ruleBits); + ByteBuffer rule = + ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); + rule.put(DEFAULT_FORMAT_VERSION_BYTES); + rule.put(ruleBytes); + RuleParser binaryParser = new RuleBinaryParser(); + + assertExpectException( + RuleParseException.class, + /* expectedExceptionMessageRegex */ "For input string:", + () -> binaryParser.parse(rule.array())); + } + + @Test + public void testBinaryString_withNoRuleList() throws RuleParseException { + ByteBuffer rule = ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length); + rule.put(DEFAULT_FORMAT_VERSION_BYTES); + RuleParser binaryParser = new RuleBinaryParser(); + + List rules = binaryParser.parse(rule.array()); + + assertThat(rules).isEmpty(); + } + + @Test + public void testBinaryString_withEmptyRule() throws RuleParseException { + String ruleBits = START_BIT; + byte[] ruleBytes = getBytes(ruleBits); + ByteBuffer rule = + ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); + rule.put(DEFAULT_FORMAT_VERSION_BYTES); + rule.put(ruleBytes); + RuleParser binaryParser = new RuleBinaryParser(); + + assertExpectException( + RuleParseException.class, + /* expectedExceptionMessageRegex */ "Invalid byte index", + () -> binaryParser.parse(rule.array())); + } + + @Test + public void testBinaryString_invalidCompoundFormula_invalidNumberOfFormulas() throws Exception { + String packageName = "com.test.app"; + String appCertificate = "test_cert"; + String ruleBits = + START_BIT + + COMPOUND_FORMULA_START_BITS + + NOT + + ATOMIC_FORMULA_START_BITS + + PACKAGE_NAME + + EQ + + IS_NOT_HASHED + + getBits(packageName.length(), VALUE_SIZE_BITS) + + getValueBits(packageName) + + ATOMIC_FORMULA_START_BITS + + APP_CERTIFICATE + + EQ + + IS_NOT_HASHED + + getBits(appCertificate.length(), VALUE_SIZE_BITS) + + getValueBits(appCertificate) + + COMPOUND_FORMULA_END_BITS + + DENY + + END_BIT; + byte[] ruleBytes = getBytes(ruleBits); + ByteBuffer rule = + ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); + rule.put(DEFAULT_FORMAT_VERSION_BYTES); + rule.put(ruleBytes); + RuleParser binaryParser = new RuleBinaryParser(); + + assertExpectException( + RuleParseException.class, + /* expectedExceptionMessageRegex */ "Connector NOT must have 1 formula only", + () -> binaryParser.parse(rule.array())); + } + + @Test + public void testBinaryString_invalidRule_invalidOperator() throws Exception { + String versionCode = "1"; + String ruleBits = + START_BIT + + COMPOUND_FORMULA_START_BITS + + NOT + + ATOMIC_FORMULA_START_BITS + + VERSION_CODE + + INVALID_OPERATOR + + IS_NOT_HASHED + + getBits(versionCode.length(), VALUE_SIZE_BITS) + + getValueBits(versionCode) + + COMPOUND_FORMULA_END_BITS + + DENY + + END_BIT; + byte[] ruleBytes = getBytes(ruleBits); + ByteBuffer rule = + ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); + rule.put(DEFAULT_FORMAT_VERSION_BYTES); + rule.put(ruleBytes); + RuleParser binaryParser = new RuleBinaryParser(); + + assertExpectException( + RuleParseException.class, + /* expectedExceptionMessageRegex */ String.format( + "Unknown operator: %d", INVALID_OPERATOR_VALUE), + () -> binaryParser.parse(rule.array())); + } + + @Test + public void testBinaryString_invalidRule_invalidEffect() throws Exception { + String packageName = "com.test.app"; + String ruleBits = + START_BIT + + COMPOUND_FORMULA_START_BITS + + NOT + + ATOMIC_FORMULA_START_BITS + + PACKAGE_NAME + + EQ + + IS_NOT_HASHED + + getBits(packageName.length(), VALUE_SIZE_BITS) + + getValueBits(packageName) + + COMPOUND_FORMULA_END_BITS + + INVALID_EFFECT + + END_BIT; + byte[] ruleBytes = getBytes(ruleBits); + ByteBuffer rule = + ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); + rule.put(DEFAULT_FORMAT_VERSION_BYTES); + rule.put(ruleBytes); + RuleParser binaryParser = new RuleBinaryParser(); + + assertExpectException( + RuleParseException.class, + /* expectedExceptionMessageRegex */ String.format( + "Unknown effect: %d", INVALID_EFFECT_VALUE), + () -> binaryParser.parse(rule.array())); + } + + @Test + public void testBinaryString_invalidRule_invalidConnector() throws Exception { + String packageName = "com.test.app"; + String ruleBits = + START_BIT + + COMPOUND_FORMULA_START_BITS + + INVALID_CONNECTOR + + ATOMIC_FORMULA_START_BITS + + PACKAGE_NAME + + EQ + + IS_NOT_HASHED + + getBits(packageName.length(), VALUE_SIZE_BITS) + + getValueBits(packageName) + + COMPOUND_FORMULA_END_BITS + + DENY + + END_BIT; + byte[] ruleBytes = getBytes(ruleBits); + ByteBuffer rule = + ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); + rule.put(DEFAULT_FORMAT_VERSION_BYTES); + rule.put(ruleBytes); + RuleParser binaryParser = new RuleBinaryParser(); + + assertExpectException( + RuleParseException.class, + /* expectedExceptionMessageRegex */ String.format( + "Unknown connector: %d", INVALID_CONNECTOR_VALUE), + () -> binaryParser.parse(rule.array())); + } + + @Test + public void testBinaryString_invalidRule_invalidKey() throws Exception { + String packageName = "com.test.app"; + String ruleBits = + START_BIT + + COMPOUND_FORMULA_START_BITS + + NOT + + ATOMIC_FORMULA_START_BITS + + INVALID_KEY + + EQ + + IS_NOT_HASHED + + getBits(packageName.length(), VALUE_SIZE_BITS) + + getValueBits(packageName) + + COMPOUND_FORMULA_END_BITS + + DENY + + END_BIT; + byte[] ruleBytes = getBytes(ruleBits); + ByteBuffer rule = + ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); + rule.put(DEFAULT_FORMAT_VERSION_BYTES); + rule.put(ruleBytes); + RuleParser binaryParser = new RuleBinaryParser(); + + assertExpectException( + RuleParseException.class, + /* expectedExceptionMessageRegex */ String.format( + "Unknown key: %d", INVALID_KEY_VALUE), + () -> binaryParser.parse(rule.array())); + } + + @Test + public void testBinaryString_invalidRule_invalidSeparator() throws Exception { + String packageName = "com.test.app"; + String ruleBits = + START_BIT + + INVALID_FORMULA_SEPARATOR_BITS + + NOT + + ATOMIC_FORMULA_START_BITS + + PACKAGE_NAME + + EQ + + IS_NOT_HASHED + + getBits(packageName.length(), VALUE_SIZE_BITS) + + getValueBits(packageName) + + COMPOUND_FORMULA_END_BITS + + DENY + + END_BIT; + byte[] ruleBytes = getBytes(ruleBits); + ByteBuffer rule = + ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); + rule.put(DEFAULT_FORMAT_VERSION_BYTES); + rule.put(ruleBytes); + RuleParser binaryParser = new RuleBinaryParser(); + + assertExpectException( + RuleParseException.class, + /* expectedExceptionMessageRegex */ String.format( + "Unknown formula separator: %d", INVALID_FORMULA_SEPARATOR_VALUE), + () -> binaryParser.parse(rule.array())); + } + + @Test + public void testBinaryString_invalidRule_invalidEndMarker() throws Exception { + String packageName = "com.test.app"; + String ruleBits = + START_BIT + + COMPOUND_FORMULA_START_BITS + + NOT + + ATOMIC_FORMULA_START_BITS + + PACKAGE_NAME + + EQ + + IS_NOT_HASHED + + getBits(packageName.length(), VALUE_SIZE_BITS) + + getValueBits(packageName) + + COMPOUND_FORMULA_END_BITS + + DENY + + INVALID_MARKER_BIT; + byte[] ruleBytes = getBytes(ruleBits); + ByteBuffer rule = + ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length); + rule.put(DEFAULT_FORMAT_VERSION_BYTES); + rule.put(ruleBytes); + RuleParser binaryParser = new RuleBinaryParser(); + + assertExpectException( + RuleParseException.class, + /* expectedExceptionMessageRegex */ "A rule must end with a '1' bit", + () -> binaryParser.parse(rule.array())); + } +} 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 index a43dbd77031a9..901277ded5dd9 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java @@ -16,6 +16,17 @@ package com.android.server.integrity.serializer; +import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START; +import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END; +import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START; +import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS; +import static com.android.server.integrity.model.ComponentBitSize.DEFAULT_FORMAT_VERSION; +import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS; +import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS; +import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS; +import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS; +import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS; +import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS; 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; @@ -43,45 +54,33 @@ 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 COMPOUND_FORMULA_START_BITS = + getBits(COMPOUND_FORMULA_START, SEPARATOR_BITS); + private static final String COMPOUND_FORMULA_END_BITS = + getBits(COMPOUND_FORMULA_END, SEPARATOR_BITS); + private static final String ATOMIC_FORMULA_START_BITS = + getBits(ATOMIC_FORMULA_START, 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 NOT = getBits(CompoundFormula.NOT, CONNECTOR_BITS); + private static final String AND = getBits(CompoundFormula.AND, CONNECTOR_BITS); + private static final String OR = getBits(CompoundFormula.OR, 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 PACKAGE_NAME = getBits(AtomicFormula.PACKAGE_NAME, KEY_BITS); + private static final String APP_CERTIFICATE = getBits(AtomicFormula.APP_CERTIFICATE, KEY_BITS); + private static final String VERSION_CODE = getBits(AtomicFormula.VERSION_CODE, KEY_BITS); + private static final String PRE_INSTALLED = getBits(AtomicFormula.PRE_INSTALLED, KEY_BITS); - private static final String EQ = getBits(AtomicFormula.EQ, RuleBinarySerializer.OPERATOR_BITS); + private static final String EQ = getBits(AtomicFormula.EQ, 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 DENY = getBits(Rule.DENY, 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)); + private static final byte[] DEFAULT_FORMAT_VERSION_BYTES = + getBytes(getBits(DEFAULT_FORMAT_VERSION, FORMAT_VERSION_BITS)); @Test public void testBinaryString_serializeEmptyRule() throws Exception { @@ -114,19 +113,19 @@ public class RuleBinarySerializerTest { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); String expectedBits = START_BIT - + COMPOUND_FORMULA_START + + COMPOUND_FORMULA_START_BITS + NOT - + ATOMIC_FORMULA_START + + ATOMIC_FORMULA_START_BITS + PACKAGE_NAME + EQ + IS_NOT_HASHED - + getBits(packageName.length(), RuleBinarySerializer.VALUE_SIZE_BITS) + + getBits(packageName.length(), VALUE_SIZE_BITS) + getValueBits(packageName) - + COMPOUND_FORMULA_END + + COMPOUND_FORMULA_END_BITS + DENY + END_BIT; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION); + byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES); byteArrayOutputStream.write(getBytes(expectedBits)); byte[] expectedRules = byteArrayOutputStream.toByteArray(); @@ -155,19 +154,19 @@ public class RuleBinarySerializerTest { RuleSerializer binarySerializer = new RuleBinarySerializer(); String expectedBits = START_BIT - + COMPOUND_FORMULA_START + + COMPOUND_FORMULA_START_BITS + NOT - + ATOMIC_FORMULA_START + + ATOMIC_FORMULA_START_BITS + PACKAGE_NAME + EQ + IS_NOT_HASHED - + getBits(packageName.length(), RuleBinarySerializer.VALUE_SIZE_BITS) + + getBits(packageName.length(), VALUE_SIZE_BITS) + getValueBits(packageName) - + COMPOUND_FORMULA_END + + COMPOUND_FORMULA_END_BITS + DENY + END_BIT; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION); + byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES); byteArrayOutputStream.write(getBytes(expectedBits)); byte[] expectedRules = byteArrayOutputStream.toByteArray(); @@ -199,25 +198,25 @@ public class RuleBinarySerializerTest { RuleSerializer binarySerializer = new RuleBinarySerializer(); String expectedBits = START_BIT - + COMPOUND_FORMULA_START + + COMPOUND_FORMULA_START_BITS + AND - + ATOMIC_FORMULA_START + + ATOMIC_FORMULA_START_BITS + PACKAGE_NAME + EQ + IS_NOT_HASHED - + getBits(packageName.length(), RuleBinarySerializer.VALUE_SIZE_BITS) + + getBits(packageName.length(), VALUE_SIZE_BITS) + getValueBits(packageName) - + ATOMIC_FORMULA_START + + ATOMIC_FORMULA_START_BITS + APP_CERTIFICATE + EQ + IS_NOT_HASHED - + getBits(appCertificate.length(), RuleBinarySerializer.VALUE_SIZE_BITS) + + getBits(appCertificate.length(), VALUE_SIZE_BITS) + getValueBits(appCertificate) - + COMPOUND_FORMULA_END + + COMPOUND_FORMULA_END_BITS + DENY + END_BIT; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION); + byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES); byteArrayOutputStream.write(getBytes(expectedBits)); byte[] expectedRules = byteArrayOutputStream.toByteArray(); @@ -249,25 +248,25 @@ public class RuleBinarySerializerTest { RuleSerializer binarySerializer = new RuleBinarySerializer(); String expectedBits = START_BIT - + COMPOUND_FORMULA_START + + COMPOUND_FORMULA_START_BITS + OR - + ATOMIC_FORMULA_START + + ATOMIC_FORMULA_START_BITS + PACKAGE_NAME + EQ + IS_NOT_HASHED - + getBits(packageName.length(), RuleBinarySerializer.VALUE_SIZE_BITS) + + getBits(packageName.length(), VALUE_SIZE_BITS) + getValueBits(packageName) - + ATOMIC_FORMULA_START + + ATOMIC_FORMULA_START_BITS + APP_CERTIFICATE + EQ + IS_NOT_HASHED - + getBits(appCertificate.length(), RuleBinarySerializer.VALUE_SIZE_BITS) + + getBits(appCertificate.length(), VALUE_SIZE_BITS) + getValueBits(appCertificate) - + COMPOUND_FORMULA_END + + COMPOUND_FORMULA_END_BITS + DENY + END_BIT; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION); + byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES); byteArrayOutputStream.write(getBytes(expectedBits)); byte[] expectedRules = byteArrayOutputStream.toByteArray(); @@ -291,16 +290,16 @@ public class RuleBinarySerializerTest { RuleSerializer binarySerializer = new RuleBinarySerializer(); String expectedBits = START_BIT - + ATOMIC_FORMULA_START + + ATOMIC_FORMULA_START_BITS + PACKAGE_NAME + EQ + IS_NOT_HASHED - + getBits(packageName.length(), RuleBinarySerializer.VALUE_SIZE_BITS) + + getBits(packageName.length(), VALUE_SIZE_BITS) + getValueBits(packageName) + DENY + END_BIT; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION); + byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES); byteArrayOutputStream.write(getBytes(expectedBits)); byte[] expectedRules = byteArrayOutputStream.toByteArray(); @@ -324,16 +323,16 @@ public class RuleBinarySerializerTest { RuleSerializer binarySerializer = new RuleBinarySerializer(); String expectedBits = START_BIT - + ATOMIC_FORMULA_START + + ATOMIC_FORMULA_START_BITS + VERSION_CODE + EQ + IS_NOT_HASHED - + getBits(versionCode.length(), RuleBinarySerializer.VALUE_SIZE_BITS) + + getBits(versionCode.length(), VALUE_SIZE_BITS) + getValueBits(versionCode) + DENY + END_BIT; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION); + byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES); byteArrayOutputStream.write(getBytes(expectedBits)); byte[] expectedRules = byteArrayOutputStream.toByteArray(); @@ -354,16 +353,16 @@ public class RuleBinarySerializerTest { RuleSerializer binarySerializer = new RuleBinarySerializer(); String expectedBits = START_BIT - + ATOMIC_FORMULA_START + + ATOMIC_FORMULA_START_BITS + PRE_INSTALLED + EQ + IS_NOT_HASHED - + getBits(preInstalled.length(), RuleBinarySerializer.VALUE_SIZE_BITS) + + getBits(preInstalled.length(), VALUE_SIZE_BITS) + getValueBits(preInstalled) + DENY + END_BIT; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION); + byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES); byteArrayOutputStream.write(getBytes(expectedBits)); byte[] expectedRules = byteArrayOutputStream.toByteArray(); @@ -393,7 +392,7 @@ public class RuleBinarySerializerTest { public void testBinaryString_serializeFormatVersion() throws Exception { int formatVersion = 1; RuleSerializer binarySerializer = new RuleBinarySerializer(); - String expectedBits = getBits(formatVersion, RuleBinarySerializer.FORMAT_VERSION_BITS); + String expectedBits = getBits(formatVersion, FORMAT_VERSION_BITS); byte[] expectedRules = getBytes(expectedBits); byte[] actualRules =