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