[uwb] Integrate admin messages handling
* Add admin message handling for CS GATT client and CP GATT server.
Bug: 242272072
Test: atest ServiceUwbTests
Change-Id: I27d1c6711cb3ea1f451ea0332625a734f0b252d3
diff --git a/service/java/com/android/server/uwb/discovery/TransportClientProvider.java b/service/java/com/android/server/uwb/discovery/TransportClientProvider.java
index 73bf432..2ecc2e7 100644
--- a/service/java/com/android/server/uwb/discovery/TransportClientProvider.java
+++ b/service/java/com/android/server/uwb/discovery/TransportClientProvider.java
@@ -24,38 +24,9 @@
public abstract class TransportClientProvider extends TransportProvider {
private static final String TAG = TransportClientProvider.class.getSimpleName();
- public enum TerminationReason {
- /** Disconnection of the remote GATT service. */
- REMOTE_DISCONNECTED,
- /** remote GATT service discovery failure. */
- SERVICE_DISCOVERY_FAILURE,
- /** Characterstic read failure */
- CHARACTERSTIC_READ_FAILURE,
- /** Characterstic write failure */
- CHARACTERSTIC_WRITE_FAILURE,
- /** Descriptor write failure */
- DESCRIPTOR_WRITE_FAILURE,
- }
-
- public @interface NotifyCharacteristicReturnValues {}
-
/** Callback for listening to transport client events. */
@WorkerThread
- public interface TransportClientCallback {
-
- /** Called when the client started processing. */
- void onProcessingStarted();
-
- /** Called when the client stopped processing. */
- void onProcessingStopped();
-
- /**
- * Called when the client terminated the connection due to an unrecoverable errors.
- *
- * @param reason indicates the termination reason.
- */
- void onTerminated(TerminationReason reason);
- }
+ public abstract static class TransportClientCallback implements TransportCallback {}
protected TransportClientProvider(int secid) {
super(secid);
diff --git a/service/java/com/android/server/uwb/discovery/TransportProvider.java b/service/java/com/android/server/uwb/discovery/TransportProvider.java
index 83d2c1e..97cd379 100644
--- a/service/java/com/android/server/uwb/discovery/TransportProvider.java
+++ b/service/java/com/android/server/uwb/discovery/TransportProvider.java
@@ -20,14 +20,60 @@
import androidx.annotation.NonNull;
+import com.android.server.uwb.discovery.info.AdminErrorMessage;
+import com.android.server.uwb.discovery.info.AdminErrorMessage.ErrorType;
+import com.android.server.uwb.discovery.info.AdminEventMessage;
+import com.android.server.uwb.discovery.info.AdminEventMessage.EventType;
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 java.nio.ByteBuffer;
+
/** Abstract class for Transport Provider */
public abstract class TransportProvider implements Transport {
private static final String TAG = TransportProvider.class.getSimpleName();
+ public enum TerminationReason {
+ /** Disconnection of the remote GATT service. */
+ REMOTE_DISCONNECTED,
+ /** remote GATT service discovery failure. */
+ SERVICE_DISCOVERY_FAILURE,
+ /** Characterstic read failure */
+ CHARACTERSTIC_READ_FAILURE,
+ /** Characterstic write failure */
+ CHARACTERSTIC_WRITE_FAILURE,
+ /** Descriptor write failure */
+ DESCRIPTOR_WRITE_FAILURE,
+ /** Remote device message error */
+ REMOTE_DEVICE_MESSAGE_ERROR,
+ /** Remote device SECID error */
+ REMOTE_DEVICE_SECID_ERROR,
+ }
+
+ /** Callback for listening to transport events. */
+ public interface TransportCallback {
+
+ /** Called when the transport started processing. */
+ void onProcessingStarted();
+
+ /** Called when the transport stopped processing. */
+ void onProcessingStopped();
+
+ /**
+ * Called when the transport terminated the connection due to an unrecoverable errors.
+ *
+ * @param reason indicates the termination reason.
+ */
+ void onTerminated(TerminationReason reason);
+ }
+
+ /**
+ * administrative SECID shall be exposed on each CS implementation at all times. It shall be
+ * marked as static.
+ */
+ public static final int ADMIN_SECID = 1;
+
private DataReceiver mDataReceiver;
/** Assigned SECID value (unsigned integer in the range 2..127, values 0 and 1 are reserved). */
@@ -39,6 +85,17 @@
*/
private int mDestinationSecid = 2;
+ /** Wraps Fira Connector Message byte array and the associated SECID. */
+ public static class MessagePacket {
+ public final int secid;
+ public ByteBuffer messageBytes;
+
+ public MessagePacket(int secid, ByteBuffer messageBytes) {
+ this.secid = secid;
+ this.messageBytes = messageBytes;
+ }
+ }
+
protected TransportProvider(@IntRange(from = 2, to = 127) int secid) {
mSecid = secid;
}
@@ -144,6 +201,10 @@
* @param message FiRa connector message.
*/
protected void onMessageReceived(int secid, FiraConnectorMessage message) {
+ if (secid == ADMIN_SECID) {
+ processAdminMessage(message);
+ return;
+ }
if (secid != mSecid) {
Log.w(
TAG,
@@ -151,10 +212,80 @@
+ mSecid
+ " Received:"
+ secid);
+ sentAdminErrorMessage(ErrorType.SECID_INVALID);
return;
}
if (mDataReceiver != null) {
mDataReceiver.onDataReceived(message.payload);
}
}
+
+ /**
+ * Send a FiRa OOB administrative Error message to the administrative SECID on the remote
+ * device.
+ *
+ * @param errorType ErrorType of the message.
+ */
+ protected void sentAdminErrorMessage(ErrorType errorType) {
+ if (!sendMessage(ADMIN_SECID, new AdminErrorMessage(errorType))) {
+ Log.w(TAG, "sentAdminErrorMessage with ErrorType:" + errorType + " failed.");
+ }
+ }
+
+ /**
+ * Send a FiRa OOB administrative Event message to the administrative SECID on the remote
+ * device.
+ *
+ * @param eventType EventType of the message.
+ * @param additionalData additional data associated with the event.
+ */
+ protected void sentAdminEventMessage(EventType eventType, byte[] additionalData) {
+ if (!sendMessage(ADMIN_SECID, new AdminEventMessage(eventType, additionalData))) {
+ Log.w(TAG, "sentAdminEventMessage with EventType:" + eventType + " failed.");
+ }
+ }
+
+ /**
+ * Process FiRa OOB administrative message from the remote device.
+ *
+ * @param message FiRa connector message.
+ */
+ private void processAdminMessage(FiraConnectorMessage message) {
+ if (AdminErrorMessage.isAdminErrorMessage(message)) {
+ AdminErrorMessage errorMessage = AdminErrorMessage.convertToAdminErrorMessage(message);
+ Log.w(TAG, "Received AdminErrorMessage:" + errorMessage);
+ switch (errorMessage.errorType) {
+ case DATA_PACKET_LENGTH_OVERFLOW:
+ case MESSAGE_LENGTH_OVERFLOW:
+ case TOO_MANY_CONCURRENT_FRAGMENTED_MESSAGE_SESSIONS:
+ terminateOnError(TerminationReason.REMOTE_DEVICE_MESSAGE_ERROR);
+ break;
+ case SECID_INVALID:
+ case SECID_INVALID_FOR_RESPONSE:
+ case SECID_BUSY:
+ case SECID_PROTOCOL_ERROR:
+ case SECID_INTERNAL_ERROR:
+ terminateOnError(TerminationReason.REMOTE_DEVICE_SECID_ERROR);
+ break;
+ }
+ } else if (AdminEventMessage.isAdminEventMessage(message)) {
+ AdminEventMessage eventMessage = AdminEventMessage.convertToAdminEventMessage(message);
+ Log.w(TAG, "Received AdminEventMessage:" + eventMessage);
+ switch (eventMessage.eventType) {
+ case CAPABILITIES_CHANGED:
+ // No-op since this is only applicatble for CS with the role of GATT Server,
+ // which isn't mandated by FiRa.
+ break;
+ }
+ } else {
+ Log.e(TAG, "Invalid Admin FiraConnectorMessage received:" + message);
+ }
+ }
+
+ /**
+ * Terminates the transport provider.
+ *
+ * @param reason reason for the termination.
+ */
+ protected abstract void terminateOnError(TerminationReason reason);
}
diff --git a/service/java/com/android/server/uwb/discovery/TransportServerProvider.java b/service/java/com/android/server/uwb/discovery/TransportServerProvider.java
index a33b381..db5a3db 100644
--- a/service/java/com/android/server/uwb/discovery/TransportServerProvider.java
+++ b/service/java/com/android/server/uwb/discovery/TransportServerProvider.java
@@ -26,20 +26,13 @@
/** Callback for listening to transport server events. */
@WorkerThread
- public interface TransportServerCallback {
-
- /** Called when the server started processing. */
- void onProcessingStarted();
-
- /** Called when the server stopped processing. */
- void onProcessingStopped();
-
+ public abstract static class TransportServerCallback implements TransportCallback {
/**
* Called when the server receive new capabilites from the remote device.
*
* @param capabilities new capabilities.
*/
- void onCapabilitesUpdated(FiraConnectorCapabilities capabilities);
+ public abstract void onCapabilitesUpdated(FiraConnectorCapabilities capabilities);
}
protected TransportServerProvider(int secid) {
diff --git a/service/java/com/android/server/uwb/discovery/ble/GattTransportClientProvider.java b/service/java/com/android/server/uwb/discovery/ble/GattTransportClientProvider.java
index b95f84b..c1d8bc1 100644
--- a/service/java/com/android/server/uwb/discovery/ble/GattTransportClientProvider.java
+++ b/service/java/com/android/server/uwb/discovery/ble/GattTransportClientProvider.java
@@ -31,8 +31,10 @@
import androidx.annotation.WorkerThread;
import com.android.server.uwb.discovery.TransportClientProvider;
-import com.android.server.uwb.discovery.TransportClientProvider.TerminationReason;
import com.android.server.uwb.discovery.TransportClientProvider.TransportClientCallback;
+import com.android.server.uwb.discovery.TransportProvider.MessagePacket;
+import com.android.server.uwb.discovery.TransportProvider.TerminationReason;
+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;
@@ -45,7 +47,7 @@
import java.util.concurrent.Executor;
/**
- * Class for UWB transport client provider using Bluetooth GATT.
+ * Class for FiRa CS UWB transport client provider using Bluetooth GATT.
*
* <p>The GATT client is responsible for the entire Service discovery procedure. Once the device
* discovery phase passed, the client establishes the Bluetooth connection and perform GATT service
@@ -84,18 +86,6 @@
*/
private ArrayDeque<FiraConnectorDataPacket> mIncompleteOutDataPacketQueue;
- /* Wraps Fira Connector Message byte array and the associated SECID.
- */
- private static class MessagePacket {
- public final int secid;
- public ByteBuffer messageBytes;
-
- MessagePacket(int secid, ByteBuffer messageBytes) {
- this.secid = secid;
- this.messageBytes = messageBytes;
- }
- }
-
/* Queue of Fira Connector Message wrapped as MessagePacket to be sent via the
* mInControlPointCharacteristic.
*/
@@ -435,6 +425,11 @@
Log.w(TAG, "processOutDataPacket failed due to server not ready for processing.");
return false;
}
+ if (bytes.length > mCapabilities.optimizedDataPacketSize) {
+ Log.w(TAG, "processOutDataPacket failed due to data packet length overflow.");
+ super.sentAdminErrorMessage(ErrorType.DATA_PACKET_LENGTH_OVERFLOW);
+ return false;
+ }
FiraConnectorDataPacket latestDataPacket = FiraConnectorDataPacket.fromBytes(bytes);
if (latestDataPacket == null) {
Log.w(
@@ -449,6 +444,7 @@
TAG,
"processOutDataPacket failed due to latest FiraConnectorDataPacket's SECID"
+ " doesn't match previous data packet.");
+ super.sentAdminErrorMessage(ErrorType.TOO_MANY_CONCURRENT_FRAGMENTED_MESSAGE_SESSIONS);
return false;
}
mIncompleteOutDataPacketQueue.add(latestDataPacket);
@@ -462,6 +458,12 @@
}
mIncompleteOutDataPacketQueue.clear();
+ if (byteStream.size() > mCapabilities.maxMessageBufferSize) {
+ Log.w(TAG, "processOutDataPacket failed due to message length overflow.");
+ super.sentAdminErrorMessage(ErrorType.MESSAGE_LENGTH_OVERFLOW);
+ return false;
+ }
+
FiraConnectorMessage message = FiraConnectorMessage.fromBytes(byteStream.toByteArray());
if (message == null) {
Log.w(
@@ -586,8 +588,9 @@
}
}
- private void terminateOnError(TerminationReason reason) {
- Log.e(TAG, "GattTransportClient terminated with reason:" + reason);
+ @Override
+ protected void terminateOnError(TerminationReason reason) {
+ Log.e(TAG, "GattTransportClientProvider terminated with reason:" + reason);
stop();
mTransportClientCallback.onTerminated(reason);
}
diff --git a/service/java/com/android/server/uwb/discovery/ble/GattTransportServerProvider.java b/service/java/com/android/server/uwb/discovery/ble/GattTransportServerProvider.java
index 9d9bfc8..17412a6 100644
--- a/service/java/com/android/server/uwb/discovery/ble/GattTransportServerProvider.java
+++ b/service/java/com/android/server/uwb/discovery/ble/GattTransportServerProvider.java
@@ -31,6 +31,8 @@
import androidx.annotation.WorkerThread;
+import com.android.server.uwb.discovery.TransportProvider.MessagePacket;
+import com.android.server.uwb.discovery.TransportProvider.TerminationReason;
import com.android.server.uwb.discovery.TransportServerProvider;
import com.android.server.uwb.discovery.TransportServerProvider.TransportServerCallback;
import com.android.server.uwb.discovery.info.FiraConnectorCapabilities;
@@ -43,7 +45,7 @@
import java.util.Arrays;
/**
- * Class for UWB transport server provider using Bluetooth GATT.
+ * Class for FiRa CP UWB transport server provider using Bluetooth GATT.
*
* <p>The GATT server simply waits for the discovery from client side. It shall also wait for at
* least one valid update of FiRa Connector Capabilities characteristic value from the client side.
@@ -79,18 +81,6 @@
*/
private ArrayDeque<FiraConnectorDataPacket> mIncompleteInDataPacketQueue;
- /* Wraps Fira Connector Message byte array and the associated SECID.
- */
- private static class MessagePacket {
- public final int secid;
- public ByteBuffer messageBytes;
-
- MessagePacket(int secid, ByteBuffer messageBytes) {
- this.secid = secid;
- this.messageBytes = messageBytes;
- }
- }
-
/* Queue of Fira Connector Message wrapped as MessagePacket to be sent via the
* mOutControlPointCharacteristic.
*/
@@ -497,4 +487,11 @@
BluetoothGattCharacteristic.PERMISSION_WRITE);
mFiraCPService.addCharacteristic(mCapabilitiesCharacteristic);
}
+
+ @Override
+ protected void terminateOnError(TerminationReason reason) {
+ Log.e(TAG, "GattTransportServerProvider terminated with reason:" + reason);
+ stop();
+ mTransportServerCallback.onTerminated(reason);
+ }
}
diff --git a/service/tests/src/com/android/server/uwb/discovery/TransportProviderTest.java b/service/tests/src/com/android/server/uwb/discovery/TransportProviderTest.java
index f1d879f..f417def 100644
--- a/service/tests/src/com/android/server/uwb/discovery/TransportProviderTest.java
+++ b/service/tests/src/com/android/server/uwb/discovery/TransportProviderTest.java
@@ -18,6 +18,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -28,6 +30,11 @@
import com.android.server.uwb.discovery.Transport.DataReceiver;
import com.android.server.uwb.discovery.Transport.SendingDataCallback;
+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.AdminEventMessage;
+import com.android.server.uwb.discovery.info.AdminEventMessage.EventType;
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;
@@ -56,6 +63,7 @@
public boolean sendMessageSuccess = true;
public FiraConnectorMessage lastSendMessage;
public int lastSendMessageSecid;
+ public TerminationReason lastTerminationReason;
FakeTransportProvider() {
super(SECID);
@@ -85,6 +93,11 @@
mStarted = false;
return true;
}
+
+ @Override
+ protected void terminateOnError(TerminationReason reason) {
+ lastTerminationReason = reason;
+ }
}
@Mock DataReceiver mMockDataReceiver;
@@ -169,4 +182,56 @@
verifyZeroInteractions(mMockDataReceiver);
}
+
+ @Test
+ public void testSentAdminErrorMessage() {
+ mTransportProvider.sentAdminErrorMessage(ErrorType.DATA_PACKET_LENGTH_OVERFLOW);
+
+ assertThat(mFakeTransportProvider.lastSendMessageSecid)
+ .isEqualTo(TransportProvider.ADMIN_SECID);
+ assertThat(mFakeTransportProvider.lastSendMessage.toString())
+ .isEqualTo(new AdminErrorMessage(ErrorType.DATA_PACKET_LENGTH_OVERFLOW).toString());
+ }
+
+ @Test
+ public void testSentAdminEventMessage() {
+ mTransportProvider.sentAdminEventMessage(EventType.CAPABILITIES_CHANGED, new byte[] {});
+
+ assertThat(mFakeTransportProvider.lastSendMessageSecid)
+ .isEqualTo(TransportProvider.ADMIN_SECID);
+ assertThat(mFakeTransportProvider.lastSendMessage.toString())
+ .isEqualTo(
+ new AdminEventMessage(EventType.CAPABILITIES_CHANGED, new byte[] {})
+ .toString());
+ }
+
+ private void verifyAdminMessageReceive(ErrorType errorType, TerminationReason reason) {
+ mTransportProvider.registerDataReceiver(mMockDataReceiver);
+ mTransportProvider.onMessageReceived(
+ TransportProvider.ADMIN_SECID, new AdminErrorMessage(errorType));
+ verify(mMockDataReceiver, never()).onDataReceived(any());
+ assertThat(mFakeTransportProvider.lastTerminationReason).isEqualTo(reason);
+ }
+
+ @Test
+ public void testOutCharactersticNotifyAndRead_receiveAdminPacket() {
+ verifyAdminMessageReceive(
+ ErrorType.DATA_PACKET_LENGTH_OVERFLOW,
+ TerminationReason.REMOTE_DEVICE_MESSAGE_ERROR);
+ verifyAdminMessageReceive(
+ ErrorType.MESSAGE_LENGTH_OVERFLOW, TerminationReason.REMOTE_DEVICE_MESSAGE_ERROR);
+ verifyAdminMessageReceive(
+ ErrorType.TOO_MANY_CONCURRENT_FRAGMENTED_MESSAGE_SESSIONS,
+ TerminationReason.REMOTE_DEVICE_MESSAGE_ERROR);
+ verifyAdminMessageReceive(
+ ErrorType.SECID_INVALID, TerminationReason.REMOTE_DEVICE_SECID_ERROR);
+ verifyAdminMessageReceive(
+ ErrorType.SECID_INVALID_FOR_RESPONSE, TerminationReason.REMOTE_DEVICE_SECID_ERROR);
+ verifyAdminMessageReceive(
+ ErrorType.SECID_BUSY, TerminationReason.REMOTE_DEVICE_SECID_ERROR);
+ verifyAdminMessageReceive(
+ ErrorType.SECID_PROTOCOL_ERROR, TerminationReason.REMOTE_DEVICE_SECID_ERROR);
+ verifyAdminMessageReceive(
+ ErrorType.SECID_INTERNAL_ERROR, TerminationReason.REMOTE_DEVICE_SECID_ERROR);
+ }
}
diff --git a/service/tests/src/com/android/server/uwb/discovery/ble/GattTransportClientProviderTest.java b/service/tests/src/com/android/server/uwb/discovery/ble/GattTransportClientProviderTest.java
index b434229..9fe1a3f 100644
--- a/service/tests/src/com/android/server/uwb/discovery/ble/GattTransportClientProviderTest.java
+++ b/service/tests/src/com/android/server/uwb/discovery/ble/GattTransportClientProviderTest.java
@@ -49,8 +49,11 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.server.uwb.discovery.Transport.DataReceiver;
-import com.android.server.uwb.discovery.TransportClientProvider.TerminationReason;
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;
@@ -81,10 +84,11 @@
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.EVENT, InstructionCode.DATA_EXCHANGE, MESSAGE_PAYLOAD1);
+ 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();
@@ -602,7 +606,7 @@
Arrays.fill(messagePayload, (byte) 3);
FiraConnectorMessage message =
new FiraConnectorMessage(
- MessageType.EVENT, InstructionCode.DATA_EXCHANGE, messagePayload);
+ MessageType.COMMAND, InstructionCode.DATA_EXCHANGE, messagePayload);
byte[] messageBytes = message.toBytes();
int payloadSize = OPTIMIZED_DATA_PACKET_SIZE - 1;
byte[] packet_bytes1 =
@@ -626,6 +630,7 @@
.toBytes();
startProcessing();
+ assertThat(mGattTransportClientProvider.setCapabilites(CAPABILITIES)).isTrue();
notifyAndReadOutCharacteristic(packet_bytes1);
notifyAndReadOutCharacteristic(packet_bytes2);
notifyAndReadOutCharacteristic(packet_bytes3);
@@ -636,4 +641,149 @@
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));
+ }
}