1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.os; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.bluetooth.BluetoothUuid; 22 import android.net.MacAddress; 23 import android.text.TextUtils; 24 import android.util.Log; 25 26 import com.android.internal.util.HexDump; 27 28 import java.util.ArrayList; 29 import java.util.function.Predicate; 30 31 /** 32 * Predicate that tests if a given {@code byte[]} value matches a set of 33 * configured rules. 34 * <p> 35 * Rules are tested in the order in which they were originally added, which 36 * means a narrow rule can reject a specific value before a later broader rule 37 * might accept that same value, or vice versa. 38 * <p> 39 * Matchers can contain rules of varying lengths, and tested values will only be 40 * matched against rules of the exact same length. This is designed to support 41 * {@link BluetoothUuid} style values which can be variable length. 42 * 43 * @hide 44 */ 45 public class BytesMatcher implements Predicate<byte[]> { 46 private static final String TAG = "BytesMatcher"; 47 48 private static final char TYPE_EXACT_ACCEPT = '+'; 49 private static final char TYPE_EXACT_REJECT = '-'; 50 private static final char TYPE_PREFIX_ACCEPT = '⊆'; 51 private static final char TYPE_PREFIX_REJECT = '⊈'; 52 53 private final ArrayList<Rule> mRules = new ArrayList<>(); 54 55 private static class Rule { 56 public final char type; 57 public final @NonNull byte[] value; 58 public final @Nullable byte[] mask; 59 Rule(char type, @NonNull byte[] value, @Nullable byte[] mask)60 public Rule(char type, @NonNull byte[] value, @Nullable byte[] mask) { 61 if (mask != null && value.length != mask.length) { 62 throw new IllegalArgumentException( 63 "Expected length " + value.length + " but found " + mask.length); 64 } 65 this.type = type; 66 this.value = value; 67 this.mask = mask; 68 } 69 70 @Override toString()71 public String toString() { 72 StringBuilder builder = new StringBuilder(); 73 encode(builder); 74 return builder.toString(); 75 } 76 encode(@onNull StringBuilder builder)77 public void encode(@NonNull StringBuilder builder) { 78 builder.append(type); 79 builder.append(HexDump.toHexString(value)); 80 if (mask != null) { 81 builder.append('/'); 82 builder.append(HexDump.toHexString(mask)); 83 } 84 } 85 test(@onNull byte[] value)86 public boolean test(@NonNull byte[] value) { 87 switch (type) { 88 case TYPE_EXACT_ACCEPT: 89 case TYPE_EXACT_REJECT: 90 if (value.length != this.value.length) { 91 return false; 92 } 93 break; 94 case TYPE_PREFIX_ACCEPT: 95 case TYPE_PREFIX_REJECT: 96 if (value.length < this.value.length) { 97 return false; 98 } 99 break; 100 } 101 for (int i = 0; i < this.value.length; i++) { 102 byte local = this.value[i]; 103 byte remote = value[i]; 104 if (this.mask != null) { 105 local &= this.mask[i]; 106 remote &= this.mask[i]; 107 } 108 if (local != remote) { 109 return false; 110 } 111 } 112 return true; 113 } 114 } 115 116 /** 117 * Add a rule that will result in {@link #test(byte[])} returning 118 * {@code true} when a value being tested matches it. This rule will only 119 * match values of the exact same length. 120 * <p> 121 * Rules are tested in the order in which they were originally added, which 122 * means a narrow rule can reject a specific value before a later broader 123 * rule might accept that same value, or vice versa. 124 * 125 * @param value to be matched 126 * @param mask to be applied to both values before testing for equality; if 127 * {@code null} then both values must match exactly 128 */ addExactAcceptRule(@onNull byte[] value, @Nullable byte[] mask)129 public void addExactAcceptRule(@NonNull byte[] value, @Nullable byte[] mask) { 130 mRules.add(new Rule(TYPE_EXACT_ACCEPT, value, mask)); 131 } 132 133 /** 134 * Add a rule that will result in {@link #test(byte[])} returning 135 * {@code false} when a value being tested matches it. This rule will only 136 * match values of the exact same length. 137 * <p> 138 * Rules are tested in the order in which they were originally added, which 139 * means a narrow rule can reject a specific value before a later broader 140 * rule might accept that same value, or vice versa. 141 * 142 * @param value to be matched 143 * @param mask to be applied to both values before testing for equality; if 144 * {@code null} then both values must match exactly 145 */ addExactRejectRule(@onNull byte[] value, @Nullable byte[] mask)146 public void addExactRejectRule(@NonNull byte[] value, @Nullable byte[] mask) { 147 mRules.add(new Rule(TYPE_EXACT_REJECT, value, mask)); 148 } 149 150 /** 151 * Add a rule that will result in {@link #test(byte[])} returning 152 * {@code true} when a value being tested matches it. This rule will match 153 * values of the exact same length or longer. 154 * <p> 155 * Rules are tested in the order in which they were originally added, which 156 * means a narrow rule can reject a specific value before a later broader 157 * rule might accept that same value, or vice versa. 158 * 159 * @param value to be matched 160 * @param mask to be applied to both values before testing for equality; if 161 * {@code null} then both values must match exactly 162 */ addPrefixAcceptRule(@onNull byte[] value, @Nullable byte[] mask)163 public void addPrefixAcceptRule(@NonNull byte[] value, @Nullable byte[] mask) { 164 mRules.add(new Rule(TYPE_PREFIX_ACCEPT, value, mask)); 165 } 166 167 /** 168 * Add a rule that will result in {@link #test(byte[])} returning 169 * {@code false} when a value being tested matches it. This rule will match 170 * values of the exact same length or longer. 171 * <p> 172 * Rules are tested in the order in which they were originally added, which 173 * means a narrow rule can reject a specific value before a later broader 174 * rule might accept that same value, or vice versa. 175 * 176 * @param value to be matched 177 * @param mask to be applied to both values before testing for equality; if 178 * {@code null} then both values must match exactly 179 */ addPrefixRejectRule(@onNull byte[] value, @Nullable byte[] mask)180 public void addPrefixRejectRule(@NonNull byte[] value, @Nullable byte[] mask) { 181 mRules.add(new Rule(TYPE_PREFIX_REJECT, value, mask)); 182 } 183 184 /** 185 * Test if the given {@code ParcelUuid} value matches the set of rules 186 * configured in this matcher. 187 */ testBluetoothUuid(@onNull ParcelUuid value)188 public boolean testBluetoothUuid(@NonNull ParcelUuid value) { 189 return test(BluetoothUuid.uuidToBytes(value)); 190 } 191 192 /** 193 * Test if the given {@code MacAddress} value matches the set of rules 194 * configured in this matcher. 195 */ testMacAddress(@onNull MacAddress value)196 public boolean testMacAddress(@NonNull MacAddress value) { 197 return test(value.toByteArray()); 198 } 199 200 /** 201 * Test if the given {@code byte[]} value matches the set of rules 202 * configured in this matcher. 203 */ 204 @Override test(@onNull byte[] value)205 public boolean test(@NonNull byte[] value) { 206 return test(value, false); 207 } 208 209 /** 210 * Test if the given {@code byte[]} value matches the set of rules 211 * configured in this matcher. 212 */ test(@onNull byte[] value, boolean defaultValue)213 public boolean test(@NonNull byte[] value, boolean defaultValue) { 214 final int size = mRules.size(); 215 for (int i = 0; i < size; i++) { 216 final Rule rule = mRules.get(i); 217 if (rule.test(value)) { 218 switch (rule.type) { 219 case TYPE_EXACT_ACCEPT: 220 case TYPE_PREFIX_ACCEPT: 221 return true; 222 case TYPE_EXACT_REJECT: 223 case TYPE_PREFIX_REJECT: 224 return false; 225 } 226 } 227 } 228 return defaultValue; 229 } 230 231 /** 232 * Encode the given matcher into a human-readable {@link String} which can 233 * be used to transport matchers across device boundaries. 234 * <p> 235 * The human-readable format is an ordered list separated by commas, where 236 * each rule is a {@code +} or {@code -} symbol indicating if the match 237 * should be accepted or rejected, then followed by a hex value and an 238 * optional hex mask. For example, {@code -caff,+cafe/ff00} is a valid 239 * encoded matcher. 240 * 241 * @see #decode(String) 242 */ encode(@onNull BytesMatcher matcher)243 public static @NonNull String encode(@NonNull BytesMatcher matcher) { 244 final StringBuilder builder = new StringBuilder(); 245 final int size = matcher.mRules.size(); 246 for (int i = 0; i < size; i++) { 247 final Rule rule = matcher.mRules.get(i); 248 rule.encode(builder); 249 builder.append(','); 250 } 251 if (builder.length() > 0) { 252 builder.deleteCharAt(builder.length() - 1); 253 } 254 return builder.toString(); 255 } 256 257 /** 258 * Decode the given human-readable {@link String} used to transport matchers 259 * across device boundaries. 260 * <p> 261 * The human-readable format is an ordered list separated by commas, where 262 * each rule is a {@code +} or {@code -} symbol indicating if the match 263 * should be accepted or rejected, then followed by a hex value and an 264 * optional hex mask. For example, {@code -caff,+cafe/ff00} is a valid 265 * encoded matcher. 266 * 267 * @see #encode(BytesMatcher) 268 */ decode(@ullable String value)269 public static @NonNull BytesMatcher decode(@Nullable String value) { 270 final BytesMatcher matcher = new BytesMatcher(); 271 if (TextUtils.isEmpty(value)) return matcher; 272 273 final int length = value.length(); 274 for (int i = 0; i < length;) { 275 final char type = value.charAt(i); 276 277 int nextRule = value.indexOf(',', i); 278 int nextMask = value.indexOf('/', i); 279 280 if (nextRule == -1) nextRule = length; 281 if (nextMask > nextRule) nextMask = -1; 282 283 final byte[] ruleValue; 284 final byte[] ruleMask; 285 if (nextMask >= 0) { 286 ruleValue = HexDump.hexStringToByteArray(value.substring(i + 1, nextMask)); 287 ruleMask = HexDump.hexStringToByteArray(value.substring(nextMask + 1, nextRule)); 288 } else { 289 ruleValue = HexDump.hexStringToByteArray(value.substring(i + 1, nextRule)); 290 ruleMask = null; 291 } 292 293 switch (type) { 294 case TYPE_EXACT_ACCEPT: 295 matcher.addExactAcceptRule(ruleValue, ruleMask); 296 break; 297 case TYPE_EXACT_REJECT: 298 matcher.addExactRejectRule(ruleValue, ruleMask); 299 break; 300 case TYPE_PREFIX_ACCEPT: 301 matcher.addPrefixAcceptRule(ruleValue, ruleMask); 302 break; 303 case TYPE_PREFIX_REJECT: 304 matcher.addPrefixRejectRule(ruleValue, ruleMask); 305 break; 306 default: 307 Log.w(TAG, "Ignoring unknown type " + type); 308 break; 309 } 310 311 i = nextRule + 1; 312 } 313 return matcher; 314 } 315 } 316