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