Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 99266924 authored by Ömer Nebil Yaveroğlu's avatar Ömer Nebil Yaveroğlu Committed by Android (Google) Code Review
Browse files

Merge "Implement a tracked bit reader (BitTrackedInputStream) which is capable...

Merge "Implement a tracked bit reader (BitTrackedInputStream) which is capable of moving the file reading cursor forward when needed."
parents c20fe0cc 918e0ec5
Loading
Loading
Loading
Loading
+69 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -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;
+33 −32
Original line number Diff line number Diff line
@@ -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));
+3 −1
Original line number Diff line number Diff line
@@ -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();
+142 −0
Original line number Diff line number Diff line
/*
 * 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));
    }
}
Loading