Merge "Implement per-field matching of ScanRecord." into sc-dev
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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 String TAG = "BytesMatcher";
|
||||
|
||||
private static final char TYPE_ACCEPT = '+';
|
||||
private static final char TYPE_REJECT = '-';
|
||||
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 final ArrayList<Rule> mRules = new ArrayList<>();
|
||||
|
||||
private static class Rule {
|
||||
public final char type;
|
||||
public final @NonNull byte[] value;
|
||||
public final @Nullable byte[] mask;
|
||||
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;
|
||||
}
|
||||
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();
|
||||
}
|
||||
@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 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;
|
||||
}
|
||||
}
|
||||
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.
|
||||
* <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 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.
|
||||
* <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));
|
||||
}
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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());
|
||||
}
|
||||
/**
|
||||
* 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 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 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 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;
|
||||
}
|
||||
/**
|
||||
* 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());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
int nextRule = value.indexOf(',', i);
|
||||
int nextMask = value.indexOf('/', i);
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
if (nextRule == -1) nextRule = length;
|
||||
if (nextMask > nextRule) nextMask = -1;
|
||||
/**
|
||||
* 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 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;
|
||||
}
|
||||
final int length = value.length();
|
||||
for (int i = 0; i < length;) {
|
||||
final char type = value.charAt(i);
|
||||
|
||||
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;
|
||||
}
|
||||
int nextRule = value.indexOf(',', i);
|
||||
int nextMask = value.indexOf('/', i);
|
||||
|
||||
i = nextRule + 1;
|
||||
}
|
||||
return matcher;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
32
core/tests/bluetoothtests/AndroidTest.xml
Normal file
32
core/tests/bluetoothtests/AndroidTest.xml
Normal file
@@ -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>
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,19 @@ public class BytesMatcherTest extends TestCase {
|
||||
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");
|
||||
@@ -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.addRejectRule(hexStringToByteArray("cafe00112233"),
|
||||
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.addRejectRule(hexStringToByteArray("beef00112233"),
|
||||
matcher.addExactRejectRule(hexStringToByteArray("beef00112233"),
|
||||
null);
|
||||
matcher.addAcceptRule(hexStringToByteArray("000000000000"),
|
||||
matcher.addExactAcceptRule(hexStringToByteArray("000000000000"),
|
||||
hexStringToByteArray("000000000000"));
|
||||
|
||||
assertFalse(matcher.test(hexStringToByteArray("cafe00ffffff")));
|
||||
@@ -115,6 +138,28 @@ public class BytesMatcherTest extends TestCase {
|
||||
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");
|
||||
|
||||
Reference in New Issue
Block a user