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