Merge changes I11fbf84d,Ia804137d
* changes: Implement rule binary parser Implement rule binary serializer
This commit is contained in:
committed by
Android (Google) Code Review
commit
b49e6f44c8
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<Rule> parse(byte[] ruleBytes) {
|
||||
// TODO: Implement binary text parser.
|
||||
return null;
|
||||
public List<Rule> 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<Rule> parse(InputStream inputStream) {
|
||||
// TODO: Implement stream parser.
|
||||
return null;
|
||||
public List<Rule> parse(InputStream inputStream) throws RuleParseException {
|
||||
try {
|
||||
byte[] ruleBytes = new byte[inputStream.available()];
|
||||
inputStream.read(ruleBytes);
|
||||
return parse(ruleBytes);
|
||||
} catch (Exception e) {
|
||||
throw new RuleParseException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Rule> parseRules(BitInputStream bitInputStream) {
|
||||
List<Rule> 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<Formula> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -16,24 +16,155 @@
|
||||
|
||||
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;
|
||||
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 {
|
||||
|
||||
// Get the byte representation for a list of rules, and write them to an output stream.
|
||||
@Override
|
||||
public void serialize(
|
||||
List<Rule> rules, Optional<Integer> formatVersion, OutputStream outputStream) {
|
||||
// TODO: Implement stream serializer.
|
||||
List<Rule> rules, Optional<Integer> formatVersion, OutputStream outputStream)
|
||||
throws RuleSerializeException {
|
||||
try {
|
||||
BitOutputStream bitOutputStream = new BitOutputStream();
|
||||
|
||||
int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION);
|
||||
bitOutputStream.setNext(FORMAT_VERSION_BITS, formatVersionValue);
|
||||
outputStream.write(bitOutputStream.toByteArray());
|
||||
|
||||
for (Rule rule : rules) {
|
||||
bitOutputStream.clear();
|
||||
serializeRule(rule, bitOutputStream);
|
||||
outputStream.write(bitOutputStream.toByteArray());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuleSerializeException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the byte representation for a list of rules.
|
||||
@Override
|
||||
public byte[] serialize(List<Rule> rules, Optional<Integer> formatVersion) {
|
||||
// TODO: Implement text serializer.
|
||||
return null;
|
||||
public byte[] serialize(List<Rule> rules, Optional<Integer> formatVersion)
|
||||
throws RuleSerializeException {
|
||||
try {
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
serialize(rules, formatVersion, byteArrayOutputStream);
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
} catch (Exception e) {
|
||||
throw new RuleSerializeException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void serializeRule(Rule rule, BitOutputStream bitOutputStream) {
|
||||
if (rule == null) {
|
||||
throw new IllegalArgumentException("Null rule can not be serialized");
|
||||
}
|
||||
|
||||
// Start with a '1' bit to mark the start of a rule.
|
||||
bitOutputStream.setNext();
|
||||
|
||||
serializeFormula(rule.getFormula(), bitOutputStream);
|
||||
bitOutputStream.setNext(EFFECT_BITS, rule.getEffect());
|
||||
|
||||
// End with a '1' bit to mark the end of a rule.
|
||||
bitOutputStream.setNext();
|
||||
}
|
||||
|
||||
private void serializeFormula(Formula formula, BitOutputStream bitOutputStream) {
|
||||
if (formula instanceof AtomicFormula) {
|
||||
serializeAtomicFormula((AtomicFormula) formula, bitOutputStream);
|
||||
} else if (formula instanceof CompoundFormula) {
|
||||
serializeCompoundFormula((CompoundFormula) formula, bitOutputStream);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Invalid formula type: %s", formula.getClass()));
|
||||
}
|
||||
}
|
||||
|
||||
private void serializeCompoundFormula(
|
||||
CompoundFormula compoundFormula, BitOutputStream bitOutputStream) {
|
||||
if (compoundFormula == null) {
|
||||
throw new IllegalArgumentException("Null compound formula can not be serialized");
|
||||
}
|
||||
|
||||
bitOutputStream.setNext(SEPARATOR_BITS, COMPOUND_FORMULA_START);
|
||||
bitOutputStream.setNext(CONNECTOR_BITS, compoundFormula.getConnector());
|
||||
for (Formula formula : compoundFormula.getFormulas()) {
|
||||
serializeFormula(formula, bitOutputStream);
|
||||
}
|
||||
bitOutputStream.setNext(SEPARATOR_BITS, COMPOUND_FORMULA_END);
|
||||
}
|
||||
|
||||
private void serializeAtomicFormula(
|
||||
AtomicFormula atomicFormula, BitOutputStream bitOutputStream) {
|
||||
if (atomicFormula == null) {
|
||||
throw new IllegalArgumentException("Null atomic formula can not be serialized");
|
||||
}
|
||||
|
||||
bitOutputStream.setNext(SEPARATOR_BITS, ATOMIC_FORMULA_START);
|
||||
bitOutputStream.setNext(KEY_BITS, atomicFormula.getKey());
|
||||
if (atomicFormula instanceof AtomicFormula.StringAtomicFormula) {
|
||||
AtomicFormula.StringAtomicFormula stringAtomicFormula =
|
||||
(AtomicFormula.StringAtomicFormula) atomicFormula;
|
||||
bitOutputStream.setNext(OPERATOR_BITS, AtomicFormula.EQ);
|
||||
serializeValue(
|
||||
stringAtomicFormula.getValue(),
|
||||
stringAtomicFormula.getIsHashedValue(),
|
||||
bitOutputStream);
|
||||
} else if (atomicFormula instanceof AtomicFormula.IntAtomicFormula) {
|
||||
AtomicFormula.IntAtomicFormula intAtomicFormula =
|
||||
(AtomicFormula.IntAtomicFormula) atomicFormula;
|
||||
bitOutputStream.setNext(OPERATOR_BITS, intAtomicFormula.getOperator());
|
||||
serializeValue(
|
||||
String.valueOf(intAtomicFormula.getValue()),
|
||||
/* isHashedValue= */ false,
|
||||
bitOutputStream);
|
||||
} else if (atomicFormula instanceof AtomicFormula.BooleanAtomicFormula) {
|
||||
AtomicFormula.BooleanAtomicFormula booleanAtomicFormula =
|
||||
(AtomicFormula.BooleanAtomicFormula) atomicFormula;
|
||||
bitOutputStream.setNext(OPERATOR_BITS, AtomicFormula.EQ);
|
||||
serializeValue(
|
||||
booleanAtomicFormula.getValue() ? "1" : "0",
|
||||
/* isHashedValue= */ false,
|
||||
bitOutputStream);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Invalid atomic formula type: %s", atomicFormula.getClass()));
|
||||
}
|
||||
}
|
||||
|
||||
private void serializeValue(
|
||||
String value, boolean isHashedValue, BitOutputStream bitOutputStream) {
|
||||
byte[] valueBytes = value.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
bitOutputStream.setNext(isHashedValue);
|
||||
bitOutputStream.setNext(VALUE_SIZE_BITS, valueBytes.length);
|
||||
for (byte valueByte : valueBytes) {
|
||||
bitOutputStream.setNext(/* numOfBits= */ 8, valueByte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Rule> rules, XmlSerializer xmlSerializer) throws IOException {
|
||||
xmlSerializer.startTag(NAMESPACE, RULE_LIST_TAG);
|
||||
for (Rule rule : rules) {
|
||||
serialize(rule, xmlSerializer);
|
||||
serializeRule(rule, xmlSerializer);
|
||||
}
|
||||
xmlSerializer.endTag(NAMESPACE, RULE_LIST_TAG);
|
||||
xmlSerializer.endDocument();
|
||||
}
|
||||
|
||||
private void serialize(Rule rule, XmlSerializer xmlSerializer) throws IOException {
|
||||
private void serializeRule(Rule rule, XmlSerializer xmlSerializer) throws IOException {
|
||||
if (rule == null) {
|
||||
return;
|
||||
}
|
||||
@@ -98,25 +98,25 @@ 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)
|
||||
|
||||
@@ -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<Rule> 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<Rule> 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<Rule> 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<Rule> 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<Rule> 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<Rule> 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<Rule> 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<Rule> 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()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,444 @@
|
||||
/*
|
||||
* 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.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.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_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, 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, 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, OPERATOR_BITS);
|
||||
|
||||
private static final String IS_NOT_HASHED = "0";
|
||||
|
||||
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_BYTES =
|
||||
getBytes(getBits(DEFAULT_FORMAT_VERSION, 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_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;
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
|
||||
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_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;
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
|
||||
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_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;
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
|
||||
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_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;
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
|
||||
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_BITS
|
||||
+ PACKAGE_NAME
|
||||
+ EQ
|
||||
+ IS_NOT_HASHED
|
||||
+ getBits(packageName.length(), VALUE_SIZE_BITS)
|
||||
+ getValueBits(packageName)
|
||||
+ DENY
|
||||
+ END_BIT;
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
|
||||
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_BITS
|
||||
+ VERSION_CODE
|
||||
+ EQ
|
||||
+ IS_NOT_HASHED
|
||||
+ getBits(versionCode.length(), VALUE_SIZE_BITS)
|
||||
+ getValueBits(versionCode)
|
||||
+ DENY
|
||||
+ END_BIT;
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
|
||||
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_BITS
|
||||
+ PRE_INSTALLED
|
||||
+ EQ
|
||||
+ IS_NOT_HASHED
|
||||
+ getBits(preInstalled.length(), VALUE_SIZE_BITS)
|
||||
+ getValueBits(preInstalled)
|
||||
+ DENY
|
||||
+ END_BIT;
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
|
||||
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, 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user