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; + } +}