1 /*
2  * Copyright (C) 2014 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.SuppressLint;
22 import android.bluetooth.BluetoothUuid;
23 import android.compat.annotation.UnsupportedAppUsage;
24 import android.os.ParcelUuid;
25 import android.util.ArrayMap;
26 import android.util.Log;
27 import android.util.SparseArray;
28 
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.function.Predicate;
34 
35 /**
36  * Represents a scan record from Bluetooth LE scan.
37  */
38 @SuppressLint("AndroidFrameworkBluetoothPermission")
39 public final class ScanRecord {
40 
41     private static final String TAG = "ScanRecord";
42 
43     // The following data type values are assigned by Bluetooth SIG.
44     // For more details refer to Bluetooth 4.1 specification, Volume 3, Part C, Section 18.
45     private static final int DATA_TYPE_FLAGS = 0x01;
46     private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02;
47     private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03;
48     private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04;
49     private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05;
50     private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06;
51     private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07;
52     private static final int DATA_TYPE_LOCAL_NAME_SHORT = 0x08;
53     private static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09;
54     private static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A;
55     private static final int DATA_TYPE_SERVICE_DATA_16_BIT = 0x16;
56     private static final int DATA_TYPE_SERVICE_DATA_32_BIT = 0x20;
57     private static final int DATA_TYPE_SERVICE_DATA_128_BIT = 0x21;
58     private static final int DATA_TYPE_SERVICE_SOLICITATION_UUIDS_16_BIT = 0x14;
59     private static final int DATA_TYPE_SERVICE_SOLICITATION_UUIDS_32_BIT = 0x1F;
60     private static final int DATA_TYPE_SERVICE_SOLICITATION_UUIDS_128_BIT = 0x15;
61     private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF;
62 
63     // Flags of the advertising data.
64     private final int mAdvertiseFlags;
65 
66     @Nullable
67     private final List<ParcelUuid> mServiceUuids;
68     @Nullable
69     private final List<ParcelUuid> mServiceSolicitationUuids;
70 
71     private final SparseArray<byte[]> mManufacturerSpecificData;
72 
73     private final Map<ParcelUuid, byte[]> mServiceData;
74 
75     // Transmission power level(in dB).
76     private final int mTxPowerLevel;
77 
78     // Local name of the Bluetooth LE device.
79     private final String mDeviceName;
80 
81     // Raw bytes of scan record.
82     private final byte[] mBytes;
83 
84     /**
85      * Returns the advertising flags indicating the discoverable mode and capability of the device.
86      * Returns -1 if the flag field is not set.
87      */
getAdvertiseFlags()88     public int getAdvertiseFlags() {
89         return mAdvertiseFlags;
90     }
91 
92     /**
93      * Returns a list of service UUIDs within the advertisement that are used to identify the
94      * bluetooth GATT services.
95      */
getServiceUuids()96     public List<ParcelUuid> getServiceUuids() {
97         return mServiceUuids;
98     }
99 
100     /**
101      * Returns a list of service solicitation UUIDs within the advertisement that are used to
102      * identify the Bluetooth GATT services.
103      */
104     @NonNull
getServiceSolicitationUuids()105     public List<ParcelUuid> getServiceSolicitationUuids() {
106         return mServiceSolicitationUuids;
107     }
108 
109     /**
110      * Returns a sparse array of manufacturer identifier and its corresponding manufacturer specific
111      * data.
112      */
getManufacturerSpecificData()113     public SparseArray<byte[]> getManufacturerSpecificData() {
114         return mManufacturerSpecificData;
115     }
116 
117     /**
118      * Returns the manufacturer specific data associated with the manufacturer id. Returns
119      * {@code null} if the {@code manufacturerId} is not found.
120      */
121     @Nullable
getManufacturerSpecificData(int manufacturerId)122     public byte[] getManufacturerSpecificData(int manufacturerId) {
123         if (mManufacturerSpecificData == null) {
124             return null;
125         }
126         return mManufacturerSpecificData.get(manufacturerId);
127     }
128 
129     /**
130      * Returns a map of service UUID and its corresponding service data.
131      */
getServiceData()132     public Map<ParcelUuid, byte[]> getServiceData() {
133         return mServiceData;
134     }
135 
136     /**
137      * Returns the service data byte array associated with the {@code serviceUuid}. Returns
138      * {@code null} if the {@code serviceDataUuid} is not found.
139      */
140     @Nullable
getServiceData(ParcelUuid serviceDataUuid)141     public byte[] getServiceData(ParcelUuid serviceDataUuid) {
142         if (serviceDataUuid == null || mServiceData == null) {
143             return null;
144         }
145         return mServiceData.get(serviceDataUuid);
146     }
147 
148     /**
149      * Returns the transmission power level of the packet in dBm. Returns {@link Integer#MIN_VALUE}
150      * if the field is not set. This value can be used to calculate the path loss of a received
151      * packet using the following equation:
152      * <p>
153      * <code>pathloss = txPowerLevel - rssi</code>
154      */
getTxPowerLevel()155     public int getTxPowerLevel() {
156         return mTxPowerLevel;
157     }
158 
159     /**
160      * Returns the local name of the BLE device. This is a UTF-8 encoded string.
161      */
162     @Nullable
getDeviceName()163     public String getDeviceName() {
164         return mDeviceName;
165     }
166 
167     /**
168      * Returns raw bytes of scan record.
169      */
getBytes()170     public byte[] getBytes() {
171         return mBytes;
172     }
173 
174     /**
175      * Test if any fields contained inside this scan record are matched by the
176      * given matcher.
177      *
178      * @hide
179      */
matchesAnyField(@onNull Predicate<byte[]> matcher)180     public boolean matchesAnyField(@NonNull Predicate<byte[]> matcher) {
181         int pos = 0;
182         while (pos < mBytes.length) {
183             final int length = mBytes[pos] & 0xFF;
184             if (length == 0) {
185                 break;
186             }
187             if (matcher.test(Arrays.copyOfRange(mBytes, pos, pos + length + 1))) {
188                 return true;
189             }
190             pos += length + 1;
191         }
192         return false;
193     }
194 
ScanRecord(List<ParcelUuid> serviceUuids, List<ParcelUuid> serviceSolicitationUuids, SparseArray<byte[]> manufacturerData, Map<ParcelUuid, byte[]> serviceData, int advertiseFlags, int txPowerLevel, String localName, byte[] bytes)195     private ScanRecord(List<ParcelUuid> serviceUuids,
196             List<ParcelUuid> serviceSolicitationUuids,
197             SparseArray<byte[]> manufacturerData,
198             Map<ParcelUuid, byte[]> serviceData,
199             int advertiseFlags, int txPowerLevel,
200             String localName, byte[] bytes) {
201         mServiceSolicitationUuids = serviceSolicitationUuids;
202         mServiceUuids = serviceUuids;
203         mManufacturerSpecificData = manufacturerData;
204         mServiceData = serviceData;
205         mDeviceName = localName;
206         mAdvertiseFlags = advertiseFlags;
207         mTxPowerLevel = txPowerLevel;
208         mBytes = bytes;
209     }
210 
211     /**
212      * Parse scan record bytes to {@link ScanRecord}.
213      * <p>
214      * The format is defined in Bluetooth 4.1 specification, Volume 3, Part C, Section 11 and 18.
215      * <p>
216      * All numerical multi-byte entities and values shall use little-endian <strong>byte</strong>
217      * order.
218      *
219      * @param scanRecord The scan record of Bluetooth LE advertisement and/or scan response.
220      * @hide
221      */
222     @UnsupportedAppUsage
parseFromBytes(byte[] scanRecord)223     public static ScanRecord parseFromBytes(byte[] scanRecord) {
224         if (scanRecord == null) {
225             return null;
226         }
227 
228         int currentPos = 0;
229         int advertiseFlag = -1;
230         List<ParcelUuid> serviceUuids = new ArrayList<ParcelUuid>();
231         List<ParcelUuid> serviceSolicitationUuids = new ArrayList<ParcelUuid>();
232         String localName = null;
233         int txPowerLevel = Integer.MIN_VALUE;
234 
235         SparseArray<byte[]> manufacturerData = new SparseArray<byte[]>();
236         Map<ParcelUuid, byte[]> serviceData = new ArrayMap<ParcelUuid, byte[]>();
237 
238         try {
239             while (currentPos < scanRecord.length) {
240                 // length is unsigned int.
241                 int length = scanRecord[currentPos++] & 0xFF;
242                 if (length == 0) {
243                     break;
244                 }
245                 // Note the length includes the length of the field type itself.
246                 int dataLength = length - 1;
247                 // fieldType is unsigned int.
248                 int fieldType = scanRecord[currentPos++] & 0xFF;
249                 switch (fieldType) {
250                     case DATA_TYPE_FLAGS:
251                         advertiseFlag = scanRecord[currentPos] & 0xFF;
252                         break;
253                     case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL:
254                     case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE:
255                         parseServiceUuid(scanRecord, currentPos,
256                                 dataLength, BluetoothUuid.UUID_BYTES_16_BIT, serviceUuids);
257                         break;
258                     case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL:
259                     case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE:
260                         parseServiceUuid(scanRecord, currentPos, dataLength,
261                                 BluetoothUuid.UUID_BYTES_32_BIT, serviceUuids);
262                         break;
263                     case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL:
264                     case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE:
265                         parseServiceUuid(scanRecord, currentPos, dataLength,
266                                 BluetoothUuid.UUID_BYTES_128_BIT, serviceUuids);
267                         break;
268                     case DATA_TYPE_SERVICE_SOLICITATION_UUIDS_16_BIT:
269                         parseServiceSolicitationUuid(scanRecord, currentPos, dataLength,
270                                 BluetoothUuid.UUID_BYTES_16_BIT, serviceSolicitationUuids);
271                         break;
272                     case DATA_TYPE_SERVICE_SOLICITATION_UUIDS_32_BIT:
273                         parseServiceSolicitationUuid(scanRecord, currentPos, dataLength,
274                                 BluetoothUuid.UUID_BYTES_32_BIT, serviceSolicitationUuids);
275                         break;
276                     case DATA_TYPE_SERVICE_SOLICITATION_UUIDS_128_BIT:
277                         parseServiceSolicitationUuid(scanRecord, currentPos, dataLength,
278                                 BluetoothUuid.UUID_BYTES_128_BIT, serviceSolicitationUuids);
279                         break;
280                     case DATA_TYPE_LOCAL_NAME_SHORT:
281                     case DATA_TYPE_LOCAL_NAME_COMPLETE:
282                         localName = new String(
283                                 extractBytes(scanRecord, currentPos, dataLength));
284                         break;
285                     case DATA_TYPE_TX_POWER_LEVEL:
286                         txPowerLevel = scanRecord[currentPos];
287                         break;
288                     case DATA_TYPE_SERVICE_DATA_16_BIT:
289                     case DATA_TYPE_SERVICE_DATA_32_BIT:
290                     case DATA_TYPE_SERVICE_DATA_128_BIT:
291                         int serviceUuidLength = BluetoothUuid.UUID_BYTES_16_BIT;
292                         if (fieldType == DATA_TYPE_SERVICE_DATA_32_BIT) {
293                             serviceUuidLength = BluetoothUuid.UUID_BYTES_32_BIT;
294                         } else if (fieldType == DATA_TYPE_SERVICE_DATA_128_BIT) {
295                             serviceUuidLength = BluetoothUuid.UUID_BYTES_128_BIT;
296                         }
297 
298                         byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos,
299                                 serviceUuidLength);
300                         ParcelUuid serviceDataUuid = BluetoothUuid.parseUuidFrom(
301                                 serviceDataUuidBytes);
302                         byte[] serviceDataArray = extractBytes(scanRecord,
303                                 currentPos + serviceUuidLength, dataLength - serviceUuidLength);
304                         serviceData.put(serviceDataUuid, serviceDataArray);
305                         break;
306                     case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA:
307                         // The first two bytes of the manufacturer specific data are
308                         // manufacturer ids in little endian.
309                         int manufacturerId = ((scanRecord[currentPos + 1] & 0xFF) << 8)
310                                 + (scanRecord[currentPos] & 0xFF);
311                         byte[] manufacturerDataBytes = extractBytes(scanRecord, currentPos + 2,
312                                 dataLength - 2);
313                         manufacturerData.put(manufacturerId, manufacturerDataBytes);
314                         break;
315                     default:
316                         // Just ignore, we don't handle such data type.
317                         break;
318                 }
319                 currentPos += dataLength;
320             }
321 
322             if (serviceUuids.isEmpty()) {
323                 serviceUuids = null;
324             }
325             return new ScanRecord(serviceUuids, serviceSolicitationUuids, manufacturerData,
326                     serviceData, advertiseFlag, txPowerLevel, localName, scanRecord);
327         } catch (Exception e) {
328             Log.e(TAG, "unable to parse scan record: " + Arrays.toString(scanRecord));
329             // As the record is invalid, ignore all the parsed results for this packet
330             // and return an empty record with raw scanRecord bytes in results
331             return new ScanRecord(null, null, null, null, -1, Integer.MIN_VALUE, null, scanRecord);
332         }
333     }
334 
335     @Override
toString()336     public String toString() {
337         return "ScanRecord [mAdvertiseFlags=" + mAdvertiseFlags + ", mServiceUuids=" + mServiceUuids
338                 + ", mServiceSolicitationUuids=" + mServiceSolicitationUuids
339                 + ", mManufacturerSpecificData=" + BluetoothLeUtils.toString(
340                 mManufacturerSpecificData)
341                 + ", mServiceData=" + BluetoothLeUtils.toString(mServiceData)
342                 + ", mTxPowerLevel=" + mTxPowerLevel + ", mDeviceName=" + mDeviceName + "]";
343     }
344 
345     // Parse service UUIDs.
parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength, int uuidLength, List<ParcelUuid> serviceUuids)346     private static int parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength,
347             int uuidLength, List<ParcelUuid> serviceUuids) {
348         while (dataLength > 0) {
349             byte[] uuidBytes = extractBytes(scanRecord, currentPos,
350                     uuidLength);
351             serviceUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes));
352             dataLength -= uuidLength;
353             currentPos += uuidLength;
354         }
355         return currentPos;
356     }
357 
358     /**
359      * Parse service Solicitation UUIDs.
360      */
parseServiceSolicitationUuid(byte[] scanRecord, int currentPos, int dataLength, int uuidLength, List<ParcelUuid> serviceSolicitationUuids)361     private static int parseServiceSolicitationUuid(byte[] scanRecord, int currentPos,
362             int dataLength, int uuidLength, List<ParcelUuid> serviceSolicitationUuids) {
363         while (dataLength > 0) {
364             byte[] uuidBytes = extractBytes(scanRecord, currentPos, uuidLength);
365             serviceSolicitationUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes));
366             dataLength -= uuidLength;
367             currentPos += uuidLength;
368         }
369         return currentPos;
370     }
371 
372     // Helper method to extract bytes from byte array.
extractBytes(byte[] scanRecord, int start, int length)373     private static byte[] extractBytes(byte[] scanRecord, int start, int length) {
374         byte[] bytes = new byte[length];
375         System.arraycopy(scanRecord, start, bytes, 0, length);
376         return bytes;
377     }
378 }
379