• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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