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