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