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

Skip to content
Snippets Groups Projects
Commit 55a81e6c authored by Treehugger Robot's avatar Treehugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Remove bytes matcher from base" am: d3dfca9c

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

Change-Id: I622fc79e47bedcfef74c60b0bda4145ba9306e38
parents 16a67262 d3dfca9c
No related branches found
No related tags found
No related merge requests found
/*
* 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;
}
}
...@@ -15,7 +15,10 @@ android_test { ...@@ -15,7 +15,10 @@ android_test {
"android.test.runner", "android.test.runner",
"android.test.base", "android.test.base",
], ],
static_libs: ["junit"], static_libs: [
"junit",
"modules-utils-bytesmatcher",
],
platform_apis: true, platform_apis: true,
certificate: "platform", certificate: "platform",
} }
...@@ -16,11 +16,11 @@ ...@@ -16,11 +16,11 @@
package android.bluetooth.le; package android.bluetooth.le;
import android.os.BytesMatcher;
import android.os.ParcelUuid; import android.os.ParcelUuid;
import android.test.suitebuilder.annotation.SmallTest; import android.test.suitebuilder.annotation.SmallTest;
import com.android.internal.util.HexDump; import com.android.internal.util.HexDump;
import com.android.modules.utils.BytesMatcher;
import junit.framework.TestCase; import junit.framework.TestCase;
......
/*
* 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")));
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment