1 /* 2 * Copyright 2023 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.bluetooth.le; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.SystemApi; 22 import android.bluetooth.BluetoothAssignedNumbers.OrganizationId; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 26 import java.util.Arrays; 27 import java.util.Objects; 28 29 /** 30 * Wrapper for filter input for Transport Discovery Data Transport Blocks. This class represents the 31 * filter for a Transport Block from a Transport Discovery Data advertisement data. 32 * 33 * @see ScanFilter 34 * @hide 35 */ 36 @SystemApi 37 public final class TransportBlockFilter implements Parcelable { 38 39 private final int mOrgId; 40 private final int mTdsFlags; 41 private final int mTdsFlagsMask; 42 private final byte[] mTransportData; 43 private final byte[] mTransportDataMask; 44 private final byte[] mWifiNanHash; 45 46 /** 47 * Length of a Wi-FI NAN hash in bytes/ 48 * 49 * @hide 50 */ 51 @SystemApi public static final int WIFI_NAN_HASH_LENGTH_BYTES = 8; 52 TransportBlockFilter( int orgId, int tdsFlags, int tdsFlagsMask, @Nullable byte[] transportData, @Nullable byte[] transportDataMask, @Nullable byte[] wifiNanHash)53 private TransportBlockFilter( 54 int orgId, 55 int tdsFlags, 56 int tdsFlagsMask, 57 @Nullable byte[] transportData, 58 @Nullable byte[] transportDataMask, 59 @Nullable byte[] wifiNanHash) { 60 if (orgId < 1) { 61 throw new IllegalArgumentException("invalid organization id " + orgId); 62 } 63 if (tdsFlags == -1) { 64 throw new IllegalArgumentException("tdsFlag is invalid"); 65 } 66 if (tdsFlagsMask == -1) { 67 throw new IllegalArgumentException("tdsFlagsMask is invalid"); 68 } 69 if (orgId == OrganizationId.WIFI_ALLIANCE_NEIGHBOR_AWARENESS_NETWORKING) { 70 if (transportData != null || transportDataMask != null) { 71 throw new IllegalArgumentException( 72 "wifiNanHash should be used instead of transportData and/or " 73 + "transportDataMask when orgId is " 74 + "WIFI_ALLIANCE_NEIGHBOR_AWARENESS_NETWORKING"); 75 } 76 if (wifiNanHash != null && wifiNanHash.length != WIFI_NAN_HASH_LENGTH_BYTES) { 77 throw new IllegalArgumentException( 78 "wifiNanHash should be WIFI_NAN_HASH_LENGTH_BYTES long, but the input is " 79 + wifiNanHash.length 80 + " bytes"); 81 } 82 } else { 83 if (wifiNanHash != null) { 84 throw new IllegalArgumentException( 85 "wifiNanHash should not be used when orgId is " 86 + "not WIFI_ALLIANCE_NEIGHBOR_AWARENESS_NETWORKING"); 87 } 88 } 89 mOrgId = orgId; 90 mTdsFlags = tdsFlags; 91 mTdsFlagsMask = tdsFlagsMask; 92 mTransportData = transportData; 93 mTransportDataMask = transportDataMask; 94 mWifiNanHash = wifiNanHash; 95 } 96 97 /** 98 * Get Organization ID assigned by Bluetooth SIG. For more details refer to Transport Discovery 99 * Service Organization IDs in <a 100 * href="https://www.bluetooth.com/specifications/assigned-numbers/">Bluetooth Assigned 101 * Numbers</a> 102 * 103 * @hide 104 */ 105 @SystemApi getOrgId()106 public int getOrgId() { 107 return mOrgId; 108 } 109 110 /** 111 * Get Transport Discovery Service (TDS) flags to filter Transport Discovery Blocks 112 * 113 * @hide 114 */ 115 @SystemApi getTdsFlags()116 public int getTdsFlags() { 117 return mTdsFlags; 118 } 119 120 /** 121 * Get masks for filtering Transport Discovery Service (TDS) flags in Transport Discovery Blocks 122 * 123 * @return a bitmask to select which bits in {@code tdsFlag} to match. 0 means no bit in 124 * tdsFlags will be used for matching 125 * @hide 126 */ 127 @SystemApi getTdsFlagsMask()128 public int getTdsFlagsMask() { 129 return mTdsFlagsMask; 130 } 131 132 /** 133 * Get data to filter Transport Discovery Blocks. 134 * 135 * <p>Cannot be used when {@code orgId} is {@link OrganizationId 136 * #WIFI_ALLIANCE_NEIGHBOR_AWARENESS_NETWORKING} 137 * 138 * @return Data to filter Transport Discovery Blocks, null if not used 139 * @hide 140 */ 141 @SystemApi 142 @Nullable getTransportData()143 public byte[] getTransportData() { 144 return mTransportData; 145 } 146 147 /** 148 * Get masks for filtering data in Transport Discovery Blocks. 149 * 150 * <p>Cannot be used when {@code orgId} is {@link 151 * OrganizationId#WIFI_ALLIANCE_NEIGHBOR_AWARENESS_NETWORKING} 152 * 153 * @return a byte array with matching length to {@code transportData} to select which bit to use 154 * in filter, null is not used 155 * @hide 156 */ 157 @SystemApi 158 @Nullable getTransportDataMask()159 public byte[] getTransportDataMask() { 160 return mTransportDataMask; 161 } 162 163 /** 164 * Get hashed bloom filter value to filter {@link 165 * OrganizationId#WIFI_ALLIANCE_NEIGHBOR_AWARENESS_NETWORKING} services in Transport Discovery 166 * Blocks. 167 * 168 * <p>Can only be used when {@code orgId} is {@link 169 * OrganizationId#WIFI_ALLIANCE_NEIGHBOR_AWARENESS_NETWORKING}. 170 * 171 * @return 8 octets Wi-Fi NAN defined bloom filter hash, null if not used 172 * @hide 173 */ 174 @SystemApi 175 @Nullable getWifiNanHash()176 public byte[] getWifiNanHash() { 177 return mWifiNanHash; 178 } 179 180 /** 181 * Check if a scan result matches this transport block filter. 182 * 183 * @param scanResult scan result to match 184 * @return true if matches 185 * @hide 186 */ matches(ScanResult scanResult)187 boolean matches(ScanResult scanResult) { 188 ScanRecord scanRecord = scanResult.getScanRecord(); 189 // Transport Discovery data match 190 TransportDiscoveryData transportDiscoveryData = scanRecord.getTransportDiscoveryData(); 191 192 if ((transportDiscoveryData != null)) { 193 for (TransportBlock transportBlock : transportDiscoveryData.getTransportBlocks()) { 194 int orgId = transportBlock.getOrgId(); 195 int tdsFlags = transportBlock.getTdsFlags(); 196 int transportDataLength = transportBlock.getTransportDataLength(); 197 byte[] transportData = transportBlock.getTransportData(); 198 199 if (mOrgId != orgId) { 200 continue; 201 } 202 if ((mTdsFlags & mTdsFlagsMask) != (tdsFlags & mTdsFlagsMask)) { 203 continue; 204 } 205 if ((mOrgId != OrganizationId.WIFI_ALLIANCE_NEIGHBOR_AWARENESS_NETWORKING) 206 && (mTransportData != null) 207 && (mTransportDataMask != null)) { 208 if (transportDataLength != 0) { 209 if (!ScanFilter.matchesPartialData( 210 mTransportData, mTransportDataMask, transportData)) { 211 continue; 212 } 213 } else { 214 continue; 215 } 216 } 217 return true; 218 } 219 } 220 221 return false; 222 } 223 224 /** @hide */ 225 @Override describeContents()226 public int describeContents() { 227 return 0; 228 } 229 230 /** 231 * {@inheritDoc} 232 * 233 * @hide 234 */ 235 @SystemApi 236 @Override writeToParcel(@onNull Parcel dest, int flags)237 public void writeToParcel(@NonNull Parcel dest, int flags) { 238 dest.writeInt(mOrgId); 239 dest.writeInt(mTdsFlags); 240 dest.writeInt(mTdsFlagsMask); 241 dest.writeInt(mTransportData == null ? 0 : 1); 242 if (mTransportData != null) { 243 dest.writeInt(mTransportData.length); 244 dest.writeByteArray(mTransportData); 245 dest.writeInt(mTransportDataMask == null ? 0 : 1); 246 if (mTransportDataMask != null) { 247 dest.writeInt(mTransportDataMask.length); 248 dest.writeByteArray(mTransportDataMask); 249 } 250 } 251 dest.writeInt(mWifiNanHash == null ? 0 : 1); 252 if (mWifiNanHash != null) { 253 dest.writeInt(mWifiNanHash.length); 254 dest.writeByteArray(mWifiNanHash); 255 } 256 } 257 258 /** Get a human-readable string for this object. */ 259 @Override toString()260 public String toString() { 261 return "TransportBlockFilter [mOrgId=" 262 + mOrgId 263 + ", mTdsFlags=" 264 + mTdsFlags 265 + ", mTdsFlagsMask=" 266 + mTdsFlagsMask 267 + ", mTransportData=" 268 + Arrays.toString(mTransportData) 269 + ", mTransportDataMask=" 270 + Arrays.toString(mTransportDataMask) 271 + ", mWifiNanHash=" 272 + Arrays.toString(mWifiNanHash) 273 + "]"; 274 } 275 276 @Override hashCode()277 public int hashCode() { 278 return Objects.hash( 279 mOrgId, 280 mTdsFlags, 281 mTdsFlagsMask, 282 Arrays.hashCode(mTransportData), 283 Arrays.hashCode(mTransportDataMask), 284 Arrays.hashCode(mWifiNanHash)); 285 } 286 287 @Override equals(@ullable Object obj)288 public boolean equals(@Nullable Object obj) { 289 if (!(obj instanceof TransportBlockFilter)) { 290 return false; 291 } 292 if (this == obj) { 293 return true; 294 } 295 final TransportBlockFilter other = (TransportBlockFilter) obj; 296 return mOrgId == other.getOrgId() 297 && mTdsFlags == other.getTdsFlags() 298 && mTdsFlagsMask == other.getTdsFlagsMask() 299 && Arrays.equals(mTransportData, other.getTransportData()) 300 && Arrays.equals(mTransportDataMask, other.getTransportDataMask()) 301 && Arrays.equals(mWifiNanHash, other.getWifiNanHash()); 302 } 303 304 /** 305 * Creator for {@link TransportBlockFilter} so that we can create it from {@link Parcel}. 306 * 307 * @hide 308 */ 309 @SystemApi @NonNull 310 public static final Creator<TransportBlockFilter> CREATOR = 311 new Creator<>() { 312 @Override 313 public TransportBlockFilter createFromParcel(Parcel source) { 314 final int orgId = source.readInt(); 315 Builder builder = new Builder(orgId); 316 builder.setTdsFlags(source.readInt(), source.readInt()); 317 if (source.readInt() == 1) { 318 int transportDataLength = source.readInt(); 319 byte[] transportData = new byte[transportDataLength]; 320 source.readByteArray(transportData); 321 byte[] transportDataMask = null; 322 if (source.readInt() == 1) { 323 int transportDataMaskLength = source.readInt(); 324 transportDataMask = new byte[transportDataMaskLength]; 325 source.readByteArray(transportDataMask); 326 } 327 builder.setTransportData(transportData, transportDataMask); 328 } 329 if (source.readInt() == 1) { 330 int wifiNanHashLength = source.readInt(); 331 byte[] wifiNanHash = new byte[wifiNanHashLength]; 332 source.readByteArray(wifiNanHash); 333 builder.setWifiNanHash(wifiNanHash); 334 } 335 return builder.build(); 336 } 337 338 @Override 339 public TransportBlockFilter[] newArray(int size) { 340 return new TransportBlockFilter[0]; 341 } 342 }; 343 344 /** 345 * Builder class for {@link TransportBlockFilter}. 346 * 347 * @hide 348 */ 349 @SystemApi 350 public static final class Builder { 351 352 private final int mOrgId; 353 private int mTdsFlags = 0; 354 private int mTdsFlagsMask = 0; 355 private byte[] mTransportData = null; 356 private byte[] mTransportDataMask = null; 357 private byte[] mWifiNanHash = null; 358 359 /** 360 * Builder for {@link TransportBlockFilter}. 361 * 362 * @param orgId Organization ID assigned by Bluetooth SIG. For more details refer to 363 * Transport Discovery Service Organization IDs in <a 364 * href="https://www.bluetooth.com/specifications/assigned-numbers/">Bluetooth Assigned 365 * Numbers</a>. 366 * @throws IllegalArgumentException If the {@code orgId} is invalid 367 * @see OrganizationId 368 * @hide 369 */ 370 @SystemApi Builder(int orgId)371 public Builder(int orgId) { 372 if (orgId < 1) { 373 throw new IllegalArgumentException("invalid organization id " + orgId); 374 } 375 mOrgId = orgId; 376 } 377 378 /** 379 * Set Transport Discovery Service (TDS) flags to filter Transport Discovery Blocks. 380 * 381 * @param tdsFlags 1 octet value that represents the role of the device and information 382 * about its state and supported features. Negative values are invalid for this 383 * argument. Default to 0. See Transport Discovery Service specification for more 384 * details. 385 * @param tdsFlagsMask a bitmask to select which bits in {@code tdsFlags} to match. Default 386 * to 0, meaning no flag match required. Negative values are invalid for this argument. 387 * @throws IllegalArgumentException if either {@code tdsFlags} or {@code tdsFlagsMask} is 388 * invalid. 389 * @return this builder 390 * @hide 391 */ 392 @SystemApi 393 @NonNull setTdsFlags(int tdsFlags, int tdsFlagsMask)394 public Builder setTdsFlags(int tdsFlags, int tdsFlagsMask) { 395 if (tdsFlags < 0) { 396 throw new IllegalArgumentException("tdsFlag is invalid"); 397 } 398 if (tdsFlagsMask < 0) { 399 throw new IllegalArgumentException("tdsFlagsMask is invalid"); 400 } 401 mTdsFlags = tdsFlags; 402 mTdsFlagsMask = tdsFlagsMask; 403 return this; 404 } 405 406 /** 407 * Set data to filter Transport Discovery Blocks. 408 * 409 * <p>Cannot be used when {@code orgId} is {@link 410 * OrganizationId#WIFI_ALLIANCE_NEIGHBOR_AWARENESS_NETWORKING} 411 * 412 * @param transportData must be valid value for the particular {@code orgId}. See Transport 413 * Discovery Service specification for more details. 414 * @param transportDataMask a byte array with matching length to {@code transportData} to 415 * select which bit to use in filter. 416 * @throws IllegalArgumentException when {@code orgId} is {@link 417 * OrganizationId#WIFI_ALLIANCE_NEIGHBOR_AWARENESS_NETWORKING} 418 * @throws NullPointerException if {@code transportData} or {@code transportDataMask} is 419 * {@code null} 420 * @throws IllegalArgumentException if {@code transportData} or {@code transportDataMask} is 421 * empty 422 * @throws IllegalArgumentException if length of {@code transportData} and {@code 423 * transportDataMask} do not match 424 * @return this builder 425 * @hide 426 */ 427 @SystemApi 428 @NonNull setTransportData( @onNull byte[] transportData, @NonNull byte[] transportDataMask)429 public Builder setTransportData( 430 @NonNull byte[] transportData, @NonNull byte[] transportDataMask) { 431 if (mOrgId == OrganizationId.WIFI_ALLIANCE_NEIGHBOR_AWARENESS_NETWORKING) { 432 throw new IllegalArgumentException( 433 "setWifiNanHash() should be used instead of setTransportData() when orgId " 434 + "is WIFI_ALLIANCE_NEIGHBOR_AWARENESS_NETWORKING"); 435 } 436 Objects.requireNonNull(transportData); 437 Objects.requireNonNull(transportDataMask); 438 if (transportData.length == 0) { 439 throw new IllegalArgumentException("transportData is empty"); 440 } 441 if (transportDataMask.length == 0) { 442 throw new IllegalArgumentException("transportDataMask is empty"); 443 } 444 if (transportData.length != transportDataMask.length) { 445 throw new IllegalArgumentException( 446 "Length of transportData and transportDataMask do not match"); 447 } 448 mTransportData = transportData; 449 mTransportDataMask = transportDataMask; 450 return this; 451 } 452 453 /** 454 * Set hashed bloom filter value to filter {@link OrganizationId 455 * #WIFI_ALLIANCE_NEIGHBOR_AWARENESS_NETWORKING} services in Transport Discovery Blocks. 456 * 457 * <p>Can only be used when {@code orgId} is {@link OrganizationId 458 * #WIFI_ALLIANCE_NEIGHBOR_AWARENESS_NETWORKING}. 459 * 460 * <p>Cannot be used together with {@link #setTransportData(byte[], byte[])} 461 * 462 * @param wifiNanHash 8 octets Wi-Fi NAN defined bloom filter hash 463 * @throws IllegalArgumentException when {@code orgId} is not {@link 464 * OrganizationId#WIFI_ALLIANCE_NEIGHBOR_AWARENESS_NETWORKING} 465 * @throws IllegalArgumentException when {@code wifiNanHash} is not {@link 466 * TransportBlockFilter#WIFI_NAN_HASH_LENGTH_BYTES} long 467 * @throws NullPointerException when {@code wifiNanHash} is null 468 * @return this builder 469 * @hide 470 */ 471 @SystemApi 472 @NonNull setWifiNanHash(@onNull byte[] wifiNanHash)473 public Builder setWifiNanHash(@NonNull byte[] wifiNanHash) { 474 if (mOrgId != OrganizationId.WIFI_ALLIANCE_NEIGHBOR_AWARENESS_NETWORKING) { 475 throw new IllegalArgumentException( 476 "setWifiNanHash() can only be used when orgId is" 477 + " WIFI_ALLIANCE_NEIGHBOR_AWARENESS_NETWORKING"); 478 } 479 Objects.requireNonNull(wifiNanHash); 480 if (wifiNanHash.length != WIFI_NAN_HASH_LENGTH_BYTES) { 481 throw new IllegalArgumentException("Wi-Fi NAN hash must be 8 octets long"); 482 } 483 mWifiNanHash = wifiNanHash; 484 return this; 485 } 486 487 /** 488 * Build {@link TransportBlockFilter}. 489 * 490 * @return {@link TransportBlockFilter} 491 * @throws IllegalStateException if the filter cannot be built 492 * @hide 493 */ 494 @SystemApi 495 @NonNull build()496 public TransportBlockFilter build() { 497 return new TransportBlockFilter( 498 mOrgId, 499 mTdsFlags, 500 mTdsFlagsMask, 501 mTransportData, 502 mTransportDataMask, 503 mWifiNanHash); 504 } 505 } 506 } 507