1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.nearby.fastpair.provider; 18 19 import static android.bluetooth.BluetoothAdapter.EXTRA_STATE; 20 import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE; 21 import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE; 22 import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE; 23 import static android.bluetooth.BluetoothAdapter.STATE_OFF; 24 import static android.bluetooth.BluetoothAdapter.STATE_ON; 25 import static android.bluetooth.BluetoothDevice.ERROR; 26 import static android.bluetooth.BluetoothGattCharacteristic.PERMISSION_READ; 27 import static android.bluetooth.BluetoothGattCharacteristic.PERMISSION_WRITE; 28 import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_INDICATE; 29 import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_NOTIFY; 30 import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_READ; 31 import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_WRITE; 32 import static android.nearby.fastpair.provider.bluetooth.BluetoothManager.wrap; 33 import static android.nearby.fastpair.provider.bluetooth.RfcommServer.State.CONNECTED; 34 35 import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.AES_BLOCK_LENGTH; 36 import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.encrypt; 37 import static com.android.server.nearby.common.bluetooth.fastpair.Bytes.toBytes; 38 import static com.android.server.nearby.common.bluetooth.fastpair.Constants.A2DP_SINK_SERVICE_UUID; 39 import static com.android.server.nearby.common.bluetooth.fastpair.Constants.TransportDiscoveryService.BLUETOOTH_SIG_ORGANIZATION_ID; 40 import static com.android.server.nearby.common.bluetooth.fastpair.EllipticCurveDiffieHellmanExchange.PUBLIC_KEY_LENGTH; 41 import static com.android.server.nearby.common.bluetooth.fastpair.MessageStreamHmacEncoder.SECTION_NONCE_LENGTH; 42 43 import static com.google.common.io.BaseEncoding.base16; 44 import static com.google.common.primitives.Bytes.concat; 45 46 import android.bluetooth.BluetoothAdapter; 47 import android.bluetooth.BluetoothClass.Device.Major; 48 import android.bluetooth.BluetoothDevice; 49 import android.bluetooth.BluetoothGatt; 50 import android.bluetooth.BluetoothGattCharacteristic; 51 import android.bluetooth.BluetoothGattDescriptor; 52 import android.bluetooth.BluetoothManager; 53 import android.bluetooth.BluetoothProfile; 54 import android.bluetooth.le.AdvertiseSettings; 55 import android.content.BroadcastReceiver; 56 import android.content.Context; 57 import android.content.Intent; 58 import android.content.IntentFilter; 59 import android.nearby.fastpair.provider.EventStreamProtocol.AcknowledgementEventCode; 60 import android.nearby.fastpair.provider.EventStreamProtocol.DeviceActionEventCode; 61 import android.nearby.fastpair.provider.EventStreamProtocol.DeviceCapabilitySyncEventCode; 62 import android.nearby.fastpair.provider.EventStreamProtocol.DeviceConfigurationEventCode; 63 import android.nearby.fastpair.provider.EventStreamProtocol.DeviceEventCode; 64 import android.nearby.fastpair.provider.EventStreamProtocol.EventGroup; 65 import android.nearby.fastpair.provider.bluetooth.BluetoothGattServerConfig; 66 import android.nearby.fastpair.provider.bluetooth.BluetoothGattServerConfig.ServiceConfig; 67 import android.nearby.fastpair.provider.bluetooth.BluetoothGattServerConnection; 68 import android.nearby.fastpair.provider.bluetooth.BluetoothGattServerConnection.Notifier; 69 import android.nearby.fastpair.provider.bluetooth.BluetoothGattServerHelper; 70 import android.nearby.fastpair.provider.bluetooth.BluetoothGattServlet; 71 import android.nearby.fastpair.provider.bluetooth.RfcommServer; 72 import android.nearby.fastpair.provider.crypto.Crypto; 73 import android.nearby.fastpair.provider.crypto.E2eeCalculator; 74 import android.nearby.fastpair.provider.utils.Logger; 75 import android.os.Build.VERSION; 76 import android.os.Build.VERSION_CODES; 77 import android.os.Handler; 78 import android.os.Looper; 79 import android.os.SystemClock; 80 import android.provider.Settings; 81 import android.text.TextUtils; 82 83 import androidx.annotation.Nullable; 84 import androidx.annotation.VisibleForTesting; 85 import androidx.core.util.Consumer; 86 87 import com.android.server.nearby.common.bloomfilter.BloomFilter; 88 import com.android.server.nearby.common.bloomfilter.FastPairBloomFilterHasher; 89 import com.android.server.nearby.common.bluetooth.BluetoothException; 90 import com.android.server.nearby.common.bluetooth.BluetoothGattException; 91 import com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption; 92 import com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress; 93 import com.android.server.nearby.common.bluetooth.fastpair.Bytes.Value; 94 import com.android.server.nearby.common.bluetooth.fastpair.Constants; 95 import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService; 96 import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.AccountKeyCharacteristic; 97 import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.BeaconActionsCharacteristic; 98 import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.BeaconActionsCharacteristic.BeaconActionType; 99 import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.FirmwareVersionCharacteristic; 100 import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic; 101 import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.NameCharacteristic; 102 import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.PasskeyCharacteristic; 103 import com.android.server.nearby.common.bluetooth.fastpair.Constants.TransportDiscoveryService; 104 import com.android.server.nearby.common.bluetooth.fastpair.Constants.TransportDiscoveryService.BrHandoverDataCharacteristic; 105 import com.android.server.nearby.common.bluetooth.fastpair.Constants.TransportDiscoveryService.ControlPointCharacteristic; 106 import com.android.server.nearby.common.bluetooth.fastpair.EllipticCurveDiffieHellmanExchange; 107 import com.android.server.nearby.common.bluetooth.fastpair.Ltv; 108 import com.android.server.nearby.common.bluetooth.fastpair.MessageStreamHmacEncoder; 109 import com.android.server.nearby.common.bluetooth.fastpair.NamingEncoder; 110 111 import com.google.common.base.Ascii; 112 import com.google.common.primitives.Bytes; 113 import com.google.protobuf.ByteString; 114 115 import java.lang.reflect.Method; 116 import java.nio.ByteOrder; 117 import java.nio.charset.StandardCharsets; 118 import java.security.GeneralSecurityException; 119 import java.security.MessageDigest; 120 import java.security.NoSuchAlgorithmException; 121 import java.security.SecureRandom; 122 import java.text.SimpleDateFormat; 123 import java.util.ArrayList; 124 import java.util.Arrays; 125 import java.util.Calendar; 126 import java.util.Collections; 127 import java.util.HashMap; 128 import java.util.HashSet; 129 import java.util.List; 130 import java.util.Locale; 131 import java.util.Map; 132 import java.util.Random; 133 import java.util.Set; 134 import java.util.concurrent.CountDownLatch; 135 import java.util.concurrent.Executors; 136 import java.util.concurrent.ScheduledExecutorService; 137 import java.util.concurrent.ScheduledFuture; 138 import java.util.concurrent.TimeUnit; 139 import java.util.concurrent.TimeoutException; 140 141 /** 142 * Simulates a Fast Pair device (e.g. a headset). 143 * 144 * <p>Note: There are two deviations from the spec: 145 * 146 * <ul> 147 * <li>Instead of using the public address when in pairing mode (discoverable), it always uses the 148 * random private address (RPA), because that's how stock Android works. To work around this, 149 * it implements the BR/EDR Handover profile (which is no longer part of the Fast Pair spec) 150 * when simulating a keyless device (i.e. Fast Pair 1.0), which allows the phone to ask for 151 * the public address. When there is an anti-spoofing key, i.e. Fast Pair 2.0, the public 152 * address is delivered via the Key-based Pairing handshake. b/79374759 tracks fixing this. 153 * <li>The simulator always identifies its device capabilities as Keyboard/Display, even when 154 * simulating a keyless (Fast Pair 1.0) device that should identify as NoInput/NoOutput. 155 * b/79377125 tracks fixing this. 156 * </ul> 157 * 158 * @see {http://go/fast-pair-2-spec} 159 */ 160 public class FastPairSimulator { 161 public static final String TAG = "FastPairSimulator"; 162 private final Logger mLogger; 163 164 private static final int BECOME_DISCOVERABLE_TIMEOUT_SEC = 3; 165 166 private static final int SCAN_MODE_REFRESH_SEC = 30; 167 168 /** 169 * Headphones. Generated by 170 * http://bluetooth-pentest.narod.ru/software/bluetooth_class_of_device-service_generator.html 171 */ 172 private static final Value CLASS_OF_DEVICE = 173 new Value(base16().decode("200418"), ByteOrder.BIG_ENDIAN); 174 175 private static final byte[] SUPPORTED_SERVICES_LTV = new Ltv( 176 TransportDiscoveryService.SERVICE_UUIDS_16_BIT_LIST_TYPE, 177 toBytes(ByteOrder.LITTLE_ENDIAN, A2DP_SINK_SERVICE_UUID) 178 ).getBytes(); 179 private static final byte[] TDS_CONTROL_POINT_RESPONSE_PARAMETER = 180 Bytes.concat(new byte[]{BLUETOOTH_SIG_ORGANIZATION_ID}, SUPPORTED_SERVICES_LTV); 181 182 private static final String SIMULATOR_FAKE_BLE_ADDRESS = "11:22:33:44:55:66"; 183 184 private static final long ADVERTISING_REFRESH_DELAY_1_MIN = TimeUnit.MINUTES.toMillis(1); 185 186 /** 187 * The size of account key filter in bytes is (1.2*n + 3), n represents the size of account key, 188 * see https://developers.google.com/nearby/fast-pair/spec#advertising_when_not_discoverable. 189 * However we'd like to advertise something else, so we could only afford 8 account keys. 190 * 191 * <ul> 192 * <li>BLE flags: 3 bytes 193 * <li>TxPower: 3 bytes 194 * <li>FastPair: max 25 bytes 195 * <ul> 196 * <li>FastPair service data: 4 bytes 197 * <li>Flags: 1 byte 198 * <li>Account key filter: max 14 bytes (1 byte: length + type, 13 bytes: max 8 account 199 * keys) 200 * <li>Salt: 2 bytes 201 * <li>Battery: 4 bytes 202 * </ul> 203 * </ul> 204 */ 205 private String mDeviceFirmwareVersion = "1.1.0"; 206 207 private byte[] mSessionNonce; 208 209 private boolean mUseLogFullEvent = true; 210 211 private enum ResultCode { 212 SUCCESS((byte) 0x00), 213 OP_CODE_NOT_SUPPORTED((byte) 0x01), 214 INVALID_PARAMETER((byte) 0x02), 215 UNSUPPORTED_ORGANIZATION_ID((byte) 0x03), 216 OPERATION_FAILED((byte) 0x04); 217 218 private final byte mByteValue; 219 ResultCode(byte byteValue)220 ResultCode(byte byteValue) { 221 this.mByteValue = byteValue; 222 } 223 } 224 225 private enum TransportState { 226 OFF((byte) 0x00), 227 ON((byte) 0x01), 228 TEMPORARILY_UNAVAILABLE((byte) 0x10); 229 230 private final byte mByteValue; 231 TransportState(byte byteValue)232 TransportState(byte byteValue) { 233 this.mByteValue = byteValue; 234 } 235 } 236 237 private final Context mContext; 238 private final Options mOptions; 239 private final Handler mUiThreadHandler = new Handler(Looper.getMainLooper()); 240 // No thread pool: Only used in test app (outside gmscore) and in javatests/.../gmscore/. 241 private final ScheduledExecutorService mExecutor = 242 Executors.newSingleThreadScheduledExecutor(); // exempt 243 private final BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 244 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 245 @Override 246 public void onReceive(Context context, Intent intent) { 247 if (mShouldFailPairing) { 248 mLogger.log("Pairing disabled by test app switch"); 249 return; 250 } 251 if (mIsDestroyed) { 252 // Sometimes this receiver does not successfully unregister in destroy() 253 // which causes events to occur after the simulator is stopped, so ignore 254 // those events. 255 mLogger.log("Intent received after simulator destroyed, ignoring"); 256 return; 257 } 258 BluetoothDevice device = intent.getParcelableExtra( 259 BluetoothDevice.EXTRA_DEVICE); 260 switch (intent.getAction()) { 261 case BluetoothAdapter.ACTION_SCAN_MODE_CHANGED: 262 if (isDiscoverable()) { 263 mIsDiscoverableLatch.countDown(); 264 } 265 break; 266 case BluetoothDevice.ACTION_PAIRING_REQUEST: 267 int variant = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, 268 ERROR); 269 int key = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, ERROR); 270 mLogger.log( 271 "Pairing request, variant=%d, key=%s", variant, 272 key == ERROR ? "(none)" : key); 273 274 // Prevent Bluetooth Settings from getting the pairing request. 275 abortBroadcast(); 276 277 mPairingDevice = device; 278 if (mSecret == null) { 279 // We haven't done the handshake over GATT to agree on the shared 280 // secret. For now, just accept anyway (so we can still simulate 281 // old 1.0 model IDs). 282 mLogger.log("No handshake, auto-accepting anyway."); 283 setPasskeyConfirmation(true); 284 } else if (variant 285 == BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION) { 286 // Store the passkey. And check it, since there's a race (see 287 // method for why). Usually this check is a no-op and we'll get 288 // the passkey later over GATT. 289 mLocalPasskey = key; 290 checkPasskey(); 291 } else if (variant == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) { 292 if (mPasskeyEventCallback != null) { 293 mPasskeyEventCallback.onPasskeyRequested( 294 FastPairSimulator.this::enterPassKey); 295 } else { 296 mLogger.log("passkeyEventCallback is not set!"); 297 enterPassKey(key); 298 } 299 } else if (variant == BluetoothDevice.PAIRING_VARIANT_CONSENT) { 300 setPasskeyConfirmation(true); 301 302 } else if (variant == BluetoothDevice.PAIRING_VARIANT_PIN) { 303 if (mPasskeyEventCallback != null) { 304 mPasskeyEventCallback.onPasskeyRequested( 305 (int pin) -> { 306 byte[] newPin = convertPinToBytes( 307 String.format(Locale.ENGLISH, "%d", pin)); 308 mPairingDevice.setPin(newPin); 309 }); 310 } 311 } else { 312 // Reject the pairing request if it's not using the Numeric 313 // Comparison (aka Passkey Confirmation) method. 314 setPasskeyConfirmation(false); 315 } 316 break; 317 case BluetoothDevice.ACTION_BOND_STATE_CHANGED: 318 int bondState = 319 intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 320 BluetoothDevice.BOND_NONE); 321 mLogger.log("Bond state to %s changed to %d", device, bondState); 322 switch (bondState) { 323 case BluetoothDevice.BOND_BONDING: 324 // If we've started bonding, we shouldn't be advertising. 325 mAdvertiser.stopAdvertising(); 326 // Not discoverable anymore, but still connectable. 327 setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE); 328 break; 329 case BluetoothDevice.BOND_BONDED: 330 // Once bonded, advertise the account keys. 331 mAdvertiser.startAdvertising(accountKeysServiceData()); 332 setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE); 333 334 // If it is subsequent pair, we need to add paired device here. 335 if (mIsSubsequentPair 336 && mSecret != null 337 && mSecret.length == AES_BLOCK_LENGTH) { 338 addAccountKey(mSecret, mPairingDevice); 339 } 340 break; 341 case BluetoothDevice.BOND_NONE: 342 // If the bonding process fails, we should be advertising again. 343 mAdvertiser.startAdvertising(getServiceData()); 344 break; 345 default: 346 break; 347 } 348 break; 349 case BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED: 350 mLogger.log( 351 "Connection state to %s changed to %d", 352 device, 353 intent.getIntExtra( 354 BluetoothAdapter.EXTRA_CONNECTION_STATE, 355 BluetoothAdapter.STATE_DISCONNECTED)); 356 break; 357 case BluetoothAdapter.ACTION_STATE_CHANGED: 358 int state = intent.getIntExtra(EXTRA_STATE, -1); 359 mLogger.log("Bluetooth adapter state=%s", state); 360 switch (state) { 361 case STATE_ON: 362 startRfcommServer(); 363 break; 364 case STATE_OFF: 365 stopRfcommServer(); 366 break; 367 default: // fall out 368 } 369 break; 370 default: 371 mLogger.log(new IllegalArgumentException(intent.toString()), 372 "Received unexpected intent"); 373 break; 374 } 375 } 376 }; 377 378 @Nullable convertPinToBytes(@ullable String pin)379 private byte[] convertPinToBytes(@Nullable String pin) { 380 if (TextUtils.isEmpty(pin)) { 381 return null; 382 } 383 byte[] pinBytes; 384 pinBytes = pin.getBytes(StandardCharsets.UTF_8); 385 if (pinBytes.length <= 0 || pinBytes.length > 16) { 386 return null; 387 } 388 return pinBytes; 389 } 390 391 private final NotifiableGattServlet mPasskeyServlet = 392 new NotifiableGattServlet() { 393 @Override 394 // Simulating deprecated API {@code PasskeyCharacteristic.ID} for testing. 395 @SuppressWarnings("deprecation") 396 public BluetoothGattCharacteristic getBaseCharacteristic() { 397 return new BluetoothGattCharacteristic( 398 PasskeyCharacteristic.CUSTOM_128_BIT_UUID, 399 PROPERTY_WRITE | PROPERTY_INDICATE, 400 PERMISSION_WRITE); 401 } 402 403 @Override 404 public void write( 405 BluetoothGattServerConnection connection, int offset, byte[] value) { 406 mLogger.log("Got value from passkey servlet: %s", base16().encode(value)); 407 if (mSecret == null) { 408 mLogger.log("Ignoring write to passkey characteristic, no pairing secret."); 409 return; 410 } 411 412 try { 413 mRemotePasskey = PasskeyCharacteristic.decrypt( 414 PasskeyCharacteristic.Type.SEEKER, mSecret, value); 415 if (mPasskeyEventCallback != null) { 416 mPasskeyEventCallback.onRemotePasskeyReceived(mRemotePasskey); 417 } 418 checkPasskey(); 419 } catch (GeneralSecurityException e) { 420 mLogger.log( 421 "Decrypting passkey value %s failed using key %s", 422 base16().encode(value), base16().encode(mSecret)); 423 } 424 } 425 }; 426 427 private final NotifiableGattServlet mDeviceNameServlet = 428 new NotifiableGattServlet() { 429 @Override 430 // Simulating deprecated API {@code NameCharacteristic.ID} for testing. 431 @SuppressWarnings("deprecation") 432 BluetoothGattCharacteristic getBaseCharacteristic() { 433 return new BluetoothGattCharacteristic( 434 NameCharacteristic.CUSTOM_128_BIT_UUID, 435 PROPERTY_WRITE | PROPERTY_INDICATE, 436 PERMISSION_WRITE); 437 } 438 439 @Override 440 public void write( 441 BluetoothGattServerConnection connection, int offset, byte[] value) { 442 mLogger.log("Got value from device naming servlet: %s", base16().encode(value)); 443 if (mSecret == null) { 444 mLogger.log("Ignoring write to name characteristic, no pairing secret."); 445 return; 446 } 447 // Parse the device name from seeker to write name into provider. 448 mLogger.log("Got name byte array size = %d", value.length); 449 try { 450 String decryptedDeviceName = 451 NamingEncoder.decodeNamingPacket(mSecret, value); 452 if (decryptedDeviceName != null) { 453 setDeviceName(decryptedDeviceName.getBytes(StandardCharsets.UTF_8)); 454 mLogger.log("write device name = %s", decryptedDeviceName); 455 } 456 } catch (GeneralSecurityException e) { 457 mLogger.log(e, "Failed to decrypt device name."); 458 } 459 // For testing to make sure we get the new provider name from simulator. 460 if (mWriteNameCountDown != null) { 461 mLogger.log("finish count down latch to write device name."); 462 mWriteNameCountDown.countDown(); 463 } 464 } 465 }; 466 467 private Value mBluetoothAddress; 468 private final FastPairAdvertiser mAdvertiser; 469 private final Map<String, BluetoothGattServerHelper> mBluetoothGattServerHelpers = 470 new HashMap<>(); 471 private CountDownLatch mIsDiscoverableLatch = new CountDownLatch(1); 472 private ScheduledFuture<?> mRevertDiscoverableFuture; 473 private boolean mShouldFailPairing = false; 474 private boolean mIsDestroyed = false; 475 private boolean mIsAdvertising; 476 @Nullable 477 private String mBleAddress; 478 private BluetoothDevice mPairingDevice; 479 private int mLocalPasskey; 480 private int mRemotePasskey; 481 @Nullable 482 private byte[] mSecret; 483 @Nullable 484 private byte[] mAccountKey; // The latest account key added. 485 // The first account key added. Eddystone treats that account as the owner of the device. 486 @Nullable 487 private byte[] mOwnerAccountKey; 488 @Nullable 489 private PasskeyConfirmationCallback mPasskeyConfirmationCallback; 490 @Nullable 491 private DeviceNameCallback mDeviceNameCallback; 492 @Nullable 493 private PasskeyEventCallback mPasskeyEventCallback; 494 private final List<BatteryValue> mBatteryValues; 495 private boolean mSuppressBatteryNotification = false; 496 private boolean mSuppressSubsequentPairingNotification = false; 497 HandshakeRequest mHandshakeRequest; 498 @Nullable 499 private CountDownLatch mWriteNameCountDown; 500 private final RfcommServer mRfcommServer = new RfcommServer(); 501 private boolean mSupportDynamicBufferSize = false; 502 private NotifiableGattServlet mBeaconActionsServlet; 503 private final FastPairSimulatorDatabase mFastPairSimulatorDatabase; 504 private boolean mIsSubsequentPair = false; 505 506 /** Sets the flag for failing paring for debug purpose. */ setShouldFailPairing(boolean shouldFailPairing)507 public void setShouldFailPairing(boolean shouldFailPairing) { 508 this.mShouldFailPairing = shouldFailPairing; 509 } 510 511 /** Gets the flag for failing paring for debug purpose. */ getShouldFailPairing()512 public boolean getShouldFailPairing() { 513 return mShouldFailPairing; 514 } 515 516 /** Clear the battery values, then no battery information is packed when advertising. */ clearBatteryValues()517 public void clearBatteryValues() { 518 mBatteryValues.clear(); 519 } 520 521 /** Sets the battery items which will be included in the advertisement packet. */ setBatteryValues(BatteryValue... batteryValues)522 public void setBatteryValues(BatteryValue... batteryValues) { 523 this.mBatteryValues.clear(); 524 Collections.addAll(this.mBatteryValues, batteryValues); 525 } 526 527 /** Sets whether the battery advertisement packet is within suppress type or not. */ setSuppressBatteryNotification(boolean suppressBatteryNotification)528 public void setSuppressBatteryNotification(boolean suppressBatteryNotification) { 529 this.mSuppressBatteryNotification = suppressBatteryNotification; 530 } 531 532 /** Sets whether the account key data is within suppress type or not. */ setSuppressSubsequentPairingNotification(boolean isSuppress)533 public void setSuppressSubsequentPairingNotification(boolean isSuppress) { 534 mSuppressSubsequentPairingNotification = isSuppress; 535 } 536 537 /** Calls this to start advertising after some values are changed. */ startAdvertising()538 public void startAdvertising() { 539 mAdvertiser.startAdvertising(getServiceData()); 540 } 541 542 /** Send Event Message on to rfcomm connected devices. */ sendEventStreamMessageToRfcommDevices(EventGroup eventGroup)543 public void sendEventStreamMessageToRfcommDevices(EventGroup eventGroup) { 544 // Send fake log when event code is logging and type is not using Log_Full event. 545 if (eventGroup == EventGroup.LOGGING && !mUseLogFullEvent) { 546 mRfcommServer.sendFakeEventStreamLoggingMessage( 547 getDeviceName() 548 + " " 549 + getBleAddress() 550 + " send log at " 551 + new SimpleDateFormat("HH:mm:ss:SSS", Locale.US) 552 .format(Calendar.getInstance().getTime())); 553 } else { 554 mRfcommServer.sendFakeEventStreamMessage(eventGroup); 555 } 556 } 557 setUseLogFullEvent(boolean useLogFullEvent)558 public void setUseLogFullEvent(boolean useLogFullEvent) { 559 this.mUseLogFullEvent = useLogFullEvent; 560 } 561 562 /** An optional way to get advertising status updates. */ 563 public interface AdvertisingChangedCallback { 564 /** 565 * Called when we change our BLE advertisement. 566 * 567 * @param isAdvertising the advertising status. 568 */ onAdvertisingChanged(boolean isAdvertising)569 void onAdvertisingChanged(boolean isAdvertising); 570 } 571 572 /** A way for tests to get callbacks when passkey confirmation is invoked. */ 573 public interface PasskeyConfirmationCallback { onPasskeyConfirmation(boolean confirm)574 void onPasskeyConfirmation(boolean confirm); 575 } 576 577 /** A way for simulator UI update to get callback when device name is changed. */ 578 public interface DeviceNameCallback { onNameChanged(String deviceName)579 void onNameChanged(String deviceName); 580 } 581 582 /** 583 * Callback when there comes a passkey input request from BT service, or receiving remote 584 * device's passkey. 585 */ 586 public interface PasskeyEventCallback { onPasskeyRequested(KeyInputCallback keyInputCallback)587 void onPasskeyRequested(KeyInputCallback keyInputCallback); 588 onRemotePasskeyReceived(int passkey)589 void onRemotePasskeyReceived(int passkey); 590 onPasskeyConfirmation(int passkey, Consumer<Boolean> isConfirmed)591 default void onPasskeyConfirmation(int passkey, Consumer<Boolean> isConfirmed) { 592 } 593 } 594 595 /** Options for the simulator. */ 596 public static class Options { 597 private final String mModelId; 598 599 // TODO(b/143117318):Remove this when app-launch type has its own anti-spoofing key. 600 private final String mAdvertisingModelId; 601 602 @Nullable 603 private final String mBluetoothAddress; 604 605 @Nullable 606 private final String mBleAddress; 607 608 private final boolean mDataOnlyConnection; 609 610 private final int mTxPowerLevel; 611 612 private final boolean mEnableNameCharacteristic; 613 614 private final AdvertisingChangedCallback mAdvertisingChangedCallback; 615 616 private final boolean mIncludeTransportDataDescriptor; 617 618 @Nullable 619 private final byte[] mAntiSpoofingPrivateKey; 620 621 private final boolean mUseRandomSaltForAccountKeyRotation; 622 623 private final boolean mBecomeDiscoverable; 624 625 private final boolean mShowsPasskeyConfirmation; 626 627 private final boolean mEnableBeaconActionsCharacteristic; 628 629 private final boolean mRemoveAllDevicesDuringPairing; 630 631 @Nullable 632 private final ByteString mEddystoneIdentityKey; 633 Options( String modelId, String advertisingModelId, @Nullable String bluetoothAddress, @Nullable String bleAddress, boolean dataOnlyConnection, int txPowerLevel, boolean enableNameCharacteristic, AdvertisingChangedCallback advertisingChangedCallback, boolean includeTransportDataDescriptor, @Nullable byte[] antiSpoofingPrivateKey, boolean useRandomSaltForAccountKeyRotation, boolean becomeDiscoverable, boolean showsPasskeyConfirmation, boolean enableBeaconActionsCharacteristic, boolean removeAllDevicesDuringPairing, @Nullable ByteString eddystoneIdentityKey)634 private Options( 635 String modelId, 636 String advertisingModelId, 637 @Nullable String bluetoothAddress, 638 @Nullable String bleAddress, 639 boolean dataOnlyConnection, 640 int txPowerLevel, 641 boolean enableNameCharacteristic, 642 AdvertisingChangedCallback advertisingChangedCallback, 643 boolean includeTransportDataDescriptor, 644 @Nullable byte[] antiSpoofingPrivateKey, 645 boolean useRandomSaltForAccountKeyRotation, 646 boolean becomeDiscoverable, 647 boolean showsPasskeyConfirmation, 648 boolean enableBeaconActionsCharacteristic, 649 boolean removeAllDevicesDuringPairing, 650 @Nullable ByteString eddystoneIdentityKey) { 651 this.mModelId = modelId; 652 this.mAdvertisingModelId = advertisingModelId; 653 this.mBluetoothAddress = bluetoothAddress; 654 this.mBleAddress = bleAddress; 655 this.mDataOnlyConnection = dataOnlyConnection; 656 this.mTxPowerLevel = txPowerLevel; 657 this.mEnableNameCharacteristic = enableNameCharacteristic; 658 this.mAdvertisingChangedCallback = advertisingChangedCallback; 659 this.mIncludeTransportDataDescriptor = includeTransportDataDescriptor; 660 this.mAntiSpoofingPrivateKey = antiSpoofingPrivateKey; 661 this.mUseRandomSaltForAccountKeyRotation = useRandomSaltForAccountKeyRotation; 662 this.mBecomeDiscoverable = becomeDiscoverable; 663 this.mShowsPasskeyConfirmation = showsPasskeyConfirmation; 664 this.mEnableBeaconActionsCharacteristic = enableBeaconActionsCharacteristic; 665 this.mRemoveAllDevicesDuringPairing = removeAllDevicesDuringPairing; 666 this.mEddystoneIdentityKey = eddystoneIdentityKey; 667 } 668 getModelId()669 public String getModelId() { 670 return mModelId; 671 } 672 673 // TODO(b/143117318):Remove this when app-launch type has its own anti-spoofing key. getAdvertisingModelId()674 public String getAdvertisingModelId() { 675 return mAdvertisingModelId; 676 } 677 678 @Nullable getBluetoothAddress()679 public String getBluetoothAddress() { 680 return mBluetoothAddress; 681 } 682 683 @Nullable getBleAddress()684 public String getBleAddress() { 685 return mBleAddress; 686 } 687 getDataOnlyConnection()688 public boolean getDataOnlyConnection() { 689 return mDataOnlyConnection; 690 } 691 getTxPowerLevel()692 public int getTxPowerLevel() { 693 return mTxPowerLevel; 694 } 695 getEnableNameCharacteristic()696 public boolean getEnableNameCharacteristic() { 697 return mEnableNameCharacteristic; 698 } 699 getAdvertisingChangedCallback()700 public AdvertisingChangedCallback getAdvertisingChangedCallback() { 701 return mAdvertisingChangedCallback; 702 } 703 getIncludeTransportDataDescriptor()704 public boolean getIncludeTransportDataDescriptor() { 705 return mIncludeTransportDataDescriptor; 706 } 707 708 @Nullable getAntiSpoofingPrivateKey()709 public byte[] getAntiSpoofingPrivateKey() { 710 return mAntiSpoofingPrivateKey; 711 } 712 getUseRandomSaltForAccountKeyRotation()713 public boolean getUseRandomSaltForAccountKeyRotation() { 714 return mUseRandomSaltForAccountKeyRotation; 715 } 716 getBecomeDiscoverable()717 public boolean getBecomeDiscoverable() { 718 return mBecomeDiscoverable; 719 } 720 getShowsPasskeyConfirmation()721 public boolean getShowsPasskeyConfirmation() { 722 return mShowsPasskeyConfirmation; 723 } 724 getEnableBeaconActionsCharacteristic()725 public boolean getEnableBeaconActionsCharacteristic() { 726 return mEnableBeaconActionsCharacteristic; 727 } 728 getRemoveAllDevicesDuringPairing()729 public boolean getRemoveAllDevicesDuringPairing() { 730 return mRemoveAllDevicesDuringPairing; 731 } 732 733 @Nullable getEddystoneIdentityKey()734 public ByteString getEddystoneIdentityKey() { 735 return mEddystoneIdentityKey; 736 } 737 738 /** Converts an instance to a builder. */ toBuilder()739 public Builder toBuilder() { 740 return new Options.Builder(this); 741 } 742 743 /** Constructs a builder. */ builder()744 public static Builder builder() { 745 return new Options.Builder(); 746 } 747 748 /** @param modelId Must be a 3-byte hex string. */ builder(String modelId)749 public static Builder builder(String modelId) { 750 return new Options.Builder() 751 .setModelId(Ascii.toUpperCase(modelId)) 752 .setAdvertisingModelId(Ascii.toUpperCase(modelId)) 753 .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) 754 .setAdvertisingChangedCallback(isAdvertising -> { 755 }) 756 .setIncludeTransportDataDescriptor(true) 757 .setUseRandomSaltForAccountKeyRotation(false) 758 .setEnableNameCharacteristic(true) 759 .setDataOnlyConnection(false) 760 .setBecomeDiscoverable(true) 761 .setShowsPasskeyConfirmation(false) 762 .setEnableBeaconActionsCharacteristic(true) 763 .setRemoveAllDevicesDuringPairing(true); 764 } 765 766 /** A builder for {@link Options}. */ 767 public static class Builder { 768 769 private String mModelId; 770 771 // TODO(b/143117318):Remove this when app-launch type has its own anti-spoofing key. 772 private String mAdvertisingModelId; 773 774 @Nullable 775 private String mBluetoothAddress; 776 777 @Nullable 778 private String mBleAddress; 779 780 private boolean mDataOnlyConnection; 781 782 private int mTxPowerLevel; 783 784 private boolean mEnableNameCharacteristic; 785 786 private AdvertisingChangedCallback mAdvertisingChangedCallback; 787 788 private boolean mIncludeTransportDataDescriptor; 789 790 @Nullable 791 private byte[] mAntiSpoofingPrivateKey; 792 793 private boolean mUseRandomSaltForAccountKeyRotation; 794 795 private boolean mBecomeDiscoverable; 796 797 private boolean mShowsPasskeyConfirmation; 798 799 private boolean mEnableBeaconActionsCharacteristic; 800 801 private boolean mRemoveAllDevicesDuringPairing; 802 803 @Nullable 804 private ByteString mEddystoneIdentityKey; 805 Builder()806 private Builder() { 807 } 808 Builder(Options option)809 private Builder(Options option) { 810 this.mModelId = option.mModelId; 811 this.mAdvertisingModelId = option.mAdvertisingModelId; 812 this.mBluetoothAddress = option.mBluetoothAddress; 813 this.mBleAddress = option.mBleAddress; 814 this.mDataOnlyConnection = option.mDataOnlyConnection; 815 this.mTxPowerLevel = option.mTxPowerLevel; 816 this.mEnableNameCharacteristic = option.mEnableNameCharacteristic; 817 this.mAdvertisingChangedCallback = option.mAdvertisingChangedCallback; 818 this.mIncludeTransportDataDescriptor = option.mIncludeTransportDataDescriptor; 819 this.mAntiSpoofingPrivateKey = option.mAntiSpoofingPrivateKey; 820 this.mUseRandomSaltForAccountKeyRotation = 821 option.mUseRandomSaltForAccountKeyRotation; 822 this.mBecomeDiscoverable = option.mBecomeDiscoverable; 823 this.mShowsPasskeyConfirmation = option.mShowsPasskeyConfirmation; 824 this.mEnableBeaconActionsCharacteristic = option.mEnableBeaconActionsCharacteristic; 825 this.mRemoveAllDevicesDuringPairing = option.mRemoveAllDevicesDuringPairing; 826 this.mEddystoneIdentityKey = option.mEddystoneIdentityKey; 827 } 828 829 /** 830 * Must be one of the {@code ADVERTISE_TX_POWER_*} levels in {@link AdvertiseSettings}. 831 * Default is HIGH. 832 */ setTxPowerLevel(int txPowerLevel)833 public Builder setTxPowerLevel(int txPowerLevel) { 834 this.mTxPowerLevel = txPowerLevel; 835 return this; 836 } 837 838 /** 839 * Must be a 6-byte hex string (optionally with colons). 840 * Default is this device's BT MAC. 841 */ setBluetoothAddress(@ullable String bluetoothAddress)842 public Builder setBluetoothAddress(@Nullable String bluetoothAddress) { 843 this.mBluetoothAddress = bluetoothAddress; 844 return this; 845 } 846 setBleAddress(@ullable String bleAddress)847 public Builder setBleAddress(@Nullable String bleAddress) { 848 this.mBleAddress = bleAddress; 849 return this; 850 } 851 852 /** A boolean to decide if enable name characteristic as simulator characteristic. */ setEnableNameCharacteristic(boolean enable)853 public Builder setEnableNameCharacteristic(boolean enable) { 854 this.mEnableNameCharacteristic = enable; 855 return this; 856 } 857 858 /** @see AdvertisingChangedCallback */ setAdvertisingChangedCallback( AdvertisingChangedCallback advertisingChangedCallback)859 public Builder setAdvertisingChangedCallback( 860 AdvertisingChangedCallback advertisingChangedCallback) { 861 this.mAdvertisingChangedCallback = advertisingChangedCallback; 862 return this; 863 } 864 setDataOnlyConnection(boolean dataOnlyConnection)865 public Builder setDataOnlyConnection(boolean dataOnlyConnection) { 866 this.mDataOnlyConnection = dataOnlyConnection; 867 return this; 868 } 869 870 /** 871 * Set whether to include the Transport Data descriptor, which has the list of supported 872 * profiles. This is required by the spec, but if we can't get it, we recover gracefully 873 * by assuming support for one of {A2DP, Headset}. Default is true. 874 */ setIncludeTransportDataDescriptor( boolean includeTransportDataDescriptor)875 public Builder setIncludeTransportDataDescriptor( 876 boolean includeTransportDataDescriptor) { 877 this.mIncludeTransportDataDescriptor = includeTransportDataDescriptor; 878 return this; 879 } 880 setAntiSpoofingPrivateKey(@ullable byte[] antiSpoofingPrivateKey)881 public Builder setAntiSpoofingPrivateKey(@Nullable byte[] antiSpoofingPrivateKey) { 882 this.mAntiSpoofingPrivateKey = antiSpoofingPrivateKey; 883 return this; 884 } 885 setUseRandomSaltForAccountKeyRotation( boolean useRandomSaltForAccountKeyRotation)886 public Builder setUseRandomSaltForAccountKeyRotation( 887 boolean useRandomSaltForAccountKeyRotation) { 888 this.mUseRandomSaltForAccountKeyRotation = useRandomSaltForAccountKeyRotation; 889 return this; 890 } 891 892 // TODO(b/143117318):Remove this when app-launch type has its own anti-spoofing key. setAdvertisingModelId(String modelId)893 public Builder setAdvertisingModelId(String modelId) { 894 this.mAdvertisingModelId = modelId; 895 return this; 896 } 897 setBecomeDiscoverable(boolean becomeDiscoverable)898 public Builder setBecomeDiscoverable(boolean becomeDiscoverable) { 899 this.mBecomeDiscoverable = becomeDiscoverable; 900 return this; 901 } 902 setShowsPasskeyConfirmation(boolean showsPasskeyConfirmation)903 public Builder setShowsPasskeyConfirmation(boolean showsPasskeyConfirmation) { 904 this.mShowsPasskeyConfirmation = showsPasskeyConfirmation; 905 return this; 906 } 907 setEnableBeaconActionsCharacteristic( boolean enableBeaconActionsCharacteristic)908 public Builder setEnableBeaconActionsCharacteristic( 909 boolean enableBeaconActionsCharacteristic) { 910 this.mEnableBeaconActionsCharacteristic = enableBeaconActionsCharacteristic; 911 return this; 912 } 913 setRemoveAllDevicesDuringPairing(boolean removeAllDevicesDuringPairing)914 public Builder setRemoveAllDevicesDuringPairing(boolean removeAllDevicesDuringPairing) { 915 this.mRemoveAllDevicesDuringPairing = removeAllDevicesDuringPairing; 916 return this; 917 } 918 919 /** 920 * Non-public because this is required to create a builder. See 921 * {@link Options#builder}. 922 */ setModelId(String modelId)923 public Builder setModelId(String modelId) { 924 this.mModelId = modelId; 925 return this; 926 } 927 setEddystoneIdentityKey(@ullable ByteString eddystoneIdentityKey)928 public Builder setEddystoneIdentityKey(@Nullable ByteString eddystoneIdentityKey) { 929 this.mEddystoneIdentityKey = eddystoneIdentityKey; 930 return this; 931 } 932 933 // Custom builder in order to normalize properties. go/autovalue/builders-howto build()934 public Options build() { 935 return new Options( 936 Ascii.toUpperCase(mModelId), 937 Ascii.toUpperCase(mAdvertisingModelId), 938 mBluetoothAddress, 939 mBleAddress, 940 mDataOnlyConnection, 941 mTxPowerLevel, 942 mEnableNameCharacteristic, 943 mAdvertisingChangedCallback, 944 mIncludeTransportDataDescriptor, 945 mAntiSpoofingPrivateKey, 946 mUseRandomSaltForAccountKeyRotation, 947 mBecomeDiscoverable, 948 mShowsPasskeyConfirmation, 949 mEnableBeaconActionsCharacteristic, 950 mRemoveAllDevicesDuringPairing, 951 mEddystoneIdentityKey); 952 } 953 } 954 } 955 FastPairSimulator(Context context, Options options)956 public FastPairSimulator(Context context, Options options) { 957 this(context, options, new Logger(TAG)); 958 } 959 FastPairSimulator(Context context, Options options, Logger logger)960 public FastPairSimulator(Context context, Options options, Logger logger) { 961 this.mContext = context; 962 this.mOptions = options; 963 this.mLogger = logger; 964 965 this.mBatteryValues = new ArrayList<>(); 966 967 String bluetoothAddress = 968 !TextUtils.isEmpty(options.getBluetoothAddress()) 969 ? options.getBluetoothAddress() 970 : Settings.Secure.getString(context.getContentResolver(), 971 "bluetooth_address"); 972 if (bluetoothAddress == null && VERSION.SDK_INT >= VERSION_CODES.O) { 973 // Requires a modified Android O build for access to bluetoothAdapter.getAddress(). 974 // See http://google3/java/com/google/location/nearby/apps/fastpair/simulator/README.md. 975 bluetoothAddress = mBluetoothAdapter.getAddress(); 976 } 977 this.mBluetoothAddress = 978 new Value(BluetoothAddress.decode(bluetoothAddress), ByteOrder.BIG_ENDIAN); 979 this.mBleAddress = options.getBleAddress(); 980 this.mAdvertiser = new OreoFastPairAdvertiser(this); 981 982 mFastPairSimulatorDatabase = new FastPairSimulatorDatabase(context); 983 984 byte[] deviceName = getDeviceNameInBytes(); 985 mLogger.log( 986 "Provider default device name is %s", 987 deviceName != null ? new String(deviceName, StandardCharsets.UTF_8) : null); 988 989 if (mOptions.getDataOnlyConnection()) { 990 // To get BLE address, we need to start advertising first, and then 991 // {@code#setBleAddress} will be called with BLE address. 992 mAdvertiser.startAdvertising(modelIdServiceData(/* forAdvertising= */ true)); 993 } else { 994 // Make this so that the simulator doesn't start automatically. 995 // This is tricky since the simulator is used in our integ tests as well. 996 start(mBleAddress != null ? mBleAddress : bluetoothAddress); 997 } 998 } 999 start(String address)1000 public void start(String address) { 1001 IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); 1002 filter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST); 1003 filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 1004 filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); 1005 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 1006 mContext.registerReceiver(mBroadcastReceiver, filter); 1007 1008 BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class); 1009 BluetoothGattServerHelper bluetoothGattServerHelper = 1010 new BluetoothGattServerHelper(mContext, wrap(bluetoothManager)); 1011 mBluetoothGattServerHelpers.put(address, bluetoothGattServerHelper); 1012 1013 if (mOptions.getBecomeDiscoverable()) { 1014 try { 1015 becomeDiscoverable(); 1016 } catch (InterruptedException | TimeoutException e) { 1017 mLogger.log(e, "Error becoming discoverable"); 1018 } 1019 } 1020 1021 mAdvertiser.startAdvertising(modelIdServiceData(/* forAdvertising= */ true)); 1022 startGattServer(bluetoothGattServerHelper); 1023 startRfcommServer(); 1024 scheduleAdvertisingRefresh(); 1025 } 1026 1027 /** 1028 * Regenerate service data on a fixed interval. 1029 * This causes the bloom filter to be refreshed and a different salt to be used for rotation. 1030 */ 1031 @SuppressWarnings("FutureReturnValueIgnored") scheduleAdvertisingRefresh()1032 private void scheduleAdvertisingRefresh() { 1033 mExecutor.scheduleAtFixedRate(() -> { 1034 if (mIsAdvertising) { 1035 mAdvertiser.startAdvertising(getServiceData()); 1036 } 1037 }, ADVERTISING_REFRESH_DELAY_1_MIN, ADVERTISING_REFRESH_DELAY_1_MIN, TimeUnit.MILLISECONDS); 1038 } 1039 destroy()1040 public void destroy() { 1041 try { 1042 mLogger.log("Destroying simulator"); 1043 mIsDestroyed = true; 1044 mContext.unregisterReceiver(mBroadcastReceiver); 1045 mAdvertiser.stopAdvertising(); 1046 for (BluetoothGattServerHelper helper : mBluetoothGattServerHelpers.values()) { 1047 helper.close(); 1048 } 1049 stopRfcommServer(); 1050 mDeviceNameCallback = null; 1051 mExecutor.shutdownNow(); 1052 } catch (IllegalArgumentException ignored) { 1053 // Happens if you haven't given us permissions yet, so we didn't register the receiver. 1054 } 1055 } 1056 isDestroyed()1057 public boolean isDestroyed() { 1058 return mIsDestroyed; 1059 } 1060 1061 @Nullable getBluetoothAddress()1062 public String getBluetoothAddress() { 1063 return BluetoothAddress.encode(mBluetoothAddress.getBytes(ByteOrder.BIG_ENDIAN)); 1064 } 1065 isAdvertising()1066 public boolean isAdvertising() { 1067 return mIsAdvertising; 1068 } 1069 setIsAdvertising(boolean isAdvertising)1070 public void setIsAdvertising(boolean isAdvertising) { 1071 if (this.mIsAdvertising != isAdvertising) { 1072 this.mIsAdvertising = isAdvertising; 1073 mOptions.getAdvertisingChangedCallback().onAdvertisingChanged(isAdvertising); 1074 } 1075 } 1076 stopAdvertising()1077 public void stopAdvertising() { 1078 mAdvertiser.stopAdvertising(); 1079 } 1080 setBleAddress(String bleAddress)1081 public void setBleAddress(String bleAddress) { 1082 this.mBleAddress = bleAddress; 1083 if (mOptions.getDataOnlyConnection()) { 1084 mBluetoothAddress = new Value(BluetoothAddress.decode(bleAddress), 1085 ByteOrder.BIG_ENDIAN); 1086 start(bleAddress); 1087 } 1088 // When BLE address changes, needs to send BLE address to the client again. 1089 sendDeviceBleAddress(bleAddress); 1090 1091 // If we are advertising something other than the model id (e.g. the bloom filter), restart 1092 // the advertisement so that it is updated with the new address. 1093 if (isAdvertising() && !isDiscoverable()) { 1094 mAdvertiser.startAdvertising(getServiceData()); 1095 } 1096 } 1097 1098 @Nullable getBleAddress()1099 public String getBleAddress() { 1100 return mBleAddress; 1101 } 1102 1103 // This method is only for testing to make test block until write name success or time out. 1104 @VisibleForTesting setCountDownLatchToWriteName(CountDownLatch countDownLatch)1105 public void setCountDownLatchToWriteName(CountDownLatch countDownLatch) { 1106 mLogger.log("Set up count down latch to write device name."); 1107 mWriteNameCountDown = countDownLatch; 1108 } 1109 areBeaconActionsNotificationsEnabled()1110 public boolean areBeaconActionsNotificationsEnabled() { 1111 return mBeaconActionsServlet.areNotificationsEnabled(); 1112 } 1113 1114 private abstract class NotifiableGattServlet extends BluetoothGattServlet { 1115 private final Map<BluetoothGattServerConnection, Notifier> mConnections = new HashMap<>(); 1116 getBaseCharacteristic()1117 abstract BluetoothGattCharacteristic getBaseCharacteristic(); 1118 1119 @Override getCharacteristic()1120 public BluetoothGattCharacteristic getCharacteristic() { 1121 // Enabling indication requires the Client Characteristic Configuration descriptor. 1122 BluetoothGattCharacteristic characteristic = getBaseCharacteristic(); 1123 characteristic.addDescriptor( 1124 new BluetoothGattDescriptor( 1125 Constants.CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID, 1126 BluetoothGattDescriptor.PERMISSION_READ 1127 | BluetoothGattDescriptor.PERMISSION_WRITE)); 1128 return characteristic; 1129 } 1130 1131 @Override enableNotification(BluetoothGattServerConnection connection, Notifier notifier)1132 public void enableNotification(BluetoothGattServerConnection connection, Notifier notifier) 1133 throws BluetoothGattException { 1134 mLogger.log("Registering notifier for %s", getCharacteristic()); 1135 mConnections.put(connection, notifier); 1136 } 1137 1138 @Override disableNotification(BluetoothGattServerConnection connection, Notifier notifier)1139 public void disableNotification(BluetoothGattServerConnection connection, Notifier notifier) 1140 throws BluetoothGattException { 1141 mLogger.log("Removing notifier for %s", getCharacteristic()); 1142 mConnections.remove(connection); 1143 } 1144 areNotificationsEnabled()1145 boolean areNotificationsEnabled() { 1146 return !mConnections.isEmpty(); 1147 } 1148 sendNotification(byte[] data)1149 void sendNotification(byte[] data) { 1150 if (mConnections.isEmpty()) { 1151 mLogger.log("Not sending notify as no notifier registered"); 1152 return; 1153 } 1154 // Needs to be on a separate thread to avoid deadlocking and timing out (waits for a 1155 // callback from OS, which happens on the main thread). 1156 mExecutor.execute( 1157 () -> { 1158 for (Map.Entry<BluetoothGattServerConnection, Notifier> entry : 1159 mConnections.entrySet()) { 1160 try { 1161 mLogger.log("Sending notify %s to %s", 1162 getCharacteristic(), 1163 entry.getKey().getDevice().getAddress()); 1164 entry.getValue().notify(data); 1165 } catch (BluetoothException e) { 1166 mLogger.log( 1167 e, 1168 "Failed to notify (indicate) result of %s to %s", 1169 getCharacteristic(), 1170 entry.getKey().getDevice().getAddress()); 1171 } 1172 } 1173 }); 1174 } 1175 } 1176 startRfcommServer()1177 private void startRfcommServer() { 1178 mRfcommServer.setRequestHandler(this::handleRfcommServerRequest); 1179 mRfcommServer.setStateMonitor(state -> { 1180 mLogger.log("RfcommServer is in %s state", state); 1181 if (CONNECTED.equals(state)) { 1182 sendModelId(); 1183 sendDeviceBleAddress(mBleAddress); 1184 sendFirmwareVersion(); 1185 sendSessionNonce(); 1186 } 1187 }); 1188 mRfcommServer.start(); 1189 } 1190 handleRfcommServerRequest(int eventGroup, int eventCode, byte[] data)1191 private void handleRfcommServerRequest(int eventGroup, int eventCode, byte[] data) { 1192 switch (eventGroup) { 1193 case EventGroup.DEVICE_VALUE: 1194 if (data == null) { 1195 break; 1196 } 1197 1198 String deviceValue = base16().encode(data); 1199 if (eventCode == DeviceEventCode.DEVICE_CAPABILITY_VALUE) { 1200 mLogger.log("Received phone capability: %s", deviceValue); 1201 } else if (eventCode == DeviceEventCode.PLATFORM_TYPE_VALUE) { 1202 mLogger.log("Received platform type: %s", deviceValue); 1203 } 1204 break; 1205 case EventGroup.DEVICE_ACTION_VALUE: 1206 if (eventCode == DeviceActionEventCode.DEVICE_ACTION_RING_VALUE) { 1207 mLogger.log("receive device action with ring value, data = %d", 1208 data[0]); 1209 sendDeviceRingActionResponse(); 1210 // Simulate notifying the seeker that the ringing has stopped due 1211 // to user interaction (such as tapping the bud). 1212 mUiThreadHandler.postDelayed(this::sendDeviceRingStoppedAction, 1213 5000); 1214 } 1215 break; 1216 case EventGroup.DEVICE_CONFIGURATION_VALUE: 1217 if (eventCode == DeviceConfigurationEventCode.CONFIGURATION_BUFFER_SIZE_VALUE) { 1218 mLogger.log( 1219 "receive device action with buffer size value, data = %s", 1220 base16().encode(data)); 1221 sendSetBufferActionResponse(data); 1222 } 1223 break; 1224 case EventGroup.DEVICE_CAPABILITY_SYNC_VALUE: 1225 if (eventCode == DeviceCapabilitySyncEventCode.REQUEST_CAPABILITY_UPDATE_VALUE) { 1226 mLogger.log("receive device capability update request."); 1227 sendCapabilitySync(); 1228 } 1229 break; 1230 default: // fall out 1231 break; 1232 } 1233 } 1234 stopRfcommServer()1235 private void stopRfcommServer() { 1236 mRfcommServer.stop(); 1237 mRfcommServer.setRequestHandler(null); 1238 mRfcommServer.setStateMonitor(null); 1239 } 1240 sendModelId()1241 private void sendModelId() { 1242 mLogger.log("Send model ID to the client"); 1243 mRfcommServer.send( 1244 EventGroup.DEVICE_VALUE, 1245 DeviceEventCode.DEVICE_MODEL_ID_VALUE, 1246 modelIdServiceData(/* forAdvertising= */ false)); 1247 } 1248 sendDeviceBleAddress(String bleAddress)1249 private void sendDeviceBleAddress(String bleAddress) { 1250 mLogger.log("Send BLE address (%s) to the client", bleAddress); 1251 if (bleAddress != null) { 1252 mRfcommServer.send( 1253 EventGroup.DEVICE_VALUE, 1254 DeviceEventCode.DEVICE_BLE_ADDRESS_VALUE, 1255 BluetoothAddress.decode(bleAddress)); 1256 } 1257 } 1258 sendFirmwareVersion()1259 private void sendFirmwareVersion() { 1260 mLogger.log("Send Firmware Version (%s) to the client", mDeviceFirmwareVersion); 1261 mRfcommServer.send( 1262 EventGroup.DEVICE_VALUE, 1263 DeviceEventCode.FIRMWARE_VERSION_VALUE, 1264 mDeviceFirmwareVersion.getBytes()); 1265 } 1266 sendSessionNonce()1267 private void sendSessionNonce() { 1268 mLogger.log("Send SessionNonce (%s) to the client", mDeviceFirmwareVersion); 1269 SecureRandom secureRandom = new SecureRandom(); 1270 mSessionNonce = new byte[SECTION_NONCE_LENGTH]; 1271 secureRandom.nextBytes(mSessionNonce); 1272 mRfcommServer.send( 1273 EventGroup.DEVICE_VALUE, DeviceEventCode.SECTION_NONCE_VALUE, mSessionNonce); 1274 } 1275 sendDeviceRingActionResponse()1276 private void sendDeviceRingActionResponse() { 1277 mLogger.log("Send device ring action response to the client"); 1278 mRfcommServer.send( 1279 EventGroup.ACKNOWLEDGEMENT_VALUE, 1280 AcknowledgementEventCode.ACKNOWLEDGEMENT_ACK_VALUE, 1281 new byte[]{ 1282 EventGroup.DEVICE_ACTION_VALUE, 1283 DeviceActionEventCode.DEVICE_ACTION_RING_VALUE 1284 }); 1285 } 1286 sendSetBufferActionResponse(byte[] data)1287 private void sendSetBufferActionResponse(byte[] data) { 1288 boolean hmacPassed = false; 1289 for (ByteString accountKey : getAccountKeys()) { 1290 try { 1291 if (MessageStreamHmacEncoder.verifyHmac( 1292 accountKey.toByteArray(), mSessionNonce, data)) { 1293 hmacPassed = true; 1294 mLogger.log("Buffer size data matches account key %s", 1295 base16().encode(accountKey.toByteArray())); 1296 break; 1297 } 1298 } catch (GeneralSecurityException e) { 1299 // Ignore. 1300 } 1301 } 1302 if (hmacPassed) { 1303 mLogger.log("Send buffer size action response %s to the client", base16().encode(data)); 1304 mRfcommServer.send( 1305 EventGroup.ACKNOWLEDGEMENT_VALUE, 1306 AcknowledgementEventCode.ACKNOWLEDGEMENT_ACK_VALUE, 1307 new byte[]{ 1308 EventGroup.DEVICE_CONFIGURATION_VALUE, 1309 DeviceConfigurationEventCode.CONFIGURATION_BUFFER_SIZE_VALUE, 1310 data[0], 1311 data[1], 1312 data[2] 1313 }); 1314 } else { 1315 mLogger.log("No matched account key for sendSetBufferActionResponse"); 1316 } 1317 } 1318 sendCapabilitySync()1319 private void sendCapabilitySync() { 1320 mLogger.log("Send capability sync to the client"); 1321 if (mSupportDynamicBufferSize) { 1322 mLogger.log("Send dynamic buffer size range to the client"); 1323 mRfcommServer.send( 1324 EventGroup.DEVICE_CAPABILITY_SYNC_VALUE, 1325 DeviceCapabilitySyncEventCode.CONFIGURABLE_BUFFER_SIZE_RANGE_VALUE, 1326 new byte[]{ 1327 0x00, 0x01, (byte) 0xf4, 0x00, 0x64, 0x00, (byte) 0xc8, 1328 0x01, 0x00, (byte) 0xff, 0x00, 0x01, 0x00, (byte) 0x88, 1329 0x02, 0x01, (byte) 0xff, 0x01, 0x01, 0x01, (byte) 0x88, 1330 0x03, 0x02, (byte) 0xff, 0x02, 0x01, 0x02, (byte) 0x88, 1331 0x04, 0x03, (byte) 0xff, 0x03, 0x01, 0x03, (byte) 0x88 1332 }); 1333 } 1334 } 1335 sendDeviceRingStoppedAction()1336 private void sendDeviceRingStoppedAction() { 1337 mLogger.log("Sending device ring stopped action to the client"); 1338 mRfcommServer.send( 1339 EventGroup.DEVICE_ACTION_VALUE, 1340 DeviceActionEventCode.DEVICE_ACTION_RING_VALUE, 1341 // Additional data for stopping ringing on all components. 1342 new byte[]{0x00}); 1343 } 1344 startGattServer(BluetoothGattServerHelper helper)1345 private void startGattServer(BluetoothGattServerHelper helper) { 1346 BluetoothGattServlet tdsControlPointServlet = 1347 new NotifiableGattServlet() { 1348 @Override 1349 public BluetoothGattCharacteristic getBaseCharacteristic() { 1350 return new BluetoothGattCharacteristic(ControlPointCharacteristic.ID, 1351 PROPERTY_WRITE | PROPERTY_INDICATE, PERMISSION_WRITE); 1352 } 1353 1354 @Override 1355 public void write( 1356 BluetoothGattServerConnection connection, int offset, byte[] value) 1357 throws BluetoothGattException { 1358 mLogger.log("Requested TDS Control Point write, value=%s", 1359 base16().encode(value)); 1360 1361 ResultCode resultCode = checkTdsControlPointRequest(value); 1362 if (resultCode == ResultCode.SUCCESS) { 1363 try { 1364 becomeDiscoverable(); 1365 } catch (TimeoutException | InterruptedException e) { 1366 mLogger.log(e, "Failed to become discoverable"); 1367 resultCode = ResultCode.OPERATION_FAILED; 1368 } 1369 } 1370 1371 mLogger.log("Request complete, resultCode=%s", resultCode); 1372 1373 mLogger.log("Sending TDS Control Point response indication"); 1374 sendNotification( 1375 Bytes.concat( 1376 new byte[]{ 1377 getTdsControlPointOpCode(value), 1378 resultCode.mByteValue, 1379 }, 1380 resultCode == ResultCode.SUCCESS 1381 ? TDS_CONTROL_POINT_RESPONSE_PARAMETER 1382 : new byte[0])); 1383 } 1384 }; 1385 1386 BluetoothGattServlet brHandoverDataServlet = 1387 new BluetoothGattServlet() { 1388 1389 @Override 1390 public BluetoothGattCharacteristic getCharacteristic() { 1391 return new BluetoothGattCharacteristic(BrHandoverDataCharacteristic.ID, 1392 PROPERTY_READ, PERMISSION_READ); 1393 } 1394 1395 @Override 1396 public byte[] read(BluetoothGattServerConnection connection, int offset) { 1397 return Bytes.concat( 1398 new byte[]{BrHandoverDataCharacteristic.BR_EDR_FEATURES}, 1399 mBluetoothAddress.getBytes(ByteOrder.LITTLE_ENDIAN), 1400 CLASS_OF_DEVICE.getBytes(ByteOrder.LITTLE_ENDIAN)); 1401 } 1402 }; 1403 1404 BluetoothGattServlet bluetoothSigServlet = 1405 new BluetoothGattServlet() { 1406 1407 @Override 1408 public BluetoothGattCharacteristic getCharacteristic() { 1409 BluetoothGattCharacteristic characteristic = 1410 new BluetoothGattCharacteristic( 1411 TransportDiscoveryService.BluetoothSigDataCharacteristic.ID, 1412 0 /* no properties */, 1413 0 /* no permissions */); 1414 1415 if (mOptions.getIncludeTransportDataDescriptor()) { 1416 characteristic.addDescriptor( 1417 new BluetoothGattDescriptor( 1418 TransportDiscoveryService.BluetoothSigDataCharacteristic 1419 .BrTransportBlockDataDescriptor.ID, 1420 BluetoothGattDescriptor.PERMISSION_READ)); 1421 } 1422 return characteristic; 1423 } 1424 1425 @Override 1426 public byte[] readDescriptor( 1427 BluetoothGattServerConnection connection, 1428 BluetoothGattDescriptor descriptor, 1429 int offset) 1430 throws BluetoothGattException { 1431 return transportDiscoveryData(); 1432 } 1433 }; 1434 1435 BluetoothGattServlet accountKeyServlet = 1436 new BluetoothGattServlet() { 1437 @Override 1438 // Simulating deprecated API {@code AccountKeyCharacteristic.ID} for testing. 1439 @SuppressWarnings("deprecation") 1440 public BluetoothGattCharacteristic getCharacteristic() { 1441 return new BluetoothGattCharacteristic( 1442 AccountKeyCharacteristic.CUSTOM_128_BIT_UUID, 1443 PROPERTY_WRITE, 1444 PERMISSION_WRITE); 1445 } 1446 1447 @Override 1448 public void write( 1449 BluetoothGattServerConnection connection, int offset, byte[] value) { 1450 mLogger.log("Got value from account key servlet: %s", 1451 base16().encode(value)); 1452 try { 1453 addAccountKey(AesEcbSingleBlockEncryption.decrypt(mSecret, value), 1454 mPairingDevice); 1455 } catch (GeneralSecurityException e) { 1456 mLogger.log(e, "Failed to decrypt account key."); 1457 } 1458 mUiThreadHandler.post( 1459 () -> mAdvertiser.startAdvertising(accountKeysServiceData())); 1460 } 1461 }; 1462 1463 BluetoothGattServlet firmwareVersionServlet = 1464 new BluetoothGattServlet() { 1465 @Override 1466 public BluetoothGattCharacteristic getCharacteristic() { 1467 return new BluetoothGattCharacteristic( 1468 FirmwareVersionCharacteristic.ID, PROPERTY_READ, PERMISSION_READ); 1469 } 1470 1471 @Override 1472 public byte[] read(BluetoothGattServerConnection connection, int offset) { 1473 return mDeviceFirmwareVersion.getBytes(); 1474 } 1475 }; 1476 1477 BluetoothGattServlet keyBasedPairingServlet = 1478 new NotifiableGattServlet() { 1479 @Override 1480 // Simulating deprecated API {@code KeyBasedPairingCharacteristic.ID} for 1481 // testing. 1482 @SuppressWarnings("deprecation") 1483 public BluetoothGattCharacteristic getBaseCharacteristic() { 1484 return new BluetoothGattCharacteristic( 1485 KeyBasedPairingCharacteristic.CUSTOM_128_BIT_UUID, 1486 PROPERTY_WRITE | PROPERTY_INDICATE, 1487 PERMISSION_WRITE); 1488 } 1489 1490 @Override 1491 public void write( 1492 BluetoothGattServerConnection connection, int offset, byte[] value) { 1493 mLogger.log("Requesting key based pairing handshake, value=%s", 1494 base16().encode(value)); 1495 1496 mSecret = null; 1497 byte[] seekerPublicAddress = null; 1498 if (value.length == AES_BLOCK_LENGTH) { 1499 1500 for (ByteString key : getAccountKeys()) { 1501 byte[] candidateSecret = key.toByteArray(); 1502 try { 1503 seekerPublicAddress = handshake(candidateSecret, value); 1504 mSecret = candidateSecret; 1505 mIsSubsequentPair = true; 1506 break; 1507 } catch (GeneralSecurityException e) { 1508 mLogger.log(e, "Failed to decrypt with %s", 1509 base16().encode(candidateSecret)); 1510 } 1511 } 1512 } else if (value.length == AES_BLOCK_LENGTH + PUBLIC_KEY_LENGTH 1513 && mOptions.getAntiSpoofingPrivateKey() != null) { 1514 try { 1515 byte[] encryptedRequest = Arrays.copyOf(value, AES_BLOCK_LENGTH); 1516 byte[] receivedPublicKey = 1517 Arrays.copyOfRange(value, AES_BLOCK_LENGTH, value.length); 1518 byte[] candidateSecret = 1519 EllipticCurveDiffieHellmanExchange.create( 1520 mOptions.getAntiSpoofingPrivateKey()) 1521 .generateSecret(receivedPublicKey); 1522 seekerPublicAddress = handshake(candidateSecret, encryptedRequest); 1523 mSecret = candidateSecret; 1524 } catch (Exception e) { 1525 mLogger.log( 1526 e, 1527 "Failed to decrypt with anti-spoofing private key %s", 1528 base16().encode(mOptions.getAntiSpoofingPrivateKey())); 1529 } 1530 } else { 1531 mLogger.log("Packet length invalid, %d", value.length); 1532 return; 1533 } 1534 1535 if (mSecret == null) { 1536 mLogger.log("Couldn't find a usable key to decrypt with."); 1537 return; 1538 } 1539 1540 mLogger.log("Found valid decryption key, %s", base16().encode(mSecret)); 1541 byte[] salt = new byte[9]; 1542 new Random().nextBytes(salt); 1543 try { 1544 byte[] data = concat( 1545 new byte[]{KeyBasedPairingCharacteristic.Response.TYPE}, 1546 mBluetoothAddress.getBytes(ByteOrder.BIG_ENDIAN), salt); 1547 byte[] encryptedAddress = encrypt(mSecret, data); 1548 mLogger.log( 1549 "Sending handshake response %s with size %d", 1550 base16().encode(encryptedAddress), encryptedAddress.length); 1551 sendNotification(encryptedAddress); 1552 1553 // Notify seeker for NameCharacteristic to get provider device name 1554 // when seeker request device name flag is true. 1555 if (mOptions.getEnableNameCharacteristic() 1556 && mHandshakeRequest.requestDeviceName()) { 1557 byte[] encryptedResponse = 1558 getDeviceNameInBytes() != null ? createEncryptedDeviceName() 1559 : new byte[0]; 1560 mLogger.log( 1561 "Sending device name response %s with size %d", 1562 base16().encode(encryptedResponse), 1563 encryptedResponse.length); 1564 mDeviceNameServlet.sendNotification(encryptedResponse); 1565 } 1566 1567 // Disconnects the current connection to allow the following pairing 1568 // request. Needs to be on a separate thread to avoid deadlocking and 1569 // timing out (waits for a callback from OS, which happens on this 1570 // thread). 1571 // 1572 // Note: The spec does not require you to disconnect from other 1573 // devices at this point. 1574 // If headphones support multiple simultaneous connections, they 1575 // should stay connected. But Android fails to pair with the new 1576 // device if we don't first disconnect from any other device. 1577 mLogger.log("Skip remove bond, value=%s", 1578 mOptions.getRemoveAllDevicesDuringPairing()); 1579 if (mOptions.getRemoveAllDevicesDuringPairing() 1580 && mHandshakeRequest.getType() 1581 == HandshakeRequest.Type.KEY_BASED_PAIRING_REQUEST 1582 && !mHandshakeRequest.requestRetroactivePair()) { 1583 mExecutor.execute(() -> disconnectAllBondedDevices()); 1584 } 1585 1586 if (mHandshakeRequest.getType() 1587 == HandshakeRequest.Type.KEY_BASED_PAIRING_REQUEST 1588 && mHandshakeRequest.requestProviderInitialBonding()) { 1589 // Run on executor to ensure it doesn't happen until after the 1590 // notify (which tells the remote device what address to expect). 1591 String seekerPublicAddressString = 1592 BluetoothAddress.encode(seekerPublicAddress); 1593 mExecutor.execute(() -> { 1594 mLogger.log("Sending pairing request to %s", 1595 seekerPublicAddressString); 1596 mBluetoothAdapter.getRemoteDevice( 1597 seekerPublicAddressString).createBond(); 1598 }); 1599 } 1600 } catch (GeneralSecurityException e) { 1601 mLogger.log(e, "Failed to notify of static mac address"); 1602 } 1603 } 1604 1605 @Nullable 1606 private byte[] handshake(byte[] key, byte[] encryptedPairingRequest) 1607 throws GeneralSecurityException { 1608 mHandshakeRequest = new HandshakeRequest(key, encryptedPairingRequest); 1609 1610 byte[] decryptedAddress = mHandshakeRequest.getVerificationData(); 1611 if (mBleAddress != null 1612 && Arrays.equals(decryptedAddress, 1613 BluetoothAddress.decode(mBleAddress)) 1614 || Arrays.equals(decryptedAddress, 1615 mBluetoothAddress.getBytes(ByteOrder.BIG_ENDIAN))) { 1616 mLogger.log("Address matches: %s", base16().encode(decryptedAddress)); 1617 } else { 1618 throw new GeneralSecurityException( 1619 "Address (BLE or BR/EDR) is not correct: " 1620 + base16().encode(decryptedAddress) 1621 + ", " 1622 + mBleAddress 1623 + ", " 1624 + getBluetoothAddress()); 1625 } 1626 1627 switch (mHandshakeRequest.getType()) { 1628 case KEY_BASED_PAIRING_REQUEST: 1629 return handleKeyBasedPairingRequest(mHandshakeRequest); 1630 case ACTION_OVER_BLE: 1631 return handleActionOverBleRequest(mHandshakeRequest); 1632 case UNKNOWN: 1633 // continue to throw the exception; 1634 } 1635 throw new GeneralSecurityException( 1636 "Type is not correct: " + mHandshakeRequest.getType()); 1637 } 1638 1639 @Nullable 1640 private byte[] handleKeyBasedPairingRequest(HandshakeRequest handshakeRequest) 1641 throws GeneralSecurityException { 1642 if (handshakeRequest.requestDiscoverable()) { 1643 mLogger.log("Requested discoverability"); 1644 setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE); 1645 } 1646 1647 mLogger.log( 1648 "KeyBasedPairing: initialBonding=%s, requestDeviceName=%s, " 1649 + "retroactivePair=%s", 1650 handshakeRequest.requestProviderInitialBonding(), 1651 handshakeRequest.requestDeviceName(), 1652 handshakeRequest.requestRetroactivePair()); 1653 1654 byte[] seekerPublicAddress = null; 1655 if (handshakeRequest.requestProviderInitialBonding() 1656 || handshakeRequest.requestRetroactivePair()) { 1657 seekerPublicAddress = handshakeRequest.getSeekerPublicAddress(); 1658 mLogger.log( 1659 "Seeker sends BR/EDR address %s to provider", 1660 BluetoothAddress.encode(seekerPublicAddress)); 1661 } 1662 1663 if (handshakeRequest.requestRetroactivePair()) { 1664 if (mBluetoothAdapter.getRemoteDevice( 1665 seekerPublicAddress).getBondState() 1666 != BluetoothDevice.BOND_BONDED) { 1667 throw new GeneralSecurityException( 1668 "Address (BR/EDR) is not bonded: " 1669 + BluetoothAddress.encode(seekerPublicAddress)); 1670 } 1671 } 1672 1673 return seekerPublicAddress; 1674 } 1675 1676 @Nullable 1677 private byte[] handleActionOverBleRequest(HandshakeRequest handshakeRequest) { 1678 // TODO(wollohchou): implement action over ble request. 1679 if (handshakeRequest.requestDeviceAction()) { 1680 mLogger.log("Requesting action over BLE, device action"); 1681 } else if (handshakeRequest.requestFollowedByAdditionalData()) { 1682 mLogger.log( 1683 "Requesting action over BLE, followed by additional data, " 1684 + "type:%s", 1685 handshakeRequest.getAdditionalDataType()); 1686 } else { 1687 mLogger.log("Requesting action over BLE"); 1688 } 1689 return null; 1690 } 1691 1692 /** 1693 * @return The encrypted device name from provider for seeker to use. 1694 */ 1695 private byte[] createEncryptedDeviceName() throws GeneralSecurityException { 1696 byte[] deviceName = getDeviceNameInBytes(); 1697 String providerName = new String(deviceName, StandardCharsets.UTF_8); 1698 mLogger.log( 1699 "Sending handshake response for device name %s with size %d", 1700 providerName, deviceName.length); 1701 return NamingEncoder.encodeNamingPacket(mSecret, providerName); 1702 } 1703 }; 1704 1705 mBeaconActionsServlet = 1706 new NotifiableGattServlet() { 1707 private static final int GATT_ERROR_UNAUTHENTICATED = 0x80; 1708 private static final int GATT_ERROR_INVALID_VALUE = 0x81; 1709 private static final int NONCE_LENGTH = 8; 1710 private static final int ONE_TIME_AUTH_KEY_OFFSET = 2; 1711 private static final int ONE_TIME_AUTH_KEY_LENGTH = 8; 1712 private static final int IDENTITY_KEY_LENGTH = 32; 1713 private static final byte TRANSMISSION_POWER = 0; 1714 1715 private final SecureRandom mRandom = new SecureRandom(); 1716 private final MessageDigest mSha256; 1717 @Nullable 1718 private byte[] mLastNonce; 1719 @Nullable 1720 private ByteString mIdentityKey = mOptions.getEddystoneIdentityKey(); 1721 1722 { 1723 try { 1724 mSha256 = MessageDigest.getInstance("SHA-256"); 1725 mSha256.reset(); 1726 } catch (NoSuchAlgorithmException e) { 1727 throw new IllegalStateException( 1728 "System missing SHA-256 implementation.", e); 1729 } 1730 } 1731 1732 @Override 1733 // Simulating deprecated API {@code BeaconActionsCharacteristic.ID} for testing. 1734 @SuppressWarnings("deprecation") 1735 public BluetoothGattCharacteristic getBaseCharacteristic() { 1736 return new BluetoothGattCharacteristic( 1737 BeaconActionsCharacteristic.CUSTOM_128_BIT_UUID, 1738 PROPERTY_READ | PROPERTY_WRITE | PROPERTY_NOTIFY, 1739 PERMISSION_READ | PERMISSION_WRITE); 1740 } 1741 1742 @Override 1743 public byte[] read(BluetoothGattServerConnection connection, int offset) { 1744 mLastNonce = new byte[NONCE_LENGTH]; 1745 mRandom.nextBytes(mLastNonce); 1746 return mLastNonce; 1747 } 1748 1749 @Override 1750 public void write( 1751 BluetoothGattServerConnection connection, int offset, byte[] value) 1752 throws BluetoothGattException { 1753 mLogger.log("Got value from beacon actions servlet: %s", 1754 base16().encode(value)); 1755 if (value.length == 0) { 1756 mLogger.log("Packet length invalid, %d", value.length); 1757 throw new BluetoothGattException("Packet length invalid", 1758 GATT_ERROR_INVALID_VALUE); 1759 } 1760 switch (value[0]) { 1761 case BeaconActionType.READ_BEACON_PARAMETERS: 1762 handleReadBeaconParameters(value); 1763 break; 1764 case BeaconActionType.READ_PROVISIONING_STATE: 1765 handleReadProvisioningState(value); 1766 break; 1767 case BeaconActionType.SET_EPHEMERAL_IDENTITY_KEY: 1768 handleSetEphemeralIdentityKey(value); 1769 break; 1770 case BeaconActionType.CLEAR_EPHEMERAL_IDENTITY_KEY: 1771 case BeaconActionType.READ_EPHEMERAL_IDENTITY_KEY: 1772 case BeaconActionType.RING: 1773 case BeaconActionType.READ_RINGING_STATE: 1774 throw new BluetoothGattException( 1775 "Unimplemented beacon action", 1776 BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED); 1777 default: 1778 throw new BluetoothGattException( 1779 "Unknown beacon action", 1780 BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED); 1781 } 1782 } 1783 1784 private boolean verifyAccountKeyToken(byte[] value, boolean ownerOnly) 1785 throws BluetoothGattException { 1786 if (value.length < ONE_TIME_AUTH_KEY_LENGTH + ONE_TIME_AUTH_KEY_OFFSET) { 1787 mLogger.log("Packet length invalid, %d", value.length); 1788 throw new BluetoothGattException( 1789 "Packet length invalid", GATT_ERROR_INVALID_VALUE); 1790 } 1791 byte[] hashedAccountKey = 1792 Arrays.copyOfRange( 1793 value, 1794 ONE_TIME_AUTH_KEY_OFFSET, 1795 ONE_TIME_AUTH_KEY_LENGTH + ONE_TIME_AUTH_KEY_OFFSET); 1796 if (mLastNonce == null) { 1797 throw new BluetoothGattException( 1798 "Nonce wasn't set", GATT_ERROR_UNAUTHENTICATED); 1799 } 1800 if (ownerOnly) { 1801 ByteString accountKey = getOwnerAccountKey(); 1802 if (accountKey != null) { 1803 mSha256.update(accountKey.toByteArray()); 1804 mSha256.update(mLastNonce); 1805 return Arrays.equals( 1806 hashedAccountKey, 1807 Arrays.copyOf(mSha256.digest(), ONE_TIME_AUTH_KEY_LENGTH)); 1808 } 1809 } else { 1810 Set<ByteString> accountKeys = getAccountKeys(); 1811 for (ByteString accountKey : accountKeys) { 1812 mSha256.update(accountKey.toByteArray()); 1813 mSha256.update(mLastNonce); 1814 if (Arrays.equals( 1815 hashedAccountKey, 1816 Arrays.copyOf(mSha256.digest(), 1817 ONE_TIME_AUTH_KEY_LENGTH))) { 1818 return true; 1819 } 1820 } 1821 } 1822 return false; 1823 } 1824 1825 private int getBeaconClock() { 1826 return (int) TimeUnit.MILLISECONDS.toSeconds(SystemClock.elapsedRealtime()); 1827 } 1828 1829 private ByteString fromBytes(byte... bytes) { 1830 return ByteString.copyFrom(bytes); 1831 } 1832 1833 private byte[] intToByteArray(int value) { 1834 byte[] data = new byte[4]; 1835 data[3] = (byte) value; 1836 data[2] = (byte) (value >>> 8); 1837 data[1] = (byte) (value >>> 16); 1838 data[0] = (byte) (value >>> 24); 1839 return data; 1840 } 1841 1842 private void handleReadBeaconParameters(byte[] value) 1843 throws BluetoothGattException { 1844 if (!verifyAccountKeyToken(value, /* ownerOnly= */ false)) { 1845 throw new BluetoothGattException( 1846 "failed to authenticate account key", 1847 GATT_ERROR_UNAUTHENTICATED); 1848 } 1849 sendNotification( 1850 fromBytes( 1851 (byte) BeaconActionType.READ_BEACON_PARAMETERS, 1852 (byte) 5 /* data length */, 1853 TRANSMISSION_POWER) 1854 .concat(ByteString.copyFrom( 1855 intToByteArray(getBeaconClock()))) 1856 .toByteArray()); 1857 } 1858 1859 private void handleReadProvisioningState(byte[] value) 1860 throws BluetoothGattException { 1861 if (!verifyAccountKeyToken(value, /* ownerOnly= */ false)) { 1862 throw new BluetoothGattException( 1863 "failed to authenticate account key", 1864 GATT_ERROR_UNAUTHENTICATED); 1865 } 1866 byte flags = 0; 1867 if (verifyAccountKeyToken(value, /* ownerOnly= */ true)) { 1868 flags |= (byte) (1 << 1); 1869 } 1870 if (mIdentityKey == null) { 1871 sendNotification( 1872 fromBytes( 1873 (byte) BeaconActionType.READ_PROVISIONING_STATE, 1874 (byte) 1 /* data length */, 1875 flags) 1876 .toByteArray()); 1877 } else { 1878 flags |= (byte) 1; 1879 sendNotification( 1880 fromBytes( 1881 (byte) BeaconActionType.READ_PROVISIONING_STATE, 1882 (byte) 21 /* data length */, 1883 flags) 1884 .concat( 1885 E2eeCalculator.computeE2eeEid( 1886 mIdentityKey, /* exponent= */ 10, 1887 getBeaconClock())) 1888 .toByteArray()); 1889 } 1890 } 1891 1892 private void handleSetEphemeralIdentityKey(byte[] value) 1893 throws BluetoothGattException { 1894 if (!verifyAccountKeyToken(value, /* ownerOnly= */ true)) { 1895 throw new BluetoothGattException( 1896 "failed to authenticate owner account key", 1897 GATT_ERROR_UNAUTHENTICATED); 1898 } 1899 if (value.length 1900 != ONE_TIME_AUTH_KEY_LENGTH + ONE_TIME_AUTH_KEY_OFFSET 1901 + IDENTITY_KEY_LENGTH) { 1902 mLogger.log("Packet length invalid, %d", value.length); 1903 throw new BluetoothGattException("Packet length invalid", 1904 GATT_ERROR_INVALID_VALUE); 1905 } 1906 if (mIdentityKey != null) { 1907 throw new BluetoothGattException( 1908 "Device is already provisioned as Eddystone", 1909 GATT_ERROR_UNAUTHENTICATED); 1910 } 1911 mIdentityKey = Crypto.aesEcbNoPaddingDecrypt( 1912 ByteString.copyFrom(mOwnerAccountKey), 1913 ByteString.copyFrom(value) 1914 .substring(ONE_TIME_AUTH_KEY_LENGTH 1915 + ONE_TIME_AUTH_KEY_OFFSET)); 1916 } 1917 }; 1918 1919 ServiceConfig fastPairServiceConfig = 1920 new ServiceConfig() 1921 .addCharacteristic(accountKeyServlet) 1922 .addCharacteristic(keyBasedPairingServlet) 1923 .addCharacteristic(mPasskeyServlet) 1924 .addCharacteristic(firmwareVersionServlet); 1925 if (mOptions.getEnableBeaconActionsCharacteristic()) { 1926 fastPairServiceConfig.addCharacteristic(mBeaconActionsServlet); 1927 } 1928 1929 BluetoothGattServerConfig config = 1930 new BluetoothGattServerConfig() 1931 .addService( 1932 TransportDiscoveryService.ID, 1933 new ServiceConfig() 1934 .addCharacteristic(tdsControlPointServlet) 1935 .addCharacteristic(brHandoverDataServlet) 1936 .addCharacteristic(bluetoothSigServlet)) 1937 .addService( 1938 FastPairService.ID, 1939 mOptions.getEnableNameCharacteristic() 1940 ? fastPairServiceConfig.addCharacteristic( 1941 mDeviceNameServlet) 1942 : fastPairServiceConfig); 1943 1944 mLogger.log( 1945 "Starting GATT server, support name characteristic %b", 1946 mOptions.getEnableNameCharacteristic()); 1947 try { 1948 helper.open(config); 1949 } catch (BluetoothException e) { 1950 mLogger.log(e, "Error starting GATT server"); 1951 } 1952 } 1953 1954 /** Callback for passkey/pin input. */ 1955 public interface KeyInputCallback { onKeyInput(int key)1956 void onKeyInput(int key); 1957 } 1958 enterPassKey(int passkey)1959 public void enterPassKey(int passkey) { 1960 mLogger.log("enterPassKey called with passkey %d.", passkey); 1961 mPairingDevice.setPairingConfirmation(true); 1962 } 1963 checkPasskey()1964 private void checkPasskey() { 1965 // There's a race between the PAIRING_REQUEST broadcast from the OS giving us the local 1966 // passkey, and the remote passkey received over GATT. Skip the check until we have both. 1967 if (mLocalPasskey == 0 || mRemotePasskey == 0) { 1968 mLogger.log( 1969 "Skipping passkey check, missing local (%s) or remote (%s).", 1970 mLocalPasskey, mRemotePasskey); 1971 return; 1972 } 1973 1974 // Regardless of whether it matches, send our (encrypted) passkey to the seeker. 1975 sendPasskeyToRemoteDevice(mLocalPasskey); 1976 1977 mLogger.log("Checking localPasskey %s == remotePasskey %s", mLocalPasskey, mRemotePasskey); 1978 boolean passkeysMatched = mLocalPasskey == mRemotePasskey; 1979 if (mOptions.getShowsPasskeyConfirmation() && passkeysMatched 1980 && mPasskeyEventCallback != null) { 1981 mLogger.log("callbacks the UI for passkey confirmation."); 1982 mPasskeyEventCallback.onPasskeyConfirmation(mLocalPasskey, 1983 this::setPasskeyConfirmation); 1984 } else { 1985 setPasskeyConfirmation(passkeysMatched); 1986 } 1987 } 1988 sendPasskeyToRemoteDevice(int passkey)1989 private void sendPasskeyToRemoteDevice(int passkey) { 1990 try { 1991 mPasskeyServlet.sendNotification( 1992 PasskeyCharacteristic.encrypt( 1993 PasskeyCharacteristic.Type.PROVIDER, mSecret, passkey)); 1994 } catch (GeneralSecurityException e) { 1995 mLogger.log(e, "Failed to encrypt passkey response."); 1996 } 1997 } 1998 setFirmwareVersion(String versionNumber)1999 public void setFirmwareVersion(String versionNumber) { 2000 mDeviceFirmwareVersion = versionNumber; 2001 } 2002 setDynamicBufferSize(boolean support)2003 public void setDynamicBufferSize(boolean support) { 2004 if (mSupportDynamicBufferSize != support) { 2005 mSupportDynamicBufferSize = support; 2006 sendCapabilitySync(); 2007 } 2008 } 2009 2010 @VisibleForTesting setPasskeyConfirmationCallback(PasskeyConfirmationCallback callback)2011 void setPasskeyConfirmationCallback(PasskeyConfirmationCallback callback) { 2012 this.mPasskeyConfirmationCallback = callback; 2013 } 2014 setDeviceNameCallback(DeviceNameCallback callback)2015 public void setDeviceNameCallback(DeviceNameCallback callback) { 2016 this.mDeviceNameCallback = callback; 2017 } 2018 setPasskeyEventCallback(PasskeyEventCallback passkeyEventCallback)2019 public void setPasskeyEventCallback(PasskeyEventCallback passkeyEventCallback) { 2020 this.mPasskeyEventCallback = passkeyEventCallback; 2021 } 2022 setPasskeyConfirmation(boolean confirm)2023 private void setPasskeyConfirmation(boolean confirm) { 2024 mPairingDevice.setPairingConfirmation(confirm); 2025 if (mPasskeyConfirmationCallback != null) { 2026 mPasskeyConfirmationCallback.onPasskeyConfirmation(confirm); 2027 } 2028 mLocalPasskey = 0; 2029 mRemotePasskey = 0; 2030 } 2031 becomeDiscoverable()2032 private void becomeDiscoverable() throws InterruptedException, TimeoutException { 2033 setDiscoverable(true); 2034 } 2035 cancelDiscovery()2036 public void cancelDiscovery() throws InterruptedException, TimeoutException { 2037 setDiscoverable(false); 2038 } 2039 setDiscoverable(boolean discoverable)2040 private void setDiscoverable(boolean discoverable) 2041 throws InterruptedException, TimeoutException { 2042 mIsDiscoverableLatch = new CountDownLatch(1); 2043 setScanMode(discoverable ? SCAN_MODE_CONNECTABLE_DISCOVERABLE : SCAN_MODE_CONNECTABLE); 2044 // If we're already discoverable, count down the latch right away. Otherwise, 2045 // we'll get a broadcast when we successfully become discoverable. 2046 if (isDiscoverable()) { 2047 mIsDiscoverableLatch.countDown(); 2048 } 2049 if (mIsDiscoverableLatch.await(BECOME_DISCOVERABLE_TIMEOUT_SEC, TimeUnit.SECONDS)) { 2050 mLogger.log("Successfully became switched discoverable mode %s", discoverable); 2051 } else { 2052 throw new TimeoutException(); 2053 } 2054 } 2055 setScanMode(int scanMode)2056 private void setScanMode(int scanMode) { 2057 if (mRevertDiscoverableFuture != null) { 2058 mRevertDiscoverableFuture.cancel(false /* may interrupt if running */); 2059 } 2060 2061 mLogger.log("Setting scan mode to %s", scanModeToString(scanMode)); 2062 try { 2063 Method method = mBluetoothAdapter.getClass().getMethod("setScanMode", Integer.TYPE); 2064 method.invoke(mBluetoothAdapter, scanMode); 2065 2066 if (scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE) { 2067 mRevertDiscoverableFuture = 2068 mExecutor.schedule(() -> setScanMode(SCAN_MODE_CONNECTABLE), 2069 SCAN_MODE_REFRESH_SEC, TimeUnit.SECONDS); 2070 } 2071 } catch (Exception e) { 2072 mLogger.log(e, "Error setting scan mode to %d", scanMode); 2073 } 2074 } 2075 scanModeToString(int scanMode)2076 public static String scanModeToString(int scanMode) { 2077 switch (scanMode) { 2078 case SCAN_MODE_CONNECTABLE_DISCOVERABLE: 2079 return "DISCOVERABLE"; 2080 case SCAN_MODE_CONNECTABLE: 2081 return "CONNECTABLE"; 2082 case SCAN_MODE_NONE: 2083 return "NOT CONNECTABLE"; 2084 default: 2085 return "UNKNOWN(" + scanMode + ")"; 2086 } 2087 } 2088 checkTdsControlPointRequest(byte[] request)2089 private ResultCode checkTdsControlPointRequest(byte[] request) { 2090 if (request.length < 2) { 2091 mLogger.log( 2092 new IllegalArgumentException(), "Expected length >= 2 for %s", 2093 base16().encode(request)); 2094 return ResultCode.INVALID_PARAMETER; 2095 } 2096 byte opCode = getTdsControlPointOpCode(request); 2097 if (opCode != ControlPointCharacteristic.ACTIVATE_TRANSPORT_OP_CODE) { 2098 mLogger.log( 2099 new IllegalArgumentException(), 2100 "Expected Activate Transport op code (0x01), got %d", 2101 opCode); 2102 return ResultCode.OP_CODE_NOT_SUPPORTED; 2103 } 2104 if (request[1] != BLUETOOTH_SIG_ORGANIZATION_ID) { 2105 mLogger.log( 2106 new IllegalArgumentException(), 2107 "Expected Bluetooth SIG organization ID (0x01), got %d", 2108 request[1]); 2109 return ResultCode.UNSUPPORTED_ORGANIZATION_ID; 2110 } 2111 return ResultCode.SUCCESS; 2112 } 2113 getTdsControlPointOpCode(byte[] request)2114 private static byte getTdsControlPointOpCode(byte[] request) { 2115 return request.length < 1 ? 0x00 : request[0]; 2116 } 2117 isDiscoverable()2118 private boolean isDiscoverable() { 2119 return mBluetoothAdapter.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE; 2120 } 2121 modelIdServiceData(boolean forAdvertising)2122 private byte[] modelIdServiceData(boolean forAdvertising) { 2123 // Note: This used to be little-endian but is now big-endian. See b/78229467 for details. 2124 byte[] modelIdPacket = 2125 base16().decode( 2126 forAdvertising ? mOptions.getAdvertisingModelId() : mOptions.getModelId()); 2127 if (!mBatteryValues.isEmpty()) { 2128 // If we are going to advertise battery values with the packet, then switch to the 2129 // non-3-byte model ID format. 2130 modelIdPacket = concat(new byte[]{0b00000110}, modelIdPacket); 2131 } 2132 return modelIdPacket; 2133 } 2134 accountKeysServiceData()2135 private byte[] accountKeysServiceData() { 2136 try { 2137 return concat(new byte[]{0x00}, generateBloomFilterFields()); 2138 } catch (NoSuchAlgorithmException e) { 2139 throw new IllegalStateException("Unable to build bloom filter.", e); 2140 } 2141 } 2142 transportDiscoveryData()2143 private byte[] transportDiscoveryData() { 2144 byte[] transportData = SUPPORTED_SERVICES_LTV; 2145 return Bytes.concat( 2146 new byte[]{BLUETOOTH_SIG_ORGANIZATION_ID}, 2147 new byte[]{tdsFlags(isDiscoverable() ? TransportState.ON : TransportState.OFF)}, 2148 new byte[]{(byte) transportData.length}, 2149 transportData); 2150 } 2151 generateBloomFilterFields()2152 private byte[] generateBloomFilterFields() throws NoSuchAlgorithmException { 2153 Set<ByteString> accountKeys = getAccountKeys(); 2154 if (accountKeys.isEmpty()) { 2155 return new byte[0]; 2156 } 2157 BloomFilter bloomFilter = 2158 new BloomFilter( 2159 new byte[(int) (1.2 * accountKeys.size()) + 3], 2160 new FastPairBloomFilterHasher()); 2161 String address = mBleAddress == null ? SIMULATOR_FAKE_BLE_ADDRESS : mBleAddress; 2162 2163 // Simulator supports Central Address Resolution characteristic, so when paired, the BLE 2164 // address in Seeker will be resolved to BR/EDR address. This caused Seeker fails on 2165 // checking the bloom filter due to different address is used for salting. In order to 2166 // let battery values notification be shown on paired device, we use random salt to 2167 // workaround it. 2168 boolean advertisingBatteryValues = !mBatteryValues.isEmpty(); 2169 byte[] salt; 2170 if (mOptions.getUseRandomSaltForAccountKeyRotation() || advertisingBatteryValues) { 2171 salt = new byte[1]; 2172 new SecureRandom().nextBytes(salt); 2173 mLogger.log("Using random salt %s for bloom filter", base16().encode(salt)); 2174 } else { 2175 salt = BluetoothAddress.decode(address); 2176 mLogger.log("Using address %s for bloom filter", address); 2177 } 2178 2179 // To prevent tampering, account filter shall be slightly modified to include battery data 2180 // when the battery values are included in the advertisement. Normally, when building the 2181 // account filter, a value V is produce by combining the account key with a salt. Instead, 2182 // when battery values are also being advertised, it be constructed as follows: 2183 // - the first 16 bytes are account key. 2184 // - the next bytes are the salt. 2185 // - the remaining bytes are the battery data. 2186 byte[] saltAndBatteryData = 2187 advertisingBatteryValues ? concat(salt, generateBatteryData()) : salt; 2188 2189 for (ByteString accountKey : accountKeys) { 2190 bloomFilter.add(concat(accountKey.toByteArray(), saltAndBatteryData)); 2191 } 2192 byte[] packet = generateAccountKeyData(bloomFilter); 2193 return mOptions.getUseRandomSaltForAccountKeyRotation() || advertisingBatteryValues 2194 // Create a header with length 1 and type 1 for a random salt. 2195 ? concat(packet, createField((byte) 0x11, salt)) 2196 // Exclude the salt from the packet, BLE address will be assumed by the client. 2197 : packet; 2198 } 2199 2200 /** 2201 * Creates a new field for the packet. 2202 * 2203 * The header is formatted 0xLLLLTTTT where LLLL is the 2204 * length of the field and TTTT is the type (0 for bloom filter, 1 for salt). 2205 */ createField(byte header, byte[] value)2206 private byte[] createField(byte header, byte[] value) { 2207 return concat(new byte[]{header}, value); 2208 } 2209 getTxPower()2210 public int getTxPower() { 2211 return mOptions.getTxPowerLevel(); 2212 } 2213 2214 @Nullable getServiceData()2215 byte[] getServiceData() { 2216 byte[] packet = 2217 isDiscoverable() 2218 ? modelIdServiceData(/* forAdvertising= */ true) 2219 : !getAccountKeys().isEmpty() ? accountKeysServiceData() : null; 2220 return addBatteryValues(packet); 2221 } 2222 2223 @Nullable addBatteryValues(byte[] packet)2224 private byte[] addBatteryValues(byte[] packet) { 2225 if (mBatteryValues.isEmpty() || packet == null) { 2226 return packet; 2227 } 2228 2229 return concat(packet, generateBatteryData()); 2230 } 2231 generateBatteryData()2232 private byte[] generateBatteryData() { 2233 // Byte 0: Battery length and type, first 4 bits are the number of battery values, second 2234 // 4 are the type. 2235 // Byte 1 - length: Battery values, the first bit is charging status, the remaining bits are 2236 // the actual value between 0 and 100, or -1 for unknown. 2237 byte[] batteryData = new byte[mBatteryValues.size() + 1]; 2238 batteryData[0] = (byte) (mBatteryValues.size() << 4 2239 | (mSuppressBatteryNotification ? 0b0100 : 0b0011)); 2240 2241 int batteryValueIndex = 1; 2242 for (BatteryValue batteryValue : mBatteryValues) { 2243 batteryData[batteryValueIndex++] = 2244 (byte) 2245 ((batteryValue.mCharging ? 0b10000000 : 0b00000000) 2246 | (0b01111111 & batteryValue.mLevel)); 2247 } 2248 2249 return batteryData; 2250 } 2251 generateAccountKeyData(BloomFilter bloomFilter)2252 private byte[] generateAccountKeyData(BloomFilter bloomFilter) { 2253 // Byte 0: length and type, first 4 bits are the length of bloom filter, second 4 are the 2254 // type which indicating the subsequent pairing notification is suppressed or not. 2255 // The following bytes are the data of bloom filter. 2256 byte[] filterBytes = bloomFilter.asBytes(); 2257 byte lengthAndType = (byte) (filterBytes.length << 4 2258 | (mSuppressSubsequentPairingNotification ? 0b0010 : 0b0000)); 2259 mLogger.log( 2260 "Generate bloom filter with suppress subsequent pairing notification:%b", 2261 mSuppressSubsequentPairingNotification); 2262 return createField(lengthAndType, filterBytes); 2263 } 2264 2265 /** Disconnects all bonded devices. */ disconnectAllBondedDevices()2266 public void disconnectAllBondedDevices() { 2267 for (BluetoothDevice device : mBluetoothAdapter.getBondedDevices()) { 2268 if (device.getBluetoothClass().getMajorDeviceClass() == Major.PHONE) { 2269 removeBond(device); 2270 } 2271 } 2272 } 2273 disconnect(BluetoothProfile profile, BluetoothDevice device)2274 public void disconnect(BluetoothProfile profile, BluetoothDevice device) { 2275 device.disconnect(); 2276 } 2277 removeBond(BluetoothDevice device)2278 public void removeBond(BluetoothDevice device) { 2279 device.removeBond(); 2280 } 2281 resetAccountKeys()2282 public void resetAccountKeys() { 2283 mFastPairSimulatorDatabase.setAccountKeys(new HashSet<>()); 2284 mFastPairSimulatorDatabase.setFastPairSeekerDevices(new HashSet<>()); 2285 mAccountKey = null; 2286 mOwnerAccountKey = null; 2287 mLogger.log("Remove all account keys"); 2288 } 2289 addAccountKey(byte[] key)2290 public void addAccountKey(byte[] key) { 2291 addAccountKey(key, /* device= */ null); 2292 } 2293 addAccountKey(byte[] key, @Nullable BluetoothDevice device)2294 private void addAccountKey(byte[] key, @Nullable BluetoothDevice device) { 2295 mAccountKey = key; 2296 if (mOwnerAccountKey == null) { 2297 mOwnerAccountKey = key; 2298 } 2299 2300 mFastPairSimulatorDatabase.addAccountKey(key); 2301 mFastPairSimulatorDatabase.addFastPairSeekerDevice(device, key); 2302 mLogger.log("Add account key: key=%s, device=%s", base16().encode(key), device); 2303 } 2304 getAccountKeys()2305 private Set<ByteString> getAccountKeys() { 2306 return mFastPairSimulatorDatabase.getAccountKeys(); 2307 } 2308 2309 /** Get the latest account key. */ 2310 @Nullable getAccountKey()2311 public ByteString getAccountKey() { 2312 if (mAccountKey == null) { 2313 return null; 2314 } 2315 return ByteString.copyFrom(mAccountKey); 2316 } 2317 2318 /** Get the owner account key (the first account key registered). */ 2319 @Nullable getOwnerAccountKey()2320 public ByteString getOwnerAccountKey() { 2321 if (mOwnerAccountKey == null) { 2322 return null; 2323 } 2324 return ByteString.copyFrom(mOwnerAccountKey); 2325 } 2326 resetDeviceName()2327 public void resetDeviceName() { 2328 mFastPairSimulatorDatabase.setLocalDeviceName(null); 2329 // Trigger simulator to update device name text view. 2330 if (mDeviceNameCallback != null) { 2331 mDeviceNameCallback.onNameChanged(getDeviceName()); 2332 } 2333 } 2334 2335 // This method is used in test case with default name in provider. setDeviceName(String deviceName)2336 public void setDeviceName(String deviceName) { 2337 setDeviceName(deviceName.getBytes(StandardCharsets.UTF_8)); 2338 } 2339 setDeviceName(@ullable byte[] deviceName)2340 private void setDeviceName(@Nullable byte[] deviceName) { 2341 mFastPairSimulatorDatabase.setLocalDeviceName(deviceName); 2342 2343 mLogger.log("Save device name : %s", getDeviceName()); 2344 // Trigger simulator to update device name text view. 2345 if (mDeviceNameCallback != null) { 2346 mDeviceNameCallback.onNameChanged(getDeviceName()); 2347 } 2348 } 2349 2350 @Nullable getDeviceNameInBytes()2351 private byte[] getDeviceNameInBytes() { 2352 return mFastPairSimulatorDatabase.getLocalDeviceName(); 2353 } 2354 2355 @Nullable getDeviceName()2356 public String getDeviceName() { 2357 String providerDeviceName = 2358 getDeviceNameInBytes() != null 2359 ? new String(getDeviceNameInBytes(), StandardCharsets.UTF_8) 2360 : null; 2361 mLogger.log("get device name = %s", providerDeviceName); 2362 return providerDeviceName; 2363 } 2364 2365 /** 2366 * Bit index: Description - Value 2367 * 2368 * <ul> 2369 * <li>0-1: Role - 0b10 (Provider only) 2370 * <li>2: Transport Data Incomplete: 0 (false) 2371 * <li>3-4: Transport State (0b00: Off, 0b01: On, 0b10: Temporarily Unavailable) 2372 * <li>5-7: Reserved for future use 2373 * </ul> 2374 */ tdsFlags(TransportState transportState)2375 private static byte tdsFlags(TransportState transportState) { 2376 return (byte) (0b00000010 & (transportState.mByteValue << 3)); 2377 } 2378 2379 /** Detailed information about battery value. */ 2380 public static class BatteryValue { 2381 boolean mCharging; 2382 2383 // The range is 0 ~ 100, and -1 represents the battery level is unknown. 2384 int mLevel; 2385 BatteryValue(boolean charging, int level)2386 public BatteryValue(boolean charging, int level) { 2387 this.mCharging = charging; 2388 this.mLevel = level; 2389 } 2390 } 2391 } 2392