1 /*
2  * Copyright (C) 2012 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 com.android.nfc.handover;
18 
19 import java.nio.BufferUnderflowException;
20 import java.nio.ByteBuffer;
21 import java.nio.ByteOrder;
22 import java.nio.charset.StandardCharsets;
23 import java.util.ArrayList;
24 import java.nio.charset.Charset;
25 import java.util.Arrays;
26 import java.util.Random;
27 
28 import android.bluetooth.BluetoothAdapter;
29 import android.bluetooth.BluetoothClass;
30 import android.bluetooth.BluetoothDevice;
31 import android.bluetooth.BluetoothUuid;
32 import android.bluetooth.OobData;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.nfc.FormatException;
36 import android.nfc.NdefMessage;
37 import android.nfc.NdefRecord;
38 import android.os.ParcelUuid;
39 import android.os.UserHandle;
40 import android.util.Log;
41 
42 /**
43  * Manages handover of NFC to other technologies.
44  */
45 public class HandoverDataParser {
46     private static final String TAG = "NfcHandover";
47     private static final boolean DBG = false;
48 
49     private static final byte[] TYPE_BT_OOB = "application/vnd.bluetooth.ep.oob"
50             .getBytes(StandardCharsets.US_ASCII);
51     private static final byte[] TYPE_BLE_OOB = "application/vnd.bluetooth.le.oob"
52             .getBytes(StandardCharsets.US_ASCII);
53 
54     private static final byte[] TYPE_NOKIA = "nokia.com:bt".getBytes(StandardCharsets.US_ASCII);
55 
56     private static final byte[] RTD_COLLISION_RESOLUTION = {0x63, 0x72}; // "cr";
57 
58     private static final int CARRIER_POWER_STATE_INACTIVE = 0;
59     private static final int CARRIER_POWER_STATE_ACTIVE = 1;
60     private static final int CARRIER_POWER_STATE_ACTIVATING = 2;
61     private static final int CARRIER_POWER_STATE_UNKNOWN = 3;
62 
63     private static final int BT_HANDOVER_TYPE_MAC = 0x1B;
64     private static final int BT_HANDOVER_TYPE_LE_ROLE = 0x1C;
65     private static final int BT_HANDOVER_TYPE_LONG_LOCAL_NAME = 0x09;
66     private static final int BT_HANDOVER_TYPE_SHORT_LOCAL_NAME = 0x08;
67     private static final int BT_HANDOVER_TYPE_16_BIT_UUIDS_PARTIAL = 0x02;
68     private static final int BT_HANDOVER_TYPE_16_BIT_UUIDS_COMPLETE = 0x03;
69     private static final int BT_HANDOVER_TYPE_32_BIT_UUIDS_PARTIAL = 0x04;
70     private static final int BT_HANDOVER_TYPE_32_BIT_UUIDS_COMPLETE = 0x05;
71     private static final int BT_HANDOVER_TYPE_128_BIT_UUIDS_PARTIAL = 0x06;
72     private static final int BT_HANDOVER_TYPE_128_BIT_UUIDS_COMPLETE = 0x07;
73     private static final int BT_HANDOVER_TYPE_CLASS_OF_DEVICE = 0x0D;
74     private static final int BT_HANDOVER_TYPE_SECURITY_MANAGER_TK = 0x10;
75     private static final int BT_HANDOVER_TYPE_APPEARANCE = 0x19;
76     private static final int BT_HANDOVER_TYPE_LE_SC_CONFIRMATION = 0x22;
77     private static final int BT_HANDOVER_TYPE_LE_SC_RANDOM = 0x23;
78 
79     public static final int BT_HANDOVER_LE_ROLE_CENTRAL_ONLY = 0x01;
80 
81     public static final int SECURITY_MANAGER_TK_SIZE = 16;
82     public static final int SECURITY_MANAGER_LE_SC_C_SIZE = 16;
83     public static final int SECURITY_MANAGER_LE_SC_R_SIZE = 16;
84     private static final int CLASS_OF_DEVICE_SIZE = 3;
85 
86     private final BluetoothAdapter mBluetoothAdapter;
87 
88     private final Object mLock = new Object();
89     // Variables below synchronized on mLock
90 
91     private String mLocalBluetoothAddress;
92 
93     public static class BluetoothHandoverData {
94         public boolean valid = false;
95         public BluetoothDevice device;
96         public String name;
97         public boolean carrierActivating = false;
98         public int transport = BluetoothDevice.TRANSPORT_AUTO;
99         public OobData oobData;
100         public ParcelUuid[] uuids = null;
101         public BluetoothClass btClass = null;
102     }
103 
104     public static class IncomingHandoverData {
105         public final NdefMessage handoverSelect;
106         public final BluetoothHandoverData handoverData;
107 
IncomingHandoverData(NdefMessage handoverSelect, BluetoothHandoverData handoverData)108         public IncomingHandoverData(NdefMessage handoverSelect,
109                                     BluetoothHandoverData handoverData) {
110             this.handoverSelect = handoverSelect;
111             this.handoverData = handoverData;
112         }
113     }
114 
HandoverDataParser()115     public HandoverDataParser() {
116         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
117     }
118 
createCollisionRecord()119     static NdefRecord createCollisionRecord() {
120         byte[] random = new byte[2];
121         new Random().nextBytes(random);
122         return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, RTD_COLLISION_RESOLUTION, null, random);
123     }
124 
createBluetoothAlternateCarrierRecord(boolean activating)125     NdefRecord createBluetoothAlternateCarrierRecord(boolean activating) {
126         byte[] payload = new byte[4];
127         payload[0] = (byte) (activating ? CARRIER_POWER_STATE_ACTIVATING :
128             CARRIER_POWER_STATE_ACTIVE);  // Carrier Power State: Activating or active
129         payload[1] = 1;   // length of carrier data reference
130         payload[2] = 'b'; // carrier data reference: ID for Bluetooth OOB data record
131         payload[3] = 0;  // Auxiliary data reference count
132         return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_ALTERNATIVE_CARRIER, null,
133                 payload);
134     }
135 
createBluetoothOobDataRecord()136     NdefRecord createBluetoothOobDataRecord() {
137         byte[] payload = new byte[8];
138         // Note: this field should be little-endian per the BTSSP spec
139         // The Android 4.1 implementation used big-endian order here.
140         // No single Android implementation has ever interpreted this
141         // length field when parsing this record though.
142         payload[0] = (byte) (payload.length & 0xFF);
143         payload[1] = (byte) ((payload.length >> 8) & 0xFF);
144 
145         synchronized (mLock) {
146             if (mLocalBluetoothAddress == null) {
147                 mLocalBluetoothAddress = mBluetoothAdapter.getAddress();
148             }
149 
150             byte[] addressBytes = addressToReverseBytes(mLocalBluetoothAddress);
151             if (addressBytes != null) {
152                 System.arraycopy(addressBytes, 0, payload, 2, 6);
153             } else {
154                 // don't cache unknown result
155                 mLocalBluetoothAddress = null;
156             }
157         }
158 
159         return new NdefRecord(NdefRecord.TNF_MIME_MEDIA, TYPE_BT_OOB, new byte[]{'b'}, payload);
160     }
161 
isHandoverSupported()162     public boolean isHandoverSupported() {
163         return (mBluetoothAdapter != null);
164     }
165 
createHandoverRequestMessage()166     public NdefMessage createHandoverRequestMessage() {
167         if (mBluetoothAdapter == null) {
168             return null;
169         }
170 
171         NdefRecord[] dataRecords = new NdefRecord[] {
172                 createBluetoothOobDataRecord()
173         };
174         return new NdefMessage(
175                 createHandoverRequestRecord(),
176                 dataRecords);
177     }
178 
createBluetoothHandoverSelectMessage(boolean activating)179     NdefMessage createBluetoothHandoverSelectMessage(boolean activating) {
180         return new NdefMessage(createHandoverSelectRecord(
181                 createBluetoothAlternateCarrierRecord(activating)),
182                 createBluetoothOobDataRecord());
183     }
184 
createHandoverSelectRecord(NdefRecord alternateCarrier)185     NdefRecord createHandoverSelectRecord(NdefRecord alternateCarrier) {
186         NdefMessage nestedMessage = new NdefMessage(alternateCarrier);
187         byte[] nestedPayload = nestedMessage.toByteArray();
188 
189         ByteBuffer payload = ByteBuffer.allocate(nestedPayload.length + 1);
190         payload.put((byte)0x12);  // connection handover v1.2
191         payload.put(nestedPayload);
192 
193         byte[] payloadBytes = new byte[payload.position()];
194         payload.position(0);
195         payload.get(payloadBytes);
196         return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_HANDOVER_SELECT, null,
197                 payloadBytes);
198     }
199 
createHandoverRequestRecord()200     NdefRecord createHandoverRequestRecord() {
201         NdefRecord[] messages = new NdefRecord[] {
202                 createBluetoothAlternateCarrierRecord(false)
203         };
204 
205         NdefMessage nestedMessage = new NdefMessage(createCollisionRecord(), messages);
206 
207         byte[] nestedPayload = nestedMessage.toByteArray();
208 
209         ByteBuffer payload = ByteBuffer.allocate(nestedPayload.length + 1);
210         payload.put((byte) 0x12);  // connection handover v1.2
211         payload.put(nestedMessage.toByteArray());
212 
213         byte[] payloadBytes = new byte[payload.position()];
214         payload.position(0);
215         payload.get(payloadBytes);
216         return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_HANDOVER_REQUEST, null,
217                 payloadBytes);
218     }
219 
220     /**
221      * Returns null if message is not a Handover Request,
222      * returns the IncomingHandoverData (Hs + parsed data) if it is.
223      */
getIncomingHandoverData(NdefMessage handoverRequest)224     public IncomingHandoverData getIncomingHandoverData(NdefMessage handoverRequest) {
225         if (handoverRequest == null) return null;
226         if (mBluetoothAdapter == null) return null;
227 
228         if (DBG) Log.d(TAG, "getIncomingHandoverData():" + handoverRequest.toString());
229 
230         NdefRecord handoverRequestRecord = handoverRequest.getRecords()[0];
231         if (handoverRequestRecord.getTnf() != NdefRecord.TNF_WELL_KNOWN) {
232             return null;
233         }
234 
235         if (!Arrays.equals(handoverRequestRecord.getType(), NdefRecord.RTD_HANDOVER_REQUEST)) {
236             return null;
237         }
238 
239         // we have a handover request, look for BT OOB record
240         BluetoothHandoverData bluetoothData = null;
241         for (NdefRecord dataRecord : handoverRequest.getRecords()) {
242             if (dataRecord.getTnf() == NdefRecord.TNF_MIME_MEDIA) {
243                 if (Arrays.equals(dataRecord.getType(), TYPE_BT_OOB)) {
244                     bluetoothData = parseBtOob(ByteBuffer.wrap(dataRecord.getPayload()));
245                 }
246             }
247         }
248 
249         NdefMessage hs = tryBluetoothHandoverRequest(bluetoothData);
250         if (hs != null) {
251             return new IncomingHandoverData(hs, bluetoothData);
252         }
253 
254         return null;
255     }
256 
getOutgoingHandoverData(NdefMessage handoverSelect)257     public BluetoothHandoverData getOutgoingHandoverData(NdefMessage handoverSelect) {
258         return parseBluetooth(handoverSelect);
259     }
260 
tryBluetoothHandoverRequest(BluetoothHandoverData bluetoothData)261     private NdefMessage tryBluetoothHandoverRequest(BluetoothHandoverData bluetoothData) {
262         NdefMessage selectMessage = null;
263         if (bluetoothData != null) {
264             // Note: there could be a race where we conclude
265             // that Bluetooth is already enabled, and shortly
266             // after the user turns it off. That will cause
267             // the transfer to fail, but there's nothing
268             // much we can do about it anyway. It shouldn't
269             // be common for the user to be changing BT settings
270             // while waiting to receive a picture.
271             boolean bluetoothActivating = !mBluetoothAdapter.isEnabled();
272 
273             // return BT OOB record so they can perform handover
274             selectMessage = (createBluetoothHandoverSelectMessage(bluetoothActivating));
275             if (DBG) Log.d(TAG, "Waiting for incoming transfer, [" +
276                     bluetoothData.device.getAddress() + "]->[" + mLocalBluetoothAddress + "]");
277         }
278 
279         return selectMessage;
280     }
281 
282 
283 
isCarrierActivating(NdefRecord handoverRec, byte[] carrierId)284     boolean isCarrierActivating(NdefRecord handoverRec, byte[] carrierId) {
285         byte[] payload = handoverRec.getPayload();
286         if (payload == null || payload.length <= 1) return false;
287         // Skip version
288         byte[] payloadNdef = new byte[payload.length - 1];
289         System.arraycopy(payload, 1, payloadNdef, 0, payload.length - 1);
290         NdefMessage msg;
291         try {
292             msg = new NdefMessage(payloadNdef);
293         } catch (FormatException e) {
294             return false;
295         }
296 
297         for (NdefRecord alt : msg.getRecords()) {
298             byte[] acPayload = alt.getPayload();
299             if (acPayload != null) {
300                 ByteBuffer buf = ByteBuffer.wrap(acPayload);
301                 int cps = buf.get() & 0x03; // Carrier Power State is in lower 2 bits
302                 int carrierRefLength = buf.get() & 0xFF;
303                 if (carrierRefLength != carrierId.length) return false;
304 
305                 byte[] carrierRefId = new byte[carrierRefLength];
306                 buf.get(carrierRefId);
307                 if (Arrays.equals(carrierRefId, carrierId)) {
308                     // Found match, returning whether power state is activating
309                     return (cps == CARRIER_POWER_STATE_ACTIVATING);
310                 }
311             }
312         }
313 
314         return true;
315     }
316 
parseBluetoothHandoverSelect(NdefMessage m)317     BluetoothHandoverData parseBluetoothHandoverSelect(NdefMessage m) {
318         // TODO we could parse this a lot more strictly; right now
319         // we just search for a BT OOB record, and try to cross-reference
320         // the carrier state inside the 'hs' payload.
321         for (NdefRecord oob : m.getRecords()) {
322             if (oob.getTnf() == NdefRecord.TNF_MIME_MEDIA &&
323                     Arrays.equals(oob.getType(), TYPE_BT_OOB)) {
324                 BluetoothHandoverData data = parseBtOob(ByteBuffer.wrap(oob.getPayload()));
325                 if (data != null && isCarrierActivating(m.getRecords()[0], oob.getId())) {
326                     data.carrierActivating = true;
327                 }
328                 return data;
329             }
330 
331             if (oob.getTnf() == NdefRecord.TNF_MIME_MEDIA &&
332                     Arrays.equals(oob.getType(), TYPE_BLE_OOB)) {
333                 return parseBleOob(ByteBuffer.wrap(oob.getPayload()));
334             }
335         }
336 
337         return null;
338     }
339 
parseBluetooth(NdefMessage m)340     public BluetoothHandoverData parseBluetooth(NdefMessage m) {
341         NdefRecord r = m.getRecords()[0];
342         short tnf = r.getTnf();
343         byte[] type = r.getType();
344 
345         // Check for BT OOB record
346         if (r.getTnf() == NdefRecord.TNF_MIME_MEDIA && Arrays.equals(r.getType(), TYPE_BT_OOB)) {
347             return parseBtOob(ByteBuffer.wrap(r.getPayload()));
348         }
349 
350         // Check for BLE OOB record
351         if (r.getTnf() == NdefRecord.TNF_MIME_MEDIA && Arrays.equals(r.getType(), TYPE_BLE_OOB)) {
352             return parseBleOob(ByteBuffer.wrap(r.getPayload()));
353         }
354 
355         // Check for Handover Select, followed by a BT OOB record
356         if (tnf == NdefRecord.TNF_WELL_KNOWN &&
357                 Arrays.equals(type, NdefRecord.RTD_HANDOVER_SELECT)) {
358             return parseBluetoothHandoverSelect(m);
359         }
360 
361         // Check for Nokia BT record, found on some Nokia BH-505 Headsets
362         if (tnf == NdefRecord.TNF_EXTERNAL_TYPE && Arrays.equals(type, TYPE_NOKIA)) {
363             return parseNokia(ByteBuffer.wrap(r.getPayload()));
364         }
365 
366         return null;
367     }
368 
parseNokia(ByteBuffer payload)369     BluetoothHandoverData parseNokia(ByteBuffer payload) {
370         BluetoothHandoverData result = new BluetoothHandoverData();
371         result.valid = false;
372 
373         try {
374             payload.position(1);
375             byte[] address = new byte[6];
376             payload.get(address);
377             result.device = mBluetoothAdapter.getRemoteDevice(address);
378             result.valid = true;
379             payload.position(14);
380             int nameLength = payload.get();
381             byte[] nameBytes = new byte[nameLength];
382             payload.get(nameBytes);
383             result.name = new String(nameBytes, StandardCharsets.UTF_8);
384         } catch (IllegalArgumentException e) {
385             Log.i(TAG, "nokia: invalid BT address");
386         } catch (BufferUnderflowException e) {
387             Log.i(TAG, "nokia: payload shorter than expected");
388         }
389         if (result.valid && result.name == null) result.name = "";
390         return result;
391     }
392 
parseBtOob(ByteBuffer payload)393     BluetoothHandoverData parseBtOob(ByteBuffer payload) {
394         BluetoothHandoverData result = new BluetoothHandoverData();
395         result.valid = false;
396 
397         try {
398             payload.position(2); // length
399             byte[] address = parseMacFromBluetoothRecord(payload);
400             result.device = mBluetoothAdapter.getRemoteDevice(address);
401             result.valid = true;
402 
403             while (payload.remaining() > 0) {
404                 boolean success = false;
405                 byte[] nameBytes;
406                 int len = payload.get();
407                 int type = payload.get();
408                 switch (type) {
409                     case BT_HANDOVER_TYPE_SHORT_LOCAL_NAME:
410                         nameBytes = new byte[len - 1];
411                         payload.get(nameBytes);
412                         result.name = new String(nameBytes, StandardCharsets.UTF_8);
413                         success = true;
414                         break;
415                     case BT_HANDOVER_TYPE_LONG_LOCAL_NAME:
416                         if (result.name != null) break;  // prefer short name
417                         nameBytes = new byte[len - 1];
418                         payload.get(nameBytes);
419                         result.name = new String(nameBytes, StandardCharsets.UTF_8);
420                         success = true;
421                         break;
422                     case BT_HANDOVER_TYPE_16_BIT_UUIDS_PARTIAL:
423                     case BT_HANDOVER_TYPE_16_BIT_UUIDS_COMPLETE:
424                     case BT_HANDOVER_TYPE_32_BIT_UUIDS_PARTIAL:
425                     case BT_HANDOVER_TYPE_32_BIT_UUIDS_COMPLETE:
426                     case BT_HANDOVER_TYPE_128_BIT_UUIDS_PARTIAL:
427                     case BT_HANDOVER_TYPE_128_BIT_UUIDS_COMPLETE:
428                         result.uuids = parseUuidFromBluetoothRecord(payload, type, len - 1);
429                         if (result.uuids != null) {
430                             success = true;
431                         }
432                         break;
433                     case BT_HANDOVER_TYPE_CLASS_OF_DEVICE:
434                         if (len - 1 != CLASS_OF_DEVICE_SIZE) {
435                             Log.i(TAG, "BT OOB: invalid size of Class of Device, should be " +
436                                   CLASS_OF_DEVICE_SIZE + " bytes.");
437                             break;
438                         }
439                         result.btClass = parseBluetoothClassFromBluetoothRecord(payload);
440                         success = true;
441                         break;
442                     default:
443                         break;
444                 }
445                 if (!success) {
446                     payload.position(payload.position() + len - 1);
447                 }
448             }
449         } catch (IllegalArgumentException e) {
450             Log.i(TAG, "BT OOB: invalid BT address");
451         } catch (BufferUnderflowException e) {
452             Log.i(TAG, "BT OOB: payload shorter than expected");
453         }
454         if (result.valid && result.name == null) result.name = "";
455         return result;
456     }
457 
parseBleOob(ByteBuffer payload)458     BluetoothHandoverData parseBleOob(ByteBuffer payload) {
459         BluetoothHandoverData result = new BluetoothHandoverData();
460         result.valid = false;
461         result.transport = BluetoothDevice.TRANSPORT_LE;
462 
463         try {
464 
465             while (payload.remaining() > 0) {
466                 int len = payload.get();
467                 int type = payload.get();
468                 switch (type) {
469                     case BT_HANDOVER_TYPE_MAC: // mac address
470 
471                         int startpos = payload.position();
472                         byte[] bdaddr = new byte[7]; // 6 bytes for mac, 1 for addres type
473                         payload.get(bdaddr);
474                         if (result.oobData == null)
475                             result.oobData = new OobData();
476                         result.oobData.setLeBluetoothDeviceAddress(bdaddr);
477                         payload.position(startpos);
478 
479                         byte[] address = parseMacFromBluetoothRecord(payload);
480                         payload.position(payload.position() + 1); // advance over random byte
481                         result.device = mBluetoothAdapter.getRemoteDevice(address);
482                         result.valid = true;
483                         break;
484                     case BT_HANDOVER_TYPE_LE_ROLE:
485                         byte role = payload.get();
486                         if (role == BT_HANDOVER_LE_ROLE_CENTRAL_ONLY) {
487                             // only central role supported, can't pair
488                             result.valid = false;
489                             return result;
490                         }
491                         break;
492                     case BT_HANDOVER_TYPE_LONG_LOCAL_NAME:
493                         byte[] nameBytes = new byte[len - 1];
494                         payload.get(nameBytes);
495                         result.name = new String(nameBytes, StandardCharsets.UTF_8);
496                         break;
497                     case BT_HANDOVER_TYPE_SECURITY_MANAGER_TK:
498                         if (len-1 != SECURITY_MANAGER_TK_SIZE) {
499                             Log.i(TAG, "BT OOB: invalid size of SM TK, should be " +
500                                   SECURITY_MANAGER_TK_SIZE + " bytes.");
501                             break;
502                         }
503 
504                         byte[] securityManagerTK = new byte[len - 1];
505                         payload.get(securityManagerTK);
506 
507                         if (result.oobData == null)
508                             result.oobData = new OobData();
509                         result.oobData.setSecurityManagerTk(securityManagerTK);
510                         break;
511 
512                     case BT_HANDOVER_TYPE_LE_SC_CONFIRMATION:
513                         if (len - 1 != SECURITY_MANAGER_LE_SC_C_SIZE) {
514                             Log.i(TAG, "BT OOB: invalid size of LE SC Confirmation, should be " +
515                                   SECURITY_MANAGER_LE_SC_C_SIZE + " bytes.");
516                             break;
517                         }
518 
519                         byte[] leScC = new byte[len - 1];
520                         payload.get(leScC);
521 
522                         if (result.oobData == null)
523                             result.oobData = new OobData();
524                         result.oobData.setLeSecureConnectionsConfirmation(leScC);
525                         break;
526 
527                     case BT_HANDOVER_TYPE_LE_SC_RANDOM:
528                         if (len-1 != SECURITY_MANAGER_LE_SC_R_SIZE) {
529                             Log.i(TAG, "BT OOB: invalid size of LE SC Random, should be " +
530                                   SECURITY_MANAGER_LE_SC_R_SIZE + " bytes.");
531                             break;
532                         }
533 
534                         byte[] leScR = new byte[len - 1];
535                         payload.get(leScR);
536 
537                         if (result.oobData == null)
538                             result.oobData = new OobData();
539                         result.oobData.setLeSecureConnectionsRandom(leScR);
540                         break;
541 
542                     default:
543                         payload.position(payload.position() + len - 1);
544                         break;
545                 }
546             }
547         } catch (IllegalArgumentException e) {
548             Log.i(TAG, "BLE OOB: error parsing OOB data", e);
549         } catch (BufferUnderflowException e) {
550             Log.i(TAG, "BT OOB: payload shorter than expected");
551         }
552         if (result.valid && result.name == null) result.name = "";
553         return result;
554     }
555 
parseMacFromBluetoothRecord(ByteBuffer payload)556     private byte[] parseMacFromBluetoothRecord(ByteBuffer payload) {
557         byte[] address = new byte[6];
558         payload.get(address);
559         // ByteBuffer.order(LITTLE_ENDIAN) doesn't work for
560         // ByteBuffer.get(byte[]), so manually swap order
561         for (int i = 0; i < 3; i++) {
562             byte temp = address[i];
563             address[i] = address[5 - i];
564             address[5 - i] = temp;
565         }
566         return address;
567     }
568 
addressToReverseBytes(String address)569     static byte[] addressToReverseBytes(String address) {
570         if (address == null) {
571             Log.w(TAG, "BT address is null");
572             return null;
573         }
574         String[] split = address.split(":");
575         if (split.length < 6) {
576             Log.w(TAG, "BT address " + address + " is invalid");
577             return null;
578         }
579         byte[] result = new byte[split.length];
580 
581         for (int i = 0; i < split.length; i++) {
582             // need to parse as int because parseByte() expects a signed byte
583             result[split.length - 1 - i] = (byte)Integer.parseInt(split[i], 16);
584         }
585 
586         return result;
587     }
588 
parseUuidFromBluetoothRecord(ByteBuffer payload, int type, int len)589     private ParcelUuid[] parseUuidFromBluetoothRecord(ByteBuffer payload, int type, int len) {
590         int uuidSize;
591         switch (type) {
592             case BT_HANDOVER_TYPE_16_BIT_UUIDS_PARTIAL:
593             case BT_HANDOVER_TYPE_16_BIT_UUIDS_COMPLETE:
594                 uuidSize = BluetoothUuid.UUID_BYTES_16_BIT;
595                 break;
596             case BT_HANDOVER_TYPE_32_BIT_UUIDS_PARTIAL:
597             case BT_HANDOVER_TYPE_32_BIT_UUIDS_COMPLETE:
598                 uuidSize = BluetoothUuid.UUID_BYTES_32_BIT;
599                 break;
600             case BT_HANDOVER_TYPE_128_BIT_UUIDS_PARTIAL:
601             case BT_HANDOVER_TYPE_128_BIT_UUIDS_COMPLETE:
602                 uuidSize = BluetoothUuid.UUID_BYTES_128_BIT;
603                 break;
604             default:
605                 Log.i(TAG, "BT OOB: invalid size of UUID");
606                 return null;
607         }
608 
609         if (len == 0 || len % uuidSize != 0) {
610             Log.i(TAG, "BT OOB: invalid size of UUIDs, should be multiples of UUID bytes length");
611             return null;
612         }
613 
614         int num = len / uuidSize;
615         ParcelUuid[] uuids = new ParcelUuid[num];
616         byte[] data = new byte[uuidSize];
617         for (int i = 0; i < num; i++) {
618             payload.get(data);
619             uuids[i] = BluetoothUuid.parseUuidFrom(data);
620         }
621         return uuids;
622     }
623 
parseBluetoothClassFromBluetoothRecord(ByteBuffer payload)624     private BluetoothClass parseBluetoothClassFromBluetoothRecord(ByteBuffer payload) {
625         byte[] btClass = new byte[CLASS_OF_DEVICE_SIZE];
626         payload.get(btClass);
627 
628         ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES);
629         buffer.put(btClass);
630         buffer.order(ByteOrder.LITTLE_ENDIAN);
631 
632         return new BluetoothClass(buffer.getInt(0));
633     }
634 }
635