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