• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.android.server.nearby.common.bluetooth.fastpair;
18 
19 import static com.android.server.nearby.common.bluetooth.fastpair.Constants.BLUETOOTH_ADDRESS_LENGTH;
20 import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.ActionOverBleFlag.ADDITIONAL_DATA_CHARACTERISTIC;
21 import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.ActionOverBleFlag.DEVICE_ACTION;
22 import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.KeyBasedPairingRequestFlag.PROVIDER_INITIATES_BONDING;
23 import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.KeyBasedPairingRequestFlag.REQUEST_DEVICE_NAME;
24 import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.KeyBasedPairingRequestFlag.REQUEST_DISCOVERABLE;
25 import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.KeyBasedPairingRequestFlag.REQUEST_RETROACTIVE_PAIR;
26 import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.ADDITIONAL_DATA_TYPE_INDEX;
27 
28 import static com.google.common.truth.Truth.assertThat;
29 
30 import static org.junit.Assert.assertThrows;
31 import static org.mockito.ArgumentMatchers.any;
32 import static org.mockito.ArgumentMatchers.anyLong;
33 import static org.mockito.Mockito.inOrder;
34 import static org.mockito.Mockito.when;
35 
36 import android.platform.test.annotations.Presubmit;
37 
38 import androidx.annotation.Nullable;
39 import androidx.core.util.Consumer;
40 import androidx.test.core.app.ApplicationProvider;
41 import androidx.test.ext.junit.runners.AndroidJUnit4;
42 import androidx.test.filters.SdkSuppress;
43 import androidx.test.filters.SmallTest;
44 
45 import com.android.server.nearby.common.bluetooth.BluetoothException;
46 import com.android.server.nearby.common.bluetooth.BluetoothGattException;
47 import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.AdditionalDataCharacteristic.AdditionalDataType;
48 import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request;
49 import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection;
50 import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothAdapter;
51 import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor;
52 import com.android.server.nearby.intdefs.NearbyEventIntDefs;
53 
54 import com.google.common.collect.ImmutableSet;
55 import com.google.common.io.BaseEncoding;
56 
57 import org.junit.Before;
58 import org.junit.Test;
59 import org.junit.runner.RunWith;
60 import org.mockito.InOrder;
61 import org.mockito.Mock;
62 import org.mockito.MockitoAnnotations;
63 
64 import java.security.GeneralSecurityException;
65 import java.time.Duration;
66 import java.util.Arrays;
67 
68 /**
69  * Unit tests for {@link HandshakeHandler}.
70  */
71 @Presubmit
72 @SmallTest
73 @RunWith(AndroidJUnit4.class)
74 public class HandshakeHandlerTest {
75 
76     public static final byte[] PUBLIC_KEY =
77             BaseEncoding.base64().decode(
78                     "d2JTfvfdS6u7LmGfMOmco3C7ra3lW1k17AOly0LrBydDZURacfTY"
79                             + "IMmo5K1ejfD9e8b6qHsDTNzselhifi10kQ==");
80     private static final String SEEKER_ADDRESS = "A1:A2:A3:A4:A5:A6";
81     private static final String PROVIDER_BLE_ADDRESS = "11:22:33:44:55:66";
82     /**
83      * The random-resolvable private address (RPA) is sometimes used when advertising over BLE, to
84      * hide the static public address (otherwise, the Fast Pair device would b
85      * identifiable/trackable whenever it's BLE advertising).
86      */
87     private static final String RANDOM_PRIVATE_ADDRESS = "BB:BB:BB:BB:BB:1E";
88     private static final byte[] SHARED_SECRET =
89             BaseEncoding.base16().decode("0123456789ABCDEF0123456789ABCDEF");
90 
91     @Mock EventLoggerWrapper mEventLoggerWrapper;
92     @Mock BluetoothGattConnection mBluetoothGattConnection;
93     @Mock BluetoothGattConnection.ChangeObserver mChangeObserver;
94     @Mock private Consumer<Integer> mRescueFromError;
95 
96     @Before
setUp()97     public void setUp() {
98         MockitoAnnotations.initMocks(this);
99     }
100 
101     @Test
102     @SdkSuppress(minSdkVersion = 32, codeName = "T")
handshakeGattError_noRetryError_failed()103     public void handshakeGattError_noRetryError_failed() throws BluetoothException {
104         HandshakeHandler.KeyBasedPairingRequest keyBasedPairingRequest =
105                 new HandshakeHandler.KeyBasedPairingRequest.Builder()
106                         .setVerificationData(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS))
107                         .build();
108         BluetoothGattException exception =
109                 new BluetoothGattException("Exception for no retry", 257);
110         when(mChangeObserver.waitForUpdate(anyLong())).thenThrow(exception);
111         GattConnectionManager gattConnectionManager =
112                 createGattConnectionManager(Preferences.builder(), () -> {});
113         gattConnectionManager.setGattConnection(mBluetoothGattConnection);
114         when(mBluetoothGattConnection.enableNotification(any(), any()))
115                 .thenReturn(mChangeObserver);
116         InOrder inOrder = inOrder(mEventLoggerWrapper);
117 
118         assertThrows(
119                 BluetoothGattException.class,
120                 () ->
121                         getHandshakeHandler(gattConnectionManager, address -> address)
122                                 .doHandshakeWithRetryAndSignalLostCheck(
123                                         PUBLIC_KEY,
124                                         keyBasedPairingRequest,
125                                         mRescueFromError));
126 
127         inOrder.verify(mEventLoggerWrapper).setCurrentEvent(
128                 NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION);
129         inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception);
130         inOrder.verifyNoMoreInteractions();
131     }
132 
133     @Test
134     @SdkSuppress(minSdkVersion = 32, codeName = "T")
handshakeGattError_retryAndNoCount_throwException()135     public void handshakeGattError_retryAndNoCount_throwException() throws BluetoothException {
136         HandshakeHandler.KeyBasedPairingRequest keyBasedPairingRequest =
137                 new HandshakeHandler.KeyBasedPairingRequest.Builder()
138                         .setVerificationData(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS))
139                         .build();
140         BluetoothGattException exception = new BluetoothGattException("Exception for retry", 133);
141         when(mChangeObserver.waitForUpdate(anyLong())).thenThrow(exception);
142         GattConnectionManager gattConnectionManager =
143                 createGattConnectionManager(Preferences.builder(), () -> {});
144         gattConnectionManager.setGattConnection(mBluetoothGattConnection);
145         when(mBluetoothGattConnection.enableNotification(any(), any()))
146                 .thenReturn(mChangeObserver);
147         InOrder inOrder = inOrder(mEventLoggerWrapper);
148 
149         HandshakeHandler.HandshakeException handshakeException =
150                 assertThrows(
151                         HandshakeHandler.HandshakeException.class,
152                         () -> getHandshakeHandler(gattConnectionManager, address -> address)
153                                 .doHandshakeWithRetryAndSignalLostCheck(
154                                         PUBLIC_KEY, keyBasedPairingRequest, mRescueFromError));
155 
156         inOrder.verify(mEventLoggerWrapper)
157                 .setCurrentEvent(NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION);
158         inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception);
159         inOrder.verify(mEventLoggerWrapper)
160                 .setCurrentEvent(NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION);
161         inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception);
162         inOrder.verify(mEventLoggerWrapper)
163                 .setCurrentEvent(NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION);
164         inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception);
165         inOrder.verify(mEventLoggerWrapper)
166                 .setCurrentEvent(NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION);
167         inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception);
168         inOrder.verifyNoMoreInteractions();
169         assertThat(handshakeException.getOriginalException()).isEqualTo(exception);
170     }
171 
172     @Test
173     @SdkSuppress(minSdkVersion = 32, codeName = "T")
handshakeGattError_noRetryOnTimeout_throwException()174     public void handshakeGattError_noRetryOnTimeout_throwException() throws BluetoothException {
175         HandshakeHandler.KeyBasedPairingRequest keyBasedPairingRequest =
176                 new HandshakeHandler.KeyBasedPairingRequest.Builder()
177                         .setVerificationData(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS))
178                         .build();
179         BluetoothOperationExecutor.BluetoothOperationTimeoutException exception =
180                 new BluetoothOperationExecutor.BluetoothOperationTimeoutException("Test timeout");
181         when(mChangeObserver.waitForUpdate(anyLong())).thenThrow(exception);
182         GattConnectionManager gattConnectionManager =
183                 createGattConnectionManager(Preferences.builder(), () -> {});
184         gattConnectionManager.setGattConnection(mBluetoothGattConnection);
185         when(mBluetoothGattConnection.enableNotification(any(), any()))
186                 .thenReturn(mChangeObserver);
187         InOrder inOrder = inOrder(mEventLoggerWrapper);
188 
189         assertThrows(
190                 HandshakeHandler.HandshakeException.class,
191                 () ->
192                         new HandshakeHandler(
193                                 gattConnectionManager,
194                                 PROVIDER_BLE_ADDRESS,
195                                 Preferences.builder().setRetrySecretHandshakeTimeout(false).build(),
196                                 mEventLoggerWrapper,
197                                 address -> address)
198                                 .doHandshakeWithRetryAndSignalLostCheck(
199                                         PUBLIC_KEY, keyBasedPairingRequest, mRescueFromError));
200 
201         inOrder.verify(mEventLoggerWrapper)
202                 .setCurrentEvent(NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION);
203         inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception);
204         inOrder.verifyNoMoreInteractions();
205     }
206 
207     @Test
208     @SdkSuppress(minSdkVersion = 32, codeName = "T")
handshakeGattError_signalLost()209     public void handshakeGattError_signalLost() throws BluetoothException {
210         HandshakeHandler.KeyBasedPairingRequest keyBasedPairingRequest =
211                 new HandshakeHandler.KeyBasedPairingRequest.Builder()
212                         .setVerificationData(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS))
213                         .build();
214         BluetoothGattException exception = new BluetoothGattException("Exception for retry", 133);
215         when(mChangeObserver.waitForUpdate(anyLong())).thenThrow(exception);
216         GattConnectionManager gattConnectionManager =
217                 createGattConnectionManager(Preferences.builder(), () -> {});
218         gattConnectionManager.setGattConnection(mBluetoothGattConnection);
219         when(mBluetoothGattConnection.enableNotification(any(), any()))
220                 .thenReturn(mChangeObserver);
221         InOrder inOrder = inOrder(mEventLoggerWrapper);
222 
223         SignalLostException signalLostException =
224                 assertThrows(
225                         SignalLostException.class,
226                         () -> getHandshakeHandler(gattConnectionManager, address -> null)
227                                 .doHandshakeWithRetryAndSignalLostCheck(
228                                         PUBLIC_KEY, keyBasedPairingRequest, mRescueFromError));
229 
230         inOrder.verify(mEventLoggerWrapper)
231                 .setCurrentEvent(NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION);
232         inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception);
233         assertThat(signalLostException).hasCauseThat().isEqualTo(exception);
234     }
235 
236     @Test
237     @SdkSuppress(minSdkVersion = 32, codeName = "T")
handshakeGattError_addressRotate()238     public void handshakeGattError_addressRotate() throws BluetoothException {
239         HandshakeHandler.KeyBasedPairingRequest keyBasedPairingRequest =
240                 new HandshakeHandler.KeyBasedPairingRequest.Builder()
241                         .setVerificationData(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS))
242                         .build();
243         BluetoothGattException exception = new BluetoothGattException("Exception for retry", 133);
244         when(mChangeObserver.waitForUpdate(anyLong())).thenThrow(exception);
245         GattConnectionManager gattConnectionManager =
246                 createGattConnectionManager(Preferences.builder(), () -> {});
247         gattConnectionManager.setGattConnection(mBluetoothGattConnection);
248         when(mBluetoothGattConnection.enableNotification(any(), any()))
249                 .thenReturn(mChangeObserver);
250         InOrder inOrder = inOrder(mEventLoggerWrapper);
251 
252         SignalRotatedException signalRotatedException =
253                 assertThrows(
254                         SignalRotatedException.class,
255                         () -> getHandshakeHandler(
256                                 gattConnectionManager, address -> "AA:BB:CC:DD:EE:FF")
257                                 .doHandshakeWithRetryAndSignalLostCheck(
258                                         PUBLIC_KEY, keyBasedPairingRequest, mRescueFromError));
259 
260         inOrder.verify(mEventLoggerWrapper).setCurrentEvent(
261                 NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION);
262         inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception);
263         assertThat(signalRotatedException.getNewAddress()).isEqualTo("AA:BB:CC:DD:EE:FF");
264         assertThat(signalRotatedException).hasCauseThat().isEqualTo(exception);
265     }
266 
267     @Test
268     @SdkSuppress(minSdkVersion = 32, codeName = "T")
constructBytes_setRetroactiveFlag_decodeCorrectly()269     public void constructBytes_setRetroactiveFlag_decodeCorrectly() throws
270             GeneralSecurityException {
271         HandshakeHandler.KeyBasedPairingRequest keyBasedPairingRequest =
272                 new HandshakeHandler.KeyBasedPairingRequest.Builder()
273                         .setVerificationData(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS))
274                         .addFlag(REQUEST_RETROACTIVE_PAIR)
275                         .setSeekerPublicAddress(BluetoothAddress.decode(SEEKER_ADDRESS))
276                         .build();
277 
278         byte[] encryptedRawMessage =
279                 AesEcbSingleBlockEncryption.encrypt(
280                         SHARED_SECRET, keyBasedPairingRequest.getBytes());
281         HandshakeRequest handshakeRequest =
282                 new HandshakeRequest(SHARED_SECRET, encryptedRawMessage);
283 
284         assertThat(handshakeRequest.getType())
285                 .isEqualTo(HandshakeRequest.Type.KEY_BASED_PAIRING_REQUEST);
286         assertThat(handshakeRequest.requestRetroactivePair()).isTrue();
287         assertThat(handshakeRequest.getVerificationData())
288                 .isEqualTo(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS));
289         assertThat(handshakeRequest.getSeekerPublicAddress())
290                 .isEqualTo(BluetoothAddress.decode(SEEKER_ADDRESS));
291         assertThat(handshakeRequest.requestDeviceName()).isFalse();
292         assertThat(handshakeRequest.requestDiscoverable()).isFalse();
293         assertThat(handshakeRequest.requestProviderInitialBonding()).isFalse();
294     }
295 
296     @Test
297     @SdkSuppress(minSdkVersion = 32, codeName = "T")
getTimeout_notOverShortRetryMaxSpentTime_getShort()298     public void getTimeout_notOverShortRetryMaxSpentTime_getShort() {
299         Preferences preferences = Preferences.builder().build();
300 
301         assertThat(getHandshakeHandler(/* getEnable128BitCustomGattCharacteristicsId= */ true)
302                 .getTimeoutMs(
303                         preferences.getSecretHandshakeShortTimeoutRetryMaxSpentTimeMs()
304                                 - 1))
305                 .isEqualTo(preferences.getSecretHandshakeShortTimeoutMs());
306     }
307 
308     @Test
309     @SdkSuppress(minSdkVersion = 32, codeName = "T")
getTimeout_overShortRetryMaxSpentTime_getLong()310     public void getTimeout_overShortRetryMaxSpentTime_getLong() {
311         Preferences preferences = Preferences.builder().build();
312 
313         assertThat(getHandshakeHandler(/* getEnable128BitCustomGattCharacteristicsId= */ true)
314                 .getTimeoutMs(
315                         preferences.getSecretHandshakeShortTimeoutRetryMaxSpentTimeMs()
316                                 + 1))
317                 .isEqualTo(preferences.getSecretHandshakeLongTimeoutMs());
318     }
319 
320     @Test
321     @SdkSuppress(minSdkVersion = 32, codeName = "T")
getTimeout_retryNotEnabled_getOrigin()322     public void getTimeout_retryNotEnabled_getOrigin() {
323         Preferences preferences = Preferences.builder().build();
324 
325         assertThat(
326                 new HandshakeHandler(
327                         createGattConnectionManager(Preferences.builder(), () -> {}),
328                         PROVIDER_BLE_ADDRESS,
329                         Preferences.builder()
330                                 .setRetryGattConnectionAndSecretHandshake(false).build(),
331                         mEventLoggerWrapper,
332                         /* fastPairSignalChecker= */ null)
333                         .getTimeoutMs(0))
334                 .isEqualTo(Duration.ofSeconds(
335                         preferences.getGattOperationTimeoutSeconds()).toMillis());
336     }
337 
338     @Test
339     @SdkSuppress(minSdkVersion = 32, codeName = "T")
triggersActionOverBle_notCrash()340     public void triggersActionOverBle_notCrash() {
341         HandshakeHandler.ActionOverBle.Builder actionOverBleBuilder =
342                 new HandshakeHandler.ActionOverBle.Builder()
343                         .addFlag(
344                                 Constants.FastPairService.KeyBasedPairingCharacteristic
345                                         .ActionOverBleFlag.ADDITIONAL_DATA_CHARACTERISTIC)
346                         .setVerificationData(BluetoothAddress.decode(RANDOM_PRIVATE_ADDRESS))
347                         .setAdditionalDataType(AdditionalDataType.PERSONALIZED_NAME)
348                         .setEvent(0, 0)
349                         .setEventAdditionalData(new byte[]{1})
350                         .getThis();
351         HandshakeHandler.ActionOverBle actionOverBle = actionOverBleBuilder.build();
352         assertThat(actionOverBle.getBytes().length).isEqualTo(16);
353         assertThat(
354                 Arrays.equals(
355                         Arrays.copyOfRange(actionOverBle.getBytes(), 0, 12),
356                         new byte[]{
357                                 (byte) 16, (byte)  -64, (byte) -69, (byte) -69,
358                                 (byte) -69, (byte)  -69, (byte) -69, (byte) 30,
359                                 (byte) 0, (byte) 0, (byte) 1, (byte) 1}))
360                 .isTrue();
361     }
362 
createGattConnectionManager( Preferences.Builder prefs, ToggleBluetoothTask toggleBluetooth)363     private GattConnectionManager createGattConnectionManager(
364             Preferences.Builder prefs, ToggleBluetoothTask toggleBluetooth) {
365         return new GattConnectionManager(
366                 ApplicationProvider.getApplicationContext(),
367                 prefs.build(),
368                 new EventLoggerWrapper(null),
369                 BluetoothAdapter.getDefaultAdapter(),
370                 toggleBluetooth,
371                 PROVIDER_BLE_ADDRESS,
372                 new TimingLogger("GattConnectionManager", prefs.build()),
373                 /* fastPairSignalChecker= */ null,
374                 /* setMtu= */ false);
375     }
376 
getHandshakeHandler( GattConnectionManager gattConnectionManager, @Nullable FastPairConnection.FastPairSignalChecker fastPairSignalChecker)377     private HandshakeHandler getHandshakeHandler(
378             GattConnectionManager gattConnectionManager,
379             @Nullable FastPairConnection.FastPairSignalChecker fastPairSignalChecker) {
380         return new HandshakeHandler(
381                 gattConnectionManager,
382                 PROVIDER_BLE_ADDRESS,
383                 Preferences.builder()
384                         .setGattConnectionAndSecretHandshakeNoRetryGattError(ImmutableSet.of(257))
385                         .setRetrySecretHandshakeTimeout(true)
386                         .build(),
387                 mEventLoggerWrapper,
388                 fastPairSignalChecker);
389     }
390 
getHandshakeHandler( boolean getEnable128BitCustomGattCharacteristicsId)391     private HandshakeHandler getHandshakeHandler(
392             boolean getEnable128BitCustomGattCharacteristicsId) {
393         return new HandshakeHandler(
394                 createGattConnectionManager(Preferences.builder(), () -> {}),
395                 PROVIDER_BLE_ADDRESS,
396                 Preferences.builder()
397                         .setGattOperationTimeoutSeconds(5)
398                         .setEnable128BitCustomGattCharacteristicsId(
399                                 getEnable128BitCustomGattCharacteristicsId)
400                         .build(),
401                 mEventLoggerWrapper,
402                 /* fastPairSignalChecker= */ null);
403     }
404 
405     private static class HandshakeRequest {
406 
407         /**
408          * 16 bytes data: 1-byte for type, 1-byte for flags, 6-bytes for provider's BLE address, 8
409          * bytes optional data.
410          *
411          * @see {go/fast-pair-spec-handshake-message1}
412          */
413         private final byte[] mDecryptedMessage;
414 
HandshakeRequest(byte[] key, byte[] encryptedPairingRequest)415         HandshakeRequest(byte[] key, byte[] encryptedPairingRequest)
416                 throws GeneralSecurityException {
417             mDecryptedMessage = AesEcbSingleBlockEncryption.decrypt(key, encryptedPairingRequest);
418         }
419 
420         /**
421          * Gets the type of this handshake request. Currently, we have 2 types: 0x00 for Key-based
422          * Pairing Request and 0x10 for Action Request.
423          */
getType()424         public Type getType() {
425             return Type.valueOf(mDecryptedMessage[Request.TYPE_INDEX]);
426         }
427 
428         /**
429          * Gets verification data of this handshake request.
430          * Currently, we use Provider's BLE address.
431          */
getVerificationData()432         public byte[] getVerificationData() {
433             return Arrays.copyOfRange(
434                     mDecryptedMessage,
435                     Request.VERIFICATION_DATA_INDEX,
436                     Request.VERIFICATION_DATA_INDEX + Request.VERIFICATION_DATA_LENGTH);
437         }
438 
439         /** Gets Seeker's public address of the handshake request. */
getSeekerPublicAddress()440         public byte[] getSeekerPublicAddress() {
441             return Arrays.copyOfRange(
442                     mDecryptedMessage,
443                     Request.SEEKER_PUBLIC_ADDRESS_INDEX,
444                     Request.SEEKER_PUBLIC_ADDRESS_INDEX + BLUETOOTH_ADDRESS_LENGTH);
445         }
446 
447         /** Checks whether the Seeker request discoverability from flags byte. */
requestDiscoverable()448         public boolean requestDiscoverable() {
449             return (getFlags() & REQUEST_DISCOVERABLE) != 0;
450         }
451 
452         /**
453          * Checks whether the Seeker requests that the Provider shall initiate bonding from
454          * flags byte.
455          */
requestProviderInitialBonding()456         public boolean requestProviderInitialBonding() {
457             return (getFlags() & PROVIDER_INITIATES_BONDING) != 0;
458         }
459 
460         /** Checks whether the Seeker requests that the Provider shall notify the existing name. */
requestDeviceName()461         public boolean requestDeviceName() {
462             return (getFlags() & REQUEST_DEVICE_NAME) != 0;
463         }
464 
465         /** Checks whether this is for retroactively writing account key. */
requestRetroactivePair()466         public boolean requestRetroactivePair() {
467             return (getFlags() & REQUEST_RETROACTIVE_PAIR) != 0;
468         }
469 
470         /** Gets the flags of this handshake request. */
getFlags()471         private byte getFlags() {
472             return mDecryptedMessage[Request.FLAGS_INDEX];
473         }
474 
475         /** Checks whether the Seeker requests a device action. */
requestDeviceAction()476         public boolean requestDeviceAction() {
477             return (getFlags() & DEVICE_ACTION) != 0;
478         }
479 
480         /**
481          * Checks whether the Seeker requests an action which will be followed by an additional
482          * data.
483          */
requestFollowedByAdditionalData()484         public boolean requestFollowedByAdditionalData() {
485             return (getFlags() & ADDITIONAL_DATA_CHARACTERISTIC) != 0;
486         }
487 
488         /** Gets the {@link AdditionalDataType} of this handshake request. */
489         @AdditionalDataType
getAdditionalDataType()490         public int getAdditionalDataType() {
491             if (!requestFollowedByAdditionalData()
492                     || mDecryptedMessage.length <= ADDITIONAL_DATA_TYPE_INDEX) {
493                 return AdditionalDataType.UNKNOWN;
494             }
495             return mDecryptedMessage[ADDITIONAL_DATA_TYPE_INDEX];
496         }
497 
498         /** Enumerates the handshake message types. */
499         public enum Type {
500             KEY_BASED_PAIRING_REQUEST(Request.TYPE_KEY_BASED_PAIRING_REQUEST),
501             ACTION_OVER_BLE(Request.TYPE_ACTION_OVER_BLE),
502             UNKNOWN((byte) 0xFF);
503 
504             private final byte mValue;
505 
Type(byte type)506             Type(byte type) {
507                 mValue = type;
508             }
509 
valueOf(byte value)510             public static Type valueOf(byte value) {
511                 for (Type type : Type.values()) {
512                     if (type.getValue() == value) {
513                         return type;
514                     }
515                 }
516                 return UNKNOWN;
517             }
518 
getValue()519             public byte getValue() {
520                 return mValue;
521             }
522         }
523     }
524 }
525