diff --git a/core/java/android/os/BytesMatcher.java b/core/java/android/os/BytesMatcher.java deleted file mode 100644 index 8974c5ee4e329d105d281c16067f4c4c5f9e277e..0000000000000000000000000000000000000000 --- a/core/java/android/os/BytesMatcher.java +++ /dev/null @@ -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. - *

- * 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. - *

- * 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 { - 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 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. - *

- * 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. - *

- * 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. - *

- * 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. - *

- * 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. - *

- * 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. - *

- * 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; - } -} diff --git a/core/tests/bluetoothtests/Android.bp b/core/tests/bluetoothtests/Android.bp index a2e4dff8f8269c15e4337e9486b7029ddef8e1a7..68416dd6546689c235c7c28d30cddb94f4eb16cb 100644 --- a/core/tests/bluetoothtests/Android.bp +++ b/core/tests/bluetoothtests/Android.bp @@ -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", } diff --git a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java index c287ea9d856719ed03a056180e8c56c70b2a6c30..4e817d4a0d91bdd207fb0c5a618debad09226d84 100644 --- a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java +++ b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java @@ -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; diff --git a/core/tests/coretests/src/android/os/BytesMatcherTest.java b/core/tests/coretests/src/android/os/BytesMatcherTest.java deleted file mode 100644 index b28e3090edd022a460f2375947779a2c5bb162a7..0000000000000000000000000000000000000000 --- a/core/tests/coretests/src/android/os/BytesMatcherTest.java +++ /dev/null @@ -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"))); - } -}