Merge "Remove bytes matcher from base" am: d3dfca9c68 am: 55a81e6cc0 am: 5ce3ad0bb0

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1853038

Change-Id: I029f17d4e31534e2501aa670605d2cee7116a7ea
This commit is contained in:
Treehugger Robot
2021-10-27 20:01:55 +00:00
committed by Automerger Merge Worker
4 changed files with 5 additions and 495 deletions

View File

@@ -1,315 +0,0 @@
/*
* Copyright (C) 2021 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 android.os;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.bluetooth.BluetoothUuid;
import android.net.MacAddress;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.util.HexDump;
import java.util.ArrayList;
import java.util.function.Predicate;
/**
* Predicate that tests if a given {@code byte[]} value matches a set of
* configured rules.
* <p>
* Rules are tested in the order in which they were originally added, which
* means a narrow rule can reject a specific value before a later broader rule
* might accept that same value, or vice versa.
* <p>
* Matchers can contain rules of varying lengths, and tested values will only be
* matched against rules of the exact same length. This is designed to support
* {@link BluetoothUuid} style values which can be variable length.
*
* @hide
*/
public class BytesMatcher implements Predicate<byte[]> {
private static final String TAG = "BytesMatcher";
private static final char TYPE_EXACT_ACCEPT = '+';
private static final char TYPE_EXACT_REJECT = '-';
private static final char TYPE_PREFIX_ACCEPT = '⊆';
private static final char TYPE_PREFIX_REJECT = '⊈';
private final ArrayList<Rule> mRules = new ArrayList<>();
private static class Rule {
public final char type;
public final @NonNull byte[] value;
public final @Nullable byte[] mask;
public Rule(char type, @NonNull byte[] value, @Nullable byte[] mask) {
if (mask != null && value.length != mask.length) {
throw new IllegalArgumentException(
"Expected length " + value.length + " but found " + mask.length);
}
this.type = type;
this.value = value;
this.mask = mask;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
encode(builder);
return builder.toString();
}
public void encode(@NonNull StringBuilder builder) {
builder.append(type);
builder.append(HexDump.toHexString(value));
if (mask != null) {
builder.append('/');
builder.append(HexDump.toHexString(mask));
}
}
public boolean test(@NonNull byte[] value) {
switch (type) {
case TYPE_EXACT_ACCEPT:
case TYPE_EXACT_REJECT:
if (value.length != this.value.length) {
return false;
}
break;
case TYPE_PREFIX_ACCEPT:
case TYPE_PREFIX_REJECT:
if (value.length < this.value.length) {
return false;
}
break;
}
for (int i = 0; i < this.value.length; i++) {
byte local = this.value[i];
byte remote = value[i];
if (this.mask != null) {
local &= this.mask[i];
remote &= this.mask[i];
}
if (local != remote) {
return false;
}
}
return true;
}
}
/**
* Add a rule that will result in {@link #test(byte[])} returning
* {@code true} when a value being tested matches it. This rule will only
* match values of the exact same length.
* <p>
* Rules are tested in the order in which they were originally added, which
* means a narrow rule can reject a specific value before a later broader
* rule might accept that same value, or vice versa.
*
* @param value to be matched
* @param mask to be applied to both values before testing for equality; if
* {@code null} then both values must match exactly
*/
public void addExactAcceptRule(@NonNull byte[] value, @Nullable byte[] mask) {
mRules.add(new Rule(TYPE_EXACT_ACCEPT, value, mask));
}
/**
* Add a rule that will result in {@link #test(byte[])} returning
* {@code false} when a value being tested matches it. This rule will only
* match values of the exact same length.
* <p>
* Rules are tested in the order in which they were originally added, which
* means a narrow rule can reject a specific value before a later broader
* rule might accept that same value, or vice versa.
*
* @param value to be matched
* @param mask to be applied to both values before testing for equality; if
* {@code null} then both values must match exactly
*/
public void addExactRejectRule(@NonNull byte[] value, @Nullable byte[] mask) {
mRules.add(new Rule(TYPE_EXACT_REJECT, value, mask));
}
/**
* Add a rule that will result in {@link #test(byte[])} returning
* {@code true} when a value being tested matches it. This rule will match
* values of the exact same length or longer.
* <p>
* Rules are tested in the order in which they were originally added, which
* means a narrow rule can reject a specific value before a later broader
* rule might accept that same value, or vice versa.
*
* @param value to be matched
* @param mask to be applied to both values before testing for equality; if
* {@code null} then both values must match exactly
*/
public void addPrefixAcceptRule(@NonNull byte[] value, @Nullable byte[] mask) {
mRules.add(new Rule(TYPE_PREFIX_ACCEPT, value, mask));
}
/**
* Add a rule that will result in {@link #test(byte[])} returning
* {@code false} when a value being tested matches it. This rule will match
* values of the exact same length or longer.
* <p>
* Rules are tested in the order in which they were originally added, which
* means a narrow rule can reject a specific value before a later broader
* rule might accept that same value, or vice versa.
*
* @param value to be matched
* @param mask to be applied to both values before testing for equality; if
* {@code null} then both values must match exactly
*/
public void addPrefixRejectRule(@NonNull byte[] value, @Nullable byte[] mask) {
mRules.add(new Rule(TYPE_PREFIX_REJECT, value, mask));
}
/**
* Test if the given {@code ParcelUuid} value matches the set of rules
* configured in this matcher.
*/
public boolean testBluetoothUuid(@NonNull ParcelUuid value) {
return test(BluetoothUuid.uuidToBytes(value));
}
/**
* Test if the given {@code MacAddress} value matches the set of rules
* configured in this matcher.
*/
public boolean testMacAddress(@NonNull MacAddress value) {
return test(value.toByteArray());
}
/**
* Test if the given {@code byte[]} value matches the set of rules
* configured in this matcher.
*/
@Override
public boolean test(@NonNull byte[] value) {
return test(value, false);
}
/**
* Test if the given {@code byte[]} value matches the set of rules
* configured in this matcher.
*/
public boolean test(@NonNull byte[] value, boolean defaultValue) {
final int size = mRules.size();
for (int i = 0; i < size; i++) {
final Rule rule = mRules.get(i);
if (rule.test(value)) {
switch (rule.type) {
case TYPE_EXACT_ACCEPT:
case TYPE_PREFIX_ACCEPT:
return true;
case TYPE_EXACT_REJECT:
case TYPE_PREFIX_REJECT:
return false;
}
}
}
return defaultValue;
}
/**
* Encode the given matcher into a human-readable {@link String} which can
* be used to transport matchers across device boundaries.
* <p>
* The human-readable format is an ordered list separated by commas, where
* each rule is a {@code +} or {@code -} symbol indicating if the match
* should be accepted or rejected, then followed by a hex value and an
* optional hex mask. For example, {@code -caff,+cafe/ff00} is a valid
* encoded matcher.
*
* @see #decode(String)
*/
public static @NonNull String encode(@NonNull BytesMatcher matcher) {
final StringBuilder builder = new StringBuilder();
final int size = matcher.mRules.size();
for (int i = 0; i < size; i++) {
final Rule rule = matcher.mRules.get(i);
rule.encode(builder);
builder.append(',');
}
if (builder.length() > 0) {
builder.deleteCharAt(builder.length() - 1);
}
return builder.toString();
}
/**
* Decode the given human-readable {@link String} used to transport matchers
* across device boundaries.
* <p>
* The human-readable format is an ordered list separated by commas, where
* each rule is a {@code +} or {@code -} symbol indicating if the match
* should be accepted or rejected, then followed by a hex value and an
* optional hex mask. For example, {@code -caff,+cafe/ff00} is a valid
* encoded matcher.
*
* @see #encode(BytesMatcher)
*/
public static @NonNull BytesMatcher decode(@Nullable String value) {
final BytesMatcher matcher = new BytesMatcher();
if (TextUtils.isEmpty(value)) return matcher;
final int length = value.length();
for (int i = 0; i < length;) {
final char type = value.charAt(i);
int nextRule = value.indexOf(',', i);
int nextMask = value.indexOf('/', i);
if (nextRule == -1) nextRule = length;
if (nextMask > nextRule) nextMask = -1;
final byte[] ruleValue;
final byte[] ruleMask;
if (nextMask >= 0) {
ruleValue = HexDump.hexStringToByteArray(value.substring(i + 1, nextMask));
ruleMask = HexDump.hexStringToByteArray(value.substring(nextMask + 1, nextRule));
} else {
ruleValue = HexDump.hexStringToByteArray(value.substring(i + 1, nextRule));
ruleMask = null;
}
switch (type) {
case TYPE_EXACT_ACCEPT:
matcher.addExactAcceptRule(ruleValue, ruleMask);
break;
case TYPE_EXACT_REJECT:
matcher.addExactRejectRule(ruleValue, ruleMask);
break;
case TYPE_PREFIX_ACCEPT:
matcher.addPrefixAcceptRule(ruleValue, ruleMask);
break;
case TYPE_PREFIX_REJECT:
matcher.addPrefixRejectRule(ruleValue, ruleMask);
break;
default:
Log.w(TAG, "Ignoring unknown type " + type);
break;
}
i = nextRule + 1;
}
return matcher;
}
}

