summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/bluetooth/le/ScanRecord.java22
-rw-r--r--core/java/android/os/BytesMatcher.java473
-rw-r--r--core/tests/bluetoothtests/AndroidTest.xml32
-rw-r--r--core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java77
-rw-r--r--core/tests/coretests/src/android/os/BytesMatcherTest.java53
5 files changed, 449 insertions, 208 deletions
diff --git a/core/java/android/bluetooth/le/ScanRecord.java b/core/java/android/bluetooth/le/ScanRecord.java
index c0c1aa1634ff..794b512772fe 100644
--- a/core/java/android/bluetooth/le/ScanRecord.java
+++ b/core/java/android/bluetooth/le/ScanRecord.java
@@ -29,6 +29,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import java.util.function.Predicate;
/**
* Represents a scan record from Bluetooth LE scan.
@@ -168,6 +169,27 @@ public final class ScanRecord {
return mBytes;
}
+ /**
+ * Test if any fields contained inside this scan record are matched by the
+ * given matcher.
+ *
+ * @hide
+ */
+ public boolean matchesAnyField(@NonNull Predicate<byte[]> matcher) {
+ int pos = 0;
+ while (pos < mBytes.length) {
+ final int length = mBytes[pos] & 0xFF;
+ if (length == 0) {
+ break;
+ }
+ if (matcher.test(Arrays.copyOfRange(mBytes, pos, pos + length + 1))) {
+ return true;
+ }
+ pos += length + 1;
+ }
+ return false;
+ }
+
private ScanRecord(List<ParcelUuid> serviceUuids,
List<ParcelUuid> serviceSolicitationUuids,
SparseArray<byte[]> manufacturerData,
diff --git a/core/java/android/os/BytesMatcher.java b/core/java/android/os/BytesMatcher.java
index 8537f47eb575..8974c5ee4e32 100644
--- a/core/java/android/os/BytesMatcher.java
+++ b/core/java/android/os/BytesMatcher.java
@@ -20,6 +20,7 @@ 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;
@@ -42,207 +43,273 @@ import java.util.function.Predicate;
* @hide
*/
public class BytesMatcher implements Predicate<byte[]> {
- private static final String TAG = "BytesMatcher";
-
- private static final char TYPE_ACCEPT = '+';
- private static final char TYPE_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) {
- if (value.length != this.value.length) {
- return false;
- }
- 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.
- * <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 addAcceptRule(@NonNull byte[] value, @Nullable byte[] mask) {
- mRules.add(new Rule(TYPE_ACCEPT, value, mask));
- }
-
- /**
- * Add a rule that will result in {@link #test(byte[])} returning
- * {@code false} when a value being tested matches it.
- * <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 addRejectRule(@NonNull byte[] value, @Nullable byte[] mask) {
- mRules.add(new Rule(TYPE_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)) {
- return (rule.type == TYPE_ACCEPT);
- }
- }
- 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(',');
- }
- 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(@NonNull String value) {
- final BytesMatcher matcher = new BytesMatcher();
- 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_ACCEPT:
- matcher.addAcceptRule(ruleValue, ruleMask);
- break;
- case TYPE_REJECT:
- matcher.addRejectRule(ruleValue, ruleMask);
- break;
- default:
- Log.w(TAG, "Ignoring unknown type " + type);
- break;
- }
-
- i = nextRule + 1;
- }
- return matcher;
- }
+ 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;
+ }
}
diff --git a/core/tests/bluetoothtests/AndroidTest.xml b/core/tests/bluetoothtests/AndroidTest.xml
new file mode 100644
index 000000000000..f93c4ebf5bf6
--- /dev/null
+++ b/core/tests/bluetoothtests/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 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.
+-->
+<configuration description="Config for Bluetooth test cases">
+ <option name="test-suite-tag" value="apct"/>
+ <option name="test-suite-tag" value="apct-instrumentation"/>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="BluetoothTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct"/>
+ <option name="test-tag" value="BluetoothTests"/>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.bluetooth.tests" />
+ <option name="hidden-api-checks" value="false"/>
+ <option name="runner" value="android.bluetooth.BluetoothTestRunner"/>
+ </test>
+</configuration>
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java
index 8b3db7e4d934..c287ea9d8567 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java
@@ -16,13 +16,18 @@
package android.bluetooth.le;
-import android.bluetooth.le.ScanRecord;
+import android.os.BytesMatcher;
import android.os.ParcelUuid;
import android.test.suitebuilder.annotation.SmallTest;
+import com.android.internal.util.HexDump;
+
import junit.framework.TestCase;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
/**
* Unit test cases for {@link ScanRecord}.
@@ -31,6 +36,66 @@ import java.util.Arrays;
* 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner'
*/
public class ScanRecordTest extends TestCase {
+ /**
+ * Example raw beacons captured from a Blue Charm BC011
+ */
+ private static final String RECORD_URL = "0201060303AAFE1716AAFE10EE01626C7565636861726D626561636F6E730009168020691E0EFE13551109426C7565436861726D5F313639363835000000";
+ private static final String RECORD_UUID = "0201060303AAFE1716AAFE00EE626C7565636861726D31000000000001000009168020691E0EFE13551109426C7565436861726D5F313639363835000000";
+ private static final String RECORD_TLM = "0201060303AAFE1116AAFE20000BF017000008874803FB93540916802069080EFE13551109426C7565436861726D5F313639363835000000000000000000";
+ private static final String RECORD_IBEACON = "0201061AFF4C000215426C7565436861726D426561636F6E730EFE1355C509168020691E0EFE13551109426C7565436861726D5F31363936383500000000";
+
+ @SmallTest
+ public void testMatchesAnyField_Eddystone_Parser() {
+ final List<String> found = new ArrayList<>();
+ final Predicate<byte[]> matcher = (v) -> {
+ found.add(HexDump.toHexString(v));
+ return false;
+ };
+ ScanRecord.parseFromBytes(HexDump.hexStringToByteArray(RECORD_URL))
+ .matchesAnyField(matcher);
+
+ assertEquals(Arrays.asList(
+ "020106",
+ "0303AAFE",
+ "1716AAFE10EE01626C7565636861726D626561636F6E7300",
+ "09168020691E0EFE1355",
+ "1109426C7565436861726D5F313639363835"), found);
+ }
+
+ @SmallTest
+ public void testMatchesAnyField_Eddystone() {
+ final BytesMatcher matcher = BytesMatcher.decode("⊆0016AAFE/00FFFFFF");
+ assertMatchesAnyField(RECORD_URL, matcher);
+ assertMatchesAnyField(RECORD_UUID, matcher);
+ assertMatchesAnyField(RECORD_TLM, matcher);
+ assertNotMatchesAnyField(RECORD_IBEACON, matcher);
+ }
+
+ @SmallTest
+ public void testMatchesAnyField_iBeacon_Parser() {
+ final List<String> found = new ArrayList<>();
+ final Predicate<byte[]> matcher = (v) -> {
+ found.add(HexDump.toHexString(v));
+ return false;
+ };
+ ScanRecord.parseFromBytes(HexDump.hexStringToByteArray(RECORD_IBEACON))
+ .matchesAnyField(matcher);
+
+ assertEquals(Arrays.asList(
+ "020106",
+ "1AFF4C000215426C7565436861726D426561636F6E730EFE1355C5",
+ "09168020691E0EFE1355",
+ "1109426C7565436861726D5F313639363835"), found);
+ }
+
+ @SmallTest
+ public void testMatchesAnyField_iBeacon() {
+ final BytesMatcher matcher = BytesMatcher.decode("⊆00FF4C0002/00FFFFFFFF");
+ assertNotMatchesAnyField(RECORD_URL, matcher);
+ assertNotMatchesAnyField(RECORD_UUID, matcher);
+ assertNotMatchesAnyField(RECORD_TLM, matcher);
+ assertMatchesAnyField(RECORD_IBEACON, matcher);
+ }
@SmallTest
public void testParser() {
@@ -70,4 +135,14 @@ public class ScanRecordTest extends TestCase {
}
}
+
+ private static void assertMatchesAnyField(String record, BytesMatcher matcher) {
+ assertTrue(ScanRecord.parseFromBytes(HexDump.hexStringToByteArray(record))
+ .matchesAnyField(matcher));
+ }
+
+ private static void assertNotMatchesAnyField(String record, BytesMatcher matcher) {
+ assertFalse(ScanRecord.parseFromBytes(HexDump.hexStringToByteArray(record))
+ .matchesAnyField(matcher));
+ }
}
diff --git a/core/tests/coretests/src/android/os/BytesMatcherTest.java b/core/tests/coretests/src/android/os/BytesMatcherTest.java
index 67c1b3c947fb..b28e3090edd0 100644
--- a/core/tests/coretests/src/android/os/BytesMatcherTest.java
+++ b/core/tests/coretests/src/android/os/BytesMatcherTest.java
@@ -59,6 +59,19 @@ public class BytesMatcherTest extends TestCase {
}
@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(
@@ -94,13 +107,23 @@ public class BytesMatcherTest extends TestCase {
}
@Test
- public void testSerialize() throws Exception {
+ 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.addRejectRule(hexStringToByteArray("cafe00112233"),
+ matcher.addExactRejectRule(hexStringToByteArray("cafe00112233"),
hexStringToByteArray("ffffff000000"));
- matcher.addRejectRule(hexStringToByteArray("beef00112233"),
+ matcher.addExactRejectRule(hexStringToByteArray("beef00112233"),
null);
- matcher.addAcceptRule(hexStringToByteArray("000000000000"),
+ matcher.addExactAcceptRule(hexStringToByteArray("000000000000"),
hexStringToByteArray("000000000000"));
assertFalse(matcher.test(hexStringToByteArray("cafe00ffffff")));
@@ -116,6 +139,28 @@ public class BytesMatcherTest extends TestCase {
}
@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")));