blob: 9fe1a3f1f3732fa3eaafd75c7b3c97bcedcef053 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.uwb.discovery.ble;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothStatusCodes;
import android.bluetooth.le.ScanResult;
import android.content.AttributionSource;
import android.content.Context;
import android.uwb.UwbTestUtils;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.uwb.discovery.Transport.DataReceiver;
import com.android.server.uwb.discovery.TransportClientProvider.TransportClientCallback;
import com.android.server.uwb.discovery.TransportProvider;
import com.android.server.uwb.discovery.TransportProvider.TerminationReason;
import com.android.server.uwb.discovery.info.AdminErrorMessage;
import com.android.server.uwb.discovery.info.AdminErrorMessage.ErrorType;
import com.android.server.uwb.discovery.info.FiraConnectorCapabilities;
import com.android.server.uwb.discovery.info.FiraConnectorDataPacket;
import com.android.server.uwb.discovery.info.FiraConnectorMessage;
import com.android.server.uwb.discovery.info.FiraConnectorMessage.InstructionCode;
import com.android.server.uwb.discovery.info.FiraConnectorMessage.MessageType;
import com.android.server.uwb.discovery.info.SecureComponentInfo;
import com.android.server.uwb.discovery.info.TransportClientInfo;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.Arrays;
import java.util.concurrent.Executor;
/** Unit test for {@link GattTransportClientProvider} */
@RunWith(AndroidJUnit4.class)
@SmallTest
public class GattTransportClientProviderTest {
private static final String TAG = GattTransportClientProviderTest.class.getSimpleName();
private static final Executor EXECUTOR = UwbTestUtils.getExecutor();
private static final int SECID = 2;
private static final int SECID2 = 3;
private static final byte[] MESSAGE_PAYLOAD1 = new byte[] {(byte) 0xF4, 0x00, 0x40};
private static final FiraConnectorMessage MESSAGE =
new FiraConnectorMessage(
MessageType.COMMAND, InstructionCode.DATA_EXCHANGE, MESSAGE_PAYLOAD1);
private static final FiraConnectorDataPacket DATA_PACKET =
new FiraConnectorDataPacket(/*lastChainingPacket=*/ true, SECID, MESSAGE.toBytes());
private static final byte[] DATA_PACKET_BYTES = DATA_PACKET.toBytes();
private static final int OPTIMIZED_DATA_PACKET_SIZE = 21;
private static final FiraConnectorCapabilities CAPABILITIES =
new FiraConnectorCapabilities.Builder()
.setOptimizedDataPacketSize(OPTIMIZED_DATA_PACKET_SIZE)
.setMaxMessageBufferSize(265)
.addSecureComponentInfo(
new SecureComponentInfo(
/*static_indication=*/ true,
SECID,
SecureComponentInfo.SecureComponentType.ESE_NONREMOVABLE,
SecureComponentInfo.SecureComponentProtocolType
.FIRA_OOB_ADMINISTRATIVE_PROTOCOL))
.build();
private static final BluetoothGattCharacteristic IN_CHARACTERSTIC =
new BluetoothGattCharacteristic(
UuidConstants.CP_IN_CONTROL_POINT_UUID.getUuid(),
BluetoothGattCharacteristic.PROPERTY_WRITE,
BluetoothGattCharacteristic.PERMISSION_WRITE);
private static final BluetoothGattCharacteristic CAPABILITIES_CHARACTERSTIC =
new BluetoothGattCharacteristic(
UuidConstants.CP_FIRA_CONNECTOR_CAPABILITIES_UUID.getUuid(),
BluetoothGattCharacteristic.PROPERTY_WRITE,
BluetoothGattCharacteristic.PERMISSION_WRITE);
private BluetoothGattCharacteristic mOutCharacterstic =
new BluetoothGattCharacteristic(
UuidConstants.CP_OUT_CONTROL_POINT_UUID.getUuid(),
BluetoothGattCharacteristic.PROPERTY_READ
| BluetoothGattCharacteristic.PROPERTY_NOTIFY,
BluetoothGattCharacteristic.PERMISSION_READ);
private BluetoothGattDescriptor mCccdDescriptor =
new BluetoothGattDescriptor(
UuidConstants.CCCD_UUID.getUuid(), BluetoothGattDescriptor.PERMISSION_READ);
@Mock AttributionSource mMockAttributionSource;
@Mock Context mMockContext;
@Mock TransportClientCallback mMockTransportClientCallback;
// @Mock TransportClientInfo mMockTransportClientInfo;
@Mock ScanResult mMockScanResult;
@Mock BluetoothDevice mMockBluetoothDevice;
@Mock BluetoothGatt mMockBluetoothGatt;
@Mock BluetoothGattService mMockBluetoothGattService;
@Mock DataReceiver mMockDataReceiver;
private TransportClientInfo mTransportClientInfo;
private GattTransportClientProvider mGattTransportClientProvider;
private BluetoothGattCallback mBluetoothGattCallback;
private FiraConnectorCapabilities mDefaultCapabilities =
new FiraConnectorCapabilities.Builder().build();
// For matching the package name of a PackageInfo
private static class CharacteristicMatcher
implements ArgumentMatcher<BluetoothGattCharacteristic> {
private final BluetoothGattCharacteristic mCharacteristic;
CharacteristicMatcher(BluetoothGattCharacteristic characteristic) {
mCharacteristic = characteristic;
}
@Override
public boolean matches(BluetoothGattCharacteristic characteristic) {
return characteristic.getUuid().equals(mCharacteristic.getUuid());
}
@Override
public String toString() {
return String.format(
"BluetoothGattCharacteristic with uuid %s'", mCharacteristic.getUuid());
}
}
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
when(mMockContext.createContext(any())).thenReturn(mMockContext);
when(mMockBluetoothGatt.connect()).thenReturn(true);
when(mMockScanResult.getDevice()).thenReturn(mMockBluetoothDevice);
mTransportClientInfo = new TransportClientInfo(mMockScanResult);
mGattTransportClientProvider =
new GattTransportClientProvider(
mMockAttributionSource,
mMockContext,
EXECUTOR,
SECID,
mTransportClientInfo,
mMockTransportClientCallback);
mGattTransportClientProvider.registerDataReceiver(mMockDataReceiver);
assertThat(mOutCharacterstic.addDescriptor(mCccdDescriptor)).isTrue();
}
@Test
public void testStartAndStop() {
when(mMockBluetoothDevice.connectGatt(eq(mMockContext), anyBoolean(), any(), anyInt()))
.thenReturn(mMockBluetoothGatt);
assertThat(mGattTransportClientProvider.start()).isTrue();
verify(mMockBluetoothDevice, times(1)).connectGatt(any(), anyBoolean(), any(), anyInt());
assertThat(mGattTransportClientProvider.isStarted()).isTrue();
assertThat(mGattTransportClientProvider.stop()).isTrue();
verify(mMockBluetoothGatt, times(1)).disconnect();
assertThat(mGattTransportClientProvider.isStarted()).isFalse();
assertThat(mGattTransportClientProvider.start()).isTrue();
verify(mMockBluetoothGatt, times(1)).connect();
}
@Test
public void testStart_successAndRejectRestart() {
when(mMockBluetoothDevice.connectGatt(eq(mMockContext), anyBoolean(), any(), anyInt()))
.thenReturn(mMockBluetoothGatt);
assertThat(mGattTransportClientProvider.start()).isTrue();
verify(mMockBluetoothDevice, times(1)).connectGatt(any(), anyBoolean(), any(), anyInt());
assertThat(mGattTransportClientProvider.start()).isFalse();
verify(mMockBluetoothDevice, times(1)).connectGatt(any(), anyBoolean(), any(), anyInt());
verify(mMockBluetoothGatt, never()).connect();
}
@Test
public void testStop_successAndRejectRestop() {
when(mMockBluetoothDevice.connectGatt(eq(mMockContext), anyBoolean(), any(), anyInt()))
.thenReturn(mMockBluetoothGatt);
assertThat(mGattTransportClientProvider.start()).isTrue();
verify(mMockBluetoothDevice, times(1)).connectGatt(any(), anyBoolean(), any(), anyInt());
assertThat(mGattTransportClientProvider.stop()).isTrue();
verify(mMockBluetoothGatt, times(1)).disconnect();
assertThat(mGattTransportClientProvider.stop()).isFalse();
verify(mMockBluetoothGatt, times(1)).disconnect();
}
private void setupGattConnect() {
Answer notifyConnectionStateChange =
new Answer() {
public BluetoothGatt answer(InvocationOnMock invocation) {
mBluetoothGattCallback =
(BluetoothGattCallback) invocation.getArgument(/*index=*/ 2);
EXECUTOR.execute(
() ->
mBluetoothGattCallback.onConnectionStateChange(
mMockBluetoothGatt,
BluetoothGatt.GATT_SUCCESS,
BluetoothProfile.STATE_CONNECTED));
return mMockBluetoothGatt;
}
};
doAnswer(notifyConnectionStateChange)
.when(mMockBluetoothDevice)
.connectGatt(eq(mMockContext), anyBoolean(), any(), anyInt());
}
private void setupGattServicesDiscover() {
when(mMockBluetoothGatt.getService(UuidConstants.FIRA_CP_PARCEL_UUID.getUuid()))
.thenReturn(mMockBluetoothGattService);
when(mMockBluetoothGattService.getCharacteristic(
UuidConstants.CP_IN_CONTROL_POINT_UUID.getUuid()))
.thenReturn(IN_CHARACTERSTIC);
when(mMockBluetoothGattService.getCharacteristic(
UuidConstants.CP_OUT_CONTROL_POINT_UUID.getUuid()))
.thenReturn(mOutCharacterstic);
when(mMockBluetoothGattService.getCharacteristic(
UuidConstants.CP_FIRA_CONNECTOR_CAPABILITIES_UUID.getUuid()))
.thenReturn(CAPABILITIES_CHARACTERSTIC);
Answer notifyGattServicesDiscovered =
new Answer() {
public Boolean answer(InvocationOnMock invocation) {
EXECUTOR.execute(
() ->
mBluetoothGattCallback.onServicesDiscovered(
(BluetoothGatt) invocation.getMock(),
BluetoothGatt.GATT_SUCCESS));
return true;
}
};
doAnswer(notifyGattServicesDiscovered).when(mMockBluetoothGatt).discoverServices();
}
private void setupGattWriteCharacteristic(BluetoothGattCharacteristic gattCharacteristic) {
Answer notifyGattCharacteristicWrite =
new Answer() {
public Integer answer(InvocationOnMock invocation) {
EXECUTOR.execute(
() ->
mBluetoothGattCallback.onCharacteristicWrite(
(BluetoothGatt) invocation.getMock(),
(BluetoothGattCharacteristic)
invocation.getArgument(/*index=*/ 0),
BluetoothGatt.GATT_SUCCESS));
return BluetoothStatusCodes.SUCCESS;
}
};
doAnswer(notifyGattCharacteristicWrite)
.when(mMockBluetoothGatt)
.writeCharacteristic(
argThat(new CharacteristicMatcher(gattCharacteristic)), any(), anyInt());
}
private void setupGattWriteDescriptor() {
Answer notifyGattDescriptorWrite =
new Answer() {
public Integer answer(InvocationOnMock invocation) {
EXECUTOR.execute(
() ->
mBluetoothGattCallback.onDescriptorWrite(
(BluetoothGatt) invocation.getMock(),
(BluetoothGattDescriptor)
invocation.getArgument(/*index=*/ 0),
BluetoothGatt.GATT_SUCCESS));
return BluetoothStatusCodes.SUCCESS;
}
};
doAnswer(notifyGattDescriptorWrite).when(mMockBluetoothGatt).writeDescriptor(any(), any());
}
private void startProcessing() {
setupGattConnect();
setupGattServicesDiscover();
setupGattWriteCharacteristic(CAPABILITIES_CHARACTERSTIC);
setupGattWriteDescriptor();
setupGattWriteCharacteristic(IN_CHARACTERSTIC);
assertThat(mGattTransportClientProvider.start()).isTrue();
verify(mMockTransportClientCallback, times(1)).onProcessingStarted();
}
@Test
public void testStartProcessing_serviceNotDiscovered() {
setupGattConnect();
when(mMockBluetoothGatt.discoverServices()).thenReturn(true);
assertThat(mGattTransportClientProvider.start()).isTrue();
verify(mMockBluetoothDevice, times(1)).connectGatt(any(), anyBoolean(), any(), anyInt());
verify(mMockBluetoothGatt, times(1)).discoverServices();
verifyZeroInteractions(mMockTransportClientCallback);
}
@Test
public void testStartProcessing_capabilitiesNotWritten() {
setupGattConnect();
setupGattServicesDiscover();
when(mMockBluetoothGatt.writeCharacteristic(any(), any(), anyInt()))
.thenReturn(BluetoothStatusCodes.ERROR_UNKNOWN);
assertThat(mGattTransportClientProvider.start()).isTrue();
verify(mMockBluetoothDevice, times(1)).connectGatt(any(), anyBoolean(), any(), anyInt());
verify(mMockBluetoothGatt, times(1)).discoverServices();
verify(mMockBluetoothGatt, times(1))
.getService(UuidConstants.FIRA_CP_PARCEL_UUID.getUuid());
verify(mMockBluetoothGattService, times(3)).getCharacteristic(any());
verify(mMockBluetoothGatt, times(1))
.writeCharacteristic(
any(),
eq(mDefaultCapabilities.toBytes()),
eq(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT));
verify(mMockTransportClientCallback, never()).onProcessingStarted();
verify(mMockTransportClientCallback, times(1))
.onTerminated(TerminationReason.CHARACTERSTIC_WRITE_FAILURE);
}
@Test
public void testStartProcessing_notificationNotEnabled() {
setupGattConnect();
setupGattServicesDiscover();
setupGattWriteCharacteristic(CAPABILITIES_CHARACTERSTIC);
when(mMockBluetoothGatt.writeDescriptor(any(), any()))
.thenReturn(BluetoothStatusCodes.ERROR_UNKNOWN);
assertThat(mGattTransportClientProvider.start()).isTrue();
verify(mMockBluetoothDevice, times(1)).connectGatt(any(), anyBoolean(), any(), anyInt());
verify(mMockBluetoothGatt, times(1)).discoverServices();
verify(mMockBluetoothGatt, times(1))
.getService(UuidConstants.FIRA_CP_PARCEL_UUID.getUuid());
verify(mMockBluetoothGattService, times(3)).getCharacteristic(any());
verify(mMockBluetoothGatt, times(1))
.writeCharacteristic(
any(),
eq(mDefaultCapabilities.toBytes()),
eq(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT));
verify(mMockBluetoothGatt, times(1))
.writeDescriptor(any(), eq(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE));
verify(mMockTransportClientCallback, never()).onProcessingStarted();
verify(mMockTransportClientCallback, times(1))
.onTerminated(TerminationReason.DESCRIPTOR_WRITE_FAILURE);
}
@Test
public void testStartProcessing_succeed() {
startProcessing();
verify(mMockBluetoothDevice, times(1)).connectGatt(any(), anyBoolean(), any(), anyInt());
verify(mMockBluetoothGatt, times(1)).discoverServices();
verify(mMockBluetoothGatt, times(1))
.getService(UuidConstants.FIRA_CP_PARCEL_UUID.getUuid());
verify(mMockBluetoothGattService, times(3)).getCharacteristic(any());
verify(mMockBluetoothGatt, times(1))
.writeCharacteristic(
any(),
eq(mDefaultCapabilities.toBytes()),
eq(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT));
verify(mMockBluetoothGatt, times(1))
.writeDescriptor(any(), eq(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE));
verify(mMockTransportClientCallback, times(1)).onProcessingStarted();
}
@Test
public void testSetCapabilities_emptyCapabilities() {
assertThat(mGattTransportClientProvider.setCapabilites(null)).isFalse();
}
@Test
public void testSetCapabilities_clientNotStarted() {
assertThat(mGattTransportClientProvider.setCapabilites(CAPABILITIES)).isFalse();
}
@Test
public void testSetCapabilities_serviceNotDiscovered() {
setupGattConnect();
when(mMockBluetoothGatt.discoverServices()).thenReturn(true);
assertThat(mGattTransportClientProvider.start()).isTrue();
assertThat(mGattTransportClientProvider.setCapabilites(CAPABILITIES)).isFalse();
}
@Test
public void testSetCapabilities_charactersticWriteFailed() {
startProcessing();
doReturn(BluetoothStatusCodes.ERROR_UNKNOWN)
.when(mMockBluetoothGatt)
.writeCharacteristic(any(), any(), anyInt());
assertThat(mGattTransportClientProvider.setCapabilites(CAPABILITIES)).isFalse();
}
@Test
public void testSetCapabilities_succeed() {
startProcessing();
assertThat(mGattTransportClientProvider.setCapabilites(CAPABILITIES)).isTrue();
}
@Test
public void testOnMtuChanged_failed() {
startProcessing();
mBluetoothGattCallback.onMtuChanged(
mMockBluetoothGatt, /*mtu=*/ 40, BluetoothStatusCodes.ERROR_UNKNOWN);
assertThat(mGattTransportClientProvider.setCapabilites(CAPABILITIES)).isTrue();
}
@Test
public void testOnMtuChanged_succeed() {
startProcessing();
mBluetoothGattCallback.onMtuChanged(
mMockBluetoothGatt, /*mtu=*/ 40, BluetoothStatusCodes.ERROR_UNKNOWN);
assertThat(mGattTransportClientProvider.setCapabilites(CAPABILITIES)).isTrue();
}
@Test
public void testSendMessage_processingNotStarted() {
assertThat(mGattTransportClientProvider.sendMessage(SECID, MESSAGE)).isFalse();
}
@Test
public void testSendMessage_messageLengthGreaterThanCapabilitites() {
startProcessing();
byte[] bytes = new byte[270];
Arrays.fill(bytes, (byte) 1);
FiraConnectorMessage message =
new FiraConnectorMessage(MessageType.EVENT, InstructionCode.DATA_EXCHANGE, bytes);
// Capabilities set the max message length to 265, so a message of length 270 exceeded the
// limit.
assertThat(mGattTransportClientProvider.sendMessage(SECID, message)).isFalse();
}
@Test
public void testSendMessage_charactersticWriteFailed() {
startProcessing();
doReturn(BluetoothStatusCodes.ERROR_UNKNOWN)
.when(mMockBluetoothGatt)
.writeCharacteristic(
argThat(new CharacteristicMatcher(IN_CHARACTERSTIC)), any(), anyInt());
assertThat(mGattTransportClientProvider.sendMessage(SECID, MESSAGE)).isFalse();
verify(mMockTransportClientCallback, times(1))
.onTerminated(TerminationReason.CHARACTERSTIC_WRITE_FAILURE);
}
@Test
public void testSendMessage_succeedOnePacket() {
startProcessing();
assertThat(mGattTransportClientProvider.sendMessage(SECID, MESSAGE)).isTrue();
verify(mMockBluetoothGatt, times(1))
.writeCharacteristic(
argThat(new CharacteristicMatcher(IN_CHARACTERSTIC)), any(), anyInt());
verify(mMockBluetoothGatt, times(1))
.writeCharacteristic(
argThat(new CharacteristicMatcher(IN_CHARACTERSTIC)),
eq(DATA_PACKET_BYTES),
eq(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT));
}
@Test
public void testSendMessage_succeedThreePacket() {
byte[] messagePayload = new byte[51];
Arrays.fill(messagePayload, (byte) 3);
FiraConnectorMessage message =
new FiraConnectorMessage(
MessageType.EVENT, InstructionCode.DATA_EXCHANGE, messagePayload);
byte[] messageBytes = message.toBytes();
int payloadSize = mDefaultCapabilities.optimizedDataPacketSize - 1;
byte[] packet_bytes1 =
new FiraConnectorDataPacket(
/*lastChainingPacket=*/ false,
SECID,
Arrays.copyOf(messageBytes, payloadSize))
.toBytes();
byte[] packet_bytes2 =
new FiraConnectorDataPacket(
/*lastChainingPacket=*/ false,
SECID,
Arrays.copyOfRange(messageBytes, payloadSize, 2 * payloadSize))
.toBytes();
byte[] packet_bytes3 =
new FiraConnectorDataPacket(
/*lastChainingPacket=*/ true,
SECID,
Arrays.copyOfRange(
messageBytes, 2 * payloadSize, messageBytes.length))
.toBytes();
startProcessing();
assertThat(mGattTransportClientProvider.sendMessage(SECID, message)).isTrue();
ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class);
verify(mMockBluetoothGatt, times(3))
.writeCharacteristic(
argThat(new CharacteristicMatcher(IN_CHARACTERSTIC)),
captor.capture(),
anyInt());
assertThat(captor.getAllValues().get(0)).isEqualTo(packet_bytes1);
assertThat(captor.getAllValues().get(1)).isEqualTo(packet_bytes2);
assertThat(captor.getAllValues().get(2)).isEqualTo(packet_bytes3);
}
private void notifyAndReadOutCharacteristic(byte[] bytes) {
Answer notifyGattCharacteristicRead =
new Answer() {
public Boolean answer(InvocationOnMock invocation) {
EXECUTOR.execute(
() ->
mBluetoothGattCallback.onCharacteristicRead(
(BluetoothGatt) invocation.getMock(),
(BluetoothGattCharacteristic)
invocation.getArgument(/*index=*/ 0),
bytes,
BluetoothGatt.GATT_SUCCESS));
return true;
}
};
doAnswer(notifyGattCharacteristicRead)
.when(mMockBluetoothGatt)
.readCharacteristic(argThat(new CharacteristicMatcher(mOutCharacterstic)));
mBluetoothGattCallback.onCharacteristicChanged(
mMockBluetoothGatt, mOutCharacterstic, bytes);
}
@Test
public void testOutCharactersticNotifyAndRead_readFailed() {
startProcessing();
when(mMockBluetoothGatt.readCharacteristic(any())).thenReturn(false);
mBluetoothGattCallback.onCharacteristicChanged(
mMockBluetoothGatt, mOutCharacterstic, DATA_PACKET_BYTES);
verify(mMockBluetoothGatt, times(1))
.readCharacteristic(argThat(new CharacteristicMatcher(mOutCharacterstic)));
verify(mMockTransportClientCallback, times(1))
.onTerminated(TerminationReason.CHARACTERSTIC_READ_FAILURE);
}
@Test
public void testOutCharactersticNotifyAndRead_emptyPacket() {
startProcessing();
notifyAndReadOutCharacteristic(new byte[] {});
verify(mMockBluetoothGatt, times(1))
.readCharacteristic(argThat(new CharacteristicMatcher(mOutCharacterstic)));
verifyNoMoreInteractions(mMockTransportClientCallback);
}
@Test
public void testOutCharactersticNotifyAndRead_succeedOnePacket() {
startProcessing();
notifyAndReadOutCharacteristic(DATA_PACKET_BYTES);
verify(mMockBluetoothGatt, times(1))
.readCharacteristic(argThat(new CharacteristicMatcher(mOutCharacterstic)));
ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class);
verify(mMockDataReceiver, times(1)).onDataReceived(captor.capture());
assertThat(captor.getValue()).isEqualTo(MESSAGE.payload);
}
@Test
public void testOutCharactersticNotifyAndRead_succeedThreePackets() {
byte[] messagePayload = new byte[51];
Arrays.fill(messagePayload, (byte) 3);
FiraConnectorMessage message =
new FiraConnectorMessage(
MessageType.COMMAND, InstructionCode.DATA_EXCHANGE, messagePayload);
byte[] messageBytes = message.toBytes();
int payloadSize = OPTIMIZED_DATA_PACKET_SIZE - 1;
byte[] packet_bytes1 =
new FiraConnectorDataPacket(
/*lastChainingPacket=*/ false,
SECID,
Arrays.copyOf(messageBytes, payloadSize))
.toBytes();
byte[] packet_bytes2 =
new FiraConnectorDataPacket(
/*lastChainingPacket=*/ false,
SECID,
Arrays.copyOfRange(messageBytes, payloadSize, 2 * payloadSize))
.toBytes();
byte[] packet_bytes3 =
new FiraConnectorDataPacket(
/*lastChainingPacket=*/ true,
SECID,
Arrays.copyOfRange(
messageBytes, 2 * payloadSize, messageBytes.length))
.toBytes();
startProcessing();
assertThat(mGattTransportClientProvider.setCapabilites(CAPABILITIES)).isTrue();
notifyAndReadOutCharacteristic(packet_bytes1);
notifyAndReadOutCharacteristic(packet_bytes2);
notifyAndReadOutCharacteristic(packet_bytes3);
verify(mMockBluetoothGatt, times(3))
.readCharacteristic(argThat(new CharacteristicMatcher(mOutCharacterstic)));
ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class);
verify(mMockDataReceiver, times(1)).onDataReceived(captor.capture());
assertThat(captor.getValue()).isEqualTo(message.payload);
}
@Test
public void testOutCharactersticNotifyAndRead_receiveAdminPacket() {
byte[] packetBytes =
new FiraConnectorDataPacket(
/*lastChainingPacket=*/ true,
TransportProvider.ADMIN_SECID,
new AdminErrorMessage(ErrorType.SECID_INVALID).toBytes())
.toBytes();
startProcessing();
notifyAndReadOutCharacteristic(packetBytes);
verify(mMockBluetoothGatt, times(1))
.readCharacteristic(argThat(new CharacteristicMatcher(mOutCharacterstic)));
verify(mMockDataReceiver, never()).onDataReceived(any());
verify(mMockTransportClientCallback, times(1))
.onTerminated(TerminationReason.REMOTE_DEVICE_SECID_ERROR);
assertThat(mGattTransportClientProvider.isStarted()).isFalse();
}
@Test
public void testOutCharactersticNotifyAndRead_packetLengthOverflow() {
byte[] messagePayload = new byte[OPTIMIZED_DATA_PACKET_SIZE + 1];
Arrays.fill(messagePayload, (byte) 3);
byte[] messageBytes =
new FiraConnectorMessage(
MessageType.COMMAND, InstructionCode.DATA_EXCHANGE, messagePayload)
.toBytes();
byte[] packet_bytes =
new FiraConnectorDataPacket(
/*lastChainingPacket=*/ false,
SECID,
Arrays.copyOf(messageBytes, messageBytes.length))
.toBytes();
FiraConnectorDataPacket expectedInPacket =
new FiraConnectorDataPacket(
/*lastChainingPacket=*/ true,
TransportProvider.ADMIN_SECID,
new AdminErrorMessage(ErrorType.DATA_PACKET_LENGTH_OVERFLOW).toBytes());
startProcessing();
assertThat(mGattTransportClientProvider.setCapabilites(CAPABILITIES)).isTrue();
notifyAndReadOutCharacteristic(packet_bytes);
verify(mMockBluetoothGatt, times(1))
.readCharacteristic(argThat(new CharacteristicMatcher(mOutCharacterstic)));
verify(mMockDataReceiver, never()).onDataReceived(any());
verify(mMockBluetoothGatt, times(1))
.writeCharacteristic(
argThat(new CharacteristicMatcher(IN_CHARACTERSTIC)),
eq(expectedInPacket.toBytes()),
eq(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT));
}
@Test
public void testOutCharactersticNotifyAndRead_tooManyConcurrentSessions() {
byte[] messagePayload = new byte[24];
Arrays.fill(messagePayload, (byte) 3);
int payloadSize = OPTIMIZED_DATA_PACKET_SIZE - 1;
byte[] messageBytes =
new FiraConnectorMessage(
MessageType.COMMAND, InstructionCode.DATA_EXCHANGE, messagePayload)
.toBytes();
byte[] packet_bytes1 =
new FiraConnectorDataPacket(
/*lastChainingPacket=*/ false,
SECID,
Arrays.copyOf(messageBytes, payloadSize))
.toBytes();
byte[] packet_bytes2 =
new FiraConnectorDataPacket(
/*lastChainingPacket=*/ true,
SECID2,
Arrays.copyOfRange(messageBytes, payloadSize, messageBytes.length))
.toBytes();
FiraConnectorDataPacket expectedInPacket =
new FiraConnectorDataPacket(
/*lastChainingPacket=*/ true,
TransportProvider.ADMIN_SECID,
new AdminErrorMessage(
ErrorType.TOO_MANY_CONCURRENT_FRAGMENTED_MESSAGE_SESSIONS)
.toBytes());
startProcessing();
assertThat(mGattTransportClientProvider.setCapabilites(CAPABILITIES)).isTrue();
notifyAndReadOutCharacteristic(packet_bytes1);
notifyAndReadOutCharacteristic(packet_bytes2);
verify(mMockBluetoothGatt, times(2))
.readCharacteristic(argThat(new CharacteristicMatcher(mOutCharacterstic)));
verify(mMockDataReceiver, never()).onDataReceived(any());
verify(mMockBluetoothGatt, times(1))
.writeCharacteristic(
argThat(new CharacteristicMatcher(IN_CHARACTERSTIC)),
eq(expectedInPacket.toBytes()),
eq(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT));
}
@Test
public void testOutCharactersticNotifyAndRead_messageLengthOverflow() {
int payloadSize = 200;
FiraConnectorCapabilities capabilities =
new FiraConnectorCapabilities.Builder()
.setOptimizedDataPacketSize(payloadSize + 1)
.setMaxMessageBufferSize(264)
.build();
byte[] messagePayload = new byte[265];
Arrays.fill(messagePayload, (byte) 3);
byte[] messageBytes =
new FiraConnectorMessage(
MessageType.COMMAND, InstructionCode.DATA_EXCHANGE, messagePayload)
.toBytes();
byte[] packet_bytes1 =
new FiraConnectorDataPacket(
/*lastChainingPacket=*/ false,
SECID,
Arrays.copyOf(messageBytes, payloadSize))
.toBytes();
byte[] packet_bytes2 =
new FiraConnectorDataPacket(
/*lastChainingPacket=*/ true,
SECID,
Arrays.copyOfRange(messageBytes, payloadSize, messageBytes.length))
.toBytes();
FiraConnectorDataPacket expectedInPacket =
new FiraConnectorDataPacket(
/*lastChainingPacket=*/ true,
TransportProvider.ADMIN_SECID,
new AdminErrorMessage(ErrorType.MESSAGE_LENGTH_OVERFLOW).toBytes());
startProcessing();
assertThat(mGattTransportClientProvider.setCapabilites(capabilities)).isTrue();
notifyAndReadOutCharacteristic(packet_bytes1);
notifyAndReadOutCharacteristic(packet_bytes2);
verify(mMockBluetoothGatt, times(2))
.readCharacteristic(argThat(new CharacteristicMatcher(mOutCharacterstic)));
verify(mMockDataReceiver, never()).onDataReceived(any());
verify(mMockBluetoothGatt, times(1))
.writeCharacteristic(
argThat(new CharacteristicMatcher(IN_CHARACTERSTIC)),
eq(expectedInPacket.toBytes()),
eq(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT));
}
}