View File

@@ -15,7 +15,10 @@ android_test {
"android.test.runner",
"android.test.base",
],
static_libs: ["junit"],
static_libs: [
"junit",
"modules-utils-bytesmatcher",
],
platform_apis: true,
certificate: "platform",
}

View File

@@ -16,11 +16,11 @@
package android.bluetooth.le;
import android.os.BytesMatcher;
import android.os.ParcelUuid;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.internal.util.HexDump;
import com.android.modules.utils.BytesMatcher;
import junit.framework.TestCase;

View File

@@ -1,178 +0,0 @@
/*
* Copyright (C) 2021 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 android.os;
import static com.android.internal.util.HexDump.hexStringToByteArray;
import android.bluetooth.BluetoothUuid;
import android.net.MacAddress;
import androidx.test.filters.SmallTest;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
@SmallTest
public class BytesMatcherTest extends TestCase {
@Test
public void testEmpty() throws Exception {
BytesMatcher matcher = BytesMatcher.decode("");
assertFalse(matcher.test(hexStringToByteArray("cafe")));
assertFalse(matcher.test(hexStringToByteArray("")));
}
@Test
public void testExact() throws Exception {
BytesMatcher matcher = BytesMatcher.decode("+cafe");
assertTrue(matcher.test(hexStringToByteArray("cafe")));
assertFalse(matcher.test(hexStringToByteArray("beef")));
assertFalse(matcher.test(hexStringToByteArray("ca")));
assertFalse(matcher.test(hexStringToByteArray("cafe00")));
}
@Test
public void testMask() throws Exception {
BytesMatcher matcher = BytesMatcher.decode("+cafe/ff00");
assertTrue(matcher.test(hexStringToByteArray("cafe")));
assertTrue(matcher.test(hexStringToByteArray("ca88")));
assertFalse(matcher.test(hexStringToByteArray("beef")));
assertFalse(matcher.test(hexStringToByteArray("ca")));
assertFalse(matcher.test(hexStringToByteArray("cafe00")));
}
@Test
public void testPrefix() throws Exception {
BytesMatcher matcher = BytesMatcher.decode("⊆cafe,⊆beef/ff00");
assertTrue(matcher.test(hexStringToByteArray("cafe")));
assertFalse(matcher.test(hexStringToByteArray("caff")));
assertTrue(matcher.test(hexStringToByteArray("cafecafe")));
assertFalse(matcher.test(hexStringToByteArray("ca")));
assertTrue(matcher.test(hexStringToByteArray("beef")));
assertTrue(matcher.test(hexStringToByteArray("beff")));
assertTrue(matcher.test(hexStringToByteArray("beffbeff")));
assertFalse(matcher.test(hexStringToByteArray("be")));
}
@Test
public void testMacAddress() throws Exception {
BytesMatcher matcher = BytesMatcher.decode("+cafe00112233/ffffff000000");
assertTrue(matcher.testMacAddress(
MacAddress.fromString("ca:fe:00:00:00:00")));
assertFalse(matcher.testMacAddress(
MacAddress.fromString("f0:0d:00:00:00:00")));
}
@Test
public void testBluetoothUuid() throws Exception {
BytesMatcher matcher = BytesMatcher.decode("+cafe/ff00");
assertTrue(matcher.testBluetoothUuid(
BluetoothUuid.parseUuidFrom(hexStringToByteArray("cafe"))));
assertFalse(matcher.testBluetoothUuid(
BluetoothUuid.parseUuidFrom(hexStringToByteArray("beef"))));
}
/**
* Verify that single matcher can be configured to match Bluetooth UUIDs of
* varying lengths.
*/
@Test
public void testBluetoothUuid_Mixed() throws Exception {
BytesMatcher matcher = BytesMatcher.decode("+aaaa/ff00,+bbbbbbbb/ffff0000");
assertTrue(matcher.testBluetoothUuid(
BluetoothUuid.parseUuidFrom(hexStringToByteArray("aaaa"))));
assertFalse(matcher.testBluetoothUuid(
BluetoothUuid.parseUuidFrom(hexStringToByteArray("bbbb"))));
assertTrue(matcher.testBluetoothUuid(
BluetoothUuid.parseUuidFrom(hexStringToByteArray("bbbbbbbb"))));
assertFalse(matcher.testBluetoothUuid(
BluetoothUuid.parseUuidFrom(hexStringToByteArray("aaaaaaaa"))));
}
@Test
public void testSerialize_Empty() throws Exception {
BytesMatcher matcher = new BytesMatcher();
matcher = BytesMatcher.decode(BytesMatcher.encode(matcher));
// Also very empty and null values
BytesMatcher.decode("");
BytesMatcher.decode(null);
}
@Test
public void testSerialize_Exact() throws Exception {
BytesMatcher matcher = new BytesMatcher();
matcher.addExactRejectRule(hexStringToByteArray("cafe00112233"),
hexStringToByteArray("ffffff000000"));
matcher.addExactRejectRule(hexStringToByteArray("beef00112233"),
null);
matcher.addExactAcceptRule(hexStringToByteArray("000000000000"),
hexStringToByteArray("000000000000"));
assertFalse(matcher.test(hexStringToByteArray("cafe00ffffff")));
assertFalse(matcher.test(hexStringToByteArray("beef00112233")));
assertTrue(matcher.test(hexStringToByteArray("beef00ffffff")));
// Bounce through serialization pass and confirm it still works
matcher = BytesMatcher.decode(BytesMatcher.encode(matcher));
assertFalse(matcher.test(hexStringToByteArray("cafe00ffffff")));
assertFalse(matcher.test(hexStringToByteArray("beef00112233")));
assertTrue(matcher.test(hexStringToByteArray("beef00ffffff")));
}
@Test
public void testSerialize_Prefix() throws Exception {
BytesMatcher matcher = new BytesMatcher();
matcher.addExactRejectRule(hexStringToByteArray("aa"), null);
matcher.addExactAcceptRule(hexStringToByteArray("bb"), null);
matcher.addPrefixAcceptRule(hexStringToByteArray("aa"), null);
matcher.addPrefixRejectRule(hexStringToByteArray("bb"), null);
assertFalse(matcher.test(hexStringToByteArray("aa")));
assertTrue(matcher.test(hexStringToByteArray("bb")));
assertTrue(matcher.test(hexStringToByteArray("aaaa")));
assertFalse(matcher.test(hexStringToByteArray("bbbb")));
// Bounce through serialization pass and confirm it still works
matcher = BytesMatcher.decode(BytesMatcher.encode(matcher));
assertFalse(matcher.test(hexStringToByteArray("aa")));
assertTrue(matcher.test(hexStringToByteArray("bb")));
assertTrue(matcher.test(hexStringToByteArray("aaaa")));
assertFalse(matcher.test(hexStringToByteArray("bbbb")));
}
@Test
public void testOrdering_RejectFirst() throws Exception {
BytesMatcher matcher = BytesMatcher.decode("-ff/0f,+ff/f0");
assertFalse(matcher.test(hexStringToByteArray("ff")));
assertTrue(matcher.test(hexStringToByteArray("f0")));
assertFalse(matcher.test(hexStringToByteArray("0f")));
}
@Test
public void testOrdering_AcceptFirst() throws Exception {
BytesMatcher matcher = BytesMatcher.decode("+ff/f0,-ff/0f");
assertTrue(matcher.test(hexStringToByteArray("ff")));
assertTrue(matcher.test(hexStringToByteArray("f0")));
assertFalse(matcher.test(hexStringToByteArray("0f")));
}
}