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