Merge "Remove mapsapi/ and refactor related files" into main
diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterServiceBinder.java b/android/app/src/com/android/bluetooth/btservice/AdapterServiceBinder.java
index 9a9a91d..c080924 100644
--- a/android/app/src/com/android/bluetooth/btservice/AdapterServiceBinder.java
+++ b/android/app/src/com/android/bluetooth/btservice/AdapterServiceBinder.java
@@ -276,12 +276,10 @@
return false;
}
- if (Flags.emptyNamesAreInvalid()) {
- requireNonNull(name);
- name = name.trim();
- if (name.isEmpty()) {
- throw new IllegalArgumentException("Empty names are not valid");
- }
+ requireNonNull(name);
+ name = name.trim();
+ if (name.isEmpty()) {
+ throw new IllegalArgumentException("Empty names are not valid");
}
Log.d(TAG, "AdapterServiceBinder.setName(" + name + ")");
diff --git a/android/app/src/com/android/bluetooth/vc/VolumeControlService.java b/android/app/src/com/android/bluetooth/vc/VolumeControlService.java
index a3789a5..48112e0 100644
--- a/android/app/src/com/android/bluetooth/vc/VolumeControlService.java
+++ b/android/app/src/com/android/bluetooth/vc/VolumeControlService.java
@@ -29,8 +29,6 @@
import static android.bluetooth.IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID;
import static android.bluetooth.IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME;
-import static com.android.bluetooth.flags.Flags.vcpDeviceVolumeApiImprovements;
-
import static java.util.Objects.requireNonNull;
import static java.util.Objects.requireNonNullElseGet;
@@ -550,12 +548,9 @@
mDeviceVolumeCache.put(device, volume);
mNativeInterface.setVolume(device, volume);
- if (vcpDeviceVolumeApiImprovements()) {
- // We only receive the volume change and mute state needs to be acquired manually
- Boolean isStreamMute =
- mAudioManager.isStreamMute(getBluetoothContextualVolumeStream());
- adjustDeviceMute(device, volume, isStreamMute);
- }
+ // We only receive the volume change and mute state needs to be acquired manually
+ Boolean isStreamMute = mAudioManager.isStreamMute(getBluetoothContextualVolumeStream());
+ adjustDeviceMute(device, volume, isStreamMute);
}
}
@@ -597,10 +592,8 @@
synchronized (mDeviceVolumeCache) {
mGroupVolumeCache.put(groupId, volume);
- if (vcpDeviceVolumeApiImprovements()) {
- for (BluetoothDevice dev : getGroupDevices(groupId)) {
- mDeviceVolumeCache.put(dev, volume);
- }
+ for (BluetoothDevice dev : getGroupDevices(groupId)) {
+ mDeviceVolumeCache.put(dev, volume);
}
}
mNativeInterface.setGroupVolume(groupId, volume);
@@ -634,7 +627,7 @@
Log.i(TAG, "Unmute the group " + groupId);
unmuteGroup(groupId);
}
- } else if (vcpDeviceVolumeApiImprovements()) {
+ } else {
for (BluetoothDevice device : getGroupDevices(groupId)) {
adjustDeviceMute(device, volume, isStreamMute);
}
@@ -648,24 +641,20 @@
* @return the cached volume
*/
public int getGroupVolume(int groupId) {
- if (vcpDeviceVolumeApiImprovements()) {
- synchronized (mDeviceVolumeCache) {
- Integer volume = mGroupVolumeCache.get(groupId);
+ synchronized (mDeviceVolumeCache) {
+ Integer volume = mGroupVolumeCache.get(groupId);
+ if (volume != null) {
+ return volume;
+ }
+ Log.d(TAG, "No group volume available");
+ for (BluetoothDevice device : getGroupDevices(groupId)) {
+ volume = mDeviceVolumeCache.get(device);
if (volume != null) {
+ Log.w(TAG, "Volume taken from device: " + device);
return volume;
}
- Log.d(TAG, "No group volume available");
- for (BluetoothDevice device : getGroupDevices(groupId)) {
- volume = mDeviceVolumeCache.get(device);
- if (volume != null) {
- Log.w(TAG, "Volume taken from device: " + device);
- return volume;
- }
- }
- return VOLUME_CONTROL_UNKNOWN_VOLUME;
}
- } else {
- return mGroupVolumeCache.getOrDefault(groupId, VOLUME_CONTROL_UNKNOWN_VOLUME);
+ return VOLUME_CONTROL_UNKNOWN_VOLUME;
}
}
@@ -676,17 +665,13 @@
* @return the cached volume
*/
public int getDeviceVolume(BluetoothDevice device) {
- if (vcpDeviceVolumeApiImprovements()) {
- synchronized (mDeviceVolumeCache) {
- Integer volume = mDeviceVolumeCache.get(device);
- if (volume != null) {
- return volume;
- }
- return mGroupVolumeCache.getOrDefault(
- getGroupId(device), VOLUME_CONTROL_UNKNOWN_VOLUME);
+ synchronized (mDeviceVolumeCache) {
+ Integer volume = mDeviceVolumeCache.get(device);
+ if (volume != null) {
+ return volume;
}
- } else {
- return mDeviceVolumeCache.getOrDefault(device, VOLUME_CONTROL_UNKNOWN_VOLUME);
+ return mGroupVolumeCache.getOrDefault(
+ getGroupId(device), VOLUME_CONTROL_UNKNOWN_VOLUME);
}
}
@@ -736,22 +721,18 @@
* @return mute status
*/
public Boolean getGroupMute(int groupId) {
- if (vcpDeviceVolumeApiImprovements()) {
- synchronized (mDeviceMuteCache) {
- Boolean isMute = mGroupMuteCache.get(groupId);
+ synchronized (mDeviceMuteCache) {
+ Boolean isMute = mGroupMuteCache.get(groupId);
+ if (isMute != null) {
+ return isMute;
+ }
+ for (BluetoothDevice device : getGroupDevices(groupId)) {
+ isMute = mDeviceMuteCache.get(device);
if (isMute != null) {
return isMute;
}
- for (BluetoothDevice device : getGroupDevices(groupId)) {
- isMute = mDeviceMuteCache.get(device);
- if (isMute != null) {
- return isMute;
- }
- }
- return false;
}
- } else {
- return mGroupMuteCache.getOrDefault(groupId, false);
+ return false;
}
}
@@ -864,32 +845,16 @@
}
// If group volume has already changed, the new group member should set it
- if (vcpDeviceVolumeApiImprovements()) {
- int volume = getDeviceVolume(device);
- if (volume != VOLUME_CONTROL_UNKNOWN_VOLUME) {
- Log.i(TAG, "Setting device/group volume:" + volume + " to the device:" + device);
- setDeviceVolume(device, volume, false);
- Boolean isDeviceMuted = getMute(device);
- Log.i(TAG, "Setting mute:" + isDeviceMuted + " to " + device);
- if (isDeviceMuted) {
- mute(device);
- } else {
- unmute(device);
- }
- }
- } else {
- Integer groupVolume = getGroupVolume(groupId);
- if (groupVolume != VOLUME_CONTROL_UNKNOWN_VOLUME) {
- Log.i(TAG, "Setting value:" + groupVolume + " to " + device);
- mNativeInterface.setVolume(device, groupVolume);
- }
-
- Boolean isGroupMuted = getGroupMute(groupId);
- Log.i(TAG, "Setting mute:" + isGroupMuted + " to " + device);
- if (isGroupMuted) {
- mNativeInterface.mute(device);
+ int volume = getDeviceVolume(device);
+ if (volume != VOLUME_CONTROL_UNKNOWN_VOLUME) {
+ Log.i(TAG, "Setting device/group volume:" + volume + " to the device:" + device);
+ setDeviceVolume(device, volume, false);
+ Boolean isDeviceMuted = getMute(device);
+ Log.i(TAG, "Setting mute:" + isDeviceMuted + " to " + device);
+ if (isDeviceMuted) {
+ mute(device);
} else {
- mNativeInterface.unmute(device);
+ unmute(device);
}
}
}
@@ -914,11 +879,9 @@
synchronized (mDeviceVolumeCache) {
mGroupVolumeCache.put(groupId, volume);
mGroupMuteCache.put(groupId, mute);
- if (vcpDeviceVolumeApiImprovements()) {
- for (BluetoothDevice dev : getGroupDevices(groupId)) {
- mDeviceVolumeCache.put(dev, volume);
- mDeviceMuteCache.put(dev, mute);
- }
+ for (BluetoothDevice dev : getGroupDevices(groupId)) {
+ mDeviceVolumeCache.put(dev, volume);
+ mDeviceMuteCache.put(dev, mute);
}
}
@@ -1004,56 +967,38 @@
if (((flags & VOLUME_FLAGS_PERSISTED_USER_SET_VOLUME_MASK) == 0x01)
&& (getConnectedDevices(groupId).size() == 1)) {
Log.i(TAG, "Setting device: " + device + " volume: " + volume + " to the system");
- if (vcpDeviceVolumeApiImprovements()) {
- // Ignore volume from AF because persisted volume was used
- setIgnoreSetVolumeFromAfFlag(true);
- }
+ // Ignore volume from AF because persisted volume was used
+ setIgnoreSetVolumeFromAfFlag(true);
updateGroupCacheAndAudioSystem(groupId, volume, mute, false);
return;
}
// Reset flag is used
- if (vcpDeviceVolumeApiImprovements()) {
- int deviceVolume = getDeviceVolume(device);
- if (deviceVolume != VOLUME_CONTROL_UNKNOWN_VOLUME) {
- Log.i(
- TAG,
- "Setting device/group volume: "
- + deviceVolume
- + " to the device: "
- + device);
- setDeviceVolume(device, deviceVolume, false);
- Boolean isDeviceMuted = getMute(device);
- Log.i(TAG, "Setting mute:" + isDeviceMuted + " to " + device);
- if (isDeviceMuted) {
- mute(device);
- } else {
- unmute(device);
- }
- if (getConnectedDevices(groupId).size() == 1) {
- // Ignore volume from AF because cached volume was used
- setIgnoreSetVolumeFromAfFlag(true);
- }
+ int deviceVolume = getDeviceVolume(device);
+ if (deviceVolume != VOLUME_CONTROL_UNKNOWN_VOLUME) {
+ Log.i(
+ TAG,
+ "Setting device/group volume: "
+ + deviceVolume
+ + " to the device: "
+ + device);
+ setDeviceVolume(device, deviceVolume, false);
+ Boolean isDeviceMuted = getMute(device);
+ Log.i(TAG, "Setting mute:" + isDeviceMuted + " to " + device);
+ if (isDeviceMuted) {
+ mute(device);
} else {
- Log.i(TAG, "Waiting for volume from AF to set to the device: " + device);
- if (getConnectedDevices(groupId).size() == 1) {
- // Clear ignore flag as volume from AF is needed
- setIgnoreSetVolumeFromAfFlag(false);
- }
+ unmute(device);
+ }
+ if (getConnectedDevices(groupId).size() == 1) {
+ // Ignore volume from AF because cached volume was used
+ setIgnoreSetVolumeFromAfFlag(true);
}
} else {
- int groupVolume = getGroupVolume(groupId);
- if (groupVolume != VOLUME_CONTROL_UNKNOWN_VOLUME) {
- Log.i(
- TAG,
- "Setting group volume: " + groupVolume + " to the device: " + device);
- setGroupVolume(groupId, groupVolume);
- } else {
- int systemVolume = getBleVolumeFromCurrentStream();
- Log.i(
- TAG,
- "Setting system volume: " + systemVolume + " to the group: " + groupId);
- setGroupVolume(groupId, systemVolume);
+ Log.i(TAG, "Waiting for volume from AF to set to the device: " + device);
+ if (getConnectedDevices(groupId).size() == 1) {
+ // Clear ignore flag as volume from AF is needed
+ setIgnoreSetVolumeFromAfFlag(false);
}
}
@@ -1079,53 +1024,6 @@
}
}
- if (!vcpDeviceVolumeApiImprovements() && !isAutonomous) {
- /* If the change is triggered by Android device, the stream is already changed.
- * However it might be called with isAutonomous, one the first read of after
- * reconnection. Make sure device has group volume. Also it might happen that
- * remote side send us wrong value - lets check it.
- */
-
- int groupVolume = getGroupVolume(groupId);
- Boolean groupMute = getGroupMute(groupId);
-
- if ((groupVolume == volume) && (groupMute == mute)) {
- Log.i(TAG, " Volume:" + volume + ", mute:" + mute + " confirmed by remote side.");
- return;
- }
-
- if (device != null) {
- // Correct the volume level only if device was already reported as connected.
- boolean can_change_volume = false;
- synchronized (mStateMachines) {
- VolumeControlStateMachine sm = mStateMachines.get(device);
- if (sm != null) {
- can_change_volume = sm.isConnected();
- }
- }
-
- if (can_change_volume && (groupVolume != volume)) {
- Log.i(TAG, "Setting value:" + groupVolume + " to " + device);
- mNativeInterface.setVolume(device, groupVolume);
- }
- if (can_change_volume && (groupMute != mute)) {
- Log.i(TAG, "Setting mute:" + groupMute + " to " + device);
- if (groupMute) {
- mNativeInterface.mute(device);
- } else {
- mNativeInterface.unmute(device);
- }
- }
- } else {
- Log.e(
- TAG,
- "Volume changed did not succeed. Volume: "
- + volume
- + " expected volume: "
- + groupVolume);
- }
- }
-
if (isAutonomous && device == null) {
/* Received group notification for autonomous change. Update cache and audio system. */
updateGroupCacheAndAudioSystem(groupId, volume, mute, true);
@@ -1565,15 +1463,8 @@
int broadcastVolume = VOLUME_CONTROL_UNKNOWN_VOLUME;
if (volume.isPresent()) {
broadcastVolume = volume.get();
- if (!vcpDeviceVolumeApiImprovements()) {
- mDeviceVolumeCache.put(dev, broadcastVolume);
- }
} else {
broadcastVolume = getDeviceVolume(dev);
- if (!vcpDeviceVolumeApiImprovements()
- && broadcastVolume == VOLUME_CONTROL_UNKNOWN_VOLUME) {
- broadcastVolume = getGroupVolume(getGroupId(dev));
- }
}
int n = callbacks.beginBroadcast();
for (int i = 0; i < n; i++) {
@@ -1667,22 +1558,6 @@
Log.d(TAG, device + " is unbond. Remove state machine");
removeStateMachine(device);
}
- } else if (!vcpDeviceVolumeApiImprovements() && toState == STATE_CONNECTED) {
- // Restore the group volume if it was changed while the device was not yet connected.
- Integer groupId = getGroupId(device);
- if (groupId != GROUP_ID_INVALID) {
- Integer groupVolume = getGroupVolume(groupId);
- if (groupVolume != VOLUME_CONTROL_UNKNOWN_VOLUME) {
- mNativeInterface.setVolume(device, groupVolume);
- }
-
- Boolean groupMute = getGroupMute(groupId);
- if (groupMute) {
- mNativeInterface.mute(device);
- } else {
- mNativeInterface.unmute(device);
- }
- }
}
mAdapterService.handleProfileConnectionStateChange(
BluetoothProfile.VOLUME_CONTROL, device, fromState, toState);
@@ -1724,17 +1599,15 @@
+ getGroupMute(entry.getKey()));
}
- if (vcpDeviceVolumeApiImprovements()) {
- for (Map.Entry<BluetoothDevice, Integer> entry : mDeviceVolumeCache.entrySet()) {
- ProfileService.println(
- sb,
- " Device: "
- + entry.getKey()
- + " volume: "
- + entry.getValue()
- + ", mute: "
- + getMute(entry.getKey()));
- }
+ for (Map.Entry<BluetoothDevice, Integer> entry : mDeviceVolumeCache.entrySet()) {
+ ProfileService.println(
+ sb,
+ " Device: "
+ + entry.getKey()
+ + " volume: "
+ + entry.getValue()
+ + ", mute: "
+ + getMute(entry.getKey()));
}
}
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java
index 07b5653..c0feb6f 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java
@@ -61,7 +61,6 @@
import android.media.AudioManager;
import android.os.Binder;
import android.os.ParcelUuid;
-import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -469,25 +468,6 @@
}
@Test
- @DisableFlags(Flags.FLAG_VCP_DEVICE_VOLUME_API_IMPROVEMENTS)
- public void volumeCacheDeprecated() {
- int groupId = 1;
- int volume = 6;
-
- assertThat(mService.getGroupVolume(groupId)).isEqualTo(-1);
- mService.setGroupVolume(groupId, volume);
-
- assertThat(mService.getGroupVolume(groupId)).isEqualTo(volume);
-
- volume = 10;
- // Send autonomous volume change.
- generateVolumeStateChanged(null, groupId, volume, 0, false, true);
-
- assertThat(mService.getGroupVolume(groupId)).isEqualTo(volume);
- }
-
- @Test
- @EnableFlags(Flags.FLAG_VCP_DEVICE_VOLUME_API_IMPROVEMENTS)
public void volumeCache() {
int groupId = 1;
int groupVolume = 6;
@@ -568,31 +548,6 @@
}
@Test
- @DisableFlags(Flags.FLAG_VCP_DEVICE_VOLUME_API_IMPROVEMENTS)
- public void muteCacheDeprecated() {
- int groupId = 1;
- int volume = 6;
-
- assertThat(mService.getGroupMute(groupId)).isFalse();
-
- // Send autonomous volume change
- generateVolumeStateChanged(null, groupId, volume, 0, false, true);
-
- // Mute
- mService.muteGroup(groupId);
- assertThat(mService.getGroupMute(groupId)).isTrue();
-
- // Make sure the volume is kept even when muted
- assertThat(mService.getGroupVolume(groupId)).isEqualTo(volume);
-
- // Send autonomous unmute
- generateVolumeStateChanged(null, groupId, volume, 0, false, true);
-
- assertThat(mService.getGroupMute(groupId)).isFalse();
- }
-
- @Test
- @EnableFlags(Flags.FLAG_VCP_DEVICE_VOLUME_API_IMPROVEMENTS)
public void muteCache() {
int groupId = 1;
int groupVolume = 6;
@@ -727,12 +682,10 @@
inOrderAudio.verify(mAudioManager, never()).setStreamVolume(anyInt(), anyInt(), anyInt());
InOrder inOrderNative = inOrder(mNativeInterface);
- if (Flags.vcpDeviceVolumeApiImprovements()) {
- // AF always call setVolume via LeAudioService at first connected remote from group
- mService.setGroupVolume(groupId, 123);
- // It should be ignored and not set to native
- inOrderNative.verify(mNativeInterface, never()).setGroupVolume(anyInt(), anyInt());
- }
+ // AF always call setVolume via LeAudioService at first connected remote from group
+ mService.setGroupVolume(groupId, 123);
+ // It should be ignored and not set to native
+ inOrderNative.verify(mNativeInterface, never()).setGroupVolume(anyInt(), anyInt());
// Make device Active now. This will trigger setting volume to AF
when(mLeAudioService.getActiveGroupId()).thenReturn(groupId);
@@ -756,15 +709,10 @@
initialAutonomousFlag);
inOrderAudio.verify(mAudioManager, never()).setStreamVolume(anyInt(), anyInt(), anyInt());
- if (Flags.vcpDeviceVolumeApiImprovements()) {
- inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(volumeDevice));
- } else {
- inOrderNative.verify(mNativeInterface).setGroupVolume(eq(groupId), eq(volumeDevice));
- }
+ inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(volumeDevice));
}
@Test
- @EnableFlags(Flags.FLAG_VCP_DEVICE_VOLUME_API_IMPROVEMENTS)
public void testClearingSetVolumeFromAF() {
int groupId = 1;
int groupId2 = 2;
@@ -860,10 +808,8 @@
InOrder inOrderAudio = inOrder(mAudioManager);
inOrderAudio.verify(mAudioManager, never()).setStreamVolume(anyInt(), anyInt(), anyInt());
InOrder inOrderNative = inOrder(mNativeInterface);
- if (Flags.vcpDeviceVolumeApiImprovements()) {
- // AF always call setVolume via LeAudioService at first connected remote from group
- mService.setGroupVolume(groupId, expectedAfVol);
- }
+ // AF always call setVolume via LeAudioService at first connected remote from group
+ mService.setGroupVolume(groupId, expectedAfVol);
inOrderNative.verify(mNativeInterface).setGroupVolume(eq(groupId), eq(expectedAfVol));
// Make device Active now. This will trigger setting volume to AF
@@ -886,11 +832,7 @@
initialAutonomousFlag);
inOrderAudio.verify(mAudioManager, never()).setStreamVolume(anyInt(), anyInt(), anyInt());
- if (Flags.vcpDeviceVolumeApiImprovements()) {
- inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(expectedAfVol));
- } else {
- inOrderNative.verify(mNativeInterface).setGroupVolume(eq(groupId), eq(expectedAfVol));
- }
+ inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(expectedAfVol));
}
/** Test if phone will set volume which is read from the buds */
@@ -934,12 +876,7 @@
assertThat(mService.getDevices()).contains(mDeviceTwo);
generateVolumeStateChanged(mDeviceTwo, LE_AUDIO_GROUP_ID_INVALID, volume_2, 0, false, true);
- if (Flags.vcpDeviceVolumeApiImprovements()) {
- inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(groupVolume));
- } else {
- inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(groupVolume));
- inOrderNative.verify(mNativeInterface).setGroupVolume(eq(groupId), eq(groupVolume));
- }
+ inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(groupVolume));
}
/**
@@ -1013,14 +950,8 @@
generateVolumeStateChanged(mDeviceTwo, LE_AUDIO_GROUP_ID_INVALID, volume_2, 0, false, true);
// Check if new device was muted
- if (Flags.vcpDeviceVolumeApiImprovements()) {
- inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(volume));
- inOrderNative.verify(mNativeInterface).mute(eq(mDeviceTwo));
- } else {
- inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(volume));
- inOrderNative.verify(mNativeInterface).mute(eq(mDeviceTwo));
- inOrderNative.verify(mNativeInterface).setGroupVolume(eq(groupId), eq(volume));
- }
+ inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(volume));
+ inOrderNative.verify(mNativeInterface).mute(eq(mDeviceTwo));
}
/**
@@ -1265,12 +1196,7 @@
generateVolumeStateChanged(
mDeviceTwo, LE_AUDIO_GROUP_ID_INVALID, groupVolume, 0, false, true);
- if (Flags.vcpDeviceVolumeApiImprovements()) {
- inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(groupVolume));
- } else {
- inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(groupVolume));
- inOrderNative.verify(mNativeInterface).setGroupVolume(eq(groupId), eq(groupVolume));
- }
+ inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(groupVolume));
// Generate events for both devices
generateDeviceOffsetChangedMessageFromNative(mDevice, 1, 100);
diff --git a/flags/adapter.aconfig b/flags/adapter.aconfig
index 20fd6d7..8061bce 100644
--- a/flags/adapter.aconfig
+++ b/flags/adapter.aconfig
@@ -29,16 +29,6 @@
}
flag {
- name: "empty_names_are_invalid"
- namespace: "bluetooth"
- description: "Make sure the names used in the stack are valid"
- bug: "395178934"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "remove_legacy_management_thread"
namespace: "bluetooth"
description: "Remove non-necessary management_thread"
diff --git a/flags/connectivity.aconfig b/flags/connectivity.aconfig
index 738c294..4896af3 100644
--- a/flags/connectivity.aconfig
+++ b/flags/connectivity.aconfig
@@ -8,3 +8,12 @@
bug: "389274300"
}
+flag {
+ name: "idempotent_direct_connect_add"
+ namespace: "bluetooth"
+ description: "Make direct_connect_add idempotent"
+ bug: "409239278"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
\ No newline at end of file
diff --git a/flags/hfp.aconfig b/flags/hfp.aconfig
index 06de4a1..3e4659c 100644
--- a/flags/hfp.aconfig
+++ b/flags/hfp.aconfig
@@ -122,3 +122,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "qc_prioritize_lc3_codec"
+ namespace: "bluetooth"
+ description: "QC patch to prioritize lc3 codec over aptx"
+ bug: "409604300"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/flags/rfcomm.aconfig b/flags/rfcomm.aconfig
index 6a4020e..a46f9f6 100644
--- a/flags/rfcomm.aconfig
+++ b/flags/rfcomm.aconfig
@@ -2,16 +2,6 @@
container: "com.android.bt"
flag {
- name: "rfcomm_always_disc_initiator_in_disc_wait_ua"
- namespace: "bluetooth"
- description: "Always be the DISC initiator in the DISC_WAIT_UA state to avoid unnecessary hang"
- bug: "350839022"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "allow_free_last_scn"
namespace: "bluetooth"
description: "Allow SCN 30 to be freed"
diff --git a/flags/vcp.aconfig b/flags/vcp.aconfig
index 12e55de..b862b9e 100644
--- a/flags/vcp.aconfig
+++ b/flags/vcp.aconfig
@@ -9,16 +9,6 @@
}
flag {
- name: "vcp_device_volume_api_improvements"
- namespace: "bluetooth"
- description: "Refactor Device Volume API for generic usage"
- bug: "381507732"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "vcp_allow_set_same_volume_if_pending"
namespace: "bluetooth"
description: "Allow to set the same volume during already pending operation"
diff --git a/system/btif/src/btif_dm.cc b/system/btif/src/btif_dm.cc
index 256f98f..81627d5 100644
--- a/system/btif/src/btif_dm.cc
+++ b/system/btif/src/btif_dm.cc
@@ -3862,7 +3862,7 @@
******************************************************************************/
void btif_dm_read_energy_info() { BTA_DmBleGetEnergyInfo(bta_energy_info_cb); }
-static const char* btif_get_default_local_name_new() {
+static const char* btif_get_default_local_name() {
using bluetooth::common::StringTrim;
static std::string default_name = "";
@@ -3889,30 +3889,6 @@
return default_name.c_str();
}
-static const char* btif_get_default_local_name() {
- if (com::android::bluetooth::flags::empty_names_are_invalid()) {
- return btif_get_default_local_name_new();
- }
- static char btif_default_local_name[DEFAULT_LOCAL_NAME_MAX + 1] = {'\0'};
-
- if (btif_default_local_name[0] == '\0') {
- int max_len = sizeof(btif_default_local_name) - 1;
-
- char prop_name[PROPERTY_VALUE_MAX];
- osi_property_get(PROPERTY_DEFAULT_DEVICE_NAME, prop_name, "");
- strncpy(btif_default_local_name, prop_name, max_len);
-
- // If no value was placed in the btif_default_local_name then use model name
- if (btif_default_local_name[0] == '\0') {
- char prop_model[PROPERTY_VALUE_MAX];
- osi_property_get(PROPERTY_PRODUCT_MODEL, prop_model, "");
- strncpy(btif_default_local_name, prop_model, max_len);
- }
- btif_default_local_name[max_len] = '\0';
- }
- return btif_default_local_name;
-}
-
static void btif_stats_add_bond_event(const RawAddress& bd_addr, bt_bond_function_t function,
bt_bond_state_t state) {
std::unique_lock<std::mutex> lock(bond_event_lock);
diff --git a/system/btif/src/btif_storage.cc b/system/btif/src/btif_storage.cc
index b5908b0..77c8f90 100644
--- a/system/btif/src/btif_storage.cc
+++ b/system/btif/src/btif_storage.cc
@@ -255,16 +255,7 @@
ret = btif_config_get_str(BTIF_STORAGE_SECTION_ADAPTER, BTIF_STORAGE_KEY_NAME,
reinterpret_cast<char*>(prop->val), &len);
}
- if (com::android::bluetooth::flags::empty_names_are_invalid()) {
- if (ret && len > 1 && len <= prop->len) { // empty names have a len of 1
- prop->len = len - 1;
- } else {
- prop->len = 0;
- ret = false;
- }
- break;
- }
- if (ret && len && len <= prop->len) {
+ if (ret && len > 1 && len <= prop->len) { // empty names have a len of 1
prop->len = len - 1;
} else {
prop->len = 0;
diff --git a/system/common/message_loop_thread.cc b/system/common/message_loop_thread.cc
index a6dd211..5af121d 100644
--- a/system/common/message_loop_thread.cc
+++ b/system/common/message_loop_thread.cc
@@ -127,12 +127,20 @@
std::string MessageLoopThread::ToString() const {
std::lock_guard<std::recursive_mutex> api_lock(api_mutex_);
+#if defined(TARGET_FLOSS) && BASE_VER >= 1419016
+ return std::format("{}({})", thread_name_, thread_id_.raw());
+#else
return std::format("{}({})", thread_name_, thread_id_);
+#endif // defined(TARGET_FLOSS) && BASE_VER >= 1419016
}
bool MessageLoopThread::IsRunning() const {
std::lock_guard<std::recursive_mutex> api_lock(api_mutex_);
+#if defined(TARGET_FLOSS) && BASE_VER >= 1419016
+ return thread_id_.raw() != -1;
+#else
return thread_id_ != -1;
+#endif // defined(TARGET_FLOSS) && BASE_VER >= 1419016
}
// Non API method, should not be protected by API mutex
@@ -187,7 +195,11 @@
{
std::lock_guard<std::recursive_mutex> api_lock(api_mutex_);
+ #if defined(TARGET_FLOSS) && BASE_VER >= 1419016
+ thread_id_ = base::PlatformThreadId(-1);
+ #else
thread_id_ = -1;
+ #endif // defined(TARGET_FLOSS) && BASE_VER >= 1419016
linux_tid_ = -1;
delete message_loop_;
message_loop_ = nullptr;
diff --git a/system/gd/hal/hci_hal_host.cc b/system/gd/hal/hci_hal_host.cc
index d81e9d0..4976258 100644
--- a/system/gd/hal/hci_hal_host.cc
+++ b/system/gd/hal/hci_hal_host.cc
@@ -245,6 +245,11 @@
incoming_packet_callback_ == nullptr && callback != nullptr,
"assert failed: incoming_packet_callback_ == nullptr && callback != nullptr");
incoming_packet_callback_ = callback;
+
+ // If the HCI socket was not connected on start, inform HCI Layer to prevent future requests.
+ if (sock_fd_ == INVALID_FD) {
+ incoming_packet_callback_->controllerNeedsReset();
+ }
}
log::info("after");
}
@@ -313,8 +318,8 @@
// We don't want to crash when the chipset is broken.
if (sock_fd_ == INVALID_FD) {
- log::error("Failed to connect to HCI socket. Aborting HAL initialization process.");
- incoming_packet_callback_->controllerNeedsReset();
+ log::error(
+ "Failed to connect to HCI socket. HAL initialization process will be aborted later.");
return;
}
diff --git a/system/gd/hal/snoop_logger_socket.cc b/system/gd/hal/snoop_logger_socket.cc
index 0405950..4ccaa72 100644
--- a/system/gd/hal/snoop_logger_socket.cc
+++ b/system/gd/hal/snoop_logger_socket.cc
@@ -67,7 +67,7 @@
}
ssize_t ret;
- RUN_NO_INTR(ret = syscall_if_->Send(client_socket, data, length, MSG_DONTWAIT));
+ RUN_NO_INTR(ret = syscall_if_->Send(client_socket, data, length, MSG_DONTWAIT | MSG_NOSIGNAL));
if (ret == -1 && syscall_if_->GetErrno() == ECONNRESET) {
SafeCloseSocket(client_socket);
diff --git a/system/gd/hal/snoop_logger_socket_thread.cc b/system/gd/hal/snoop_logger_socket_thread.cc
index f622fb2..b8c1cdf 100644
--- a/system/gd/hal/snoop_logger_socket_thread.cc
+++ b/system/gd/hal/snoop_logger_socket_thread.cc
@@ -47,6 +47,10 @@
std::future<bool> SnoopLoggerSocketThread::Start() {
log::debug("");
std::promise<bool> thread_started;
+ if (listen_thread_) {
+ thread_started.set_value(true);
+ return thread_started.get_future();
+ }
auto future = thread_started.get_future();
stop_thread_ = false;
listen_thread_ = std::make_unique<std::thread>(&SnoopLoggerSocketThread::Run, this,
@@ -63,6 +67,7 @@
if (listen_thread_ && listen_thread_->joinable()) {
listen_thread_->join();
listen_thread_.reset();
+ socket_->Cleanup();
}
}
@@ -87,7 +92,12 @@
while (!stop_thread_ && socket_->ProcessIncomingRequest()) {
}
- socket_->Cleanup();
+ // We don't call `socket_->Cleanup()` here because it's possible for that to lead to SIGPIPE: in
+ // `Stop` it sets `stop_thread_` to true, and then calls `socket_->NotifySocketListener()`. Within
+ // that small window, we might have checked `stop_thread_` above, and if we were to call
+ // `socket_->Cleanup` here, that would then mean that `socket_->NotifySocketListener()` could
+ // result in SIGPIPE, which, by default will terminate the process.
+
listen_thread_running_ = false;
}
diff --git a/system/gd/hal/snoop_logger_socket_thread.h b/system/gd/hal/snoop_logger_socket_thread.h
index f508ff9..ca783ac 100644
--- a/system/gd/hal/snoop_logger_socket_thread.h
+++ b/system/gd/hal/snoop_logger_socket_thread.h
@@ -51,10 +51,8 @@
// Socket thread for listening to incoming connections.
std::unique_ptr<std::thread> listen_thread_;
- bool listen_thread_running_ = false;
+ std::atomic<bool> listen_thread_running_ = false;
- std::condition_variable listen_thread_running_cv_;
- std::mutex listen_thread_running_mutex_;
std::atomic<bool> stop_thread_;
};
diff --git a/system/gd/hal/snoop_logger_socket_thread_test.cc b/system/gd/hal/snoop_logger_socket_thread_test.cc
index 7f0dc48..5cfa457 100644
--- a/system/gd/hal/snoop_logger_socket_thread_test.cc
+++ b/system/gd/hal/snoop_logger_socket_thread_test.cc
@@ -150,7 +150,6 @@
sls.Stop();
ASSERT_FALSE(sls.ThreadIsRunning());
- close(socket_fd);
}
TEST_F(SnoopLoggerSocketThreadModuleTest, socket_send_no_start_test) {
diff --git a/system/metrics/Android.bp b/system/metrics/Android.bp
index 959307a..90dd3c3 100644
--- a/system/metrics/Android.bp
+++ b/system/metrics/Android.bp
@@ -128,7 +128,6 @@
srcs: [
":TestCommonMockFunctions",
"mock/metrics_mock.cc",
- "mock/mock_main_shim_metric_id_api.cc",
"src/bluetooth_event.cc",
"src/metric_id_manager.cc",
],
diff --git a/system/metrics/mock/metrics_mock.cc b/system/metrics/mock/metrics_mock.cc
index 6895b21..4f1d23fa 100644
--- a/system/metrics/mock/metrics_mock.cc
+++ b/system/metrics/mock/metrics_mock.cc
@@ -28,6 +28,56 @@
metricsInstance = std::move(instance);
}
+bool InitMetricIdAllocator(const std::unordered_map<RawAddress, int>& paired_device_map,
+ CallbackLegacy save_id_callback, CallbackLegacy forget_device_callback) {
+ if (metricsInstance) {
+ return metricsInstance->InitMetricIdAllocator(paired_device_map, save_id_callback,
+ forget_device_callback);
+ }
+ return true;
+}
+
+bool CloseMetricIdAllocator() {
+ if (metricsInstance) {
+ return metricsInstance->CloseMetricIdAllocator();
+ }
+ return true;
+}
+
+bool IsEmptyMetricIdAllocator() {
+ if (metricsInstance) {
+ return metricsInstance->IsEmptyMetricIdAllocator();
+ }
+ return false;
+}
+
+bool IsValidIdFromMetricIdAllocator(int id) {
+ if (metricsInstance) {
+ return metricsInstance->IsValidIdFromMetricIdAllocator(id);
+ }
+ return false;
+}
+
+bool SaveDeviceOnMetricIdAllocator(const RawAddress& address) {
+ if (metricsInstance) {
+ return metricsInstance->SaveDeviceOnMetricIdAllocator(address);
+ }
+ return false;
+}
+
+int AllocateIdFromMetricIdAllocator(const RawAddress& address) {
+ if (metricsInstance) {
+ return metricsInstance->AllocateIdFromMetricIdAllocator(address);
+ }
+ return 0;
+}
+
+void ForgetDeviceFromMetricIdAllocator(const RawAddress& address) {
+ if (metricsInstance) {
+ return metricsInstance->ForgetDeviceFromMetricIdAllocator(address);
+ }
+}
+
void LogMetricLinkLayerConnectionEvent(const hci::Address& address, uint32_t connection_handle,
android::bluetooth::DirectionEnum direction,
uint16_t link_type, uint32_t hci_cmd, uint16_t hci_event,
diff --git a/system/metrics/mock/metrics_mock.h b/system/metrics/mock/metrics_mock.h
index 128eba27..361d582 100644
--- a/system/metrics/mock/metrics_mock.h
+++ b/system/metrics/mock/metrics_mock.h
@@ -16,6 +16,7 @@
#pragma once
+#include <bluetooth/metrics/metric_id_api.h>
#include <bluetooth/metrics/os_metrics.h>
#include <gmock/gmock.h>
@@ -25,6 +26,17 @@
public:
static void SetInstance(std::shared_ptr<MockMetrics> instance);
+ // Methods from metric_id_api.h
+ MOCK_METHOD(bool, InitMetricIdAllocator,
+ ((const std::unordered_map<RawAddress, int>&), CallbackLegacy, CallbackLegacy));
+ MOCK_METHOD(bool, CloseMetricIdAllocator, ());
+ MOCK_METHOD(bool, IsEmptyMetricIdAllocator, ());
+ MOCK_METHOD(int, AllocateIdFromMetricIdAllocator, (const RawAddress&));
+ MOCK_METHOD(bool, SaveDeviceOnMetricIdAllocator, (const RawAddress&));
+ MOCK_METHOD(void, ForgetDeviceFromMetricIdAllocator, (const RawAddress&));
+ MOCK_METHOD(bool, IsValidIdFromMetricIdAllocator, (int));
+
+ // Methods from os_metrics.h
MOCK_METHOD(void, LogMetricLinkLayerConnectionEvent,
(const hci::Address&, uint32_t, android::bluetooth::DirectionEnum, uint16_t, uint32_t,
uint16_t, uint16_t, uint16_t, uint16_t));
diff --git a/system/metrics/mock/mock_main_shim_metric_id_api.cc b/system/metrics/mock/mock_main_shim_metric_id_api.cc
deleted file mode 100644
index 71483d9..0000000
--- a/system/metrics/mock/mock_main_shim_metric_id_api.cc
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2021 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.
- */
-
-/*
- * Generated mock file from original source file
- * Functions generated:7
- */
-
-#include <bluetooth/metrics/metric_id_api.h>
-
-#include "test/common/mock_functions.h"
-#include "types/raw_address.h"
-
-namespace bluetooth {
-namespace metrics {
-
-bool InitMetricIdAllocator(const std::unordered_map<RawAddress, int>& /* paired_device_map */,
- CallbackLegacy /* save_id_callback */,
- CallbackLegacy /* forget_device_callback */) {
- inc_func_call_count(__func__);
- return true;
-}
-bool CloseMetricIdAllocator() {
- inc_func_call_count(__func__);
- return true;
-}
-bool IsEmptyMetricIdAllocator() {
- inc_func_call_count(__func__);
- return false;
-}
-bool IsValidIdFromMetricIdAllocator(const int /* id */) {
- inc_func_call_count(__func__);
- return false;
-}
-bool SaveDeviceOnMetricIdAllocator(const RawAddress& /* raw_address */) {
- inc_func_call_count(__func__);
- return false;
-}
-int AllocateIdFromMetricIdAllocator(const RawAddress& /* raw_address */) {
- inc_func_call_count(__func__);
- return 0;
-}
-void ForgetDeviceFromMetricIdAllocator(const RawAddress& /* raw_address */) {
- inc_func_call_count(__func__);
-}
-
-} // namespace metrics
-} // namespace bluetooth
diff --git a/system/rust/src/core/shared_box.rs b/system/rust/src/core/shared_box.rs
index 33cae9f..1e1b1a1 100644
--- a/system/rust/src/core/shared_box.rs
+++ b/system/rust/src/core/shared_box.rs
@@ -21,6 +21,11 @@
Self(t.into())
}
+ /// Same as Rc::new_cyclic.
+ pub fn new_cyclic(data_fn: impl FnOnce(WeakBox<T>) -> T) -> Self {
+ Self(Rc::new_cyclic(|weak| data_fn(WeakBox(Weak::clone(weak)))))
+ }
+
/// Produce a weak reference to the contents
pub fn downgrade(&self) -> WeakBox<T> {
WeakBox(Rc::downgrade(&self.0))
diff --git a/system/rust/src/gatt/callbacks.rs b/system/rust/src/gatt/callbacks.rs
index bfeb3a5..3e17f2f 100644
--- a/system/rust/src/gatt/callbacks.rs
+++ b/system/rust/src/gatt/callbacks.rs
@@ -81,7 +81,7 @@
}
/// Whether to commit or cancel a transaction
-#[derive(Clone, Copy, Debug)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[allow(dead_code)]
pub enum TransactionDecision {
/// Commit all pending writes
@@ -124,7 +124,6 @@
);
/// Execute or cancel any prepared writes
- #[allow(dead_code)]
async fn execute(
&self,
tcb_idx: TransportIndex,
diff --git a/system/rust/src/gatt/mocks/mock_database_callbacks.rs b/system/rust/src/gatt/mocks/mock_database_callbacks.rs
index bb50bc5..48c4cb3 100644
--- a/system/rust/src/gatt/mocks/mock_database_callbacks.rs
+++ b/system/rust/src/gatt/mocks/mock_database_callbacks.rs
@@ -2,10 +2,10 @@
use std::ops::RangeInclusive;
-use crate::core::shared_box::{SharedBox, WeakBox};
+use crate::core::shared_box::SharedBox;
use crate::gatt::ids::{AttHandle, TransportIndex};
-use crate::gatt::server::att_server_bearer::AttServerBearer;
-use crate::gatt::server::gatt_database::{AttDatabaseImpl, GattDatabaseCallbacks};
+use crate::gatt::server::att_client::{AttClient, WeakAttClient};
+use crate::gatt::server::gatt_database::GattDatabaseCallbacks;
use tokio::sync::mpsc::{self, unbounded_channel, UnboundedReceiver};
/// Routes calls to GattDatabaseCallbacks into a channel of MockCallbackEvents
@@ -24,7 +24,7 @@
pub enum MockCallbackEvents {
/// GattDatabaseCallbacks#on_le_connect invoked
#[allow(dead_code)]
- OnLeConnect(TransportIndex, WeakBox<AttServerBearer<AttDatabaseImpl>>),
+ OnLeConnect(WeakAttClient),
/// GattDatabaseCallbacks#on_le_disconnect invoked
OnLeDisconnect(TransportIndex),
/// GattDatabaseCallbacks#on_service_change invoked
@@ -32,12 +32,8 @@
}
impl GattDatabaseCallbacks for MockCallbacks {
- fn on_le_connect(
- &self,
- tcb_idx: TransportIndex,
- bearer: &SharedBox<AttServerBearer<AttDatabaseImpl>>,
- ) {
- self.0.send(MockCallbackEvents::OnLeConnect(tcb_idx, bearer.downgrade())).ok().unwrap();
+ fn on_le_connect(&self, client: &SharedBox<AttClient>) {
+ self.0.send(MockCallbackEvents::OnLeConnect(client.downgrade())).ok().unwrap();
}
fn on_le_disconnect(&self, tcb_idx: TransportIndex) {
diff --git a/system/rust/src/gatt/server.rs b/system/rust/src/gatt/server.rs
index 4d55df8..f4b60c4 100644
--- a/system/rust/src/gatt/server.rs
+++ b/system/rust/src/gatt/server.rs
@@ -1,6 +1,7 @@
//! This module is a simple GATT server that shares the ATT channel with the
//! existing C++ GATT client.
+pub mod att_client;
mod att_database;
pub mod att_server_bearer;
pub mod gatt_database;
@@ -18,12 +19,13 @@
use std::rc::Rc;
use std::sync::{Arc, Mutex, MutexGuard};
-use crate::core::shared_box::{SharedBox, WeakBox};
+use crate::core::shared_box::SharedBox;
+use crate::gatt::server::att_client::AttClient;
use crate::gatt::server::gatt_database::GattDatabase;
use self::super::ids::ServerId;
use self::att_server_bearer::AttServerBearer;
-use self::gatt_database::{AttDatabaseImpl, GattServiceWithHandle};
+use self::gatt_database::GattServiceWithHandle;
use self::isolation_manager::IsolationManager;
use self::services::register_builtin_services;
@@ -35,9 +37,11 @@
pub use indication_handler::IndicationError;
+type AttClients = HashMap<TransportIndex, SharedBox<AttClient>>;
+
#[allow(missing_docs)]
pub struct GattModule {
- connections: HashMap<TransportIndex, GattConnection>,
+ clients: AttClients,
databases: HashMap<ServerId, SharedBox<GattDatabase>>,
transport: Rc<dyn AttTransport>,
// NOTE: this is logically owned by the GattModule. We share it behind a Mutex just so we
@@ -46,23 +50,13 @@
isolation_manager: Arc<Mutex<IsolationManager>>,
}
-struct GattConnection {
- bearer: SharedBox<AttServerBearer<AttDatabaseImpl>>,
- database: WeakBox<GattDatabase>,
-}
-
impl GattModule {
/// Constructor.
pub fn new(
transport: Rc<dyn AttTransport>,
isolation_manager: Arc<Mutex<IsolationManager>>,
) -> Self {
- Self {
- connections: HashMap::new(),
- databases: HashMap::new(),
- transport,
- isolation_manager,
- }
+ Self { clients: AttClients::new(), databases: HashMap::new(), transport, isolation_manager }
}
/// Handle LE link connect
@@ -83,12 +77,14 @@
};
let transport = self.transport.clone();
- let bearer = SharedBox::new(AttServerBearer::new(
- database.get_att_database(tcb_idx),
- move |packet| transport.send_packet(tcb_idx, packet),
- ));
- database.on_bearer_ready(tcb_idx, &bearer);
- self.connections.insert(tcb_idx, GattConnection { bearer, database: database.downgrade() });
+ self.clients.insert(
+ tcb_idx,
+ AttClient::new_client_and_bearer(
+ tcb_idx,
+ move |packet| transport.send_packet(tcb_idx, packet),
+ database,
+ ),
+ );
Ok(())
}
@@ -96,12 +92,9 @@
pub fn on_le_disconnect(&mut self, tcb_idx: TransportIndex) -> Result<()> {
info!("disconnected conn_id {tcb_idx:?}");
self.isolation_manager.lock().unwrap().on_le_disconnect(tcb_idx);
- let connection = self.connections.remove(&tcb_idx);
- let Some(connection) = connection else {
+ if self.clients.remove(&tcb_idx).is_none() {
bail!("got disconnection from {tcb_idx:?} but bearer does not exist");
};
- drop(connection.bearer);
- connection.database.with(|db| db.map(|db| db.on_bearer_dropped(tcb_idx)));
Ok(())
}
@@ -157,11 +150,8 @@
}
/// Get an ATT bearer for a particular connection
- pub fn get_bearer(
- &self,
- tcb_idx: TransportIndex,
- ) -> Option<&SharedBox<AttServerBearer<AttDatabaseImpl>>> {
- self.connections.get(&tcb_idx).map(|x| &x.bearer)
+ pub fn get_bearer(&self, tcb_idx: TransportIndex) -> Option<&SharedBox<AttServerBearer>> {
+ self.clients.get(&tcb_idx).map(|c| c.bearer())
}
/// Get the IsolationManager to manage associations between servers + advertisers
diff --git a/system/rust/src/gatt/server/att_client.rs b/system/rust/src/gatt/server/att_client.rs
new file mode 100644
index 0000000..ee561ff
--- /dev/null
+++ b/system/rust/src/gatt/server/att_client.rs
@@ -0,0 +1,361 @@
+use crate::core::shared_box::{SharedBox, WeakBox};
+use crate::gatt::callbacks::{GattWriteRequestType, RawGattDatastore, TransactionDecision};
+use crate::gatt::ffi::AttributeBackingType;
+use crate::gatt::ids::{AttHandle, TransportIndex};
+use crate::gatt::server::att_database::AttAttribute;
+use crate::gatt::server::att_server_bearer::AttServerBearer;
+use crate::gatt::server::gatt_database::{
+ AttAttributeBackingValue, AttAttributeWithBackingValue, GattDatabase,
+};
+use crate::packets::att::{self, AttErrorCode};
+use log::{error, warn};
+use pdl_runtime::EncodeError;
+use std::cell::RefCell;
+#[cfg(test)]
+use std::future::Future;
+use std::rc::Rc;
+
+/// AttClient represents a connection from a *remote* device to a *local* server i.e. it is for
+/// *inbound* connections but it holds the required state for our server. All connections with the
+/// same remote address map to the same client, and there is a one-to-one mapping to the transport
+/// index which identifies the client in the legacy stack. For now, we only support a single bearer
+/// but in future, with EATT, there may be more bearers. A client has access to only one particular
+/// database (which may be shared amongst many clients).
+pub struct AttClient {
+ // The identifier for the client in the legacy stack.
+ tcb_idx: TransportIndex,
+
+ bearer: SharedBox<AttServerBearer>,
+
+ // The underlying database for this client. This is weak because databases can be destroyed
+ // independently of clients.
+ gatt_db: WeakBox<GattDatabase>,
+
+ // Prepared writes all have to be for the same datastore because we are unable to atomically
+ // execute writes across different datastores.
+ datastore_for_prepared_writes: RefCell<Option<Rc<dyn RawGattDatastore>>>,
+}
+
+impl AttClient {
+ pub fn new_client_and_bearer(
+ tcb_idx: TransportIndex,
+ send_packet: impl Fn(att::Att) -> Result<(), EncodeError> + 'static,
+ db: &SharedBox<GattDatabase>,
+ ) -> SharedBox<Self> {
+ let this = SharedBox::new_cyclic(|weak| Self {
+ tcb_idx,
+ bearer: SharedBox::new(AttServerBearer::new(weak, send_packet)),
+ gatt_db: db.downgrade(),
+ datastore_for_prepared_writes: RefCell::default(),
+ });
+ db.on_client_connect(&this);
+ this
+ }
+
+ /// Returns a test client and a receiver for all packets sent.
+ #[cfg(test)]
+ pub fn new_test_client(
+ tcb_idx: TransportIndex,
+ db: &SharedBox<GattDatabase>,
+ ) -> (SharedBox<Self>, tokio::sync::mpsc::UnboundedReceiver<att::Att>) {
+ let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
+ (
+ Self::new_client_and_bearer(
+ tcb_idx,
+ move |packet| {
+ tx.send(packet).unwrap();
+ Ok(())
+ },
+ db,
+ ),
+ rx,
+ )
+ }
+
+ pub fn tcb_idx(&self) -> TransportIndex {
+ self.tcb_idx
+ }
+
+ pub fn bearer(&self) -> &SharedBox<AttServerBearer> {
+ &self.bearer
+ }
+
+ pub fn list_attributes(&self) -> Vec<AttAttribute> {
+ self.gatt_db.with(|db| db.map(|db| db.list_attributes()).unwrap_or_default())
+ }
+
+ pub fn write_no_response_attribute(&self, handle: AttHandle, data: &[u8]) {
+ if self.gatt_db.with(|db| {
+ db.and_then(|db| db.with_attribute(handle, |attr| {
+ if !attr.attribute.permissions.writable_without_response() {
+ warn!("trying to write without response to {handle:?}, which doesn't support it");
+ return;
+ }
+ match &attr.value {
+ AttAttributeBackingValue::Static(val) => {
+ error!("A static attribute {val:?} is marked as writable - ignoring it and rejecting the write...");
+ }
+ AttAttributeBackingValue::DynamicCharacteristic(datastore) => {
+ datastore.write_no_response(
+ self.tcb_idx,
+ handle,
+ AttributeBackingType::Characteristic,
+ data,
+ );
+ }
+ AttAttributeBackingValue::DynamicDescriptor(datastore) => {
+ datastore.write_no_response(
+ self.tcb_idx,
+ handle,
+ AttributeBackingType::Descriptor,
+ data,
+ );
+ }
+ }
+ }))}).is_none() {
+ warn!("cannot find handle {handle:?}");
+ }
+ }
+}
+
+impl Drop for AttClient {
+ fn drop(&mut self) {
+ self.gatt_db.with(|db| {
+ if let Some(db) = db {
+ db.on_client_dropped(self.tcb_idx);
+ }
+ });
+ }
+}
+
+impl SharedBox<AttClient> {
+ #[cfg(test)]
+ pub fn read_attribute(
+ &self,
+ handle: AttHandle,
+ ) -> impl Future<Output = Result<Vec<u8>, AttErrorCode>> {
+ let this = self.downgrade();
+ async move { this.read_attribute(handle).await }
+ }
+
+ #[cfg(test)]
+ pub fn write_attribute(
+ &self,
+ handle: AttHandle,
+ data: &[u8],
+ ) -> impl Future<Output = Result<(), AttErrorCode>> {
+ let this = self.downgrade();
+ let data = data.to_vec();
+ async move { this.write_attribute(handle, &data).await }
+ }
+}
+
+// Strong references to AttClient should not be held over await points (since that has the potential
+// to lead to reference cycles), so async methods operate need to operate on `WeakAttClient`.
+pub type WeakAttClient = WeakBox<AttClient>;
+
+impl WeakAttClient {
+ /// Calls `f` with the client and database. Returns an `InvalidHandle` error if either the
+ /// client or database has been closed.
+ fn with_db<T, E: From<AttErrorCode>>(
+ &self,
+ f: impl FnOnce(&AttClient, &GattDatabase) -> Result<T, E>,
+ ) -> Result<T, E> {
+ self.with(|client| {
+ client.map_or(Err(AttErrorCode::InvalidHandle.into()), |client| {
+ client.gatt_db.with(|db| {
+ db.map_or(Err(AttErrorCode::InvalidHandle.into()), |db| f(client, db))
+ })
+ })
+ })
+ }
+
+ /// Calls `f` with the client and attribute. Returns an `InvalidHandle` error if either the
+ /// client or database has been closed, or if the handle doesn't exist.
+ pub fn with_attribute<T, E: From<AttErrorCode>>(
+ &self,
+ handle: AttHandle,
+ f: impl FnOnce(&AttClient, &AttAttributeWithBackingValue) -> Result<T, E>,
+ ) -> Result<T, E> {
+ self.with_db(|client, db| {
+ db.with_attribute(handle, |attr| f(client, attr))
+ .unwrap_or(Err(AttErrorCode::InvalidHandle.into()))
+ })
+ }
+
+ pub async fn read_attribute(&self, handle: AttHandle) -> Result<Vec<u8>, AttErrorCode> {
+ let (tcb_idx, value) = self.with_attribute(handle, |client, attr| {
+ if !attr.attribute.permissions.readable() {
+ Err(AttErrorCode::ReadNotPermitted)
+ } else {
+ Ok((client.tcb_idx, attr.value.clone()))
+ }
+ })?;
+
+ match value {
+ AttAttributeBackingValue::Static(val) => Ok(val),
+ AttAttributeBackingValue::DynamicCharacteristic(datastore) => {
+ datastore
+ .read(
+ tcb_idx,
+ handle,
+ /* offset */ 0,
+ AttributeBackingType::Characteristic,
+ )
+ .await
+ }
+ AttAttributeBackingValue::DynamicDescriptor(datastore) => {
+ datastore
+ .read(tcb_idx, handle, /* offset */ 0, AttributeBackingType::Descriptor)
+ .await
+ }
+ }
+ }
+
+ pub async fn write_attribute(
+ &self,
+ handle: AttHandle,
+ data: &[u8],
+ ) -> Result<(), AttErrorCode> {
+ let (tcb_idx, value) = self.with_attribute(handle, |client, attr| {
+ if !attr.attribute.permissions.writable_with_response() {
+ Err(AttErrorCode::WriteNotPermitted)
+ } else {
+ Ok((client.tcb_idx, attr.value.clone()))
+ }
+ })?;
+
+ match value {
+ AttAttributeBackingValue::Static(val) => {
+ error!(
+ "A static attribute {val:?} (handle={handle:?}) is marked as writable - \
+ ignoring it and rejecting the write..."
+ );
+ Err(AttErrorCode::WriteNotPermitted)
+ }
+ AttAttributeBackingValue::DynamicCharacteristic(datastore) => {
+ datastore
+ .write(
+ tcb_idx,
+ handle,
+ AttributeBackingType::Characteristic,
+ GattWriteRequestType::Request,
+ data,
+ )
+ .await
+ }
+ AttAttributeBackingValue::DynamicDescriptor(datastore) => {
+ datastore
+ .write(
+ tcb_idx,
+ handle,
+ AttributeBackingType::Descriptor,
+ GattWriteRequestType::Request,
+ data,
+ )
+ .await
+ }
+ }
+ }
+
+ /// Returns the list of attributes or an empty list if either the client or database has been
+ /// closed. This isn't async, but it exists as a convenience method since this often needs to be
+ /// called on `WeakAttClient`.
+ pub fn list_attributes(&self) -> Vec<AttAttribute> {
+ self.with(|client| client.map(|c| c.list_attributes()).unwrap_or_default())
+ }
+
+ pub fn write_no_response_attribute(&self, handle: AttHandle, data: &[u8]) {
+ self.with(|client| {
+ if let Some(client) = client {
+ client.write_no_response_attribute(handle, data);
+ }
+ });
+ }
+
+ /// Queues a write for later execution. As per the Bluetooth Core Specification v6.0, Vol 3,
+ /// Part F, 3.4.6.1, the offset and data length are not validated until execution time.
+ pub async fn prepare_write_attribute(
+ &self,
+ handle: AttHandle,
+ offset: u32,
+ data: &[u8],
+ ) -> Result<(), AttErrorCode> {
+ let (tcb_idx, backing_type, datastore) = self.with_attribute(handle, |client, attr| {
+ if !attr.attribute.permissions.writable_with_response() {
+ return Err(AttErrorCode::WriteNotPermitted);
+ }
+
+ let (backing_type, datastore) = match &attr.value {
+ AttAttributeBackingValue::Static(val) => {
+ error!(
+ "A static attribute {val:?} is marked as writable - ignoring it and \
+ rejecting the write..."
+ );
+ return Err(AttErrorCode::WriteNotPermitted);
+ }
+ AttAttributeBackingValue::DynamicCharacteristic(datastore) => {
+ (AttributeBackingType::Characteristic, Rc::clone(datastore))
+ }
+ AttAttributeBackingValue::DynamicDescriptor(datastore) => {
+ (AttributeBackingType::Descriptor, Rc::clone(datastore))
+ }
+ };
+
+ // We only support writing attributes within the same data-store as already queued
+ // writes (since there is no way for us to atomically update attributes across different
+ // stores). We check for equality using pointer comparisons: in practice there is a
+ // single, separate, datastore for each service.
+ match &mut *client.datastore_for_prepared_writes.borrow_mut() {
+ Some(existing_datastore) => {
+ if !Rc::ptr_eq(existing_datastore, &datastore) {
+ return Err(AttErrorCode::RequestNotSupported);
+ }
+ }
+ v @ None => {
+ // NOTE: If the prepare fails below, we won't unset the datastore which means
+ // that if there is a subsequent attempt to write to a different datastore, it
+ // will fail with `RequestNotSupported` even though there might not be actually
+ // any write queued. Supporting this unlikely edge case is tricky (e.g. consider
+ // how to make it work if writes are queued concurrently over different bearers)
+ // and deemed not worth the effort.
+ *v = Some(Rc::clone(&datastore));
+ }
+ }
+
+ Ok((client.tcb_idx, backing_type, datastore))
+ })?;
+
+ datastore
+ .write(tcb_idx, handle, backing_type, GattWriteRequestType::Prepare { offset }, data)
+ .await?;
+
+ Ok(())
+ }
+
+ /// Executes the queue of prepared writes.
+ pub async fn execute(
+ &self,
+ decision: TransactionDecision,
+ ) -> Result<(), (AttHandle, AttErrorCode)> {
+ match self.with(|client| {
+ if let Some(client) = client {
+ Ok(client
+ .datastore_for_prepared_writes
+ .borrow_mut()
+ .take()
+ .map(|datastore| (client.tcb_idx, datastore)))
+ } else {
+ // The client has gone away.
+ Err((AttHandle(0), AttErrorCode::UnlikelyError))
+ }
+ })? {
+ Some((tcb_idx, datastore)) => {
+ // NOTE: the handle in error isn't currently plumbed through, so we have to just
+ // use zero for now.
+ datastore.execute(tcb_idx, decision).await.map_err(|c| (AttHandle(0), c))
+ }
+ None => Ok(()),
+ }
+ }
+}
diff --git a/system/rust/src/gatt/server/att_database.rs b/system/rust/src/gatt/server/att_database.rs
index bc171a8..01b3195 100644
--- a/system/rust/src/gatt/server/att_database.rs
+++ b/system/rust/src/gatt/server/att_database.rs
@@ -1,9 +1,8 @@
-use async_trait::async_trait;
use bitflags::bitflags;
use crate::core::uuid::Uuid;
use crate::gatt::ids::AttHandle;
-use crate::packets::att::{self, AttErrorCode};
+use crate::packets::att;
impl From<att::AttHandle> for AttHandle {
fn from(value: att::AttHandle) -> Self {
@@ -61,66 +60,3 @@
self.contains(AttPermissions::INDICATE)
}
}
-
-#[async_trait(?Send)]
-pub trait AttDatabase {
- /// Read an attribute by handle
- async fn read_attribute(&self, handle: AttHandle) -> Result<Vec<u8>, AttErrorCode>;
-
- /// Write to an attribute by handle
- async fn write_attribute(&self, handle: AttHandle, data: &[u8]) -> Result<(), AttErrorCode>;
-
- /// Write to an attribute by handle
- fn write_no_response_attribute(&self, handle: AttHandle, data: &[u8]);
-
- /// List all the attributes in this database.
- ///
- /// Expected to return them in sorted order.
- fn list_attributes(&self) -> Vec<AttAttribute>;
-
- /// Produce an implementation of StableAttDatabase
- fn snapshot(&self) -> SnapshottedAttDatabase<'_>
- where
- Self: Sized,
- {
- SnapshottedAttDatabase { attributes: self.list_attributes(), backing: self }
- }
-}
-
-/// Marker trait indicating that the backing attribute list of this
-/// database is guaranteed to remain unchanged across async points.
-///
-/// Useful if we want to call list_attributes() multiple times, rather than
-/// caching its result the first time.
-pub trait StableAttDatabase: AttDatabase {
- fn find_attribute(&self, handle: AttHandle) -> Option<AttAttribute> {
- self.list_attributes().into_iter().find(|attr| attr.handle == handle)
- }
-}
-
-/// A snapshot of an AttDatabase implementing StableAttDatabase.
-pub struct SnapshottedAttDatabase<'a> {
- attributes: Vec<AttAttribute>,
- backing: &'a (dyn AttDatabase),
-}
-
-#[async_trait(?Send)]
-impl AttDatabase for SnapshottedAttDatabase<'_> {
- async fn read_attribute(&self, handle: AttHandle) -> Result<Vec<u8>, AttErrorCode> {
- self.backing.read_attribute(handle).await
- }
-
- async fn write_attribute(&self, handle: AttHandle, data: &[u8]) -> Result<(), AttErrorCode> {
- self.backing.write_attribute(handle, data).await
- }
-
- fn write_no_response_attribute(&self, handle: AttHandle, data: &[u8]) {
- self.backing.write_no_response_attribute(handle, data);
- }
-
- fn list_attributes(&self) -> Vec<AttAttribute> {
- self.attributes.clone()
- }
-}
-
-impl StableAttDatabase for SnapshottedAttDatabase<'_> {}
diff --git a/system/rust/src/gatt/server/att_server_bearer.rs b/system/rust/src/gatt/server/att_server_bearer.rs
index 20b138e..17b4a91 100644
--- a/system/rust/src/gatt/server/att_server_bearer.rs
+++ b/system/rust/src/gatt/server/att_server_bearer.rs
@@ -1,6 +1,6 @@
//! This module handles an individual connection on the ATT fixed channel.
//! It handles ATT transactions and unacknowledged operations, backed by an
-//! AttDatabase (that may in turn be backed by an upper-layer protocol)
+//! database (that may in turn be backed by an upper-layer protocol)
use pdl_runtime::EncodeError;
use std::cell::Cell;
@@ -10,21 +10,21 @@
use log::{error, trace, warn};
use tokio::task::spawn_local;
-use crate::core::shared_box::{SharedBox, WeakBox};
+use crate::core::shared_box::SharedBox;
use crate::core::shared_mutex::SharedMutex;
use crate::gatt::ids::AttHandle;
use crate::gatt::mtu::{AttMtu, MtuEvent};
use crate::gatt::opcode_types::{AttRequest, AttType};
+use crate::gatt::server::att_client::WeakAttClient;
use crate::packets::att::{self, AttErrorCode};
use crate::utils::owned_handle::OwnedHandle;
-use super::att_database::AttDatabase;
use super::command_handler::AttCommandHandler;
use super::indication_handler::{ConfirmationWatcher, IndicationError, IndicationHandler};
use super::request_handler::AttRequestHandler;
-enum AttRequestState<T: AttDatabase> {
- Idle(AttRequestHandler<T>),
+enum AttRequestState {
+ Idle(AttRequestHandler),
Pending { _task: OwnedHandle<()> },
Replacing,
}
@@ -42,46 +42,49 @@
/// This represents a single ATT bearer (currently, always the unenhanced fixed
/// channel on LE) The AttRequestState ensures that only one transaction can
/// take place at a time
-pub struct AttServerBearer<T: AttDatabase> {
+pub struct AttServerBearer {
// general
send_packet: Box<dyn Fn(att::Att) -> Result<(), EncodeError>>,
mtu: AttMtu,
// request state
- curr_request: Cell<AttRequestState<T>>,
+ curr_request: Cell<AttRequestState>,
// indication state
- indication_handler: SharedMutex<IndicationHandler<T>>,
+ indication_handler: SharedMutex<IndicationHandler>,
pending_confirmation: ConfirmationWatcher,
// command handler (across all bearers)
- command_handler: AttCommandHandler<T>,
+ command_handler: AttCommandHandler,
}
-impl<T: AttDatabase + Clone + 'static> AttServerBearer<T> {
- /// Constructor, wrapping an ATT channel (for outgoing packets) and an
- /// AttDatabase
- pub fn new(db: T, send_packet: impl Fn(att::Att) -> Result<(), EncodeError> + 'static) -> Self {
- let (indication_handler, pending_confirmation) = IndicationHandler::new(db.clone());
+impl AttServerBearer {
+ /// Constructor, wrapping an ATT channel (for outgoing packets).
+ pub fn new(
+ client: WeakAttClient,
+ send_packet: impl Fn(att::Att) -> Result<(), EncodeError> + 'static,
+ ) -> Self {
+ let (indication_handler, pending_confirmation) = IndicationHandler::new(client.clone());
Self {
send_packet: Box::new(send_packet),
mtu: AttMtu::new(),
- curr_request: AttRequestState::Idle(AttRequestHandler::new(db.clone())).into(),
+ curr_request: AttRequestState::Idle(AttRequestHandler::new(client.clone())).into(),
indication_handler: SharedMutex::new(indication_handler),
pending_confirmation,
- command_handler: AttCommandHandler::new(db),
+ command_handler: AttCommandHandler::new(client),
}
}
- fn send_packet(&self, packet: att::Att) -> Result<(), EncodeError> {
+ /// Sends the specified packet on the bearer.
+ pub fn send_packet(&self, packet: att::Att) -> Result<(), EncodeError> {
(self.send_packet)(packet)
}
}
-impl<T: AttDatabase + Clone + 'static> SharedBox<AttServerBearer<T>> {
+impl SharedBox<AttServerBearer> {
/// Handle an incoming packet, and send outgoing packets as appropriate
/// using the owned ATT channel.
pub fn handle_packet(&self, packet: att::Att) {
@@ -107,7 +110,6 @@
let locked_indication_handler = self.indication_handler.lock();
let pending_mtu = self.mtu.snapshot();
- let this = self.downgrade();
async move {
// first wait until we are at the head of the queue and are ready to send
@@ -126,7 +128,7 @@
IndicationError::SendError(SendError::ConnectionDropped)
})?;
// finally, send, and wait for a response
- indication_handler.send(handle, &data, mtu, |packet| this.try_send_packet(packet)).await
+ indication_handler.send(handle, &data, mtu).await
}
}
@@ -185,25 +187,12 @@
}
}
-impl<T: AttDatabase + Clone + 'static> WeakBox<AttServerBearer<T>> {
- fn try_send_packet(&self, packet: att::Att) -> Result<(), SendError> {
- self.with(|this| {
- this.ok_or_else(|| {
- warn!("connection dropped before packet sent");
- SendError::ConnectionDropped
- })?
- .send_packet(packet)
- .map_err(SendError::SerializeError)
- })
- }
-}
-
#[cfg(test)]
mod test {
use std::rc::Rc;
use tokio::sync::mpsc::error::TryRecvError;
- use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver};
+ use tokio::sync::mpsc::UnboundedReceiver;
use super::*;
@@ -212,11 +201,12 @@
use crate::gatt::ffi::AttributeBackingType;
use crate::gatt::ids::TransportIndex;
use crate::gatt::mocks::mock_datastore::{MockDatastore, MockDatastoreEvents};
+ use crate::gatt::server::att_client::AttClient;
use crate::gatt::server::att_database::{AttAttribute, AttPermissions};
use crate::gatt::server::gatt_database::{
GattCharacteristicWithHandle, GattDatabase, GattServiceWithHandle,
};
- use crate::gatt::server::test::test_att_db::TestAttDatabase;
+ use crate::gatt::server::test::test_att_db::new_test_database;
use crate::packets::att;
use crate::utils::task::{block_on_locally, try_await};
@@ -227,8 +217,8 @@
const TCB_IDX: TransportIndex = TransportIndex(1);
fn open_connection(
- ) -> (SharedBox<AttServerBearer<TestAttDatabase>>, UnboundedReceiver<att::Att>) {
- let db = TestAttDatabase::new(vec![
+ ) -> (SharedBox<GattDatabase>, SharedBox<AttClient>, UnboundedReceiver<att::Att>) {
+ let db = new_test_database(vec![
(
AttAttribute {
handle: VALID_HANDLE,
@@ -246,19 +236,15 @@
vec![5, 6],
),
]);
- let (tx, rx) = unbounded_channel();
- let conn = AttServerBearer::new(db, move |packet| {
- tx.send(packet).unwrap();
- Ok(())
- })
- .into();
- (conn, rx)
+ let (client, rx) = AttClient::new_test_client(TCB_IDX, &db);
+ (db, client, rx)
}
#[test]
fn test_single_transaction() {
block_on_locally(async {
- let (conn, mut rx) = open_connection();
+ let (_db, client, mut rx) = open_connection();
+ let conn = client.bearer();
conn.handle_packet(
att::AttReadRequest { attribute_handle: VALID_HANDLE.into() }.try_into().unwrap(),
);
@@ -270,7 +256,8 @@
#[test]
fn test_sequential_transactions() {
block_on_locally(async {
- let (conn, mut rx) = open_connection();
+ let (_db, client, mut rx) = open_connection();
+ let conn = client.bearer();
conn.handle_packet(
att::AttReadRequest { attribute_handle: INVALID_HANDLE.into() }.try_into().unwrap(),
);
@@ -314,12 +301,8 @@
datastore,
)
.unwrap();
- let (tx, mut rx) = unbounded_channel();
- let send_packet = move |packet| {
- tx.send(packet).unwrap();
- Ok(())
- };
- let conn = SharedBox::new(AttServerBearer::new(db.get_att_database(TCB_IDX), send_packet));
+ let (client, mut rx) = AttClient::new_test_client(TCB_IDX, &db);
+ let conn = client.bearer();
let data = [1, 2];
// act: send two read requests before replying to either read
@@ -357,7 +340,8 @@
fn test_indication_confirmation() {
block_on_locally(async {
// arrange
- let (conn, mut rx) = open_connection();
+ let (_db, client, mut rx) = open_connection();
+ let conn = client.bearer();
// act: send an indication
let pending_send = spawn_local(conn.send_indication(VALID_HANDLE, vec![1, 2, 3]));
@@ -375,7 +359,8 @@
fn test_sequential_indications() {
block_on_locally(async {
// arrange
- let (conn, mut rx) = open_connection();
+ let (_db, client, mut rx) = open_connection();
+ let conn = client.bearer();
// act: send the first indication
let pending_send1 = spawn_local(conn.send_indication(VALID_HANDLE, vec![1, 2, 3]));
@@ -404,7 +389,8 @@
fn test_queued_indications_only_one_sent() {
block_on_locally(async {
// arrange
- let (conn, mut rx) = open_connection();
+ let (_db, client, mut rx) = open_connection();
+ let conn = client.bearer();
// act: send two indications simultaneously
let pending_send1 = spawn_local(conn.send_indication(VALID_HANDLE, vec![1, 2, 3]));
@@ -423,7 +409,8 @@
fn test_queued_indications_dequeue_second() {
block_on_locally(async {
// arrange
- let (conn, mut rx) = open_connection();
+ let (_db, client, mut rx) = open_connection();
+ let conn = client.bearer();
// act: send two indications simultaneously
let pending_send1 = spawn_local(conn.send_indication(VALID_HANDLE, vec![1, 2, 3]));
@@ -451,7 +438,8 @@
fn test_queued_indications_complete_both() {
block_on_locally(async {
// arrange
- let (conn, mut rx) = open_connection();
+ let (_db, client, mut rx) = open_connection();
+ let conn = client.bearer();
// act: send two indications simultaneously
let pending_send1 = spawn_local(conn.send_indication(VALID_HANDLE, vec![1, 2, 3]));
@@ -480,12 +468,13 @@
fn test_indication_connection_drop() {
block_on_locally(async {
// arrange: a pending indication
- let (conn, mut rx) = open_connection();
+ let (_db, client, mut rx) = open_connection();
+ let conn = client.bearer();
let pending_send = spawn_local(conn.send_indication(VALID_HANDLE, vec![1, 2, 3]));
// act: drop the connection after the indication is sent
rx.recv().await.unwrap();
- drop(conn);
+ drop(client);
// assert: the pending indication fails with the appropriate error
assert!(matches!(
@@ -499,7 +488,8 @@
fn test_single_indication_pending_mtu() {
block_on_locally(async {
// arrange: pending MTU negotiation
- let (conn, mut rx) = open_connection();
+ let (_db, client, mut rx) = open_connection();
+ let conn = client.bearer();
conn.handle_mtu_event(MtuEvent::OutgoingRequest).unwrap();
// act: try to send an indication with a large payload size
@@ -516,7 +506,8 @@
fn test_single_indication_pending_mtu_fail() {
block_on_locally(async {
// arrange: pending MTU negotiation
- let (conn, _) = open_connection();
+ let (_db, client, _rx) = open_connection();
+ let conn = client.bearer();
conn.handle_mtu_event(MtuEvent::OutgoingRequest).unwrap();
// act: try to send an indication with a large payload size
@@ -534,7 +525,8 @@
fn test_server_transaction_pending_mtu() {
block_on_locally(async {
// arrange: pending MTU negotiation
- let (conn, mut rx) = open_connection();
+ let (_db, client, mut rx) = open_connection();
+ let conn = client.bearer();
conn.handle_mtu_event(MtuEvent::OutgoingRequest).unwrap();
// act: send server packet
@@ -551,7 +543,8 @@
fn test_queued_indication_pending_mtu_uses_mtu_on_dequeue() {
block_on_locally(async {
// arrange: an outstanding indication
- let (conn, mut rx) = open_connection();
+ let (_db, client, mut rx) = open_connection();
+ let conn = client.bearer();
let _ = try_await(conn.send_indication(VALID_HANDLE, vec![1, 2, 3])).await;
rx.recv().await.unwrap(); // flush rx_queue
diff --git a/system/rust/src/gatt/server/command_handler.rs b/system/rust/src/gatt/server/command_handler.rs
index 36ccb25..2be705d 100644
--- a/system/rust/src/gatt/server/command_handler.rs
+++ b/system/rust/src/gatt/server/command_handler.rs
@@ -1,29 +1,27 @@
use log::warn;
use crate::gatt::opcode_types::AttCommand;
+use crate::gatt::server::att_client::WeakAttClient;
use crate::packets::att;
-use super::att_database::AttDatabase;
-
/// This struct handles all ATT commands.
-pub struct AttCommandHandler<Db: AttDatabase> {
- db: Db,
+pub struct AttCommandHandler {
+ client: WeakAttClient,
}
-impl<Db: AttDatabase> AttCommandHandler<Db> {
- pub fn new(db: Db) -> Self {
- Self { db }
+impl AttCommandHandler {
+ pub fn new(client: WeakAttClient) -> Self {
+ Self { client }
}
pub fn process_packet(&self, packet: AttCommand) {
- let snapshotted_db = self.db.snapshot();
match packet.opcode {
att::AttOpcode::WriteCommand => {
let Ok(packet) = att::AttWriteCommand::try_from(&*packet) else {
warn!("failed to parse WRITE_COMMAND packet");
return;
};
- snapshotted_db.write_no_response_attribute(packet.handle.into(), &packet.value);
+ self.client.write_no_response_attribute(packet.handle.into(), &packet.value);
}
_ => {
warn!("Dropping unsupported opcode {:?}", packet.opcode);
@@ -35,19 +33,22 @@
#[cfg(test)]
mod test {
use crate::core::uuid::Uuid;
- use crate::gatt::ids::AttHandle;
+ use crate::gatt::ids::{AttHandle, TransportIndex};
use crate::gatt::opcode_types::AttCommand;
- use crate::gatt::server::att_database::{AttAttribute, AttDatabase};
+ use crate::gatt::server::att_client::AttClient;
+ use crate::gatt::server::att_database::AttAttribute;
use crate::gatt::server::command_handler::AttCommandHandler;
use crate::gatt::server::gatt_database::AttPermissions;
- use crate::gatt::server::test::test_att_db::TestAttDatabase;
+ use crate::gatt::server::test::test_att_db::new_test_database;
use crate::packets::att;
use crate::utils::task::block_on_locally;
+ const TCB_IDX: TransportIndex = TransportIndex(1);
+
#[test]
fn test_write_command() {
// arrange
- let db = TestAttDatabase::new(vec![(
+ let db = new_test_database(vec![(
AttAttribute {
handle: AttHandle(3),
type_: Uuid::new(0x1234),
@@ -55,7 +56,8 @@
},
vec![1, 2, 3],
)]);
- let handler = AttCommandHandler { db: db.clone() };
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
+ let handler = AttCommandHandler::new(client.downgrade());
let data = [1, 2];
// act: send write command
@@ -67,14 +69,15 @@
handler.process_packet(att_view);
// assert: the db has been updated
- assert_eq!(block_on_locally(db.read_attribute(AttHandle(3))).unwrap(), data);
+ assert_eq!(block_on_locally(client.read_attribute(AttHandle(3))).unwrap(), data);
}
#[test]
fn test_unsupported_command() {
// arrange
- let db = TestAttDatabase::new(vec![]);
- let handler = AttCommandHandler { db };
+ let db = new_test_database(vec![]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
+ let handler = AttCommandHandler::new(client.downgrade());
// act: send a packet that should not be handled here
let att_view = AttCommand::new(att::AttSignedWriteCommand {
diff --git a/system/rust/src/gatt/server/gatt_database.rs b/system/rust/src/gatt/server/gatt_database.rs
index 7d3704f..6aa7258 100644
--- a/system/rust/src/gatt/server/gatt_database.rs
+++ b/system/rust/src/gatt/server/gatt_database.rs
@@ -9,18 +9,15 @@
use std::rc::Rc;
use anyhow::{bail, Result};
-use async_trait::async_trait;
-use log::{error, warn};
-use crate::core::shared_box::{SharedBox, WeakBox};
+use crate::core::shared_box::SharedBox;
use crate::core::uuid::Uuid;
-use crate::gatt::callbacks::{GattWriteRequestType, RawGattDatastore};
-use crate::gatt::ffi::AttributeBackingType;
+use crate::gatt::callbacks::RawGattDatastore;
use crate::gatt::ids::{AttHandle, TransportIndex};
-use crate::packets::att::{self, AttErrorCode};
+use crate::gatt::server::att_client::AttClient;
+use crate::packets::att;
-use super::att_database::{AttAttribute, AttDatabase};
-use super::att_server_bearer::AttServerBearer;
+use super::att_database::AttAttribute;
pub use super::att_database::AttPermissions;
@@ -75,8 +72,8 @@
pub permissions: AttPermissions,
}
-/// The GattDatabase implements AttDatabase, and converts attribute reads/writes
-/// into GATT operations to be sent to the upper layers
+/// The GattDatabase converts attribute reads/writes into GATT operations to be sent to the upper
+/// layers.
#[derive(Default)]
pub struct GattDatabase {
schema: RefCell<GattDatabaseSchema>,
@@ -89,16 +86,16 @@
}
#[derive(Clone)]
-enum AttAttributeBackingValue {
+pub enum AttAttributeBackingValue {
Static(Vec<u8>),
DynamicCharacteristic(Rc<dyn RawGattDatastore>),
DynamicDescriptor(Rc<dyn RawGattDatastore>),
}
#[derive(Clone)]
-struct AttAttributeWithBackingValue {
- attribute: AttAttribute,
- value: AttAttributeBackingValue,
+pub struct AttAttributeWithBackingValue {
+ pub attribute: AttAttribute,
+ pub value: AttAttributeBackingValue,
}
/// Callbacks that can be registered on the GattDatabase to watch for
@@ -110,11 +107,7 @@
/// will NEVER be invoked.
pub trait GattDatabaseCallbacks {
/// A peer device on the given bearer has connected to this database (and can see its attributes)
- fn on_le_connect(
- &self,
- tcb_idx: TransportIndex,
- bearer: &SharedBox<AttServerBearer<AttDatabaseImpl>>,
- );
+ fn on_le_connect(&self, client: &SharedBox<AttClient>);
/// A peer device has disconnected from this database
fn on_le_disconnect(&self, tcb_idx: TransportIndex);
/// The attributes in the specified range have changed
@@ -127,6 +120,26 @@
Default::default()
}
+ /// Returns access to the attributes in this database.
+ pub fn with_attribute<T>(
+ &self,
+ handle: AttHandle,
+ f: impl FnOnce(&AttAttributeWithBackingValue) -> T,
+ ) -> Option<T> {
+ self.schema.borrow().attributes.get(&handle).map(f)
+ }
+
+ /// Returns the specified attribute.
+ #[cfg(test)]
+ pub fn get(&self, handle: AttHandle) -> Option<AttAttributeWithBackingValue> {
+ self.schema.borrow().attributes.get(&handle).cloned()
+ }
+
+ /// Returns the attribute handles (without access to the backing values).
+ pub fn list_attributes(&self) -> Vec<AttAttribute> {
+ self.schema.borrow().attributes.values().map(|attr| attr.attribute).collect()
+ }
+
/// Register an event listener
pub fn register_listener(&self, callbacks: Rc<dyn GattDatabaseCallbacks>) {
self.listeners.borrow_mut().push(callbacks);
@@ -134,39 +147,38 @@
/// When a connection has been made with access to this database.
/// The supplied bearer is guaranteed to be ready for use.
- pub fn on_bearer_ready(
- &self,
- tcb_idx: TransportIndex,
- bearer: &SharedBox<AttServerBearer<AttDatabaseImpl>>,
- ) {
+ pub fn on_client_connect(&self, client: &SharedBox<AttClient>) {
for listener in self.listeners.borrow().iter() {
- listener.on_le_connect(tcb_idx, bearer);
+ listener.on_le_connect(client);
}
}
/// When the connection has dropped.
- pub fn on_bearer_dropped(&self, tcb_idx: TransportIndex) {
+ pub fn on_client_dropped(&self, tcb_idx: TransportIndex) {
for listener in self.listeners.borrow().iter() {
listener.on_le_disconnect(tcb_idx);
}
}
- /// Add a service with pre-allocated handles (for co-existence with C++) backed by the supplied datastore
- /// Assumes that the characteristic DECLARATION handles are one less than
- /// the characteristic handles.
- /// Returns failure if handles overlap with ones already allocated
+ /// Add a service with pre-allocated handles (for co-existence with C++) backed by the supplied
+ /// datastore Assumes that the characteristic DECLARATION handles are one less than the
+ /// characteristic handles. Returns failure if handles overlap with ones already allocated.
pub fn add_service_with_handles(
&self,
service: GattServiceWithHandle,
datastore: Rc<dyn RawGattDatastore>,
) -> Result<()> {
let mut attributes = BTreeMap::new();
- let mut attribute_cnt = 0;
- let mut add_attribute = |attribute: AttAttribute, value: AttAttributeBackingValue| {
- attribute_cnt += 1;
- attributes.insert(attribute.handle, AttAttributeWithBackingValue { attribute, value })
- };
+ let mut add_attribute =
+ |attribute: AttAttribute, value: AttAttributeBackingValue| match attributes
+ .insert(attribute.handle, AttAttributeWithBackingValue { attribute, value })
+ {
+ None => Ok(()),
+ Some(_) => {
+ Err(anyhow::anyhow!("duplicate handle detected: {:?}", attribute.handle))
+ }
+ };
let mut characteristics = vec![];
@@ -184,7 +196,7 @@
anyhow::anyhow!("failed to encode primary service declaration: {e:?}")
})?,
),
- );
+ )?;
// characteristics
for characteristic in service.characteristics {
@@ -224,7 +236,7 @@
anyhow::anyhow!("failed to encode characteristic declaration: {e:?}")
})?,
),
- );
+ )?;
// value
add_attribute(
@@ -234,7 +246,7 @@
permissions: characteristic.permissions,
},
AttAttributeBackingValue::DynamicCharacteristic(datastore.clone()),
- );
+ )?;
// descriptors
for descriptor in characteristic.descriptors {
@@ -245,7 +257,7 @@
permissions: descriptor.permissions,
},
AttAttributeBackingValue::DynamicDescriptor(datastore.clone()),
- );
+ )?;
}
}
@@ -254,12 +266,9 @@
for handle in attributes.keys() {
if static_data.attributes.contains_key(handle) {
- bail!("duplicate handle detected");
+ bail!("handle conflicts with existing service: {handle:?}");
}
}
- if attributes.len() != attribute_cnt {
- bail!("duplicate handle detected");
- }
// if we made it here, we successfully loaded the new service
static_data.attributes.extend(attributes.clone());
@@ -318,171 +327,34 @@
Ok(())
}
-}
-impl SharedBox<GattDatabase> {
- /// Generate an impl AttDatabase from a backing GattDatabase, associated
- /// with a given connection.
- ///
- /// Note: After the AttDatabaseImpl is constructed, we MUST call on_bearer_ready() with
- /// the resultant bearer, so that the listeners get the correct sequence of callbacks.
- pub fn get_att_database(&self, tcb_idx: TransportIndex) -> AttDatabaseImpl {
- AttDatabaseImpl { gatt_db: self.downgrade(), tcb_idx }
- }
-}
-
-/// An implementation of AttDatabase wrapping an underlying GattDatabase
-pub struct AttDatabaseImpl {
- gatt_db: WeakBox<GattDatabase>,
- tcb_idx: TransportIndex,
-}
-
-#[async_trait(?Send)]
-impl AttDatabase for AttDatabaseImpl {
- async fn read_attribute(&self, handle: AttHandle) -> Result<Vec<u8>, AttErrorCode> {
- let value = self.gatt_db.with(|gatt_db| {
- let Some(gatt_db) = gatt_db else {
- // db must have been closed
- return Err(AttErrorCode::InvalidHandle);
- };
- let services = gatt_db.schema.borrow();
- let Some(attr) = services.attributes.get(&handle) else {
- return Err(AttErrorCode::InvalidHandle);
- };
- if !attr.attribute.permissions.readable() {
- return Err(AttErrorCode::ReadNotPermitted);
- }
- Ok(attr.value.clone())
- })?;
-
- match value {
- AttAttributeBackingValue::Static(val) => return Ok(val),
- AttAttributeBackingValue::DynamicCharacteristic(datastore) => {
- datastore
- .read(
- self.tcb_idx,
- handle,
- /* offset */ 0,
- AttributeBackingType::Characteristic,
- )
- .await
- }
- AttAttributeBackingValue::DynamicDescriptor(datastore) => {
- datastore
- .read(
- self.tcb_idx,
- handle,
- /* offset */ 0,
- AttributeBackingType::Descriptor,
- )
- .await
- }
+ /// Returns a database with the exact characteristics specified. No additional characteristics
+ /// are added for the service or characteristic declarations.
+ #[cfg(test)]
+ pub fn with_characteristics(
+ characteristics: impl IntoIterator<Item = AttAttribute>,
+ datastore: Rc<dyn RawGattDatastore>,
+ ) -> Self {
+ Self {
+ schema: RefCell::new(GattDatabaseSchema {
+ attributes: characteristics
+ .into_iter()
+ .map(|attribute| {
+ (
+ attribute.handle,
+ AttAttributeWithBackingValue {
+ attribute,
+ value: AttAttributeBackingValue::DynamicCharacteristic(Rc::clone(
+ &datastore,
+ )),
+ },
+ )
+ })
+ .collect(),
+ }),
+ listeners: RefCell::default(),
}
}
-
- async fn write_attribute(&self, handle: AttHandle, data: &[u8]) -> Result<(), AttErrorCode> {
- let value = self.gatt_db.with(|gatt_db| {
- let Some(gatt_db) = gatt_db else {
- // db must have been closed
- return Err(AttErrorCode::InvalidHandle);
- };
- let services = gatt_db.schema.borrow();
- let Some(attr) = services.attributes.get(&handle) else {
- return Err(AttErrorCode::InvalidHandle);
- };
- if !attr.attribute.permissions.writable_with_response() {
- return Err(AttErrorCode::WriteNotPermitted);
- }
- Ok(attr.value.clone())
- })?;
-
- match value {
- AttAttributeBackingValue::Static(val) => {
- error!("A static attribute {val:?} is marked as writable - ignoring it and rejecting the write...");
- return Err(AttErrorCode::WriteNotPermitted);
- }
- AttAttributeBackingValue::DynamicCharacteristic(datastore) => {
- datastore
- .write(
- self.tcb_idx,
- handle,
- AttributeBackingType::Characteristic,
- GattWriteRequestType::Request,
- data,
- )
- .await
- }
- AttAttributeBackingValue::DynamicDescriptor(datastore) => {
- datastore
- .write(
- self.tcb_idx,
- handle,
- AttributeBackingType::Descriptor,
- GattWriteRequestType::Request,
- data,
- )
- .await
- }
- }
- }
-
- fn write_no_response_attribute(&self, handle: AttHandle, data: &[u8]) {
- let value = self.gatt_db.with(|gatt_db| {
- let Some(gatt_db) = gatt_db else {
- // db must have been closed
- return None;
- };
- let services = gatt_db.schema.borrow();
- let Some(attr) = services.attributes.get(&handle) else {
- warn!("cannot find handle {handle:?}");
- return None;
- };
- if !attr.attribute.permissions.writable_without_response() {
- warn!("trying to write without response to {handle:?}, which doesn't support it");
- return None;
- }
- Some(attr.value.clone())
- });
-
- let Some(value) = value else {
- return;
- };
-
- match value {
- AttAttributeBackingValue::Static(val) => {
- error!("A static attribute {val:?} is marked as writable - ignoring it and rejecting the write...");
- }
- AttAttributeBackingValue::DynamicCharacteristic(datastore) => {
- datastore.write_no_response(
- self.tcb_idx,
- handle,
- AttributeBackingType::Characteristic,
- data,
- );
- }
- AttAttributeBackingValue::DynamicDescriptor(datastore) => {
- datastore.write_no_response(
- self.tcb_idx,
- handle,
- AttributeBackingType::Descriptor,
- data,
- );
- }
- };
- }
-
- fn list_attributes(&self) -> Vec<AttAttribute> {
- self.gatt_db.with(|db| {
- db.map(|db| db.schema.borrow().attributes.values().map(|attr| attr.attribute).collect())
- .unwrap_or_default()
- })
- }
-}
-
-impl Clone for AttDatabaseImpl {
- fn clone(&self) -> Self {
- Self { gatt_db: self.gatt_db.clone(), tcb_idx: self.tcb_idx }
- }
}
#[cfg(test)]
@@ -491,10 +363,12 @@
use tokio::sync::mpsc::error::TryRecvError;
use tokio::task::spawn_local;
+ use crate::gatt::ffi::AttributeBackingType;
use crate::gatt::mocks::mock_database_callbacks::{MockCallbackEvents, MockCallbacks};
use crate::gatt::mocks::mock_datastore::{MockDatastore, MockDatastoreEvents};
use crate::gatt::mocks::mock_raw_datastore::{MockRawDatastore, MockRawDatastoreEvents};
- use crate::packets::att;
+ use crate::gatt::server::att_client::AttClient;
+ use crate::packets::att::{self, AttErrorCode};
use crate::utils::task::block_on_locally;
use super::*;
@@ -514,9 +388,9 @@
#[test]
fn test_read_empty_db() {
let gatt_db = SharedBox::new(GattDatabase::new());
- let att_db = gatt_db.get_att_database(TCB_IDX);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &gatt_db);
- let resp = tokio_test::block_on(att_db.read_attribute(AttHandle(1)));
+ let resp = tokio_test::block_on(client.read_attribute(AttHandle(1)));
assert_eq!(resp, Err(AttErrorCode::InvalidHandle))
}
@@ -535,10 +409,9 @@
Rc::new(gatt_datastore),
)
.unwrap();
- let att_db = gatt_db.get_att_database(TCB_IDX);
-
- let attrs = att_db.list_attributes();
- let service_value = tokio_test::block_on(att_db.read_attribute(SERVICE_HANDLE));
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &gatt_db);
+ let attrs = client.list_attributes();
+ let service_value = tokio_test::block_on(client.read_attribute(SERVICE_HANDLE));
assert_eq!(
attrs,
@@ -608,12 +481,12 @@
gatt_datastore,
)
.unwrap();
- let att_db = gatt_db.get_att_database(TCB_IDX);
- assert_eq!(att_db.list_attributes().len(), 9);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &gatt_db);
+ assert_eq!(client.list_attributes().len(), 9);
// act: remove the middle service
gatt_db.remove_service_at_handle(AttHandle(4)).unwrap();
- let attrs = att_db.list_attributes();
+ let attrs = client.list_attributes();
// assert that the middle service is gone
assert_eq!(attrs.len(), 6, "{attrs:?}");
@@ -658,11 +531,11 @@
Rc::new(gatt_datastore),
)
.unwrap();
- let att_db = gatt_db.get_att_database(TCB_IDX);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &gatt_db);
- let attrs = att_db.list_attributes();
+ let attrs = client.list_attributes();
let characteristic_decl =
- tokio_test::block_on(att_db.read_attribute(CHARACTERISTIC_DECLARATION_HANDLE));
+ tokio_test::block_on(client.read_attribute(CHARACTERISTIC_DECLARATION_HANDLE));
assert_eq!(attrs.len(), 3, "{attrs:?}");
assert_eq!(attrs[0].type_, PRIMARY_SERVICE_DECLARATION_UUID);
@@ -711,7 +584,7 @@
// arrange
let (gatt_datastore, _) = MockDatastore::new();
let gatt_db = SharedBox::new(GattDatabase::new());
- let att_db = gatt_db.get_att_database(TCB_IDX);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &gatt_db);
// act: add a characteristic with all permission bits set
gatt_db
@@ -732,7 +605,7 @@
// assert: the characteristic declaration has all the bits we support set
let characteristic_decl =
- tokio_test::block_on(att_db.read_attribute(CHARACTERISTIC_DECLARATION_HANDLE));
+ tokio_test::block_on(client.read_attribute(CHARACTERISTIC_DECLARATION_HANDLE));
assert_eq!(
characteristic_decl,
att::GattCharacteristicDeclarationValue {
@@ -774,7 +647,7 @@
Rc::new(gatt_datastore),
)
.unwrap();
- let att_db = gatt_db.get_att_database(TCB_IDX);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &gatt_db);
let data = [1, 2];
// act: read from the database, and supply a value from the backing datastore
@@ -792,7 +665,7 @@
};
reply.send(Ok(data.to_vec())).unwrap();
},
- att_db.read_attribute(CHARACTERISTIC_VALUE_HANDLE)
+ client.read_attribute(CHARACTERISTIC_VALUE_HANDLE)
)
.1
});
@@ -821,9 +694,9 @@
)
.unwrap();
- let characteristic_value = tokio_test::block_on(
- gatt_db.get_att_database(TCB_IDX).read_attribute(CHARACTERISTIC_VALUE_HANDLE),
- );
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &gatt_db);
+ let characteristic_value =
+ tokio_test::block_on(client.downgrade().read_attribute(CHARACTERISTIC_VALUE_HANDLE));
assert_eq!(characteristic_value, Err(AttErrorCode::ReadNotPermitted));
}
@@ -899,14 +772,14 @@
Rc::new(gatt_datastore),
)
.unwrap();
- let att_db = gatt_db.get_att_database(TCB_IDX);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &gatt_db);
let data = [1, 2];
// act: write to the database
let recv_data = block_on_locally(async {
// start write task
spawn_local(async move {
- att_db.write_attribute(CHARACTERISTIC_VALUE_HANDLE, &data).await.unwrap();
+ client.write_attribute(CHARACTERISTIC_VALUE_HANDLE, &data).await.unwrap();
});
let MockDatastoreEvents::Write(
@@ -946,7 +819,7 @@
Rc::new(gatt_datastore),
)
.unwrap();
- let att_db = gatt_db.get_att_database(TCB_IDX);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &gatt_db);
let data = [1, 2];
// act: write to the database
@@ -960,7 +833,7 @@
};
reply.send(Err(AttErrorCode::UnlikelyError)).unwrap();
},
- att_db.write_attribute(CHARACTERISTIC_VALUE_HANDLE, &data)
+ client.write_attribute(CHARACTERISTIC_VALUE_HANDLE, &data)
)
.1
});
@@ -990,8 +863,9 @@
.unwrap();
let data = [1, 2];
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &gatt_db);
let characteristic_value = tokio_test::block_on(
- gatt_db.get_att_database(TCB_IDX).write_attribute(CHARACTERISTIC_VALUE_HANDLE, &data),
+ client.downgrade().write_attribute(CHARACTERISTIC_VALUE_HANDLE, &data),
);
assert_eq!(characteristic_value, Err(AttErrorCode::WriteNotPermitted));
@@ -1020,13 +894,13 @@
Rc::new(gatt_datastore),
)
.unwrap();
- let att_db = gatt_db.get_att_database(TCB_IDX);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &gatt_db);
let data = [1, 2];
let descriptor_value = block_on_locally(async {
// start write task
let pending_read =
- spawn_local(async move { att_db.read_attribute(DESCRIPTOR_HANDLE).await.unwrap() });
+ spawn_local(async move { client.read_attribute(DESCRIPTOR_HANDLE).await.unwrap() });
let MockDatastoreEvents::Read(
TCB_IDX,
@@ -1070,14 +944,14 @@
Rc::new(gatt_datastore),
)
.unwrap();
- let att_db = gatt_db.get_att_database(TCB_IDX);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &gatt_db);
let data = [1, 2];
// act: write, and wait for the callback to be invoked
block_on_locally(async {
// start write task
spawn_local(
- async move { att_db.write_attribute(DESCRIPTOR_HANDLE, &data).await.unwrap() },
+ async move { client.write_attribute(DESCRIPTOR_HANDLE, &data).await.unwrap() },
);
let MockDatastoreEvents::Write(
@@ -1141,7 +1015,8 @@
.unwrap();
// act: get the attributes
- let attributes = gatt_db.get_att_database(TCB_IDX).list_attributes();
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &gatt_db);
+ let attributes = client.downgrade().list_attributes();
// assert: check the attributes are in the correct order
assert_eq!(attributes.len(), 8);
@@ -1205,7 +1080,7 @@
)
.unwrap();
- let att_db = gatt_db.get_att_database(TCB_IDX);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &gatt_db);
let data = [1, 2];
// act: read from the second characteristic and supply a response from the second datastore
@@ -1223,7 +1098,7 @@
};
reply.send(Ok(data.to_vec())).unwrap();
},
- att_db.read_attribute(AttHandle(6))
+ client.read_attribute(AttHandle(6))
)
.1
});
@@ -1236,28 +1111,19 @@
assert_eq!(data_evts_2.try_recv().unwrap_err(), TryRecvError::Empty);
}
- fn make_bearer(
- gatt_db: &SharedBox<GattDatabase>,
- ) -> SharedBox<AttServerBearer<AttDatabaseImpl>> {
- SharedBox::new(AttServerBearer::new(gatt_db.get_att_database(TCB_IDX), |_| {
- unreachable!();
- }))
- }
-
#[test]
fn test_connection_listener() {
// arrange: db with a listener
let gatt_db = SharedBox::new(GattDatabase::new());
let (callbacks, mut rx) = MockCallbacks::new();
gatt_db.register_listener(Rc::new(callbacks));
- let bearer = make_bearer(&gatt_db);
// act: open a connection
- gatt_db.on_bearer_ready(TCB_IDX, &bearer);
+ let _client = AttClient::new_test_client(TCB_IDX, &gatt_db);
// assert: we got the callback
let event = rx.blocking_recv().unwrap();
- assert!(matches!(event, MockCallbackEvents::OnLeConnect(TCB_IDX, _)));
+ assert!(matches!(event, MockCallbackEvents::OnLeConnect(_)));
}
#[test]
@@ -1265,10 +1131,11 @@
// arrange: db with a listener
let gatt_db = SharedBox::new(GattDatabase::new());
let (callbacks, mut rx) = MockCallbacks::new();
+ let client = AttClient::new_test_client(TCB_IDX, &gatt_db);
gatt_db.register_listener(Rc::new(callbacks));
// act: disconnect
- gatt_db.on_bearer_dropped(TCB_IDX);
+ std::mem::drop(client);
// assert: we got the callback
let event = rx.blocking_recv().unwrap();
@@ -1279,13 +1146,15 @@
fn test_multiple_listeners() {
// arrange: db with two listeners
let gatt_db = SharedBox::new(GattDatabase::new());
+ let client = AttClient::new_test_client(TCB_IDX, &gatt_db);
+
let (callbacks1, mut rx1) = MockCallbacks::new();
gatt_db.register_listener(Rc::new(callbacks1));
let (callbacks2, mut rx2) = MockCallbacks::new();
gatt_db.register_listener(Rc::new(callbacks2));
// act: disconnect
- gatt_db.on_bearer_dropped(TCB_IDX);
+ std::mem::drop(client);
// assert: we got the callback on both listeners
let event = rx1.blocking_recv().unwrap();
@@ -1464,11 +1333,11 @@
Rc::new(gatt_datastore),
)
.unwrap();
- let att_db = gatt_db.get_att_database(TCB_IDX);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &gatt_db);
let data = [1, 2];
// act: write without response to the database
- att_db.write_no_response_attribute(CHARACTERISTIC_VALUE_HANDLE, &data);
+ client.write_no_response_attribute(CHARACTERISTIC_VALUE_HANDLE, &data);
// assert: we got a callback
let event = data_evts.blocking_recv().unwrap();
@@ -1505,11 +1374,11 @@
Rc::new(gatt_datastore),
)
.unwrap();
- let att_db = gatt_db.get_att_database(TCB_IDX);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &gatt_db);
let data = [1, 2];
// act: try writing without response to this characteristic
- att_db.write_no_response_attribute(CHARACTERISTIC_VALUE_HANDLE, &data);
+ client.write_no_response_attribute(CHARACTERISTIC_VALUE_HANDLE, &data);
// assert: no callback was sent
assert_eq!(data_events.try_recv().unwrap_err(), TryRecvError::Empty);
diff --git a/system/rust/src/gatt/server/indication_handler.rs b/system/rust/src/gatt/server/indication_handler.rs
index 501f580..12c0239 100644
--- a/system/rust/src/gatt/server/indication_handler.rs
+++ b/system/rust/src/gatt/server/indication_handler.rs
@@ -6,9 +6,9 @@
use tokio::time::timeout;
use crate::gatt::ids::AttHandle;
-use crate::packets::att;
+use crate::gatt::server::att_client::WeakAttClient;
+use crate::packets::att::{self, AttErrorCode};
-use super::att_database::{AttDatabase, StableAttDatabase};
use super::att_server_bearer::SendError;
#[derive(Debug)]
@@ -34,15 +34,21 @@
ConnectionDroppedWhileWaitingForConfirmation,
}
-pub struct IndicationHandler<T> {
- db: T,
+impl From<AttErrorCode> for IndicationError {
+ fn from(_value: AttErrorCode) -> Self {
+ IndicationError::AttributeNotFound
+ }
+}
+
+pub struct IndicationHandler {
+ client: WeakAttClient,
pending_confirmation: mpsc::Receiver<()>,
}
-impl<T: AttDatabase> IndicationHandler<T> {
- pub fn new(db: T) -> (Self, ConfirmationWatcher) {
+impl IndicationHandler {
+ pub fn new(client: WeakAttClient) -> (Self, ConfirmationWatcher) {
let (tx, rx) = mpsc::channel(1);
- (Self { db, pending_confirmation: rx }, ConfirmationWatcher(tx))
+ (Self { client, pending_confirmation: rx }, ConfirmationWatcher(tx))
}
pub async fn send(
@@ -50,7 +56,6 @@
handle: AttHandle,
data: &[u8],
mtu: usize,
- send_packet: impl FnOnce(att::Att) -> Result<(), SendError>,
) -> Result<(), IndicationError> {
let data_size = data.len();
// As per Core Spec 5.3 Vol 3F 3.4.7.2, the indicated value must be at most
@@ -59,27 +64,26 @@
return Err(IndicationError::DataExceedsMtu { mtu: mtu - 3 });
}
- if !self
- .db
- .snapshot()
- .find_attribute(handle)
- .ok_or(IndicationError::AttributeNotFound)?
- .permissions
- .indicate()
- {
- warn!("cannot send indication for {handle:?} since it does not support indications");
- return Err(IndicationError::IndicationsNotSupported);
- }
+ self.client.with_attribute(handle, |client, attr| {
+ if !attr.attribute.permissions.indicate() {
+ warn!(
+ "cannot send indication for {handle:?} since it does not support indications"
+ );
+ return Err(IndicationError::IndicationsNotSupported);
+ }
- // flushing any confirmations that arrived before we sent the next indication
- let _ = self.pending_confirmation.try_recv();
+ // flushing any confirmations that arrived before we sent the next indication
+ let _ = self.pending_confirmation.try_recv();
- send_packet(
- att::AttHandleValueIndication { handle: handle.into(), value: data.to_vec() }
- .try_into()
- .unwrap(),
- )
- .map_err(IndicationError::SendError)?;
+ client
+ .bearer()
+ .send_packet(
+ att::AttHandleValueIndication { handle: handle.into(), value: data.to_vec() }
+ .try_into()
+ .unwrap(),
+ )
+ .map_err(|e| IndicationError::SendError(SendError::SerializeError(e)))
+ })?;
match timeout(Duration::from_secs(30), self.pending_confirmation.recv()).await {
Ok(Some(())) => Ok(()),
@@ -116,26 +120,33 @@
#[cfg(test)]
mod test {
use crate::packets::att;
- use tokio::sync::oneshot;
use tokio::task::spawn_local;
use tokio::time::Instant;
+ use crate::core::shared_box::SharedBox;
use crate::core::uuid::Uuid;
+ use crate::gatt::ids::TransportIndex;
+ use crate::gatt::server::att_client::AttClient;
use crate::gatt::server::att_database::AttAttribute;
- use crate::gatt::server::gatt_database::AttPermissions;
- use crate::gatt::server::test::test_att_db::TestAttDatabase;
+ use crate::gatt::server::gatt_database::{AttPermissions, GattDatabase};
+ use crate::gatt::server::test::test_att_db::new_test_database;
use crate::utils::task::block_on_locally;
use super::*;
- const HANDLE: AttHandle = AttHandle(1);
- const NONEXISTENT_HANDLE: AttHandle = AttHandle(2);
- const NON_INDICATE_HANDLE: AttHandle = AttHandle(3);
+ const HANDLE: AttHandle = AttHandle(3);
+ const NONEXISTENT_HANDLE: AttHandle = AttHandle(4);
+ const NON_INDICATE_HANDLE: AttHandle = AttHandle(6);
const MTU: usize = 32;
const DATA: [u8; 3] = [1, 2, 3];
+ const TCB_IDX: TransportIndex = TransportIndex(1);
- fn get_att_database() -> TestAttDatabase {
- TestAttDatabase::new(vec![
+ fn set_up() -> (
+ SharedBox<GattDatabase>,
+ SharedBox<AttClient>,
+ tokio::sync::mpsc::UnboundedReceiver<att::Att>,
+ ) {
+ let db = new_test_database(vec![
(
AttAttribute {
handle: HANDLE,
@@ -152,29 +163,24 @@
},
vec![],
),
- ])
+ ]);
+ let (client, rx) = AttClient::new_test_client(TCB_IDX, &db);
+ (db, client, rx)
}
#[test]
fn test_indication_sent() {
block_on_locally(async move {
// arrange
+ let (_db, client, mut rx) = set_up();
let (mut indication_handler, _confirmation_watcher) =
- IndicationHandler::new(get_att_database());
- let (tx, rx) = oneshot::channel();
+ IndicationHandler::new(client.downgrade());
// act: send an indication
- spawn_local(async move {
- indication_handler
- .send(HANDLE, &DATA, MTU, move |packet| {
- tx.send(packet).unwrap();
- Ok(())
- })
- .await
- });
+ spawn_local(async move { indication_handler.send(HANDLE, &DATA, MTU).await });
// assert: that an AttHandleValueIndication was sent on the channel
- let indication = rx.await.unwrap();
+ let indication = rx.recv().await.unwrap();
assert_eq!(
Ok(indication),
att::AttHandleValueIndication { handle: HANDLE.into(), value: DATA.to_vec() }
@@ -187,13 +193,12 @@
fn test_invalid_handle() {
block_on_locally(async move {
// arrange
+ let (_db, client, _rx) = set_up();
let (mut indication_handler, _confirmation_watcher) =
- IndicationHandler::new(get_att_database());
+ IndicationHandler::new(client.downgrade());
// act: send an indication on a nonexistent handle
- let ret = indication_handler
- .send(NONEXISTENT_HANDLE, &DATA, MTU, move |_| unreachable!())
- .await;
+ let ret = indication_handler.send(NONEXISTENT_HANDLE, &DATA, MTU).await;
// assert: that we failed with IndicationError::AttributeNotFound
assert!(matches!(ret, Err(IndicationError::AttributeNotFound)));
@@ -204,13 +209,12 @@
fn test_unsupported_permission() {
block_on_locally(async move {
// arrange
+ let (_db, client, _rx) = set_up();
let (mut indication_handler, _confirmation_watcher) =
- IndicationHandler::new(get_att_database());
+ IndicationHandler::new(client.downgrade());
// act: send an indication on an attribute that does not support indications
- let ret = indication_handler
- .send(NON_INDICATE_HANDLE, &DATA, MTU, move |_| unreachable!())
- .await;
+ let ret = indication_handler.send(NON_INDICATE_HANDLE, &DATA, MTU).await;
// assert: that we failed with IndicationError::IndicationsNotSupported
assert!(matches!(ret, Err(IndicationError::IndicationsNotSupported)));
@@ -221,21 +225,15 @@
fn test_confirmation_handled() {
block_on_locally(async move {
// arrange
+ let (_db, client, mut rx) = set_up();
let (mut indication_handler, confirmation_watcher) =
- IndicationHandler::new(get_att_database());
- let (tx, rx) = oneshot::channel();
+ IndicationHandler::new(client.downgrade());
// act: send an indication
- let pending_result = spawn_local(async move {
- indication_handler
- .send(HANDLE, &DATA, MTU, move |packet| {
- tx.send(packet).unwrap();
- Ok(())
- })
- .await
- });
+ let pending_result =
+ spawn_local(async move { indication_handler.send(HANDLE, &DATA, MTU).await });
// when the indication is sent, send a confirmation in response
- rx.await.unwrap();
+ rx.recv().await.unwrap();
confirmation_watcher.on_confirmation();
// assert: the indication was successfully sent
@@ -247,22 +245,16 @@
fn test_unblock_on_disconnect() {
block_on_locally(async move {
// arrange
+ let (_db, client, mut rx) = set_up();
let (mut indication_handler, confirmation_watcher) =
- IndicationHandler::new(get_att_database());
- let (tx, rx) = oneshot::channel();
+ IndicationHandler::new(client.downgrade());
// act: send an indication
- let pending_result = spawn_local(async move {
- indication_handler
- .send(HANDLE, &DATA, MTU, move |packet| {
- tx.send(packet).unwrap();
- Ok(())
- })
- .await
- });
+ let pending_result =
+ spawn_local(async move { indication_handler.send(HANDLE, &DATA, MTU).await });
// when the indication is sent, drop the confirmation watcher (as would happen
// upon a disconnection)
- rx.await.unwrap();
+ rx.recv().await.unwrap();
drop(confirmation_watcher);
// assert: we get the appropriate error
@@ -277,24 +269,18 @@
fn test_spurious_confirmations() {
block_on_locally(async move {
// arrange: send a few confirmations in advance
+ let (_db, client, mut rx) = set_up();
let (mut indication_handler, confirmation_watcher) =
- IndicationHandler::new(get_att_database());
- let (tx, rx) = oneshot::channel();
+ IndicationHandler::new(client.downgrade());
confirmation_watcher.on_confirmation();
confirmation_watcher.on_confirmation();
// act: send an indication
- let pending_result = spawn_local(async move {
- indication_handler
- .send(HANDLE, &DATA, MTU, move |packet| {
- tx.send(packet).unwrap();
- Ok(())
- })
- .await
- });
+ let pending_result =
+ spawn_local(async move { indication_handler.send(HANDLE, &DATA, MTU).await });
// when the indication is sent, drop the confirmation watcher (so we won't block
// forever)
- rx.await.unwrap();
+ rx.recv().await.unwrap();
drop(confirmation_watcher);
// assert: we get the appropriate error, rather than an Ok(())
@@ -311,24 +297,18 @@
fn test_indication_timeout() {
block_on_locally(async move {
// arrange: send a few confirmations in advance
+ let (_db, client, mut rx) = set_up();
let (mut indication_handler, confirmation_watcher) =
- IndicationHandler::new(get_att_database());
- let (tx, rx) = oneshot::channel();
+ IndicationHandler::new(client.downgrade());
confirmation_watcher.on_confirmation();
confirmation_watcher.on_confirmation();
// act: send an indication
let time_sent = Instant::now();
- let pending_result = spawn_local(async move {
- indication_handler
- .send(HANDLE, &DATA, MTU, move |packet| {
- tx.send(packet).unwrap();
- Ok(())
- })
- .await
- });
+ let pending_result =
+ spawn_local(async move { indication_handler.send(HANDLE, &DATA, MTU).await });
// after it is sent, wait for the timer to fire
- rx.await.unwrap();
+ rx.recv().await.unwrap();
// assert: we get the appropriate error
assert!(matches!(
@@ -348,11 +328,12 @@
fn test_mtu_exceeds() {
block_on_locally(async move {
// arrange
+ let (_db, client, _rx) = set_up();
let (mut indication_handler, _confirmation_watcher) =
- IndicationHandler::new(get_att_database());
+ IndicationHandler::new(client.downgrade());
// act: send an indication with an ATT_MTU of 4 and data length of 3
- let res = indication_handler.send(HANDLE, &DATA, 4, move |_| unreachable!()).await;
+ let res = indication_handler.send(HANDLE, &DATA, 4).await;
// assert: that we got the expected error, indicating the max data size (not the
// ATT_MTU, but ATT_MTU-3)
diff --git a/system/rust/src/gatt/server/request_handler.rs b/system/rust/src/gatt/server/request_handler.rs
index 76832bb..7375ed7 100644
--- a/system/rust/src/gatt/server/request_handler.rs
+++ b/system/rust/src/gatt/server/request_handler.rs
@@ -3,9 +3,9 @@
use crate::gatt::ids::AttHandle;
use crate::gatt::opcode_types::AttRequest;
+use crate::gatt::server::att_client::WeakAttClient;
use crate::packets::att::{self, AttErrorCode};
-use super::att_database::AttDatabase;
use super::transactions::find_by_type_value::handle_find_by_type_value_request;
use super::transactions::find_information_request::handle_find_information_request;
use super::transactions::read_by_group_type_request::handle_read_by_group_type_request;
@@ -13,12 +13,14 @@
use super::transactions::read_request::{
handle_read_blob_request, handle_read_multiple_variable_request, handle_read_request,
};
-use super::transactions::write_request::handle_write_request;
+use super::transactions::write_request::{
+ handle_execute_write_request, handle_prepare_write_request, handle_write_request,
+};
/// This struct handles all requests needing ACKs. Only ONE should exist per
/// bearer per database, to ensure serialization.
-pub struct AttRequestHandler<Db: AttDatabase> {
- db: Db,
+pub struct AttRequestHandler {
+ client: WeakAttClient,
}
/// Type of errors raised by request handlers.
@@ -41,9 +43,9 @@
}
}
-impl<Db: AttDatabase> AttRequestHandler<Db> {
- pub fn new(db: Db) -> Self {
- Self { db }
+impl AttRequestHandler {
+ pub fn new(client: WeakAttClient) -> Self {
+ Self { client }
}
// Runs a task to process an incoming *request* packet. There should be only one instance of
@@ -71,34 +73,38 @@
packet: &AttRequest,
mtu: usize,
) -> Result<att::Att, ProcessingError> {
- let snapshotted_db = self.db.snapshot();
let packet = &**packet;
match packet.opcode {
att::AttOpcode::ReadRequest => {
- Ok(handle_read_request(packet.try_into()?, mtu, &self.db).await?)
+ Ok(handle_read_request(packet.try_into()?, mtu, &self.client).await?)
}
att::AttOpcode::ReadBlobRequest => {
- Ok(handle_read_blob_request(packet.try_into()?, mtu, &self.db).await?)
+ Ok(handle_read_blob_request(packet.try_into()?, mtu, &self.client).await?)
}
att::AttOpcode::ReadMultipleVariableRequest => {
- Ok(handle_read_multiple_variable_request(packet.try_into()?, mtu, &self.db).await?)
+ Ok(handle_read_multiple_variable_request(packet.try_into()?, mtu, &self.client)
+ .await?)
}
att::AttOpcode::ReadByGroupTypeRequest => {
- Ok(handle_read_by_group_type_request(packet.try_into()?, mtu, &snapshotted_db)
- .await?)
+ Ok(handle_read_by_group_type_request(packet.try_into()?, mtu, &self.client).await?)
}
att::AttOpcode::ReadByTypeRequest => {
- Ok(handle_read_by_type_request(packet.try_into()?, mtu, &snapshotted_db).await?)
+ Ok(handle_read_by_type_request(packet.try_into()?, mtu, &self.client).await?)
}
att::AttOpcode::FindInformationRequest => {
- Ok(handle_find_information_request(packet.try_into()?, mtu, &snapshotted_db)?)
+ Ok(handle_find_information_request(packet.try_into()?, mtu, &self.client)?)
}
att::AttOpcode::FindByTypeValueRequest => {
- Ok(handle_find_by_type_value_request(packet.try_into()?, mtu, &snapshotted_db)
- .await?)
+ Ok(handle_find_by_type_value_request(packet.try_into()?, mtu, &self.client).await?)
}
att::AttOpcode::WriteRequest => {
- Ok(handle_write_request(packet.try_into()?, &self.db).await?)
+ Ok(handle_write_request(packet.try_into()?, &self.client).await?)
+ }
+ att::AttOpcode::PrepareWriteRequest => {
+ Ok(handle_prepare_write_request(packet.try_into()?, &self.client).await?)
+ }
+ att::AttOpcode::ExecuteWriteRequest => {
+ Ok(handle_execute_write_request(packet.try_into()?, &self.client).await?)
}
_ => {
warn!("Dropping unsupported opcode {:?}", packet.opcode);
@@ -112,17 +118,25 @@
mod test {
use super::*;
+ use crate::core::shared_box::SharedBox;
use crate::core::uuid::Uuid;
+ use crate::gatt::ids::TransportIndex;
+ use crate::gatt::server::att_client::AttClient;
use crate::gatt::server::att_database::{AttAttribute, AttPermissions};
+ use crate::gatt::server::gatt_database::{
+ GattCharacteristicWithHandle, GattDatabase, GattServiceWithHandle,
+ };
use crate::gatt::server::request_handler::AttRequestHandler;
- use crate::gatt::server::test::test_att_db::TestAttDatabase;
+ use crate::gatt::server::test::test_att_db::{new_test_database, TestDatastore};
use crate::packets::att;
use pdl_runtime::Packet;
+ const TCB_IDX: TransportIndex = TransportIndex(1);
+
#[test]
fn test_read_request() {
// arrange
- let db = TestAttDatabase::new(vec![(
+ let db = new_test_database(vec![(
AttAttribute {
handle: AttHandle(3),
type_: Uuid::new(0x1234),
@@ -130,7 +144,8 @@
},
vec![1, 2, 3],
)]);
- let mut handler = AttRequestHandler { db };
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
+ let mut handler = AttRequestHandler::new(client.downgrade());
let att_view =
AttRequest::new(att::AttReadRequest { attribute_handle: AttHandle(3).into() }).unwrap();
@@ -145,7 +160,7 @@
fn test_read_blob_request() {
// arrange
let data: Vec<u8> = (0..255).collect();
- let db = TestAttDatabase::new(vec![(
+ let db = new_test_database(vec![(
AttAttribute {
handle: AttHandle(3),
type_: Uuid::new(0x1234),
@@ -153,7 +168,8 @@
},
data.clone(),
)]);
- let mut handler = AttRequestHandler { db };
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
+ let mut handler = AttRequestHandler::new(client.downgrade());
const MTU: usize = 31;
// Returns the expected part of `data` for the `offset`.
@@ -182,7 +198,7 @@
fn test_read_blob_request_with_bad_offset() {
// arrange
let data: Vec<u8> = (0..255).collect();
- let db = TestAttDatabase::new(vec![(
+ let db = new_test_database(vec![(
AttAttribute {
handle: AttHandle(3),
type_: Uuid::new(0x1234),
@@ -190,7 +206,8 @@
},
data.clone(),
)]);
- let mut handler = AttRequestHandler { db };
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
+ let mut handler = AttRequestHandler::new(client.downgrade());
let att_view = AttRequest::new(att::AttReadBlobRequest {
attribute_handle: AttHandle(3).into(),
@@ -217,7 +234,7 @@
fn test_read_blob_request_with_invalid_handle() {
// arrange
let data: Vec<u8> = (0..255).collect();
- let db = TestAttDatabase::new(vec![(
+ let db = new_test_database(vec![(
AttAttribute {
handle: AttHandle(3),
type_: Uuid::new(0x1234),
@@ -225,7 +242,8 @@
},
data.clone(),
)]);
- let mut handler = AttRequestHandler { db };
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
+ let mut handler = AttRequestHandler::new(client.downgrade());
let att_view = AttRequest::new(att::AttReadBlobRequest {
attribute_handle: AttHandle(4).into(),
@@ -251,7 +269,7 @@
#[test]
fn test_read_multiple_variable_request() {
// arrange
- let db = TestAttDatabase::new(vec![
+ let db = new_test_database(vec![
(
AttAttribute {
handle: AttHandle(3),
@@ -269,7 +287,8 @@
vec![b'4'],
),
]);
- let mut handler = AttRequestHandler { db };
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
+ let mut handler = AttRequestHandler::new(client.downgrade());
let att_view = AttRequest::new(att::AttReadMultipleVariableRequest {
attribute_handles: [AttHandle(3).into(), AttHandle(4).into()].into(),
@@ -297,7 +316,7 @@
fn test_read_multiple_variable_request_truncated() {
let data: Vec<u8> = (0..255).collect();
// arrange
- let db = TestAttDatabase::new(vec![
+ let db = new_test_database(vec![
(
AttAttribute {
handle: AttHandle(3),
@@ -323,7 +342,8 @@
vec![b'5'],
),
]);
- let mut handler = AttRequestHandler { db };
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
+ let mut handler = AttRequestHandler::new(client.downgrade());
let att_view = AttRequest::new(att::AttReadMultipleVariableRequest {
attribute_handles: [AttHandle(3).into(), AttHandle(4).into(), AttHandle(5).into()]
@@ -361,7 +381,7 @@
let data = vec![0xaf; MTU - 1 - 3 - 2 - 1];
// arrange
- let db = TestAttDatabase::new(vec![
+ let db = new_test_database(vec![
(
AttAttribute {
handle: AttHandle(3),
@@ -387,7 +407,8 @@
vec![b'5'],
),
]);
- let mut handler = AttRequestHandler { db };
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
+ let mut handler = AttRequestHandler::new(client.downgrade());
let att_view = AttRequest::new(att::AttReadMultipleVariableRequest {
attribute_handles: [AttHandle(3).into(), AttHandle(4).into(), AttHandle(5).into()]
@@ -417,7 +438,7 @@
#[test]
fn test_read_multiple_variable_request_at_least_two() {
// arrange
- let db = TestAttDatabase::new(vec![(
+ let db = new_test_database(vec![(
AttAttribute {
handle: AttHandle(3),
type_: Uuid::new(0x1234),
@@ -425,7 +446,8 @@
},
vec![b'3'],
)]);
- let mut handler = AttRequestHandler { db };
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
+ let mut handler = AttRequestHandler::new(client.downgrade());
let att_view = AttRequest::new(att::AttReadMultipleVariableRequest {
attribute_handles: [AttHandle(3).into()].into(),
@@ -450,7 +472,7 @@
#[test]
fn test_read_multiple_variable_request_all_handles_valid() {
// arrange
- let db = TestAttDatabase::new(vec![(
+ let db = new_test_database(vec![(
AttAttribute {
handle: AttHandle(3),
type_: Uuid::new(0x1234),
@@ -458,7 +480,8 @@
},
vec![0xaf; 255],
)]);
- let mut handler = AttRequestHandler { db };
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
+ let mut handler = AttRequestHandler::new(client.downgrade());
let att_view = AttRequest::new(att::AttReadMultipleVariableRequest {
attribute_handles: [AttHandle(3).into(), AttHandle(5).into()].into(),
@@ -483,7 +506,7 @@
#[test]
fn test_read_multiple_variable_request_value_longer_than_65535_bytes() {
// arrange
- let db = TestAttDatabase::new(vec![
+ let db = new_test_database(vec![
(
AttAttribute {
handle: AttHandle(3),
@@ -501,7 +524,8 @@
vec![0xaf; 65536],
),
]);
- let mut handler = AttRequestHandler { db };
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
+ let mut handler = AttRequestHandler::new(client.downgrade());
let att_view = AttRequest::new(att::AttReadMultipleVariableRequest {
attribute_handles: [AttHandle(3).into(), AttHandle(4).into()].into(),
@@ -524,9 +548,457 @@
}
#[test]
+ fn test_queued_writes() {
+ // arrange
+ let data = vec![b'3'; 100];
+ let db = new_test_database(vec![(
+ AttAttribute {
+ handle: AttHandle(3),
+ type_: Uuid::new(0x1234),
+ permissions: AttPermissions::READABLE | AttPermissions::WRITABLE_WITH_RESPONSE,
+ },
+ data.clone(),
+ )]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
+ let mut handler = AttRequestHandler::new(client.downgrade());
+
+ // act & assert
+ tokio_test::block_on(async {
+ let mut expected = data.clone();
+
+ let request = att::AttPrepareWriteRequest {
+ handle: AttHandle(3).into(),
+ offset: 5,
+ value: vec![b'4'; 10],
+ };
+ expected[5..15].fill(b'4');
+
+ let expected_response = att::AttPrepareWriteResponse {
+ handle: request.handle.clone(),
+ offset: request.offset,
+ value: request.value.clone(),
+ };
+
+ assert_eq!(
+ Ok(handler.process_packet(AttRequest::new(request).unwrap(), 255).await),
+ expected_response.try_into()
+ );
+
+ // Reading the attribute should return the original value.
+ assert_eq!(
+ Ok(handler
+ .process_packet(
+ AttRequest::new(att::AttReadRequest {
+ attribute_handle: AttHandle(3).into()
+ })
+ .unwrap(),
+ 255
+ )
+ .await),
+ att::AttReadResponse { value: data.clone() }.try_into()
+ );
+
+ let request = att::AttPrepareWriteRequest {
+ handle: AttHandle(3).into(),
+ offset: 30,
+ value: vec![b'5'; 7],
+ };
+ expected[30..37].fill(b'5');
+
+ let expected_response = att::AttPrepareWriteResponse {
+ handle: request.handle.clone(),
+ offset: request.offset,
+ value: request.value.clone(),
+ };
+
+ assert_eq!(
+ Ok(handler.process_packet(AttRequest::new(request).unwrap(), 255).await),
+ expected_response.try_into()
+ );
+
+ // Reading the attribute should return the original value.
+ assert_eq!(
+ Ok(handler
+ .process_packet(
+ AttRequest::new(att::AttReadRequest {
+ attribute_handle: AttHandle(3).into()
+ })
+ .unwrap(),
+ 255
+ )
+ .await),
+ att::AttReadResponse { value: data.clone() }.try_into()
+ );
+
+ // Execute the queued requests.
+ assert_eq!(
+ Ok(handler
+ .process_packet(
+ AttRequest::new(att::AttExecuteWriteRequest { commit: 1 }).unwrap(),
+ 255
+ )
+ .await),
+ att::AttExecuteWriteResponse {}.try_into()
+ );
+
+ // Reading the attribute should return the new value.
+ assert_eq!(
+ Ok(handler
+ .process_packet(
+ AttRequest::new(att::AttReadRequest {
+ attribute_handle: AttHandle(3).into()
+ })
+ .unwrap(),
+ 255
+ )
+ .await),
+ att::AttReadResponse { value: expected }.try_into()
+ );
+ });
+ }
+
+ #[test]
+ fn test_cancelling_queued_writes() {
+ // arrange
+ let data = vec![b'3'; 100];
+ let db = new_test_database(vec![(
+ AttAttribute {
+ handle: AttHandle(3),
+ type_: Uuid::new(0x1234),
+ permissions: AttPermissions::READABLE | AttPermissions::WRITABLE_WITH_RESPONSE,
+ },
+ data.clone(),
+ )]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
+ let mut handler = AttRequestHandler::new(client.downgrade());
+
+ // act & assert
+ tokio_test::block_on(async {
+ let mut expected = data.clone();
+
+ let request = att::AttPrepareWriteRequest {
+ handle: AttHandle(3).into(),
+ offset: 5,
+ value: vec![b'4'; 10],
+ };
+
+ let expected_response = att::AttPrepareWriteResponse {
+ handle: request.handle.clone(),
+ offset: request.offset,
+ value: request.value.clone(),
+ };
+
+ assert_eq!(
+ Ok(handler.process_packet(AttRequest::new(request).unwrap(), 255).await),
+ expected_response.try_into()
+ );
+
+ // Cancel the queued request.
+ assert_eq!(
+ Ok(handler
+ .process_packet(
+ AttRequest::new(att::AttExecuteWriteRequest { commit: 0 }).unwrap(),
+ 255
+ )
+ .await),
+ att::AttExecuteWriteResponse {}.try_into()
+ );
+
+ // Prepare and execute another request and it shouldn't have the canceled request.
+
+ // Reading the attribute should return the original value.
+ assert_eq!(
+ Ok(handler
+ .process_packet(
+ AttRequest::new(att::AttReadRequest {
+ attribute_handle: AttHandle(3).into()
+ })
+ .unwrap(),
+ 255
+ )
+ .await),
+ att::AttReadResponse { value: data.clone() }.try_into()
+ );
+
+ let request = att::AttPrepareWriteRequest {
+ handle: AttHandle(3).into(),
+ offset: 30,
+ value: vec![b'5'; 7],
+ };
+ expected[30..37].fill(b'5');
+
+ let expected_response = att::AttPrepareWriteResponse {
+ handle: request.handle.clone(),
+ offset: request.offset,
+ value: request.value.clone(),
+ };
+
+ assert_eq!(
+ Ok(handler.process_packet(AttRequest::new(request).unwrap(), 255).await),
+ expected_response.try_into()
+ );
+
+ // Execute the queued requests.
+ assert_eq!(
+ Ok(handler
+ .process_packet(
+ AttRequest::new(att::AttExecuteWriteRequest { commit: 1 }).unwrap(),
+ 255
+ )
+ .await),
+ att::AttExecuteWriteResponse {}.try_into()
+ );
+
+ // Reading the attribute should return the new value.
+ assert_eq!(
+ Ok(handler
+ .process_packet(
+ AttRequest::new(att::AttReadRequest {
+ attribute_handle: AttHandle(3).into()
+ })
+ .unwrap(),
+ 255
+ )
+ .await),
+ att::AttReadResponse { value: expected }.try_into()
+ );
+ });
+ }
+
+ #[test]
+ fn test_queued_write_with_invalid_offset() {
+ // arrange
+ let data = vec![b'3'; 100];
+ let db = new_test_database(vec![(
+ AttAttribute {
+ handle: AttHandle(3),
+ type_: Uuid::new(0x1234),
+ permissions: AttPermissions::READABLE | AttPermissions::WRITABLE_WITH_RESPONSE,
+ },
+ data.clone(),
+ )]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
+ let mut handler = AttRequestHandler::new(client.downgrade());
+
+ // act & assert
+ tokio_test::block_on(async {
+ let request = att::AttPrepareWriteRequest {
+ handle: AttHandle(3).into(),
+ offset: 110,
+ value: vec![b'4'; 10],
+ };
+
+ // The prepare should succeed.
+ let expected_response = att::AttPrepareWriteResponse {
+ handle: request.handle.clone(),
+ offset: request.offset,
+ value: request.value.clone(),
+ };
+
+ assert_eq!(
+ Ok(handler.process_packet(AttRequest::new(request).unwrap(), 255).await),
+ expected_response.try_into()
+ );
+
+ // Execute the queued requests.
+ assert_eq!(
+ Ok(handler
+ .process_packet(
+ AttRequest::new(att::AttExecuteWriteRequest { commit: 1 }).unwrap(),
+ 255
+ )
+ .await),
+ att::AttErrorResponse {
+ opcode_in_error: att::AttOpcode::ExecuteWriteRequest,
+ handle_in_error: AttHandle(0).into(), // Ideally this would be 3.
+ error_code: AttErrorCode::InvalidOffset
+ }
+ .try_into()
+ );
+ });
+ }
+
+ #[test]
+ fn test_queued_write_with_invalid_length() {
+ // arrange
+ let data = vec![b'3'; 100];
+ let db = new_test_database(vec![(
+ AttAttribute {
+ handle: AttHandle(3),
+ type_: Uuid::new(0x1234),
+ permissions: AttPermissions::READABLE | AttPermissions::WRITABLE_WITH_RESPONSE,
+ },
+ data.clone(),
+ )]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
+ let mut handler = AttRequestHandler::new(client.downgrade());
+
+ // act & assert
+ tokio_test::block_on(async {
+ let request = att::AttPrepareWriteRequest {
+ handle: AttHandle(3).into(),
+ offset: 90,
+ value: vec![b'4'; 30],
+ };
+
+ // The prepare should succeed.
+ let expected_response = att::AttPrepareWriteResponse {
+ handle: request.handle.clone(),
+ offset: request.offset,
+ value: request.value.clone(),
+ };
+
+ assert_eq!(
+ Ok(handler.process_packet(AttRequest::new(request).unwrap(), 255).await),
+ expected_response.try_into()
+ );
+
+ // Execute the queued requests.
+ assert_eq!(
+ Ok(handler
+ .process_packet(
+ AttRequest::new(att::AttExecuteWriteRequest { commit: 1 }).unwrap(),
+ 255
+ )
+ .await),
+ att::AttErrorResponse {
+ opcode_in_error: att::AttOpcode::ExecuteWriteRequest,
+ handle_in_error: AttHandle(0).into(), // Ideally this would be 3.
+ error_code: AttErrorCode::InvalidAttributeValueLength,
+ }
+ .try_into()
+ );
+ });
+ }
+
+ #[test]
+ fn test_queued_write_to_different_datastores() {
+ // arrange
+ let handle1 = AttHandle(5);
+ let handle1_value = [5];
+ let datastore1 = TestDatastore::new([(handle1, handle1_value.into())]);
+ let handle2 = AttHandle(8);
+ let handle2_value = [8];
+ let datastore2 = TestDatastore::new([(handle2, handle2_value.into())]);
+
+ let db = SharedBox::new(GattDatabase::new());
+
+ db.add_service_with_handles(
+ GattServiceWithHandle {
+ handle: AttHandle(3),
+ type_: Uuid::new(1),
+ characteristics: vec![GattCharacteristicWithHandle {
+ handle: handle1,
+ type_: Uuid::new(0x1234),
+ permissions: AttPermissions::READABLE | AttPermissions::WRITABLE_WITH_RESPONSE,
+ descriptors: vec![],
+ }],
+ },
+ datastore1,
+ )
+ .unwrap();
+ db.add_service_with_handles(
+ GattServiceWithHandle {
+ handle: AttHandle(6),
+ type_: Uuid::new(1),
+ characteristics: vec![GattCharacteristicWithHandle {
+ handle: handle2,
+ type_: Uuid::new(0x1234),
+ permissions: AttPermissions::READABLE | AttPermissions::WRITABLE_WITH_RESPONSE,
+ descriptors: vec![],
+ }],
+ },
+ datastore2,
+ )
+ .unwrap();
+
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
+ let mut handler = AttRequestHandler::new(client.downgrade());
+
+ // act & assert
+ tokio_test::block_on(async {
+ let mut expected = handle1_value.to_vec();
+ let request =
+ att::AttPrepareWriteRequest { handle: handle1.into(), offset: 0, value: vec![15] };
+ expected[0] = 15;
+
+ // The prepare should succeed.
+ let expected_response = att::AttPrepareWriteResponse {
+ handle: request.handle.clone(),
+ offset: request.offset,
+ value: request.value.clone(),
+ };
+
+ assert_eq!(
+ Ok(handler.process_packet(AttRequest::new(request).unwrap(), 255).await),
+ expected_response.try_into()
+ );
+
+ // A prepare to a different datastore should fail.
+ let request =
+ att::AttPrepareWriteRequest { handle: handle2.into(), offset: 0, value: vec![6] };
+ assert_eq!(
+ Ok(handler.process_packet(AttRequest::new(request.clone()).unwrap(), 255).await),
+ att::AttErrorResponse {
+ opcode_in_error: att::AttOpcode::PrepareWriteRequest,
+ handle_in_error: handle2.into(),
+ error_code: AttErrorCode::RequestNotSupported,
+ }
+ .try_into()
+ );
+
+ // Executing the existing queued request should still succeed.
+ assert_eq!(
+ Ok(handler
+ .process_packet(
+ AttRequest::new(att::AttExecuteWriteRequest { commit: 1 }).unwrap(),
+ 255
+ )
+ .await),
+ att::AttExecuteWriteResponse {}.try_into()
+ );
+
+ // Reading the attribute should return the new value.
+ assert_eq!(
+ Ok(handler
+ .process_packet(
+ AttRequest::new(att::AttReadRequest { attribute_handle: handle1.into() })
+ .unwrap(),
+ 255
+ )
+ .await),
+ att::AttReadResponse { value: expected }.try_into()
+ );
+
+ // Reading attribute 4 should be unchanged.
+ assert_eq!(
+ Ok(handler
+ .process_packet(
+ AttRequest::new(att::AttReadRequest { attribute_handle: handle2.into() })
+ .unwrap(),
+ 255
+ )
+ .await),
+ att::AttReadResponse { value: handle2_value.into() }.try_into()
+ );
+
+ // A prepare to a different datastore should now succeed.
+ let expected_response = att::AttPrepareWriteResponse {
+ handle: request.handle.clone(),
+ offset: request.offset,
+ value: request.value.clone(),
+ };
+ assert_eq!(
+ Ok(handler.process_packet(AttRequest::new(request).unwrap(), 255).await),
+ expected_response.try_into(),
+ );
+ });
+ }
+
+ #[test]
fn test_unsupported_request() {
// arrange
- let db = TestAttDatabase::new(vec![
+ let db = new_test_database(vec![
(
AttAttribute {
handle: AttHandle(3),
@@ -544,7 +1016,8 @@
vec![1, 2, 3],
),
]);
- let mut handler = AttRequestHandler { db };
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
+ let mut handler = AttRequestHandler::new(client.downgrade());
let att_view = AttRequest::new(att::AttReadMultipleRequest {
attribute_handles: vec![AttHandle(3).into(), AttHandle(4).into()],
})
diff --git a/system/rust/src/gatt/server/services/gap.rs b/system/rust/src/gatt/server/services/gap.rs
index 6402375..5ef3621 100644
--- a/system/rust/src/gatt/server/services/gap.rs
+++ b/system/rust/src/gatt/server/services/gap.rs
@@ -92,20 +92,21 @@
use super::*;
use crate::core::shared_box::SharedBox;
- use crate::gatt::server::att_database::AttDatabase;
use crate::gatt::server::gatt_database::{
GattDatabase, CHARACTERISTIC_UUID, PRIMARY_SERVICE_DECLARATION_UUID,
};
+ use crate::gatt::server::AttClient;
use crate::utils::task::block_on_locally;
const TCB_IDX: TransportIndex = TransportIndex(1);
- fn init_dbs() -> (SharedBox<GattDatabase>, impl AttDatabase) {
+ fn init_dbs() -> (SharedBox<GattDatabase>, SharedBox<AttClient>) {
let mut gatt_database = GattDatabase::new();
register_gap_service(&mut gatt_database).unwrap();
let gatt_database = SharedBox::new(gatt_database);
- let att_database = gatt_database.get_att_database(TCB_IDX);
- (gatt_database, att_database)
+ let att_client =
+ AttClient::new_client_and_bearer(TCB_IDX, |_| unreachable!(), &gatt_database);
+ (gatt_database, att_client)
}
#[test]
@@ -136,10 +137,10 @@
#[test]
fn test_read_device_name_not_discoverable() {
// arrange
- let (_gatt_db, att_db) = init_dbs();
+ let (_gatt_db, client) = init_dbs();
// act: try to read the device name
- let name = block_on_locally(att_db.read_attribute(DEVICE_NAME_HANDLE));
+ let name = block_on_locally(client.read_attribute(DEVICE_NAME_HANDLE));
// assert: the name is not readable
assert_eq!(name, Err(AttErrorCode::InsufficientAuthentication));
@@ -148,10 +149,10 @@
#[test]
fn test_read_device_appearance() {
// arrange
- let (_gatt_db, att_db) = init_dbs();
+ let (_gatt_db, client) = init_dbs();
// act: try to read the device name
- let name = block_on_locally(att_db.read_attribute(DEVICE_APPEARANCE_HANDLE));
+ let name = block_on_locally(client.read_attribute(DEVICE_APPEARANCE_HANDLE));
// assert: the name is not readable
assert_eq!(name, Ok(vec![0x00, 0x00]));
diff --git a/system/rust/src/gatt/server/services/gatt.rs b/system/rust/src/gatt/server/services/gatt.rs
index 11417b0..d7bff05 100644
--- a/system/rust/src/gatt/server/services/gatt.rs
+++ b/system/rust/src/gatt/server/services/gatt.rs
@@ -11,15 +11,15 @@
use log::{error, warn};
use tokio::task::spawn_local;
-use crate::core::shared_box::{SharedBox, WeakBox};
+use crate::core::shared_box::SharedBox;
use crate::core::uuid::Uuid;
use crate::gatt::callbacks::GattDatastore;
use crate::gatt::ffi::AttributeBackingType;
use crate::gatt::ids::{AttHandle, TransportIndex};
-use crate::gatt::server::att_server_bearer::AttServerBearer;
+use crate::gatt::server::att_client::{AttClient, WeakAttClient};
use crate::gatt::server::gatt_database::{
- AttDatabaseImpl, AttPermissions, GattCharacteristicWithHandle, GattDatabase,
- GattDatabaseCallbacks, GattDescriptorWithHandle, GattServiceWithHandle,
+ AttPermissions, GattCharacteristicWithHandle, GattDatabase, GattDatabaseCallbacks,
+ GattDescriptorWithHandle, GattServiceWithHandle,
};
use crate::packets::att::{self, AttErrorCode};
@@ -30,7 +30,7 @@
#[derive(Clone)]
struct ClientState {
- bearer: WeakBox<AttServerBearer<AttDatabaseImpl>>,
+ client: WeakAttClient,
registered_for_service_change: bool,
}
@@ -100,15 +100,11 @@
}
impl GattDatabaseCallbacks for GattService {
- fn on_le_connect(
- &self,
- tcb_idx: TransportIndex,
- bearer: &SharedBox<AttServerBearer<AttDatabaseImpl>>,
- ) {
+ fn on_le_connect(&self, client: &SharedBox<AttClient>) {
// TODO(aryarahul): registered_for_service_change may not be false for bonded devices
self.clients.borrow_mut().insert(
- tcb_idx,
- ClientState { bearer: bearer.downgrade(), registered_for_service_change: false },
+ client.tcb_idx(),
+ ClientState { client: client.downgrade(), registered_for_service_change: false },
);
}
@@ -119,10 +115,10 @@
fn on_service_change(&self, range: RangeInclusive<AttHandle>) {
for (conn_id, client) in self.clients.borrow().clone() {
if client.registered_for_service_change {
- client.bearer.with(|bearer| match bearer {
- Some(bearer) => {
+ client.client.with(|client| match client {
+ Some(client) => {
spawn_local(
- bearer.send_indication(
+ client.bearer().send_indication(
SERVICE_CHANGE_HANDLE,
att::GattServiceChanged {
start_handle: (*range.start()).into(),
@@ -134,7 +130,7 @@
);
}
None => {
- error!("Registered client's bearer has been destructed ({conn_id:?})")
+ error!("Registered client has been destructed ({conn_id:?})")
}
});
}
@@ -169,13 +165,11 @@
}
#[cfg(test)]
mod test {
- use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver};
-
use super::*;
use crate::core::shared_box::SharedBox;
use crate::gatt::mocks::mock_datastore::MockDatastore;
- use crate::gatt::server::att_database::AttDatabase;
+ use crate::gatt::server::att_client::{AttClient, WeakAttClient};
use crate::gatt::server::gatt_database::{
GattDatabase, CHARACTERISTIC_UUID, PRIMARY_SERVICE_DECLARATION_UUID,
};
@@ -193,29 +187,14 @@
SharedBox::new(gatt_database)
}
- fn add_connection(
- gatt_database: &SharedBox<GattDatabase>,
- tcb_idx: TransportIndex,
- ) -> (AttDatabaseImpl, SharedBox<AttServerBearer<AttDatabaseImpl>>, UnboundedReceiver<att::Att>)
- {
- let att_database = gatt_database.get_att_database(tcb_idx);
- let (tx, rx) = unbounded_channel();
- let bearer = SharedBox::new(AttServerBearer::new(att_database.clone(), move |packet| {
- tx.send(packet).unwrap();
- Ok(())
- }));
- gatt_database.on_bearer_ready(tcb_idx, &bearer);
- (att_database, bearer, rx)
- }
-
#[test]
fn test_gatt_service_discovery() {
// arrange
let gatt_db = init_gatt_db();
- let (att_db, _, _) = add_connection(&gatt_db, TCB_IDX);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &gatt_db);
// act: discover all services
- let attrs = att_db.list_attributes();
+ let attrs = client.list_attributes();
// assert: 1 service + 1 char decl + 1 char value + 1 char descriptor = 4 attrs
assert_eq!(attrs.len(), 4);
@@ -239,11 +218,11 @@
fn test_default_indication_subscription() {
// arrange
let gatt_db = init_gatt_db();
- let (att_db, _, _) = add_connection(&gatt_db, TCB_IDX);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &gatt_db);
// act: try to read the CCC descriptor
let resp =
- block_on_locally(att_db.read_attribute(SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE)).unwrap();
+ block_on_locally(client.read_attribute(SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE)).unwrap();
assert_eq!(
Ok(resp),
@@ -253,10 +232,10 @@
}
async fn register_for_indication(
- att_db: &impl AttDatabase,
+ client: WeakAttClient,
handle: AttHandle,
) -> Result<(), AttErrorCode> {
- att_db
+ client
.write_attribute(
handle,
&att::GattClientCharacteristicConfiguration { notification: 0, indication: 1 }
@@ -270,14 +249,17 @@
fn test_subscribe_to_indication() {
// arrange
let gatt_db = init_gatt_db();
- let (att_db, _, _) = add_connection(&gatt_db, TCB_IDX);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &gatt_db);
// act: register for service change indication
- block_on_locally(register_for_indication(&att_db, SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE))
- .unwrap();
+ block_on_locally(register_for_indication(
+ client.downgrade(),
+ SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE,
+ ))
+ .unwrap();
// read our registration status
let resp =
- block_on_locally(att_db.read_attribute(SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE)).unwrap();
+ block_on_locally(client.read_attribute(SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE)).unwrap();
// assert: we are registered for indications
assert_eq!(
@@ -291,11 +273,11 @@
fn test_unsubscribe_to_indication() {
// arrange
let gatt_db = init_gatt_db();
- let (att_db, _, _) = add_connection(&gatt_db, TCB_IDX);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &gatt_db);
// act: register for service change indication
block_on_locally(
- att_db.write_attribute(
+ client.write_attribute(
SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE,
&att::GattClientCharacteristicConfiguration { notification: 0, indication: 1 }
.encode_to_vec()
@@ -305,7 +287,7 @@
.unwrap();
// act: next, unregister from this indication
block_on_locally(
- att_db.write_attribute(
+ client.write_attribute(
SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE,
&att::GattClientCharacteristicConfiguration { notification: 0, indication: 0 }
.encode_to_vec()
@@ -315,7 +297,7 @@
.unwrap();
// read our registration status
let resp =
- block_on_locally(att_db.read_attribute(SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE)).unwrap();
+ block_on_locally(client.read_attribute(SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE)).unwrap();
// assert: we are not registered for indications
assert_eq!(
@@ -330,10 +312,12 @@
block_on_locally(async {
// arrange
let gatt_db = init_gatt_db();
- let (att_db, _bearer, mut rx) = add_connection(&gatt_db, TCB_IDX);
+ let (client, mut rx) = AttClient::new_test_client(TCB_IDX, &gatt_db);
let (gatt_datastore, _) = MockDatastore::new();
let gatt_datastore = Rc::new(gatt_datastore);
- register_for_indication(&att_db, SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE).await.unwrap();
+ register_for_indication(client.downgrade(), SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE)
+ .await
+ .unwrap();
// act: register some new service
gatt_db
@@ -370,11 +354,15 @@
block_on_locally(async {
// arrange: two connections, both registered
let gatt_db = init_gatt_db();
- let (att_db_1, _bearer, mut rx1) = add_connection(&gatt_db, TCB_IDX);
- let (att_db_2, _bearer, mut rx2) = add_connection(&gatt_db, ANOTHER_TCB_IDX);
+ let (client1, mut rx1) = AttClient::new_test_client(TCB_IDX, &gatt_db);
+ let (client2, mut rx2) = AttClient::new_test_client(ANOTHER_TCB_IDX, &gatt_db);
- register_for_indication(&att_db_1, SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE).await.unwrap();
- register_for_indication(&att_db_2, SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE).await.unwrap();
+ register_for_indication(client1.downgrade(), SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE)
+ .await
+ .unwrap();
+ register_for_indication(client2.downgrade(), SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE)
+ .await
+ .unwrap();
let (gatt_datastore, _) = MockDatastore::new();
let gatt_datastore = Rc::new(gatt_datastore);
@@ -409,10 +397,12 @@
block_on_locally(async {
// arrange: two connections, only the first is registered
let gatt_db = init_gatt_db();
- let (att_db_1, _bearer, mut rx1) = add_connection(&gatt_db, TCB_IDX);
- let (_, _bearer, mut rx2) = add_connection(&gatt_db, ANOTHER_TCB_IDX);
+ let (client1, mut rx1) = AttClient::new_test_client(TCB_IDX, &gatt_db);
+ let (_client2, mut rx2) = AttClient::new_test_client(ANOTHER_TCB_IDX, &gatt_db);
- register_for_indication(&att_db_1, SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE).await.unwrap();
+ register_for_indication(client1.downgrade(), SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE)
+ .await
+ .unwrap();
let (gatt_datastore, _) = MockDatastore::new();
let gatt_datastore = Rc::new(gatt_datastore);
@@ -447,14 +437,17 @@
block_on_locally(async {
// arrange: two connections, both register, but the second one disconnects
let gatt_db = init_gatt_db();
- let (att_db_1, _bearer, mut rx1) = add_connection(&gatt_db, TCB_IDX);
- let (att_db_2, bearer_2, mut rx2) = add_connection(&gatt_db, ANOTHER_TCB_IDX);
+ let (client1, mut rx1) = AttClient::new_test_client(TCB_IDX, &gatt_db);
+ let (client2, mut rx2) = AttClient::new_test_client(ANOTHER_TCB_IDX, &gatt_db);
- register_for_indication(&att_db_1, SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE).await.unwrap();
- register_for_indication(&att_db_2, SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE).await.unwrap();
+ register_for_indication(client1.downgrade(), SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE)
+ .await
+ .unwrap();
+ register_for_indication(client2.downgrade(), SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE)
+ .await
+ .unwrap();
- drop(bearer_2);
- gatt_db.on_bearer_dropped(ANOTHER_TCB_IDX);
+ drop(client2);
let (gatt_datastore, _) = MockDatastore::new();
let gatt_datastore = Rc::new(gatt_datastore);
diff --git a/system/rust/src/gatt/server/test/test_att_db.rs b/system/rust/src/gatt/server/test/test_att_db.rs
index 2011baf..6f74568 100644
--- a/system/rust/src/gatt/server/test/test_att_db.rs
+++ b/system/rust/src/gatt/server/test/test_att_db.rs
@@ -1,84 +1,132 @@
-use crate::gatt::ids::AttHandle;
-use crate::gatt::server::att_database::{AttAttribute, AttDatabase, StableAttDatabase};
+use crate::core::shared_box::SharedBox;
+use crate::gatt::callbacks::{GattWriteRequestType, TransactionDecision};
+use crate::gatt::ffi::AttributeBackingType;
+use crate::gatt::ids::{AttHandle, TransportIndex};
+use crate::gatt::server::att_database::AttAttribute;
+use crate::gatt::server::gatt_database::GattDatabase;
+use crate::gatt::server::RawGattDatastore;
use crate::packets::att::AttErrorCode;
use async_trait::async_trait;
-use log::{info, warn};
use std::cell::RefCell;
-use std::collections::BTreeMap;
+use std::collections::{BTreeMap, HashMap};
use std::rc::Rc;
-#[derive(Clone, Debug)]
-pub struct TestAttDatabase {
- attributes: Rc<BTreeMap<AttHandle, TestAttributeWithData>>,
+pub struct TestDatastore {
+ values: BTreeMap<AttHandle, RefCell<Vec<u8>>>,
+ queued_writes: RefCell<HashMap<TransportIndex, Vec<(AttHandle, u32, Vec<u8>)>>>,
}
-#[derive(Debug)]
-struct TestAttributeWithData {
- attribute: AttAttribute,
- data: RefCell<Vec<u8>>,
-}
-
-impl TestAttDatabase {
- pub fn new(attributes: Vec<(AttAttribute, Vec<u8>)>) -> Self {
- Self {
- attributes: Rc::new(
- attributes
- .into_iter()
- .map(|(attribute, data)| {
- (attribute.handle, TestAttributeWithData { attribute, data: data.into() })
- })
- .collect(),
- ),
- }
+impl TestDatastore {
+ pub fn new(values: impl IntoIterator<Item = (AttHandle, Vec<u8>)>) -> Rc<Self> {
+ Rc::new(Self {
+ values: values.into_iter().map(|(handle, data)| (handle, RefCell::new(data))).collect(),
+ queued_writes: RefCell::default(),
+ })
}
-}
-#[async_trait(?Send)]
-impl AttDatabase for TestAttDatabase {
- async fn read_attribute(&self, handle: AttHandle) -> Result<Vec<u8>, AttErrorCode> {
- info!("reading {handle:?}");
- match self.attributes.get(&handle) {
- Some(TestAttributeWithData { attribute: AttAttribute { permissions, .. }, .. })
- if !permissions.readable() =>
- {
- Err(AttErrorCode::ReadNotPermitted)
- }
- Some(TestAttributeWithData { data, .. }) => Ok(data.borrow().clone()),
- None => Err(AttErrorCode::InvalidHandle),
- }
- }
- async fn write_attribute(&self, handle: AttHandle, data: &[u8]) -> Result<(), AttErrorCode> {
- match self.attributes.get(&handle) {
- Some(TestAttributeWithData { attribute: AttAttribute { permissions, .. }, .. })
- if !permissions.writable_with_response() =>
- {
- Err(AttErrorCode::WriteNotPermitted)
- }
- Some(TestAttributeWithData { data: data_cell, .. }) => {
- data_cell.replace(data.to_vec());
+ fn write_impl(&self, handle: AttHandle, data: &[u8]) -> Result<(), AttErrorCode> {
+ match self.values.get(&handle) {
+ Some(value) => {
+ *value.borrow_mut() = data.into();
Ok(())
}
None => Err(AttErrorCode::InvalidHandle),
}
}
- fn write_no_response_attribute(&self, handle: AttHandle, data: &[u8]) {
- match self.attributes.get(&handle) {
- Some(TestAttributeWithData {
- attribute: AttAttribute { permissions, .. },
- data: data_cell,
- }) if !permissions.writable_with_response() => {
- data_cell.replace(data.to_vec());
- }
- _ => {
- warn!("rejecting write command to {handle:?}")
+}
+
+#[async_trait(?Send)]
+impl RawGattDatastore for TestDatastore {
+ async fn read(
+ &self,
+ _tcb_idx: TransportIndex,
+ handle: AttHandle,
+ offset: u32,
+ attr_type: AttributeBackingType,
+ ) -> Result<Vec<u8>, AttErrorCode> {
+ assert_eq!(offset, 0);
+ assert_eq!(attr_type, AttributeBackingType::Characteristic);
+ match self.values.get(&handle) {
+ Some(value) => Ok(value.borrow().clone()),
+ None => Err(AttErrorCode::InvalidHandle),
+ }
+ }
+
+ async fn write(
+ &self,
+ tcb_idx: TransportIndex,
+ handle: AttHandle,
+ attr_type: AttributeBackingType,
+ write_type: GattWriteRequestType,
+ data: &[u8],
+ ) -> Result<(), AttErrorCode> {
+ assert_eq!(attr_type, AttributeBackingType::Characteristic);
+ match write_type {
+ GattWriteRequestType::Request => self.write_impl(handle, data),
+ GattWriteRequestType::Prepare { offset } => {
+ if !self.values.contains_key(&handle) {
+ return Err(AttErrorCode::InvalidHandle);
+ }
+ self.queued_writes.borrow_mut().entry(tcb_idx).or_default().push((
+ handle,
+ offset,
+ data.to_vec(),
+ ));
+ Ok(())
}
}
}
- fn list_attributes(&self) -> Vec<AttAttribute> {
- self.attributes.values().map(|attr| attr.attribute).collect()
+
+ fn write_no_response(
+ &self,
+ _tcb_idx: TransportIndex,
+ handle: AttHandle,
+ attr_type: AttributeBackingType,
+ data: &[u8],
+ ) {
+ assert_eq!(attr_type, AttributeBackingType::Characteristic);
+ let _ = self.write_impl(handle, data);
+ }
+
+ async fn execute(
+ &self,
+ tcb_idx: TransportIndex,
+ decision: TransactionDecision,
+ ) -> Result<(), AttErrorCode> {
+ if let Some(writes) = self.queued_writes.borrow_mut().remove(&tcb_idx) {
+ if decision == TransactionDecision::Cancel {
+ return Ok(());
+ }
+ for (handle, offset, data) in &writes {
+ let value = self.values[handle].borrow();
+ let offset = *offset as usize;
+ if offset > value.len() {
+ return Err(AttErrorCode::InvalidOffset);
+ }
+ if offset + data.len() > value.len() {
+ return Err(AttErrorCode::InvalidAttributeValueLength);
+ }
+ }
+ for (handle, offset, data) in writes {
+ let offset = offset as usize;
+ self.values[&handle].borrow_mut()[offset..offset + data.len()]
+ .copy_from_slice(&data);
+ }
+ }
+ Ok(())
}
}
-// We guarantee that the contents of a TestAttDatabase will remain stable
-impl StableAttDatabase for TestAttDatabase {}
+/// Creates a new test database with the specified characteristics.
+pub fn new_test_database(
+ mut characteristics: Vec<(AttAttribute, Vec<u8>)>,
+) -> SharedBox<GattDatabase> {
+ let datastore = TestDatastore::new(
+ characteristics.iter_mut().map(|(a, data)| (a.handle, std::mem::take(data))),
+ );
+ SharedBox::new(GattDatabase::with_characteristics(
+ characteristics.into_iter().map(|(a, _)| a),
+ datastore,
+ ))
+}
diff --git a/system/rust/src/gatt/server/transactions/find_by_type_value.rs b/system/rust/src/gatt/server/transactions/find_by_type_value.rs
index c967158..994fcf6 100644
--- a/system/rust/src/gatt/server/transactions/find_by_type_value.rs
+++ b/system/rust/src/gatt/server/transactions/find_by_type_value.rs
@@ -3,7 +3,8 @@
use crate::core::uuid::Uuid;
use crate::gatt::ids::AttHandle;
-use crate::gatt::server::att_database::{AttAttribute, StableAttDatabase};
+use crate::gatt::server::att_client::WeakAttClient;
+use crate::gatt::server::att_database::AttAttribute;
use crate::packets::att::{self, AttErrorCode};
use super::helpers::att_grouping::find_group_end;
@@ -13,12 +14,13 @@
pub async fn handle_find_by_type_value_request(
request: att::AttFindByTypeValueRequest,
mtu: usize,
- db: &impl StableAttDatabase,
+ client: &WeakAttClient,
) -> Result<att::Att, EncodeError> {
- let Some(attrs) = filter_to_range(
+ let attrs = client.list_attributes();
+ let Some(filtered) = filter_to_range(
request.starting_handle.clone().into(),
request.ending_handle.into(),
- db.list_attributes().into_iter(),
+ attrs.iter(),
) else {
return att::AttErrorResponse {
opcode_in_error: att::AttOpcode::FindByTypeValueRequest,
@@ -31,18 +33,18 @@
// ATT_MTU-1 limit comes from Spec 5.3 Vol 3F Sec 3.4.3.4
let mut matches = PayloadAccumulator::new(mtu - 1);
- for attr @ AttAttribute { handle, type_, .. } in attrs {
- if Uuid::from(request.attribute_type.clone()) != type_ {
+ for attr @ AttAttribute { handle, type_, .. } in filtered {
+ if &Uuid::from(request.attribute_type.clone()) != type_ {
continue;
}
- if let Ok(value) = db.read_attribute(handle).await {
+ if let Ok(value) = client.read_attribute(*handle).await {
if value == request.attribute_value {
// match found
if !matches.push(att::AttributeHandleRange {
- found_attribute_handle: handle.into(),
- group_end_handle: find_group_end(db, attr)
+ found_attribute_handle: (*handle).into(),
+ group_end_handle: find_group_end(&attrs, attr)
.map(|attr| attr.handle)
- .unwrap_or(handle)
+ .unwrap_or(*handle)
.into(),
}) {
break;
@@ -68,10 +70,12 @@
#[cfg(test)]
mod test {
use crate::gatt::ffi::Uuid;
+ use crate::gatt::ids::TransportIndex;
+ use crate::gatt::server::att_client::AttClient;
use crate::gatt::server::gatt_database::{
AttPermissions, CHARACTERISTIC_UUID, PRIMARY_SERVICE_DECLARATION_UUID,
};
- use crate::gatt::server::test::test_att_db::TestAttDatabase;
+ use crate::gatt::server::test::test_att_db::new_test_database;
use crate::packets::att;
use super::*;
@@ -81,11 +85,12 @@
const VALUE: [u8; 2] = [1, 2];
const ANOTHER_VALUE: [u8; 2] = [3, 4];
+ const TCB_IDX: TransportIndex = TransportIndex(1);
#[test]
fn test_uuid_match() {
// arrange: db all with same value, but some with different UUID
- let db = TestAttDatabase::new(vec![
+ let db = new_test_database(vec![
(
AttAttribute {
handle: AttHandle(3),
@@ -111,6 +116,7 @@
VALUE.into(),
),
]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act
let att_view = att::AttFindByTypeValueRequest {
@@ -119,7 +125,11 @@
attribute_type: UUID.try_into().unwrap(),
attribute_value: VALUE.to_vec(),
};
- let response = tokio_test::block_on(handle_find_by_type_value_request(att_view, 128, &db));
+ let response = tokio_test::block_on(handle_find_by_type_value_request(
+ att_view,
+ 128,
+ &client.downgrade(),
+ ));
// assert: we only matched the ones with the correct UUID
assert_eq!(
@@ -143,7 +153,7 @@
#[test]
fn test_value_match() {
// arrange: db all with same type, but some with different value
- let db = TestAttDatabase::new(vec![
+ let db = new_test_database(vec![
(
AttAttribute {
handle: AttHandle(3),
@@ -169,6 +179,7 @@
VALUE.into(),
),
]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act
let att_view = att::AttFindByTypeValueRequest {
@@ -177,7 +188,11 @@
attribute_type: UUID.try_into().unwrap(),
attribute_value: VALUE.to_vec(),
};
- let response = tokio_test::block_on(handle_find_by_type_value_request(att_view, 128, &db));
+ let response = tokio_test::block_on(handle_find_by_type_value_request(
+ att_view,
+ 128,
+ &client.downgrade(),
+ ));
// assert
assert_eq!(
@@ -201,7 +216,8 @@
#[test]
fn test_range_check() {
// arrange: empty db
- let db = TestAttDatabase::new(vec![]);
+ let db = new_test_database(vec![]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act: provide an invalid handle range
let att_view = att::AttFindByTypeValueRequest {
@@ -210,7 +226,11 @@
attribute_type: UUID.try_into().unwrap(),
attribute_value: VALUE.to_vec(),
};
- let response = tokio_test::block_on(handle_find_by_type_value_request(att_view, 128, &db));
+ let response = tokio_test::block_on(handle_find_by_type_value_request(
+ att_view,
+ 128,
+ &client.downgrade(),
+ ));
// assert
assert_eq!(
@@ -227,7 +247,7 @@
#[test]
fn test_empty_response() {
// arrange
- let db = TestAttDatabase::new(vec![(
+ let db = new_test_database(vec![(
AttAttribute {
handle: AttHandle(3),
type_: UUID,
@@ -235,6 +255,7 @@
},
VALUE.into(),
)]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act: query using a range that does not overlap with matching attributes
let att_view = att::AttFindByTypeValueRequest {
@@ -243,7 +264,11 @@
attribute_type: UUID.try_into().unwrap(),
attribute_value: VALUE.to_vec(),
};
- let response = tokio_test::block_on(handle_find_by_type_value_request(att_view, 128, &db));
+ let response = tokio_test::block_on(handle_find_by_type_value_request(
+ att_view,
+ 128,
+ &client.downgrade(),
+ ));
// assert: got ATTRIBUTE_NOT_FOUND error
assert_eq!(
@@ -260,7 +285,7 @@
#[test]
fn test_grouping_uuid() {
// arrange
- let db = TestAttDatabase::new(vec![
+ let db = new_test_database(vec![
(
AttAttribute {
handle: AttHandle(3),
@@ -286,6 +311,7 @@
VALUE.into(),
),
]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act: look for a particular characteristic declaration
let att_view = att::AttFindByTypeValueRequest {
@@ -294,7 +320,11 @@
attribute_type: CHARACTERISTIC_UUID.try_into().unwrap(),
attribute_value: VALUE.to_vec(),
};
- let response = tokio_test::block_on(handle_find_by_type_value_request(att_view, 128, &db));
+ let response = tokio_test::block_on(handle_find_by_type_value_request(
+ att_view,
+ 128,
+ &client.downgrade(),
+ ));
// assert
assert_eq!(
@@ -312,7 +342,7 @@
#[test]
fn test_limit_total_size() {
// arrange
- let db = TestAttDatabase::new(vec![
+ let db = new_test_database(vec![
(
AttAttribute {
handle: AttHandle(3),
@@ -330,6 +360,7 @@
VALUE.into(),
),
]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act: use MTU = 5, so we can only fit one element in the output
let att_view = att::AttFindByTypeValueRequest {
@@ -338,7 +369,11 @@
attribute_type: UUID.try_into().unwrap(),
attribute_value: VALUE.to_vec(),
};
- let response = tokio_test::block_on(handle_find_by_type_value_request(att_view, 5, &db));
+ let response = tokio_test::block_on(handle_find_by_type_value_request(
+ att_view,
+ 5,
+ &client.downgrade(),
+ ));
// assert: only one of the two matches produced
assert_eq!(
diff --git a/system/rust/src/gatt/server/transactions/find_information_request.rs b/system/rust/src/gatt/server/transactions/find_information_request.rs
index b33cacc..34ecb0d 100644
--- a/system/rust/src/gatt/server/transactions/find_information_request.rs
+++ b/system/rust/src/gatt/server/transactions/find_information_request.rs
@@ -1,19 +1,21 @@
-use crate::gatt::server::att_database::{AttAttribute, AttDatabase};
+use crate::gatt::server::att_client::WeakAttClient;
+use crate::gatt::server::att_database::AttAttribute;
use crate::packets::att::{self, AttErrorCode};
use pdl_runtime::EncodeError;
use super::helpers::att_range_filter::filter_to_range;
use super::helpers::payload_accumulator::PayloadAccumulator;
-pub fn handle_find_information_request<T: AttDatabase>(
+pub fn handle_find_information_request(
request: att::AttFindInformationRequest,
mtu: usize,
- db: &T,
+ client: &WeakAttClient,
) -> Result<att::Att, EncodeError> {
+ let attrs = client.list_attributes();
let Some(attrs) = filter_to_range(
request.starting_handle.clone().into(),
request.ending_handle.into(),
- db.list_attributes().into_iter(),
+ attrs.iter(),
) else {
return att::AttErrorResponse {
opcode_in_error: att::AttOpcode::FindInformationRequest,
@@ -39,15 +41,17 @@
/// Returns a builder IF we can return at least one attribute, otherwise returns
/// None
-fn handle_find_information_request_short(
- attributes: impl Iterator<Item = AttAttribute>,
+fn handle_find_information_request_short<'a>(
+ attributes: impl Iterator<Item = &'a AttAttribute>,
mtu: usize,
) -> Option<att::AttFindInformationShortResponse> {
// Core Spec 5.3 Vol 3F 3.4.3.2 gives the ATT_MTU - 2 limit
let mut out = PayloadAccumulator::new(mtu - 2);
for AttAttribute { handle, type_: uuid, .. } in attributes {
- if let Ok(uuid) = uuid.try_into() {
- if out.push(att::AttFindInformationResponseShortEntry { handle: handle.into(), uuid }) {
+ if let Ok(uuid) = (*uuid).try_into() {
+ if out
+ .push(att::AttFindInformationResponseShortEntry { handle: (*handle).into(), uuid })
+ {
// If we successfully pushed a 16-bit UUID, continue. In all other cases, we
// should break.
continue;
@@ -63,8 +67,8 @@
}
}
-fn handle_find_information_request_long(
- attributes: impl Iterator<Item = AttAttribute>,
+fn handle_find_information_request_long<'a>(
+ attributes: impl Iterator<Item = &'a AttAttribute>,
mtu: usize,
) -> Option<att::AttFindInformationLongResponse> {
// Core Spec 5.3 Vol 3F 3.4.3.2 gives the ATT_MTU - 2 limit
@@ -72,8 +76,8 @@
for AttAttribute { handle, type_: uuid, .. } in attributes {
if !out.push(att::AttFindInformationResponseLongEntry {
- handle: handle.into(),
- uuid: uuid.into(),
+ handle: (*handle).into(),
+ uuid: (*uuid).into(),
}) {
break;
}
@@ -89,17 +93,21 @@
#[cfg(test)]
mod test {
use crate::core::uuid::Uuid;
+ use crate::gatt::ids::TransportIndex;
+ use crate::gatt::server::att_client::AttClient;
use crate::gatt::server::gatt_database::AttPermissions;
- use crate::gatt::server::test::test_att_db::TestAttDatabase;
+ use crate::gatt::server::test::test_att_db::new_test_database;
use crate::gatt::server::AttHandle;
use crate::packets::att;
use super::*;
+ const TCB_IDX: TransportIndex = TransportIndex(1);
+
#[test]
fn test_long_uuids() {
// arrange
- let db = TestAttDatabase::new(vec![
+ let db = new_test_database(vec![
(
AttAttribute {
handle: AttHandle(3),
@@ -125,13 +133,14 @@
vec![4, 5],
),
]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act
let att_view = att::AttFindInformationRequest {
starting_handle: AttHandle(3).into(),
ending_handle: AttHandle(4).into(),
};
- let response = handle_find_information_request(att_view, 128, &db);
+ let response = handle_find_information_request(att_view, 128, &client.downgrade());
// assert
assert_eq!(
@@ -155,7 +164,7 @@
#[test]
fn test_short_uuids() {
// arrange
- let db = TestAttDatabase::new(vec![
+ let db = new_test_database(vec![
(
AttAttribute {
handle: AttHandle(3),
@@ -181,13 +190,14 @@
vec![4, 5],
),
]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act
let att_view = att::AttFindInformationRequest {
starting_handle: AttHandle(3).into(),
ending_handle: AttHandle(5).into(),
};
- let response = handle_find_information_request(att_view, 128, &db);
+ let response = handle_find_information_request(att_view, 128, &client.downgrade());
// assert
assert_eq!(
@@ -211,14 +221,15 @@
#[test]
fn test_handle_validation() {
// arrange: empty db
- let db = TestAttDatabase::new(vec![]);
+ let db = new_test_database(vec![]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act: use an invalid handle range
let att_view = att::AttFindInformationRequest {
starting_handle: AttHandle(3).into(),
ending_handle: AttHandle(2).into(),
};
- let response = handle_find_information_request(att_view, 128, &db);
+ let response = handle_find_information_request(att_view, 128, &client.downgrade());
// assert: got INVALID_HANDLE
assert_eq!(
@@ -235,7 +246,7 @@
#[test]
fn test_limit_total_size() {
// arrange
- let db = TestAttDatabase::new(vec![
+ let db = new_test_database(vec![
(
AttAttribute {
handle: AttHandle(3),
@@ -253,13 +264,14 @@
vec![4, 5],
),
]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act: use MTU = 6, so only one entry can fit
let att_view = att::AttFindInformationRequest {
starting_handle: AttHandle(3).into(),
ending_handle: AttHandle(5).into(),
};
- let response = handle_find_information_request(att_view, 6, &db);
+ let response = handle_find_information_request(att_view, 6, &client.downgrade());
// assert: only one entry (not two) provided
assert_eq!(
@@ -277,7 +289,7 @@
#[test]
fn test_empty_output() {
// arrange
- let db = TestAttDatabase::new(vec![(
+ let db = new_test_database(vec![(
AttAttribute {
handle: AttHandle(3),
type_: Uuid::new(0x0102),
@@ -285,13 +297,14 @@
},
vec![4, 5],
)]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act: use a range that matches no attributes
let att_view = att::AttFindInformationRequest {
starting_handle: AttHandle(4).into(),
ending_handle: AttHandle(5).into(),
};
- let response = handle_find_information_request(att_view, 6, &db);
+ let response = handle_find_information_request(att_view, 6, &client.downgrade());
// assert: got ATTRIBUTE_NOT_FOUND
assert_eq!(
diff --git a/system/rust/src/gatt/server/transactions/helpers/att_filter_by_size_type.rs b/system/rust/src/gatt/server/transactions/helpers/att_filter_by_size_type.rs
index 1e8870c..12153a9 100644
--- a/system/rust/src/gatt/server/transactions/helpers/att_filter_by_size_type.rs
+++ b/system/rust/src/gatt/server/transactions/helpers/att_filter_by_size_type.rs
@@ -2,7 +2,8 @@
//! length, used in READ_BY_TYPE_REQ and READ_BY_GROUP_TYPE_REQ
use crate::core::uuid::Uuid;
-use crate::gatt::server::att_database::{AttAttribute, StableAttDatabase};
+use crate::gatt::server::att_client::WeakAttClient;
+use crate::gatt::server::att_database::AttAttribute;
use crate::packets::att::AttErrorCode;
/// An attribute and the value
@@ -23,18 +24,18 @@
/// Attributes are truncated to the attr_size limit before size comparison.
/// If an error occurs while reading, do not output further attributes.
pub async fn filter_read_attributes_by_size_type(
- db: &impl StableAttDatabase,
- attrs: impl Iterator<Item = AttAttribute>,
+ client: &WeakAttClient,
+ attrs: impl IntoIterator<Item = &AttAttribute>,
target: Uuid,
size_limit: usize,
) -> Result<impl Iterator<Item = AttributeWithValue>, AttErrorCode> {
- let target_attrs = attrs.filter(|attr| attr.type_ == target);
+ let target_attrs = attrs.into_iter().filter(|attr| attr.type_ == target);
let mut out = vec![];
let mut curr_elem_size = None;
for attr @ AttAttribute { handle, .. } in target_attrs {
- match db.read_attribute(handle).await {
+ match client.read_attribute(*handle).await {
Ok(mut value) => {
value.truncate(size_limit);
let value_size = value.len();
@@ -47,7 +48,7 @@
curr_elem_size = Some(value_size)
}
- out.push(AttributeWithValue { attr, value });
+ out.push(AttributeWithValue { attr: *attr, value });
}
Err(err) => {
if out.is_empty() {
@@ -66,18 +67,20 @@
use super::*;
use crate::core::uuid::Uuid;
- use crate::gatt::ids::AttHandle;
- use crate::gatt::server::att_database::{AttAttribute, AttDatabase, StableAttDatabase};
+ use crate::gatt::ids::{AttHandle, TransportIndex};
+ use crate::gatt::server::att_client::AttClient;
+ use crate::gatt::server::att_database::AttAttribute;
use crate::gatt::server::gatt_database::AttPermissions;
- use crate::gatt::server::test::test_att_db::TestAttDatabase;
+ use crate::gatt::server::test::test_att_db::new_test_database;
const UUID: Uuid = Uuid::new(1234);
const ANOTHER_UUID: Uuid = Uuid::new(2345);
+ const TCB_IDX: TransportIndex = TransportIndex(1);
#[test]
fn test_single_matching_attr() {
// arrange
- let db = TestAttDatabase::new(vec![(
+ let db = new_test_database(vec![(
AttAttribute {
handle: AttHandle(3),
type_: UUID,
@@ -85,21 +88,26 @@
},
vec![4, 5],
)]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act
- let response = tokio_test::block_on(filter_read_attributes_by_size_type(
- &db,
- db.list_attributes().into_iter(),
- UUID,
- 31,
- ))
- .unwrap();
+ let response: Vec<_> = tokio_test::block_on(async {
+ filter_read_attributes_by_size_type(
+ &client.downgrade(),
+ &client.list_attributes(),
+ UUID,
+ 31,
+ )
+ .await
+ .unwrap()
+ .collect()
+ });
// assert
assert_eq!(
- response.collect::<Vec<_>>(),
+ response,
vec![AttributeWithValue {
- attr: db.find_attribute(AttHandle(3)).unwrap(),
+ attr: db.get(AttHandle(3)).map(|a| a.attribute).unwrap(),
value: vec![4, 5],
}]
)
@@ -108,7 +116,7 @@
#[test]
fn test_skip_mismatching_attrs() {
// arrange
- let db = TestAttDatabase::new(vec![
+ let db = new_test_database(vec![
(
AttAttribute {
handle: AttHandle(3),
@@ -134,26 +142,31 @@
vec![6, 7],
),
]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act
- let response = tokio_test::block_on(filter_read_attributes_by_size_type(
- &db,
- db.list_attributes().into_iter(),
- UUID,
- 31,
- ))
- .unwrap();
+ let response: Vec<_> = tokio_test::block_on(async {
+ filter_read_attributes_by_size_type(
+ &client.downgrade(),
+ &client.list_attributes(),
+ UUID,
+ 31,
+ )
+ .await
+ .unwrap()
+ .collect()
+ });
// assert
assert_eq!(
- response.collect::<Vec<_>>(),
+ response,
vec![
AttributeWithValue {
- attr: db.find_attribute(AttHandle(3)).unwrap(),
+ attr: db.get(AttHandle(3)).map(|a| a.attribute).unwrap(),
value: vec![4, 5],
},
AttributeWithValue {
- attr: db.find_attribute(AttHandle(6)).unwrap(),
+ attr: db.get(AttHandle(6)).map(|a| a.attribute).unwrap(),
value: vec![6, 7],
}
]
@@ -163,7 +176,7 @@
#[test]
fn test_stop_once_length_changes() {
// arrange
- let db = TestAttDatabase::new(vec![
+ let db = new_test_database(vec![
(
AttAttribute {
handle: AttHandle(3),
@@ -182,28 +195,33 @@
),
(
AttAttribute {
- handle: AttHandle(6),
+ handle: AttHandle(7),
type_: UUID,
permissions: AttPermissions::READABLE,
},
vec![6, 7],
),
]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act
- let response = tokio_test::block_on(filter_read_attributes_by_size_type(
- &db,
- db.list_attributes().into_iter(),
- UUID,
- 31,
- ))
- .unwrap();
+ let response: Vec<_> = tokio_test::block_on(async {
+ filter_read_attributes_by_size_type(
+ &client.downgrade(),
+ &client.list_attributes(),
+ UUID,
+ 31,
+ )
+ .await
+ .unwrap()
+ .collect()
+ });
// assert
assert_eq!(
- response.collect::<Vec<_>>(),
+ response,
vec![AttributeWithValue {
- attr: db.find_attribute(AttHandle(3)).unwrap(),
+ attr: db.get(AttHandle(3)).map(|a| a.attribute).unwrap(),
value: vec![4, 5],
},]
);
@@ -212,7 +230,7 @@
#[test]
fn test_truncate_to_mtu() {
// arrange: attr with data of length 3
- let db = TestAttDatabase::new(vec![(
+ let db = new_test_database(vec![(
AttAttribute {
handle: AttHandle(3),
type_: UUID,
@@ -220,21 +238,26 @@
},
vec![4, 5, 6],
)]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act: read the attribute with max_size = 2
- let response = tokio_test::block_on(filter_read_attributes_by_size_type(
- &db,
- db.list_attributes().into_iter(),
- UUID,
- 2,
- ))
- .unwrap();
+ let response: Vec<_> = tokio_test::block_on(async {
+ filter_read_attributes_by_size_type(
+ &client.downgrade(),
+ &client.list_attributes(),
+ UUID,
+ 2,
+ )
+ .await
+ .unwrap()
+ .collect()
+ });
// assert: the length of the read attribute is 2
assert_eq!(
- response.collect::<Vec<_>>(),
+ response,
vec![AttributeWithValue {
- attr: db.find_attribute(AttHandle(3)).unwrap(),
+ attr: db.get(AttHandle(3)).map(|a| a.attribute).unwrap(),
value: vec![4, 5],
},]
);
@@ -243,25 +266,30 @@
#[test]
fn test_no_results() {
// arrange: an empty database
- let db = TestAttDatabase::new(vec![]);
+ let db = new_test_database(vec![]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act
- let response = tokio_test::block_on(filter_read_attributes_by_size_type(
- &db,
- db.list_attributes().into_iter(),
- UUID,
- 31,
- ))
- .unwrap();
+ let response: Vec<_> = tokio_test::block_on(async {
+ filter_read_attributes_by_size_type(
+ &client.downgrade(),
+ &client.list_attributes(),
+ UUID,
+ 31,
+ )
+ .await
+ .unwrap()
+ .collect()
+ });
// assert: no results
- assert_eq!(response.count(), 0)
+ assert!(response.is_empty())
}
#[test]
fn test_read_failure_on_first_attr() {
// arrange: put a non-readable attribute in the db with the right type
- let db = TestAttDatabase::new(vec![(
+ let db = new_test_database(vec![(
AttAttribute {
handle: AttHandle(3),
type_: UUID,
@@ -269,11 +297,15 @@
},
vec![4, 5, 6],
)]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
+
+ let weak_client = client.downgrade();
+ let attrs = client.list_attributes();
// act
let response = tokio_test::block_on(filter_read_attributes_by_size_type(
- &db,
- db.list_attributes().into_iter(),
+ &weak_client,
+ &attrs,
UUID,
31,
));
@@ -286,7 +318,7 @@
fn test_read_failure_on_subsequent_attr() {
// arrange: put a non-readable attribute in the db with the right
// type
- let db = TestAttDatabase::new(vec![
+ let db = new_test_database(vec![
(
AttAttribute {
handle: AttHandle(3),
@@ -312,22 +344,27 @@
vec![8, 9, 10],
),
]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act
- let response = tokio_test::block_on(filter_read_attributes_by_size_type(
- &db,
- db.list_attributes().into_iter(),
- UUID,
- 31,
- ))
- .unwrap();
+ let response: Vec<_> = tokio_test::block_on(async {
+ filter_read_attributes_by_size_type(
+ &client.downgrade(),
+ &client.list_attributes(),
+ UUID,
+ 31,
+ )
+ .await
+ .unwrap()
+ .collect()
+ });
// assert: we reply with the first attribute, but not the second or third
// (since we stop on the first failure)
assert_eq!(
- response.collect::<Vec<_>>(),
+ response,
vec![AttributeWithValue {
- attr: db.find_attribute(AttHandle(3)).unwrap(),
+ attr: db.get(AttHandle(3)).map(|a| a.attribute).unwrap(),
value: vec![4, 5, 6],
},]
);
@@ -337,7 +374,7 @@
fn test_skip_unreadable_mismatching_attr() {
// arrange: put a non-readable attribute in the db with the wrong type
// between two attributes of interest
- let db = TestAttDatabase::new(vec![
+ let db = new_test_database(vec![
(
AttAttribute {
handle: AttHandle(3),
@@ -363,26 +400,31 @@
vec![6, 7, 8],
),
]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act
- let response = tokio_test::block_on(filter_read_attributes_by_size_type(
- &db,
- db.list_attributes().into_iter(),
- UUID,
- 31,
- ))
- .unwrap();
+ let response: Vec<_> = tokio_test::block_on(async {
+ filter_read_attributes_by_size_type(
+ &client.downgrade(),
+ &client.list_attributes(),
+ UUID,
+ 31,
+ )
+ .await
+ .unwrap()
+ .collect()
+ });
// assert: we reply with the first and third attributes, but not the second
assert_eq!(
- response.collect::<Vec<_>>(),
+ response,
vec![
AttributeWithValue {
- attr: db.find_attribute(AttHandle(3)).unwrap(),
+ attr: db.get(AttHandle(3)).map(|a| a.attribute).unwrap(),
value: vec![4, 5, 6],
},
AttributeWithValue {
- attr: db.find_attribute(AttHandle(5)).unwrap(),
+ attr: db.get(AttHandle(5)).map(|a| a.attribute).unwrap(),
value: vec![6, 7, 8],
}
]
diff --git a/system/rust/src/gatt/server/transactions/helpers/att_grouping.rs b/system/rust/src/gatt/server/transactions/helpers/att_grouping.rs
index 1596475..2374c4f 100644
--- a/system/rust/src/gatt/server/transactions/helpers/att_grouping.rs
+++ b/system/rust/src/gatt/server/transactions/helpers/att_grouping.rs
@@ -4,7 +4,7 @@
use crate::core::uuid::Uuid;
-use crate::gatt::server::att_database::{AttAttribute, StableAttDatabase};
+use crate::gatt::server::att_database::AttAttribute;
use crate::gatt::server::gatt_database::{
CHARACTERISTIC_UUID, PRIMARY_SERVICE_DECLARATION_UUID, SECONDARY_SERVICE_DECLARATION_UUID,
};
@@ -29,16 +29,16 @@
/// Expects `attrs` to be in sorted order by attribute handle.
///
/// Attribute grouping is defined in 5.3 Vol 3G Sec 2.5.3 Attribute Grouping
-pub fn find_group_end(
- db: &impl StableAttDatabase,
- group_start: AttAttribute,
-) -> Option<AttAttribute> {
+pub fn find_group_end<'a>(
+ attributes: impl IntoIterator<Item = &'a AttAttribute>,
+ group_start: &'a AttAttribute,
+) -> Option<&'a AttAttribute> {
if !GROUPING_ATTRIBUTES.contains(&group_start.type_) {
return None; // invalid / unsupported grouping attribute
}
Some(
- db.list_attributes()
+ attributes
.into_iter()
// ignore attributes at or before the current position
.skip_while(|attr| attr.handle <= group_start.handle)
@@ -54,16 +54,17 @@
#[cfg(test)]
mod test {
+ use crate::core::shared_box::SharedBox;
use crate::gatt::ids::AttHandle;
- use crate::gatt::server::gatt_database::AttPermissions;
- use crate::gatt::server::test::test_att_db::TestAttDatabase;
+ use crate::gatt::server::gatt_database::{AttPermissions, GattDatabase};
+ use crate::gatt::server::test::test_att_db::new_test_database;
use super::*;
const OTHER_UUID: Uuid = Uuid::new(1234);
- fn db_from_attrs(attrs: impl IntoIterator<Item = AttAttribute>) -> TestAttDatabase {
- TestAttDatabase::new(attrs.into_iter().map(|attr| (attr, vec![])).collect())
+ fn db_from_attrs(attrs: impl IntoIterator<Item = AttAttribute>) -> SharedBox<GattDatabase> {
+ new_test_database(attrs.into_iter().map(|attr| (attr, vec![])).collect())
}
fn attr(handle: AttHandle, type_: Uuid) -> AttAttribute {
@@ -80,7 +81,9 @@
attr(AttHandle(40), PRIMARY_SERVICE_DECLARATION_UUID),
]);
- let group_end = find_group_end(&db, attr(AttHandle(10), PRIMARY_SERVICE_DECLARATION_UUID));
+ let attrs = db.list_attributes();
+ let group_start = attr(AttHandle(10), PRIMARY_SERVICE_DECLARATION_UUID);
+ let group_end = find_group_end(&attrs, &group_start);
assert_eq!(group_end.unwrap().handle, AttHandle(30))
}
@@ -94,7 +97,9 @@
attr(AttHandle(40), SECONDARY_SERVICE_DECLARATION_UUID),
]);
- let group_end = find_group_end(&db, attr(AttHandle(10), PRIMARY_SERVICE_DECLARATION_UUID));
+ let attrs = db.list_attributes();
+ let group_start = attr(AttHandle(10), PRIMARY_SERVICE_DECLARATION_UUID);
+ let group_end = find_group_end(&attrs, &group_start);
assert_eq!(group_end.unwrap().handle, AttHandle(30))
}
@@ -109,8 +114,9 @@
attr(AttHandle(50), PRIMARY_SERVICE_DECLARATION_UUID),
]);
- let group_end =
- find_group_end(&db, attr(AttHandle(10), SECONDARY_SERVICE_DECLARATION_UUID));
+ let attrs = db.list_attributes();
+ let group_start = attr(AttHandle(10), SECONDARY_SERVICE_DECLARATION_UUID);
+ let group_end = find_group_end(&attrs, &group_start);
assert_eq!(group_end.unwrap().handle, AttHandle(40))
}
@@ -125,8 +131,9 @@
attr(AttHandle(50), SECONDARY_SERVICE_DECLARATION_UUID),
]);
- let group_end =
- find_group_end(&db, attr(AttHandle(10), SECONDARY_SERVICE_DECLARATION_UUID));
+ let attrs = db.list_attributes();
+ let group_start = attr(AttHandle(10), SECONDARY_SERVICE_DECLARATION_UUID);
+ let group_end = find_group_end(&attrs, &group_start);
assert_eq!(group_end.unwrap().handle, AttHandle(40))
}
@@ -139,7 +146,9 @@
attr(AttHandle(30), SECONDARY_SERVICE_DECLARATION_UUID),
]);
- let group_end = find_group_end(&db, attr(AttHandle(10), CHARACTERISTIC_UUID));
+ let attrs = db.list_attributes();
+ let group_start = attr(AttHandle(10), CHARACTERISTIC_UUID);
+ let group_end = find_group_end(&attrs, &group_start);
assert_eq!(group_end.unwrap().handle, AttHandle(20))
}
@@ -152,7 +161,9 @@
attr(AttHandle(30), CHARACTERISTIC_UUID),
]);
- let group_end = find_group_end(&db, attr(AttHandle(10), CHARACTERISTIC_UUID));
+ let attrs = db.list_attributes();
+ let group_start = attr(AttHandle(10), CHARACTERISTIC_UUID);
+ let group_end = find_group_end(&attrs, &group_start);
assert_eq!(group_end.unwrap().handle, AttHandle(20))
}
@@ -165,7 +176,9 @@
attr(AttHandle(30), OTHER_UUID),
]);
- let group_end = find_group_end(&db, attr(AttHandle(10), PRIMARY_SERVICE_DECLARATION_UUID));
+ let attrs = db.list_attributes();
+ let group_start = attr(AttHandle(10), PRIMARY_SERVICE_DECLARATION_UUID);
+ let group_end = find_group_end(&attrs, &group_start);
assert_eq!(group_end.unwrap().handle, AttHandle(30))
}
@@ -174,7 +187,9 @@
fn test_empty_non_terminated_group() {
let db = db_from_attrs([attr(AttHandle(10), CHARACTERISTIC_UUID)]);
- let group_end = find_group_end(&db, attr(AttHandle(10), CHARACTERISTIC_UUID));
+ let attrs = db.list_attributes();
+ let group_start = attr(AttHandle(10), CHARACTERISTIC_UUID);
+ let group_end = find_group_end(&attrs, &group_start);
assert_eq!(group_end.unwrap().handle, AttHandle(10))
}
@@ -186,7 +201,9 @@
attr(AttHandle(20), SECONDARY_SERVICE_DECLARATION_UUID),
]);
- let group_end = find_group_end(&db, attr(AttHandle(10), CHARACTERISTIC_UUID));
+ let attrs = db.list_attributes();
+ let group_start = attr(AttHandle(10), CHARACTERISTIC_UUID);
+ let group_end = find_group_end(&attrs, &group_start);
assert_eq!(group_end.unwrap().handle, AttHandle(10))
}
@@ -198,7 +215,9 @@
attr(AttHandle(20), SECONDARY_SERVICE_DECLARATION_UUID),
]);
- let group_end = find_group_end(&db, attr(AttHandle(10), OTHER_UUID));
+ let attrs = db.list_attributes();
+ let group_start = attr(AttHandle(10), OTHER_UUID);
+ let group_end = find_group_end(&attrs, &group_start);
assert_eq!(group_end, None)
}
diff --git a/system/rust/src/gatt/server/transactions/helpers/att_range_filter.rs b/system/rust/src/gatt/server/transactions/helpers/att_range_filter.rs
index b70119b..9da1e35 100644
--- a/system/rust/src/gatt/server/transactions/helpers/att_range_filter.rs
+++ b/system/rust/src/gatt/server/transactions/helpers/att_range_filter.rs
@@ -8,11 +8,11 @@
/// Filter a (sorted) iterator of attributes to those that lie within
/// the specified range. If the range is invalid (start = 0, or start > end),
/// return None.
-pub fn filter_to_range(
+pub fn filter_to_range<'a>(
start_handle: AttHandle,
end_handle: AttHandle,
- attrs: impl Iterator<Item = AttAttribute> + Clone,
-) -> Option<impl Iterator<Item = AttAttribute> + Clone> {
+ attrs: impl Iterator<Item = &'a AttAttribute> + Clone,
+) -> Option<impl Iterator<Item = &'a AttAttribute> + Clone> {
if start_handle.0 == 0 || end_handle < start_handle {
return None;
}
@@ -56,22 +56,17 @@
fn test_trivial_range() {
// call with a range where start == end, make sure it gets the relevant
// attribute
- let res =
- filter_to_range(AttHandle(3), AttHandle(3), [attr(2), attr(3), attr(4)].into_iter())
- .unwrap();
+ let attrs = [attr(2), attr(3), attr(4)];
+ let res = filter_to_range(AttHandle(3), AttHandle(3), attrs.iter()).unwrap();
- assert_eq!(res.collect::<Vec<_>>(), vec![attr(3)])
+ assert_eq!(res.cloned().collect::<Vec<_>>(), vec![attr(3)])
}
#[test]
fn test_nontrivial_range() {
- let res = filter_to_range(
- AttHandle(3),
- AttHandle(4),
- [attr(2), attr(3), attr(4), attr(5)].into_iter(),
- )
- .unwrap();
+ let attrs = [attr(2), attr(3), attr(4), attr(5)];
+ let res = filter_to_range(AttHandle(3), AttHandle(4), attrs.iter()).unwrap();
- assert_eq!(res.collect::<Vec<_>>(), vec![attr(3), attr(4)])
+ assert_eq!(res.cloned().collect::<Vec<_>>(), vec![attr(3), attr(4)])
}
}
diff --git a/system/rust/src/gatt/server/transactions/read_by_group_type_request.rs b/system/rust/src/gatt/server/transactions/read_by_group_type_request.rs
index 7bff62c..6e6ee07 100644
--- a/system/rust/src/gatt/server/transactions/read_by_group_type_request.rs
+++ b/system/rust/src/gatt/server/transactions/read_by_group_type_request.rs
@@ -1,5 +1,5 @@
use crate::core::uuid::Uuid;
-use crate::gatt::server::att_database::StableAttDatabase;
+use crate::gatt::server::att_client::WeakAttClient;
use crate::gatt::server::gatt_database::{
PRIMARY_SERVICE_DECLARATION_UUID, SECONDARY_SERVICE_DECLARATION_UUID,
};
@@ -16,7 +16,7 @@
pub async fn handle_read_by_group_type_request(
request: att::AttReadByGroupTypeRequest,
mtu: usize,
- db: &impl StableAttDatabase,
+ client: &WeakAttClient,
) -> Result<att::Att, EncodeError> {
// As per spec (5.3 Vol 3F 3.4.4.9)
// > If an attribute in the set of requested attributes would cause an
@@ -38,10 +38,11 @@
return failure_response.try_into();
};
- let Some(attrs) = filter_to_range(
+ let all_attrs = client.list_attributes();
+ let Some(filtered) = filter_to_range(
request.starting_handle.into(),
request.ending_handle.into(),
- db.list_attributes().into_iter(),
+ all_attrs.iter(),
) else {
failure_response.error_code = AttErrorCode::InvalidHandle;
return failure_response.try_into();
@@ -60,12 +61,12 @@
let mut matches = PayloadAccumulator::new(mtu - 2);
// MTU-6 limit comes from Core Spec 5.3 Vol 3F 3.4.4.9
- match filter_read_attributes_by_size_type(db, attrs, group_type, mtu - 6).await {
+ match filter_read_attributes_by_size_type(client, filtered, group_type, mtu - 6).await {
Ok(attrs) => {
for AttributeWithValue { attr, value } in attrs {
if !matches.push(att::AttReadByGroupTypeDataElement {
handle: attr.handle.into(),
- end_group_handle: find_group_end(db, attr)
+ end_group_handle: find_group_end(&all_attrs, &attr)
.expect("should never be None, since grouping UUID was validated earlier")
.handle
.into(),
@@ -90,18 +91,20 @@
#[cfg(test)]
mod test {
- use crate::gatt::ids::AttHandle;
+ use crate::gatt::ids::{AttHandle, TransportIndex};
+ use crate::gatt::server::att_client::AttClient;
use crate::gatt::server::att_database::AttAttribute;
use crate::gatt::server::gatt_database::{AttPermissions, CHARACTERISTIC_UUID};
- use crate::gatt::server::test::test_att_db::TestAttDatabase;
+ use crate::gatt::server::test::test_att_db::new_test_database;
use crate::packets::att;
use super::*;
+ const TCB_IDX: TransportIndex = TransportIndex(1);
#[test]
fn test_simple_grouping() {
// arrange: one service with a child attribute, another service with no children
- let db = TestAttDatabase::new(vec![
+ let db = new_test_database(vec![
(
AttAttribute {
handle: AttHandle(3),
@@ -127,6 +130,7 @@
vec![6, 7],
),
]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act
let att_view = att::AttReadByGroupTypeRequest {
@@ -134,7 +138,11 @@
ending_handle: AttHandle(6).into(),
attribute_group_type: PRIMARY_SERVICE_DECLARATION_UUID.into(),
};
- let response = tokio_test::block_on(handle_read_by_group_type_request(att_view, 31, &db));
+ let response = tokio_test::block_on(handle_read_by_group_type_request(
+ att_view,
+ 31,
+ &client.downgrade(),
+ ));
// assert: we identified both service groups
assert_eq!(
@@ -160,7 +168,8 @@
#[test]
fn test_invalid_group_type() {
// arrange
- let db = TestAttDatabase::new(vec![]);
+ let db = new_test_database(vec![]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act: try using an unsupported group type
let att_view = att::AttReadByGroupTypeRequest {
@@ -168,7 +177,11 @@
ending_handle: AttHandle(6).into(),
attribute_group_type: CHARACTERISTIC_UUID.into(),
};
- let response = tokio_test::block_on(handle_read_by_group_type_request(att_view, 31, &db));
+ let response = tokio_test::block_on(handle_read_by_group_type_request(
+ att_view,
+ 31,
+ &client.downgrade(),
+ ));
// assert: got UNSUPPORTED_GROUP_TYPE
assert_eq!(
@@ -185,7 +198,8 @@
#[test]
fn test_range_validation() {
// arrange: an empty (irrelevant) db
- let db = TestAttDatabase::new(vec![]);
+ let db = new_test_database(vec![]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act: query with an invalid attribute range
let att_view = att::AttReadByGroupTypeRequest {
@@ -193,7 +207,11 @@
ending_handle: AttHandle(2).into(),
attribute_group_type: PRIMARY_SERVICE_DECLARATION_UUID.into(),
};
- let response = tokio_test::block_on(handle_read_by_group_type_request(att_view, 31, &db));
+ let response = tokio_test::block_on(handle_read_by_group_type_request(
+ att_view,
+ 31,
+ &client.downgrade(),
+ ));
// assert: we return an INVALID_HANDLE error
assert_eq!(
@@ -210,7 +228,7 @@
#[test]
fn test_attribute_truncation() {
// arrange: one service with a value of length 5
- let db = TestAttDatabase::new(vec![(
+ let db = new_test_database(vec![(
AttAttribute {
handle: AttHandle(3),
type_: PRIMARY_SERVICE_DECLARATION_UUID,
@@ -218,6 +236,7 @@
},
vec![1, 2, 3, 4, 5],
)]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act: read the service value with MTU = 7, so the value is truncated to MTU-6
// = 1
@@ -226,7 +245,11 @@
ending_handle: AttHandle(6).into(),
attribute_group_type: PRIMARY_SERVICE_DECLARATION_UUID.into(),
};
- let response = tokio_test::block_on(handle_read_by_group_type_request(att_view, 7, &db));
+ let response = tokio_test::block_on(handle_read_by_group_type_request(
+ att_view,
+ 7,
+ &client.downgrade(),
+ ));
// assert: we identified both service groups
assert_eq!(
@@ -245,7 +268,7 @@
#[test]
fn test_limit_total_size() {
// arrange
- let db = TestAttDatabase::new(vec![
+ let db = new_test_database(vec![
(
AttAttribute {
handle: AttHandle(3),
@@ -263,6 +286,7 @@
vec![5, 6, 7],
),
]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act: read with MTU = 9, so we can only fit the first attribute (untruncated)
let att_view = att::AttReadByGroupTypeRequest {
@@ -270,7 +294,11 @@
ending_handle: AttHandle(6).into(),
attribute_group_type: PRIMARY_SERVICE_DECLARATION_UUID.into(),
};
- let response = tokio_test::block_on(handle_read_by_group_type_request(att_view, 9, &db));
+ let response = tokio_test::block_on(handle_read_by_group_type_request(
+ att_view,
+ 9,
+ &client.downgrade(),
+ ));
// assert: we return only the first attribute
assert_eq!(
@@ -289,7 +317,7 @@
#[test]
fn test_group_end_outside_range() {
// arrange: one service with a child attribute
- let db = TestAttDatabase::new(vec![
+ let db = new_test_database(vec![
(
AttAttribute {
handle: AttHandle(3),
@@ -307,6 +335,7 @@
vec![5, 6],
),
]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act: search in an interval that includes the service but not its child
let att_view = att::AttReadByGroupTypeRequest {
@@ -314,7 +343,11 @@
ending_handle: AttHandle(3).into(),
attribute_group_type: PRIMARY_SERVICE_DECLARATION_UUID.into(),
};
- let response = tokio_test::block_on(handle_read_by_group_type_request(att_view, 31, &db));
+ let response = tokio_test::block_on(handle_read_by_group_type_request(
+ att_view,
+ 31,
+ &client.downgrade(),
+ ));
// assert: the end_group_handle is correct, even though it exceeds the query
// interval
@@ -334,7 +367,7 @@
#[test]
fn test_no_results() {
// arrange: read out of the bounds where attributes of interest exist
- let db = TestAttDatabase::new(vec![
+ let db = new_test_database(vec![
(
AttAttribute {
handle: AttHandle(3),
@@ -352,6 +385,7 @@
vec![4, 5, 6],
),
]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act
let att_view = att::AttReadByGroupTypeRequest {
@@ -359,7 +393,11 @@
ending_handle: AttHandle(6).into(),
attribute_group_type: PRIMARY_SERVICE_DECLARATION_UUID.into(),
};
- let response = tokio_test::block_on(handle_read_by_group_type_request(att_view, 31, &db));
+ let response = tokio_test::block_on(handle_read_by_group_type_request(
+ att_view,
+ 31,
+ &client.downgrade(),
+ ));
// assert: we return ATTRIBUTE_NOT_FOUND
assert_eq!(
diff --git a/system/rust/src/gatt/server/transactions/read_by_type_request.rs b/system/rust/src/gatt/server/transactions/read_by_type_request.rs
index eec8815..8858609 100644
--- a/system/rust/src/gatt/server/transactions/read_by_type_request.rs
+++ b/system/rust/src/gatt/server/transactions/read_by_type_request.rs
@@ -1,5 +1,5 @@
use crate::core::uuid::Uuid;
-use crate::gatt::server::att_database::StableAttDatabase;
+use crate::gatt::server::att_client::WeakAttClient;
use crate::packets::att::{self, AttErrorCode};
use pdl_runtime::EncodeError;
@@ -12,7 +12,7 @@
pub async fn handle_read_by_type_request(
request: att::AttReadByTypeRequest,
mtu: usize,
- db: &impl StableAttDatabase,
+ client: &WeakAttClient,
) -> Result<att::Att, EncodeError> {
// As per spec (5.3 Vol 3F 3.4.4.1)
// > If an attribute in the set of requested attributes would cause an
@@ -34,10 +34,11 @@
return failure_response.try_into();
};
+ let all_attrs = client.list_attributes();
let Some(attrs) = filter_to_range(
request.starting_handle.into(),
request.ending_handle.into(),
- db.list_attributes().into_iter(),
+ all_attrs.iter(),
) else {
failure_response.error_code = AttErrorCode::InvalidHandle;
return failure_response.try_into();
@@ -47,7 +48,7 @@
let mut out = PayloadAccumulator::new(mtu - 2);
// MTU-4 limit comes from Core Spec 5.3 Vol 3F 3.4.4.1
- match filter_read_attributes_by_size_type(db, attrs, request_type, mtu - 4).await {
+ match filter_read_attributes_by_size_type(client, attrs, request_type, mtu - 4).await {
Ok(attrs) => {
for AttributeWithValue { attr, value } in attrs {
if !out.push(att::AttReadByTypeDataElement { handle: attr.handle.into(), value }) {
@@ -73,19 +74,21 @@
use super::*;
use crate::core::uuid::Uuid;
- use crate::gatt::ids::AttHandle;
+ use crate::gatt::ids::{AttHandle, TransportIndex};
+ use crate::gatt::server::att_client::AttClient;
use crate::gatt::server::att_database::AttAttribute;
use crate::gatt::server::gatt_database::AttPermissions;
- use crate::gatt::server::test::test_att_db::TestAttDatabase;
+ use crate::gatt::server::test::test_att_db::new_test_database;
use crate::packets::att;
const UUID: Uuid = Uuid::new(1234);
const ANOTHER_UUID: Uuid = Uuid::new(2345);
+ const TCB_IDX: TransportIndex = TransportIndex(1);
#[test]
fn test_single_matching_attr() {
// arrange
- let db = TestAttDatabase::new(vec![(
+ let db = new_test_database(vec![(
AttAttribute {
handle: AttHandle(3),
type_: UUID,
@@ -93,6 +96,7 @@
},
vec![4, 5],
)]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act
let att_view = att::AttReadByTypeRequest {
@@ -100,7 +104,8 @@
ending_handle: AttHandle(6).into(),
attribute_type: UUID.into(),
};
- let response = tokio_test::block_on(handle_read_by_type_request(att_view, 31, &db));
+ let response =
+ tokio_test::block_on(handle_read_by_type_request(att_view, 31, &client.downgrade()));
// assert
assert_eq!(
@@ -118,7 +123,7 @@
#[test]
fn test_type_filtering() {
// arrange
- let db = TestAttDatabase::new(vec![
+ let db = new_test_database(vec![
(
AttAttribute {
handle: AttHandle(3),
@@ -144,6 +149,7 @@
vec![6, 7],
),
]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act
let att_view = att::AttReadByTypeRequest {
@@ -151,7 +157,8 @@
ending_handle: AttHandle(6).into(),
attribute_type: UUID.into(),
};
- let response = tokio_test::block_on(handle_read_by_type_request(att_view, 31, &db));
+ let response =
+ tokio_test::block_on(handle_read_by_type_request(att_view, 31, &client.downgrade()));
// assert: we correctly filtered by type (so we are using the filter_by_type
// utility)
@@ -176,7 +183,7 @@
#[test]
fn test_limit_total_size() {
// arrange
- let db = TestAttDatabase::new(vec![
+ let db = new_test_database(vec![
(
AttAttribute {
handle: AttHandle(3),
@@ -194,6 +201,7 @@
vec![5, 6, 7],
),
]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act: read with MTU = 8, so we can only fit the first attribute (untruncated)
let att_view = att::AttReadByTypeRequest {
@@ -201,7 +209,8 @@
ending_handle: AttHandle(6).into(),
attribute_type: UUID.into(),
};
- let response = tokio_test::block_on(handle_read_by_type_request(att_view, 8, &db));
+ let response =
+ tokio_test::block_on(handle_read_by_type_request(att_view, 8, &client.downgrade()));
// assert: we return only the first attribute
assert_eq!(
@@ -219,7 +228,7 @@
#[test]
fn test_no_results() {
// arrange: read out of the bounds where attributes of interest exist
- let db = TestAttDatabase::new(vec![
+ let db = new_test_database(vec![
(
AttAttribute {
handle: AttHandle(3),
@@ -237,6 +246,7 @@
vec![4, 5, 6],
),
]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act
let att_view = att::AttReadByTypeRequest {
@@ -244,7 +254,8 @@
ending_handle: AttHandle(6).into(),
attribute_type: UUID.into(),
};
- let response = tokio_test::block_on(handle_read_by_type_request(att_view, 31, &db));
+ let response =
+ tokio_test::block_on(handle_read_by_type_request(att_view, 31, &client.downgrade()));
// assert: we return ATTRIBUTE_NOT_FOUND
assert_eq!(
@@ -261,7 +272,8 @@
#[test]
fn test_range_validation() {
// arrange: put a non-readable attribute in the db with the right type
- let db = TestAttDatabase::new(vec![]);
+ let db = new_test_database(vec![]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act
let att_view = att::AttReadByTypeRequest {
@@ -269,7 +281,8 @@
ending_handle: AttHandle(6).into(),
attribute_type: UUID.into(),
};
- let response = tokio_test::block_on(handle_read_by_type_request(att_view, 31, &db));
+ let response =
+ tokio_test::block_on(handle_read_by_type_request(att_view, 31, &client.downgrade()));
// assert: we return an INVALID_HANDLE error
assert_eq!(
diff --git a/system/rust/src/gatt/server/transactions/read_request.rs b/system/rust/src/gatt/server/transactions/read_request.rs
index 32d4e48..2d0fb45 100644
--- a/system/rust/src/gatt/server/transactions/read_request.rs
+++ b/system/rust/src/gatt/server/transactions/read_request.rs
@@ -1,15 +1,15 @@
-use crate::gatt::server::att_database::AttDatabase;
+use crate::gatt::server::att_client::WeakAttClient;
use crate::packets::att;
use pdl_runtime::EncodeError;
-pub async fn handle_read_request<T: AttDatabase>(
+pub async fn handle_read_request(
request: att::AttReadRequest,
mtu: usize,
- db: &T,
+ client: &WeakAttClient,
) -> Result<att::Att, EncodeError> {
let handle = request.attribute_handle.into();
- match db.read_attribute(handle).await {
+ match client.read_attribute(handle).await {
Ok(mut data) => {
// as per 5.3 3F 3.4.4.4 ATT_READ_RSP, we truncate to MTU - 1
data.truncate(mtu - 1);
@@ -24,14 +24,14 @@
}
}
-pub async fn handle_read_blob_request<T: AttDatabase>(
+pub async fn handle_read_blob_request(
request: att::AttReadBlobRequest,
mtu: usize,
- db: &T,
+ client: &WeakAttClient,
) -> Result<att::Att, EncodeError> {
let handle = request.attribute_handle.into();
- match db.read_attribute(handle).await {
+ match client.read_attribute(handle).await {
Ok(data) => {
let offset = request.offset as usize;
if offset > data.len() {
@@ -57,10 +57,10 @@
}
}
-pub async fn handle_read_multiple_variable_request<T: AttDatabase>(
+pub async fn handle_read_multiple_variable_request(
request: att::AttReadMultipleVariableRequest,
mtu: usize,
- db: &T,
+ client: &WeakAttClient,
) -> Result<att::Att, EncodeError> {
if request.attribute_handles.len() < 2 {
return att::AttErrorResponse {
@@ -75,7 +75,7 @@
};
let mut space = mtu - 1; // -1 for op code.
for handle in request.attribute_handles {
- match db.read_attribute(handle.clone().into()).await {
+ match client.read_attribute(handle.clone().into()).await {
Ok(data) => {
if space >= 2 {
let amount = std::cmp::min(data.len(), space - 2);
@@ -111,14 +111,19 @@
mod test {
use super::*;
+ use crate::core::shared_box::SharedBox;
use crate::core::uuid::Uuid;
- use crate::gatt::ids::AttHandle;
+ use crate::gatt::ids::{AttHandle, TransportIndex};
+ use crate::gatt::server::att_client::AttClient;
use crate::gatt::server::att_database::{AttAttribute, AttPermissions};
- use crate::gatt::server::test::test_att_db::TestAttDatabase;
+ use crate::gatt::server::gatt_database::GattDatabase;
+ use crate::gatt::server::test::test_att_db::new_test_database;
use crate::packets::att;
- fn make_db_with_handle_and_value(handle: u16, value: Vec<u8>) -> TestAttDatabase {
- TestAttDatabase::new(vec![(
+ const TCB_IDX: TransportIndex = TransportIndex(1);
+
+ fn make_db_with_handle_and_value(handle: u16, value: Vec<u8>) -> SharedBox<GattDatabase> {
+ new_test_database(vec![(
AttAttribute {
handle: AttHandle(handle),
type_: Uuid::new(0x1234),
@@ -131,17 +136,18 @@
fn do_read_request_with_handle_and_mtu(
handle: u16,
mtu: usize,
- db: &TestAttDatabase,
+ client: &WeakAttClient,
) -> Result<att::Att, EncodeError> {
let att_view = att::AttReadRequest { attribute_handle: AttHandle(handle).into() };
- tokio_test::block_on(handle_read_request(att_view, mtu, db))
+ tokio_test::block_on(handle_read_request(att_view, mtu, client))
}
#[test]
fn test_simple_read() {
let db = make_db_with_handle_and_value(3, vec![4, 5]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
- let response = do_read_request_with_handle_and_mtu(3, 31, &db);
+ let response = do_read_request_with_handle_and_mtu(3, 31, &client.downgrade());
assert_eq!(response, att::AttReadResponse { value: vec![4, 5] }.try_into());
}
@@ -149,9 +155,10 @@
#[test]
fn test_truncated_read() {
let db = make_db_with_handle_and_value(3, vec![4, 5]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act
- let response = do_read_request_with_handle_and_mtu(3, 2, &db);
+ let response = do_read_request_with_handle_and_mtu(3, 2, &client.downgrade());
// assert
assert_eq!(response, att::AttReadResponse { value: vec![4] }.try_into());
@@ -160,9 +167,10 @@
#[test]
fn test_missed_read() {
let db = make_db_with_handle_and_value(3, vec![4, 5]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act
- let response = do_read_request_with_handle_and_mtu(4, 31, &db);
+ let response = do_read_request_with_handle_and_mtu(4, 31, &client.downgrade());
// assert
assert_eq!(
@@ -176,8 +184,8 @@
);
}
- fn make_db_with_unreadable_handle(handle: u16) -> TestAttDatabase {
- TestAttDatabase::new(vec![(
+ fn make_db_with_unreadable_handle(handle: u16) -> SharedBox<GattDatabase> {
+ new_test_database(vec![(
AttAttribute {
handle: AttHandle(handle),
type_: Uuid::new(0x1234),
@@ -190,9 +198,10 @@
#[test]
fn test_not_readable() {
let db = make_db_with_unreadable_handle(3);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act
- let response = do_read_request_with_handle_and_mtu(3, 31, &db);
+ let response = do_read_request_with_handle_and_mtu(3, 31, &client.downgrade());
// assert
assert_eq!(
diff --git a/system/rust/src/gatt/server/transactions/write_request.rs b/system/rust/src/gatt/server/transactions/write_request.rs
index f3a41e6..c225536 100644
--- a/system/rust/src/gatt/server/transactions/write_request.rs
+++ b/system/rust/src/gatt/server/transactions/write_request.rs
@@ -1,14 +1,15 @@
-use crate::gatt::server::att_database::AttDatabase;
+use crate::gatt::callbacks::TransactionDecision;
+use crate::gatt::server::att_client::WeakAttClient;
use crate::packets::att;
use pdl_runtime::EncodeError;
-pub async fn handle_write_request<T: AttDatabase>(
+pub async fn handle_write_request(
request: att::AttWriteRequest,
- db: &T,
+ client: &WeakAttClient,
) -> Result<att::Att, EncodeError> {
let handle = request.handle.into();
let value = request.value;
- match db.write_attribute(handle, &value).await {
+ match client.write_attribute(handle, &value).await {
Ok(()) => att::AttWriteResponse {}.try_into(),
Err(error_code) => att::AttErrorResponse {
opcode_in_error: att::AttOpcode::WriteRequest,
@@ -19,6 +20,44 @@
}
}
+pub async fn handle_prepare_write_request(
+ request: att::AttPrepareWriteRequest,
+ client: &WeakAttClient,
+) -> Result<att::Att, EncodeError> {
+ let att::AttPrepareWriteRequest { handle, offset, value } = request;
+ match client.prepare_write_attribute(handle.clone().into(), offset as u32, &value).await {
+ Ok(()) => att::AttPrepareWriteResponse { handle, offset, value }.try_into(),
+ Err(error_code) => att::AttErrorResponse {
+ opcode_in_error: att::AttOpcode::PrepareWriteRequest,
+ handle_in_error: handle,
+ error_code,
+ }
+ .try_into(),
+ }
+}
+
+pub async fn handle_execute_write_request(
+ request: att::AttExecuteWriteRequest,
+ client: &WeakAttClient,
+) -> Result<att::Att, EncodeError> {
+ match client
+ .execute(if request.commit & 1 == 1 {
+ TransactionDecision::Execute
+ } else {
+ TransactionDecision::Cancel
+ })
+ .await
+ {
+ Ok(()) => att::AttExecuteWriteResponse {}.try_into(),
+ Err((handle, error_code)) => att::AttErrorResponse {
+ opcode_in_error: att::AttOpcode::ExecuteWriteRequest,
+ handle_in_error: handle.into(),
+ error_code,
+ }
+ .try_into(),
+ }
+}
+
#[cfg(test)]
mod test {
use super::*;
@@ -26,16 +65,19 @@
use tokio_test::block_on;
use crate::core::uuid::Uuid;
- use crate::gatt::ids::AttHandle;
- use crate::gatt::server::att_database::{AttAttribute, AttDatabase};
+ use crate::gatt::ids::{AttHandle, TransportIndex};
+ use crate::gatt::server::att_client::AttClient;
+ use crate::gatt::server::att_database::AttAttribute;
use crate::gatt::server::gatt_database::AttPermissions;
- use crate::gatt::server::test::test_att_db::TestAttDatabase;
+ use crate::gatt::server::test::test_att_db::new_test_database;
use crate::packets::att;
+ const TCB_IDX: TransportIndex = TransportIndex(1);
+
#[test]
fn test_successful_write() {
// arrange: db with one writable attribute
- let db = TestAttDatabase::new(vec![(
+ let db = new_test_database(vec![(
AttAttribute {
handle: AttHandle(1),
type_: Uuid::new(0x1234),
@@ -44,20 +86,21 @@
vec![],
)]);
let data = vec![1, 2];
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act: write to the attribute
let att_view = att::AttWriteRequest { handle: AttHandle(1).into(), value: data.clone() };
- let resp = block_on(handle_write_request(att_view, &db));
+ let resp = block_on(handle_write_request(att_view, &client.downgrade()));
// assert: that the write succeeded
assert_eq!(resp, att::AttWriteResponse {}.try_into());
- assert_eq!(block_on(db.read_attribute(AttHandle(1))).unwrap(), data);
+ assert_eq!(block_on(client.read_attribute(AttHandle(1))).unwrap(), data);
}
#[test]
fn test_failed_write() {
// arrange: db with no writable attributes
- let db = TestAttDatabase::new(vec![(
+ let db = new_test_database(vec![(
AttAttribute {
handle: AttHandle(1),
type_: Uuid::new(0x1234),
@@ -65,9 +108,10 @@
},
vec![],
)]);
+ let (client, _) = AttClient::new_test_client(TCB_IDX, &db);
// act: write to the attribute
let att_view = att::AttWriteRequest { handle: AttHandle(1).into(), value: vec![1, 2] };
- let resp = block_on(handle_write_request(att_view, &db));
+ let resp = block_on(handle_write_request(att_view, &client.downgrade()));
// assert: that the write failed
assert_eq!(
diff --git a/system/rust/src/packets.pdl b/system/rust/src/packets.pdl
index e15a054..7168e65 100644
--- a/system/rust/src/packets.pdl
+++ b/system/rust/src/packets.pdl
@@ -65,6 +65,7 @@
INVALID_OFFSET = 0x07,
ATTRIBUTE_NOT_FOUND = 0x0A,
ATTRIBUTE_NOT_LONG = 0x0B,
+ INVALID_ATTRIBUTE_VALUE_LENGTH = 0x0D,
UNLIKELY_ERROR = 0x0E,
UNSUPPORTED_GROUP_TYPE = 0x10,
APPLICATION_ERROR = 0x80,
@@ -251,6 +252,25 @@
packet AttWriteResponse : Att(opcode = WRITE_RESPONSE) {}
+packet AttPrepareWriteRequest : Att(opcode = PREPARE_WRITE_REQUEST) {
+ handle : AttHandle,
+ offset : 16,
+ value : 8[],
+}
+
+packet AttPrepareWriteResponse : Att(opcode = PREPARE_WRITE_RESPONSE) {
+ handle : AttHandle,
+ offset : 16,
+ value : 8[],
+}
+
+packet AttExecuteWriteRequest : Att(opcode = EXECUTE_WRITE_REQUEST) {
+ commit : 1, // true = commit, false = cancel
+ _reserved_ : 7,
+}
+
+packet AttExecuteWriteResponse : Att(opcode = EXECUTE_WRITE_RESPONSE) {}
+
packet AttErrorResponse : Att(opcode = ERROR_RESPONSE) {
opcode_in_error: AttOpcode,
handle_in_error: AttHandle,
diff --git a/system/stack/rfcomm/rfc_port_fsm.cc b/system/stack/rfcomm/rfc_port_fsm.cc
index dec82b6..c2e32f6 100644
--- a/system/stack/rfcomm/rfc_port_fsm.cc
+++ b/system/stack/rfcomm/rfc_port_fsm.cc
@@ -576,17 +576,15 @@
case RFC_PORT_EVENT_DM:
log::warn("RFC_EVENT_DM|RFC_EVENT_UA[{}], port_handle:{}", event, p_port->handle);
- if (com::android::bluetooth::flags::rfcomm_always_disc_initiator_in_disc_wait_ua()) {
- // If we got a DM in RFC_STATE_DISC_WAIT_UA, it's likely that both ends
- // attempt to DISC at the same time and both get a DM.
- // Without setting this flag the both ends would start the same timers,
- // wait, and still DISC the multiplexer at the same time eventually.
- // The wait is meaningless and would block all other services that rely
- // on RFCOMM such as HFP.
- // Thus, setting this flag here to save us a timeout and doesn't
- // introduce further RFCOMM event changes.
- p_port->rfc.p_mcb->is_disc_initiator = true;
- }
+ // If we got a DM in RFC_STATE_DISC_WAIT_UA, it's likely that both ends
+ // attempt to DISC at the same time and both get a DM.
+ // Without setting this flag the both ends would start the same timers,
+ // wait, and still DISC the multiplexer at the same time eventually.
+ // The wait is meaningless and would block all other services that rely
+ // on RFCOMM such as HFP.
+ // Thus, setting this flag here to save us a timeout and doesn't
+ // introduce further RFCOMM event changes.
+ p_port->rfc.p_mcb->is_disc_initiator = true;
rfc_port_closed(p_port);
return;