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