Implement a tracked bit reader (BitTrackedInputStream) which is capable
of moving the file reading cursor forward when needed. Bug: 145493956 Test: atest FrameworksServicesTests:BitTrackedInputStreamTest Change-Id: I18f95e17f3cce4d5d818219cdde07b354bfa9081
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* An input stream that tracks the total number read bytes since construction and allows moving
|
||||
* fast forward to a certain byte any time during the execution.
|
||||
*
|
||||
* This class is used for efficient reading of rules based on the rule indexing.
|
||||
*/
|
||||
public class BitTrackedInputStream extends BitInputStream {
|
||||
|
||||
private static int sReadBitsCount;
|
||||
|
||||
/** Constructor with byte array. */
|
||||
public BitTrackedInputStream(byte[] inputStream) {
|
||||
super(inputStream);
|
||||
sReadBitsCount = 0;
|
||||
}
|
||||
|
||||
/** Constructor with input stream. */
|
||||
public BitTrackedInputStream(InputStream inputStream) {
|
||||
super(inputStream);
|
||||
sReadBitsCount = 0;
|
||||
}
|
||||
|
||||
/** Obtains an integer value of the next {@code numOfBits}. */
|
||||
@Override
|
||||
public int getNext(int numOfBits) throws IOException {
|
||||
sReadBitsCount += numOfBits;
|
||||
return super.getNext(numOfBits);
|
||||
}
|
||||
|
||||
/** Returns the current cursor position showing the number of bits that are read. */
|
||||
public int getReadBitsCount() {
|
||||
return sReadBitsCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cursor to the specified byte location.
|
||||
*
|
||||
* Note that the integer parameter specifies the location in bytes -- not bits.
|
||||
*/
|
||||
public void setCursorToByteLocation(int byteLocation) throws IOException {
|
||||
int bitCountToRead = byteLocation * 8 - sReadBitsCount;
|
||||
if (bitCountToRead < 0) {
|
||||
throw new IllegalStateException("The byte position is already read.");
|
||||
}
|
||||
super.getNext(bitCountToRead);
|
||||
sReadBitsCount = byteLocation * 8;
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.integrity.serializer;
|
||||
package com.android.server.integrity.model;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
@@ -37,7 +37,7 @@ import android.content.integrity.CompoundFormula;
|
||||
import android.content.integrity.Formula;
|
||||
import android.content.integrity.Rule;
|
||||
|
||||
import com.android.server.integrity.model.BitInputStream;
|
||||
import com.android.server.integrity.model.BitTrackedInputStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -47,13 +47,11 @@ import java.util.List;
|
||||
/** A helper class to parse rules into the {@link Rule} model from Binary representation. */
|
||||
public class RuleBinaryParser implements RuleParser {
|
||||
|
||||
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
|
||||
|
||||
@Override
|
||||
public List<Rule> parse(byte[] ruleBytes) throws RuleParseException {
|
||||
try {
|
||||
BitInputStream bitInputStream = new BitInputStream(ruleBytes);
|
||||
return parseRules(bitInputStream);
|
||||
BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(ruleBytes);
|
||||
return parseRules(bitTrackedInputStream);
|
||||
} catch (Exception e) {
|
||||
throw new RuleParseException(e.getMessage(), e);
|
||||
}
|
||||
@@ -62,46 +60,46 @@ public class RuleBinaryParser implements RuleParser {
|
||||
@Override
|
||||
public List<Rule> parse(InputStream inputStream) throws RuleParseException {
|
||||
try {
|
||||
BitInputStream bitInputStream = new BitInputStream(inputStream);
|
||||
return parseRules(bitInputStream);
|
||||
BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(inputStream);
|
||||
return parseRules(bitTrackedInputStream);
|
||||
} catch (Exception e) {
|
||||
throw new RuleParseException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Rule> parseRules(BitInputStream bitInputStream) throws IOException {
|
||||
private List<Rule> parseRules(BitTrackedInputStream bitTrackedInputStream) throws IOException {
|
||||
List<Rule> parsedRules = new ArrayList<>();
|
||||
|
||||
// Read the rule binary file format version.
|
||||
bitInputStream.getNext(FORMAT_VERSION_BITS);
|
||||
bitTrackedInputStream.getNext(FORMAT_VERSION_BITS);
|
||||
|
||||
while (bitInputStream.hasNext()) {
|
||||
if (bitInputStream.getNext(SIGNAL_BIT) == 1) {
|
||||
parsedRules.add(parseRule(bitInputStream));
|
||||
while (bitTrackedInputStream.hasNext()) {
|
||||
if (bitTrackedInputStream.getNext(SIGNAL_BIT) == 1) {
|
||||
parsedRules.add(parseRule(bitTrackedInputStream));
|
||||
}
|
||||
}
|
||||
|
||||
return parsedRules;
|
||||
}
|
||||
|
||||
private Rule parseRule(BitInputStream bitInputStream) throws IOException {
|
||||
Formula formula = parseFormula(bitInputStream);
|
||||
int effect = bitInputStream.getNext(EFFECT_BITS);
|
||||
private Rule parseRule(BitTrackedInputStream bitTrackedInputStream) throws IOException {
|
||||
Formula formula = parseFormula(bitTrackedInputStream);
|
||||
int effect = bitTrackedInputStream.getNext(EFFECT_BITS);
|
||||
|
||||
if (bitInputStream.getNext(SIGNAL_BIT) != 1) {
|
||||
if (bitTrackedInputStream.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) throws IOException {
|
||||
int separator = bitInputStream.getNext(SEPARATOR_BITS);
|
||||
private Formula parseFormula(BitTrackedInputStream bitTrackedInputStream) throws IOException {
|
||||
int separator = bitTrackedInputStream.getNext(SEPARATOR_BITS);
|
||||
switch (separator) {
|
||||
case ATOMIC_FORMULA_START:
|
||||
return parseAtomicFormula(bitInputStream);
|
||||
return parseAtomicFormula(bitTrackedInputStream);
|
||||
case COMPOUND_FORMULA_START:
|
||||
return parseCompoundFormula(bitInputStream);
|
||||
return parseCompoundFormula(bitTrackedInputStream);
|
||||
case COMPOUND_FORMULA_END:
|
||||
return null;
|
||||
default:
|
||||
@@ -110,37 +108,40 @@ public class RuleBinaryParser implements RuleParser {
|
||||
}
|
||||
}
|
||||
|
||||
private CompoundFormula parseCompoundFormula(BitInputStream bitInputStream) throws IOException {
|
||||
int connector = bitInputStream.getNext(CONNECTOR_BITS);
|
||||
private CompoundFormula parseCompoundFormula(BitTrackedInputStream bitTrackedInputStream)
|
||||
throws IOException {
|
||||
int connector = bitTrackedInputStream.getNext(CONNECTOR_BITS);
|
||||
List<Formula> formulas = new ArrayList<>();
|
||||
|
||||
Formula parsedFormula = parseFormula(bitInputStream);
|
||||
Formula parsedFormula = parseFormula(bitTrackedInputStream);
|
||||
while (parsedFormula != null) {
|
||||
formulas.add(parsedFormula);
|
||||
parsedFormula = parseFormula(bitInputStream);
|
||||
parsedFormula = parseFormula(bitTrackedInputStream);
|
||||
}
|
||||
|
||||
return new CompoundFormula(connector, formulas);
|
||||
}
|
||||
|
||||
private AtomicFormula parseAtomicFormula(BitInputStream bitInputStream) throws IOException {
|
||||
int key = bitInputStream.getNext(KEY_BITS);
|
||||
int operator = bitInputStream.getNext(OPERATOR_BITS);
|
||||
private AtomicFormula parseAtomicFormula(BitTrackedInputStream bitTrackedInputStream)
|
||||
throws IOException {
|
||||
int key = bitTrackedInputStream.getNext(KEY_BITS);
|
||||
int operator = bitTrackedInputStream.getNext(OPERATOR_BITS);
|
||||
|
||||
switch (key) {
|
||||
case AtomicFormula.PACKAGE_NAME:
|
||||
case AtomicFormula.APP_CERTIFICATE:
|
||||
case AtomicFormula.INSTALLER_NAME:
|
||||
case AtomicFormula.INSTALLER_CERTIFICATE:
|
||||
boolean isHashedValue = bitInputStream.getNext(IS_HASHED_BITS) == 1;
|
||||
int valueSize = bitInputStream.getNext(VALUE_SIZE_BITS);
|
||||
String stringValue = getStringValue(bitInputStream, valueSize, isHashedValue);
|
||||
boolean isHashedValue = bitTrackedInputStream.getNext(IS_HASHED_BITS) == 1;
|
||||
int valueSize = bitTrackedInputStream.getNext(VALUE_SIZE_BITS);
|
||||
String stringValue = getStringValue(bitTrackedInputStream, valueSize,
|
||||
isHashedValue);
|
||||
return new AtomicFormula.StringAtomicFormula(key, stringValue, isHashedValue);
|
||||
case AtomicFormula.VERSION_CODE:
|
||||
int intValue = getIntValue(bitInputStream);
|
||||
int intValue = getIntValue(bitTrackedInputStream);
|
||||
return new AtomicFormula.IntAtomicFormula(key, operator, intValue);
|
||||
case AtomicFormula.PRE_INSTALLED:
|
||||
boolean booleanValue = getBooleanValue(bitInputStream);
|
||||
boolean booleanValue = getBooleanValue(bitTrackedInputStream);
|
||||
return new AtomicFormula.BooleanAtomicFormula(key, booleanValue);
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format("Unknown key: %d", key));
|
||||
|
||||
@@ -42,6 +42,7 @@ import android.content.integrity.Rule;
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.android.server.integrity.IntegrityUtils;
|
||||
import com.android.server.integrity.model.BitOutputStream;
|
||||
import com.android.server.integrity.model.ByteTrackedOutputStream;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
@@ -109,7 +110,8 @@ public class RuleBinarySerializer implements RuleSerializer {
|
||||
}
|
||||
|
||||
private void serializeRuleFileMetadata(Optional<Integer> formatVersion,
|
||||
ByteTrackedOutputStream outputStream) throws IOException {
|
||||
ByteTrackedOutputStream outputStream)
|
||||
throws IOException {
|
||||
int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION);
|
||||
|
||||
BitOutputStream bitOutputStream = new BitOutputStream();
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 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.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.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.testng.Assert.assertThrows;
|
||||
|
||||
import android.content.integrity.AtomicFormula;
|
||||
import android.content.integrity.CompoundFormula;
|
||||
import android.content.integrity.Rule;
|
||||
|
||||
import com.android.server.integrity.parser.BinaryFileOperations;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class BitTrackedInputStreamTest {
|
||||
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 PACKAGE_NAME = getBits(AtomicFormula.PACKAGE_NAME, KEY_BITS);
|
||||
private static final String EQ = getBits(AtomicFormula.EQ, OPERATOR_BITS);
|
||||
private static final String DENY = getBits(Rule.DENY, EFFECT_BITS);
|
||||
|
||||
private static final String IS_NOT_HASHED = "0";
|
||||
private static final String START_BIT = "1";
|
||||
private static final String END_BIT = "1";
|
||||
|
||||
@Test
|
||||
public void testBitOperationsCountBitsCorrectly() throws IOException {
|
||||
String packageName = "com.test.app";
|
||||
byte[] testInput =
|
||||
getBytes(
|
||||
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);
|
||||
|
||||
BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(testInput);
|
||||
|
||||
// Right after construction, the read bits count should be 0.
|
||||
assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(0);
|
||||
|
||||
// Get next 10 bits should result with 10 bits read.
|
||||
bitTrackedInputStream.getNext(10);
|
||||
assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(10);
|
||||
|
||||
// When we move the cursor 8 bytes, we should point to 64 bits.
|
||||
bitTrackedInputStream.setCursorToByteLocation(8);
|
||||
assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(64);
|
||||
|
||||
// Read until the end and the total size of the input stream should be available.
|
||||
while (bitTrackedInputStream.hasNext()) {
|
||||
bitTrackedInputStream.getNext(1);
|
||||
}
|
||||
assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(128);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBitInputStreamOperationsStillWork() throws IOException {
|
||||
String packageName = "com.test.app";
|
||||
byte[] testInput =
|
||||
getBytes(
|
||||
IS_NOT_HASHED
|
||||
+ getBits(packageName.length(), VALUE_SIZE_BITS)
|
||||
+ getValueBits(packageName));
|
||||
|
||||
BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(testInput);
|
||||
assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(0);
|
||||
|
||||
// Read until the string parameter.
|
||||
String stringValue = BinaryFileOperations.getStringValue(bitTrackedInputStream);
|
||||
|
||||
// Verify that the read bytes are counted.
|
||||
assertThat(stringValue).isEqualTo(packageName);
|
||||
assertThat(bitTrackedInputStream.getReadBitsCount()).isGreaterThan(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBitTrackedInputStream_moveCursorForwardFailsIfAlreadyRead() throws IOException {
|
||||
String packageName = "com.test.app";
|
||||
byte[] testInput =
|
||||
getBytes(
|
||||
IS_NOT_HASHED
|
||||
+ getBits(packageName.length(), VALUE_SIZE_BITS)
|
||||
+ getValueBits(packageName));
|
||||
|
||||
BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(testInput);
|
||||
|
||||
// Read more than two bytes.
|
||||
bitTrackedInputStream.getNext(20);
|
||||
|
||||
// Ask to move the cursor to the second byte.
|
||||
assertThrows(
|
||||
IllegalStateException.class,
|
||||
() -> bitTrackedInputStream.setCursorToByteLocation(2));
|
||||
}
|
||||
}
|
||||
@@ -14,12 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.integrity.serializer;
|
||||
package com.android.server.integrity.model;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import com.android.server.integrity.model.BitOutputStream;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
Reference in New Issue
Block a user