diff options
author | 2024-12-03 21:26:50 +0000 | |
---|---|---|
committer | 2024-12-03 21:26:50 +0000 | |
commit | 3b15d68f4f6fbcdec59cd5abf0ffe30c0dec1cfc (patch) | |
tree | 0171fda7e18bbcf0f43f1090a8365813e238dbe1 | |
parent | 811f3370319cc0480550cbc6707ac20ae0046fed (diff) | |
parent | 5615c053c730befc06a851ad42ede829f570fc0a (diff) |
Merge changes from topic "offload_socket_aosp" into main
* changes:
Add conditional privileged permission annotation on pbap client
Read RFCOMM socket offload capabilities from bluetooth low power proccesor
Notify offloaded LE COC socket channel info to socket hal
Determine local mtu for offload LE socket
Set the initial local credits to 0 for offload LE socket
Send signal to indicate if app is accepting incoming connection on listen socket
Add bluetooth offload socket API
Read socket offload capabilities from bluetooth low power proccesor
Add low power processor offload manager with socket hal shim
Add helper functions to get LE L2CAP channel and ACL handle information
72 files changed, 3188 insertions, 207 deletions
diff --git a/android/app/aidl/android/bluetooth/IBluetooth.aidl b/android/app/aidl/android/bluetooth/IBluetooth.aidl index 45692376c8..158e37823f 100644 --- a/android/app/aidl/android/bluetooth/IBluetooth.aidl +++ b/android/app/aidl/android/bluetooth/IBluetooth.aidl @@ -338,4 +338,10 @@ interface IBluetooth @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)") oneway void killBluetoothProcess(); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)") + boolean isLeCocSocketOffloadSupported(in AttributionSource source); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)") + boolean isRfcommSocketOffloadSupported(in AttributionSource source); } diff --git a/android/app/aidl/android/bluetooth/IBluetoothSocketManager.aidl b/android/app/aidl/android/bluetooth/IBluetoothSocketManager.aidl index 1bbf692cda..05f647d4a9 100644 --- a/android/app/aidl/android/bluetooth/IBluetoothSocketManager.aidl +++ b/android/app/aidl/android/bluetooth/IBluetoothSocketManager.aidl @@ -29,8 +29,12 @@ interface IBluetoothSocketManager { @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)") @nullable ParcelFileDescriptor connectSocket(in BluetoothDevice device, int type, in @nullable ParcelUuid uuid, int port, int flag); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") + @nullable ParcelFileDescriptor connectSocketwithOffload(in BluetoothDevice device, int type, in @nullable ParcelUuid uuid, int port, int flag, int dataPath, in String socketName, long hubId, long endpointId, int maximumPacketSize); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)") @nullable ParcelFileDescriptor createSocketChannel(int type, in @nullable String serviceName, in @nullable ParcelUuid uuid, int port, int flag); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") + @nullable ParcelFileDescriptor createSocketChannelWithOffload(int type, in @nullable String serviceName, in @nullable ParcelUuid uuid, int port, int flag, int dataPath, in String socketName, long hubId, long endpointId, int maximumPacketSize); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)") void requestMaximumTxDataLength(in BluetoothDevice device); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") diff --git a/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp b/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp index 3a0a8d1aac..805e22b0df 100644 --- a/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp +++ b/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp @@ -1782,11 +1782,14 @@ static jboolean setBufferLengthMillisNative(JNIEnv* /* env */, jobject /* obj */ } static jint connectSocketNative(JNIEnv* env, jobject /* obj */, jbyteArray address, jint type, - jbyteArray uuid, jint port, jint flag, jint callingUid) { + jbyteArray uuid, jint port, jint flag, jint callingUid, + jint dataPath, jstring socketName, jlong hubId, jlong endPointId, + jint maxRxPacketSize) { int socket_fd = INVALID_FD; jbyte* addr = nullptr; jbyte* uuidBytes = nullptr; Uuid btUuid; + const char* nativeSocketName = nullptr; if (!sBluetoothSocketInterface) { goto done; @@ -1799,9 +1802,13 @@ static jint connectSocketNative(JNIEnv* env, jobject /* obj */, jbyteArray addre } btUuid = Uuid::From128BitBE(reinterpret_cast<uint8_t*>(uuidBytes)); + if (socketName != nullptr) { + nativeSocketName = env->GetStringUTFChars(socketName, nullptr); + } if (sBluetoothSocketInterface->connect(reinterpret_cast<RawAddress*>(addr), (btsock_type_t)type, - &btUuid, port, &socket_fd, flag, - callingUid) != BT_STATUS_SUCCESS) { + &btUuid, port, &socket_fd, flag, callingUid, + (btsock_data_path_t)dataPath, nativeSocketName, hubId, + endPointId, maxRxPacketSize) != BT_STATUS_SUCCESS) { socket_fd = INVALID_FD; } @@ -1812,16 +1819,21 @@ done: if (uuidBytes) { env->ReleaseByteArrayElements(uuid, uuidBytes, 0); } + if (nativeSocketName) { + env->ReleaseStringUTFChars(socketName, nativeSocketName); + } return socket_fd; } static jint createSocketChannelNative(JNIEnv* env, jobject /* obj */, jint type, jstring serviceName, jbyteArray uuid, jint port, jint flag, - jint callingUid) { + jint callingUid, jint dataPath, jstring socketName, + jlong hubId, jlong endPointId, jint maxRxPacketSize) { int socket_fd = INVALID_FD; jbyte* uuidBytes = nullptr; Uuid btUuid; const char* nativeServiceName = nullptr; + const char* nativeSocketName = nullptr; if (!sBluetoothSocketInterface) { goto done; @@ -1835,9 +1847,14 @@ static jint createSocketChannelNative(JNIEnv* env, jobject /* obj */, jint type, goto done; } btUuid = Uuid::From128BitBE(reinterpret_cast<uint8_t*>(uuidBytes)); + if (socketName != nullptr) { + nativeSocketName = env->GetStringUTFChars(socketName, nullptr); + } if (sBluetoothSocketInterface->listen((btsock_type_t)type, nativeServiceName, &btUuid, port, - &socket_fd, flag, callingUid) != BT_STATUS_SUCCESS) { + &socket_fd, flag, callingUid, (btsock_data_path_t)dataPath, + nativeSocketName, hubId, endPointId, + maxRxPacketSize) != BT_STATUS_SUCCESS) { socket_fd = INVALID_FD; } @@ -1848,6 +1865,9 @@ done: if (nativeServiceName) { env->ReleaseStringUTFChars(serviceName, nativeServiceName); } + if (nativeSocketName) { + env->ReleaseStringUTFChars(socketName, nativeSocketName); + } return socket_fd; } @@ -2267,8 +2287,9 @@ int register_com_android_bluetooth_btservice_AdapterService(JNIEnv* env) { {"setBufferLengthMillisNative", "(II)Z", reinterpret_cast<void*>(setBufferLengthMillisNative)}, {"getMetricIdNative", "([B)I", reinterpret_cast<void*>(getMetricIdNative)}, - {"connectSocketNative", "([BI[BIII)I", reinterpret_cast<void*>(connectSocketNative)}, - {"createSocketChannelNative", "(ILjava/lang/String;[BIII)I", + {"connectSocketNative", "([BI[BIIIILjava/lang/String;JJI)I", + reinterpret_cast<void*>(connectSocketNative)}, + {"createSocketChannelNative", "(ILjava/lang/String;[BIIIILjava/lang/String;JJI)I", reinterpret_cast<void*>(createSocketChannelNative)}, {"requestMaximumTxDataLengthNative", "([B)V", reinterpret_cast<void*>(requestMaximumTxDataLengthNative)}, diff --git a/android/app/src/com/android/bluetooth/btservice/AbstractionLayer.java b/android/app/src/com/android/bluetooth/btservice/AbstractionLayer.java index f1f0b51691..981b2db0c7 100644 --- a/android/app/src/com/android/bluetooth/btservice/AbstractionLayer.java +++ b/android/app/src/com/android/bluetooth/btservice/AbstractionLayer.java @@ -49,6 +49,7 @@ public final class AbstractionLayer { static final int BT_PROPERTY_REMOTE_ASHA_CAPABILITY = 0X15; static final int BT_PROPERTY_REMOTE_ASHA_TRUNCATED_HISYNCID = 0X16; static final int BT_PROPERTY_REMOTE_MODEL_NUM = 0x17; + static final int BT_PROPERTY_LPP_OFFLOAD_FEATURES = 0x1B; public static final int BT_DEVICE_TYPE_BREDR = 0x01; public static final int BT_DEVICE_TYPE_BLE = 0x02; diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterNativeInterface.java b/android/app/src/com/android/bluetooth/btservice/AdapterNativeInterface.java index 33bf3c3f8f..64b5a0dfae 100644 --- a/android/app/src/com/android/bluetooth/btservice/AdapterNativeInterface.java +++ b/android/app/src/com/android/bluetooth/btservice/AdapterNativeInterface.java @@ -195,13 +195,56 @@ public class AdapterNativeInterface { return getMetricIdNative(address); } - int connectSocket(byte[] address, int type, byte[] uuid, int port, int flag, int callingUid) { - return connectSocketNative(address, type, uuid, port, flag, callingUid); + int connectSocket( + byte[] address, + int type, + byte[] uuid, + int port, + int flag, + int callingUid, + int dataPath, + String socketName, + long hubId, + long endpointId, + int maximumPacketSize) { + return connectSocketNative( + address, + type, + uuid, + port, + flag, + callingUid, + dataPath, + socketName, + hubId, + endpointId, + maximumPacketSize); } int createSocketChannel( - int type, String serviceName, byte[] uuid, int port, int flag, int callingUid) { - return createSocketChannelNative(type, serviceName, uuid, port, flag, callingUid); + int type, + String serviceName, + byte[] uuid, + int port, + int flag, + int callingUid, + int dataPath, + String socketName, + long hubId, + long endpointId, + int maximumPacketSize) { + return createSocketChannelNative( + type, + serviceName, + uuid, + port, + flag, + callingUid, + dataPath, + socketName, + hubId, + endpointId, + maximumPacketSize); } void requestMaximumTxDataLength(byte[] address) { @@ -359,10 +402,30 @@ public class AdapterNativeInterface { private native int getMetricIdNative(byte[] address); private native int connectSocketNative( - byte[] address, int type, byte[] uuid, int port, int flag, int callingUid); + byte[] address, + int type, + byte[] uuid, + int port, + int flag, + int callingUid, + int dataPath, + String socketName, + long hubId, + long endpointId, + int maximumPacketSize); private native int createSocketChannelNative( - int type, String serviceName, byte[] uuid, int port, int flag, int callingUid); + int type, + String serviceName, + byte[] uuid, + int port, + int flag, + int callingUid, + int dataPath, + String socketName, + long hubId, + long endpointId, + int maximumPacketSize); private native void requestMaximumTxDataLengthNative(byte[] address); diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterProperties.java b/android/app/src/com/android/bluetooth/btservice/AdapterProperties.java index 851df2c030..e0c3f0c59d 100644 --- a/android/app/src/com/android/bluetooth/btservice/AdapterProperties.java +++ b/android/app/src/com/android/bluetooth/btservice/AdapterProperties.java @@ -134,6 +134,9 @@ class AdapterProperties { private boolean mIsLeIsochronousBroadcasterSupported; private boolean mIsLeChannelSoundingSupported; + private int mNumberOfSupportedOffloadedLeCocSockets; + private int mNumberOfSupportedOffloadedRfcommSockets = 0; + // Lock for all getters and setters. // If finer grained locking is needer, more locks // can be added here. @@ -908,6 +911,10 @@ class AdapterProperties { updateDynamicAudioBufferSupport(val); break; + case AbstractionLayer.BT_PROPERTY_LPP_OFFLOAD_FEATURES: + updateLppOffloadFeatureSupport(val); + break; + default: Log.e(TAG, "Property change not handled in Java land:" + type); } @@ -1022,6 +1029,37 @@ class AdapterProperties { mBufferConstraintList.complete(bufferConstraintList); } + /** + * @return the mNumberOfSupportedOffloadedLeCocSockets + */ + int getNumberOfSupportedOffloadedLeCocSockets() { + return mNumberOfSupportedOffloadedLeCocSockets; + } + + /** + * @return the mNumberOfSupportedOffloadedRfcommSockets + */ + int getNumberOfSupportedOffloadedRfcommSockets() { + return mNumberOfSupportedOffloadedRfcommSockets; + } + + private void updateLppOffloadFeatureSupport(byte[] val) { + if (val.length < 1) { + Log.e(TAG, "BT_PROPERTY_LPP_OFFLOAD_FEATURES: invalid value length"); + return; + } + // TODO(b/342012881) Read mNumberOfSupportedOffloadedRfcommSockets from host stack + mNumberOfSupportedOffloadedLeCocSockets = (0xFF & ((int) val[0])); + + Log.d( + TAG, + "BT_PROPERTY_LPP_OFFLOAD_FEATURES: update from Offload HAL" + + " mNumberOfSupportedOffloadedLeCocSockets = " + + mNumberOfSupportedOffloadedLeCocSockets + + " mNumberOfSupportedOffloadedRfcommSockets = " + + mNumberOfSupportedOffloadedRfcommSockets); + } + void onBluetoothReady() { debugLog( "onBluetoothReady, state=" diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterService.java b/android/app/src/com/android/bluetooth/btservice/AdapterService.java index 3a3e3c13a2..3639b86940 100644 --- a/android/app/src/com/android/bluetooth/btservice/AdapterService.java +++ b/android/app/src/com/android/bluetooth/btservice/AdapterService.java @@ -4387,6 +4387,26 @@ public class AdapterService extends Service { service.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); return service.mDatabaseManager.isMicrophonePreferredForCalls(device); } + + @Override + public boolean isLeCocSocketOffloadSupported(AttributionSource source) { + AdapterService service = getService(); + if (service == null) { + return false; + } + service.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); + return service.isLeCocSocketOffloadSupported(); + } + + @Override + public boolean isRfcommSocketOffloadSupported(AttributionSource source) { + AdapterService service = getService(); + if (service == null) { + return false; + } + service.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); + return service.isRfcommSocketOffloadSupported(); + } } /** @@ -7156,4 +7176,26 @@ public class AdapterService extends Service { Log.e(TAG, "Error happened while removing contents: ", e); } } + + /** Get the number of the supported offloaded LE COC sockets. */ + public int getNumberOfSupportedOffloadedLeCocSockets() { + return mAdapterProperties.getNumberOfSupportedOffloadedLeCocSockets(); + } + + /** Check if the offloaded LE COC socket is supported. */ + public boolean isLeCocSocketOffloadSupported() { + int val = getNumberOfSupportedOffloadedLeCocSockets(); + return val > 0; + } + + /** Get the number of the supported offloaded RFCOMM sockets. */ + public int getNumberOfSupportedOffloadedRfcommSockets() { + return mAdapterProperties.getNumberOfSupportedOffloadedRfcommSockets(); + } + + /** Check if the offloaded RFCOMM socket is supported. */ + public boolean isRfcommSocketOffloadSupported() { + int val = getNumberOfSupportedOffloadedRfcommSockets(); + return val > 0; + } } diff --git a/android/app/src/com/android/bluetooth/btservice/BluetoothSocketManagerBinder.java b/android/app/src/com/android/bluetooth/btservice/BluetoothSocketManagerBinder.java index d3062c3707..05b0362f41 100644 --- a/android/app/src/com/android/bluetooth/btservice/BluetoothSocketManagerBinder.java +++ b/android/app/src/com/android/bluetooth/btservice/BluetoothSocketManagerBinder.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; +import android.bluetooth.BluetoothSocketSettings; import android.bluetooth.IBluetoothSocketManager; import android.content.AttributionSource; import android.os.Binder; @@ -86,7 +87,85 @@ class BluetoothSocketManagerBinder extends IBluetoothSocketManager.Stub { Utils.uuidToByteArray(uuid), port, flag, - Binder.getCallingUid())); + Binder.getCallingUid(), + 0, + "", + 0, + 0, + 0)); + } + + @Override + public ParcelFileDescriptor connectSocketwithOffload( + BluetoothDevice device, + int type, + ParcelUuid uuid, + int port, + int flag, + int dataPath, + String socketName, + long hubId, + long endpointId, + int maximumPacketSize) { + + enforceActiveUser(); + + if (!Utils.checkConnectPermissionForPreflight(mService)) { + return null; + } + + if (dataPath != BluetoothSocketSettings.DATA_PATH_NO_OFFLOAD) { + mService.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); + if (type != BluetoothSocket.TYPE_LE || !mService.isLeCocSocketOffloadSupported()) { + throw new IllegalStateException("Unsupported socket type for offload " + type); + } + } + + String brEdrAddress = + Flags.identityAddressNullIfNotKnown() + ? Utils.getBrEdrAddress(device) + : mService.getIdentityAddress(device.getAddress()); + + Log.i( + TAG, + "connectSocketwithOffload: device=" + + device + + ", type=" + + type + + ", uuid=" + + uuid + + ", port=" + + port + + ", from " + + Utils.getUidPidString() + + ", dataPath=" + + dataPath + + ", socketName=" + + socketName + + ", hubId=" + + hubId + + ", endpointId=" + + endpointId + + ", maximumPacketSize=" + + maximumPacketSize); + + return marshalFd( + mService.getNative() + .connectSocket( + Utils.getBytesFromAddress( + type == BluetoothSocket.TYPE_L2CAP_LE + ? device.getAddress() + : brEdrAddress), + type, + Utils.uuidToByteArray(uuid), + port, + flag, + Binder.getCallingUid(), + dataPath, + socketName, + hubId, + endpointId, + maximumPacketSize)); } @Override @@ -120,7 +199,77 @@ class BluetoothSocketManagerBinder extends IBluetoothSocketManager.Stub { Utils.uuidToByteArray(uuid), port, flag, - Binder.getCallingUid())); + Binder.getCallingUid(), + 0, + "", + 0, + 0, + 0)); + } + + @Override + public ParcelFileDescriptor createSocketChannelWithOffload( + int type, + String serviceName, + ParcelUuid uuid, + int port, + int flag, + int dataPath, + String socketName, + long hubId, + long endpointId, + int maximumPacketSize) { + + enforceActiveUser(); + + if (!Utils.checkConnectPermissionForPreflight(mService)) { + return null; + } + + if (dataPath != BluetoothSocketSettings.DATA_PATH_NO_OFFLOAD) { + mService.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); + if (type != BluetoothSocket.TYPE_LE || !mService.isLeCocSocketOffloadSupported()) { + throw new IllegalStateException("Unsupported socket type for offload " + type); + } + } + + Log.i( + TAG, + "createSocketChannelWithOffload: type=" + + type + + ", serviceName=" + + serviceName + + ", uuid=" + + uuid + + ", port=" + + port + + ", from " + + Utils.getUidPidString() + + ", dataPath=" + + dataPath + + ", socketName=" + + socketName + + ", hubId=" + + hubId + + ", endpointId=" + + endpointId + + ", maximumPacketSize=" + + maximumPacketSize); + + return marshalFd( + mService.getNative() + .createSocketChannel( + type, + serviceName, + Utils.uuidToByteArray(uuid), + port, + flag, + Binder.getCallingUid(), + dataPath, + socketName, + hubId, + endpointId, + maximumPacketSize)); } @Override diff --git a/android/app/src/com/android/bluetooth/pbapclient/obex/PbapClientObexClient.java b/android/app/src/com/android/bluetooth/pbapclient/obex/PbapClientObexClient.java index 760b67d900..8099c269c8 100644 --- a/android/app/src/com/android/bluetooth/pbapclient/obex/PbapClientObexClient.java +++ b/android/app/src/com/android/bluetooth/pbapclient/obex/PbapClientObexClient.java @@ -17,6 +17,7 @@ package com.android.bluetooth.pbapclient; import static android.Manifest.permission.BLUETOOTH_CONNECT; +import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; import android.accounts.Account; import android.annotation.RequiresPermission; @@ -468,7 +469,9 @@ class PbapClientObexClient { /* Utilize SDP, if available, to create a socket connection over L2CAP, RFCOMM specified * channel, or RFCOMM default channel. */ - @RequiresPermission(BLUETOOTH_CONNECT) + @RequiresPermission( + allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}, + conditional = true) private PbapClientSocket connectSocket(int transport, int channelOrPsm) { debug( "Connect socket, transport=" diff --git a/android/app/src/com/android/bluetooth/pbapclient/obex/PbapClientSocket.java b/android/app/src/com/android/bluetooth/pbapclient/obex/PbapClientSocket.java index 4a17721a3e..7438a02fe9 100644 --- a/android/app/src/com/android/bluetooth/pbapclient/obex/PbapClientSocket.java +++ b/android/app/src/com/android/bluetooth/pbapclient/obex/PbapClientSocket.java @@ -17,6 +17,7 @@ package com.android.bluetooth.pbapclient; import static android.Manifest.permission.BLUETOOTH_CONNECT; +import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; import android.annotation.RequiresPermission; import android.bluetooth.BluetoothDevice; @@ -101,7 +102,9 @@ public class PbapClientSocket { } /** Invokes the underlying BluetoothSocket#connect(), or does nothing if a socket is injected */ - @RequiresPermission(BLUETOOTH_CONNECT) + @RequiresPermission( + allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}, + conditional = true) public void connect() throws IOException { if (mSocket != null) { mSocket.connect(); diff --git a/framework/api/current.txt b/framework/api/current.txt index f77b9ec329..8ee8e08f2b 100644 --- a/framework/api/current.txt +++ b/framework/api/current.txt @@ -52,7 +52,7 @@ package android.bluetooth { method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothServerSocket listenUsingInsecureRfcommWithServiceRecord(String, java.util.UUID) throws java.io.IOException; method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothServerSocket listenUsingL2capChannel() throws java.io.IOException; method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothServerSocket listenUsingRfcommWithServiceRecord(String, java.util.UUID) throws java.io.IOException; - method @FlaggedApi("com.android.bluetooth.flags.socket_settings_api") @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothServerSocket listenUsingSocketSettings(@NonNull android.bluetooth.BluetoothSocketSettings) throws java.io.IOException; + method @FlaggedApi("com.android.bluetooth.flags.socket_settings_api") @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}, conditional=true) public android.bluetooth.BluetoothServerSocket listenUsingSocketSettings(@NonNull android.bluetooth.BluetoothSocketSettings) throws java.io.IOException; method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean setName(String); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean startDiscovery(); method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean startLeScan(android.bluetooth.BluetoothAdapter.LeScanCallback); @@ -1094,7 +1094,7 @@ package android.bluetooth { public final class BluetoothSocket implements java.io.Closeable { method public void close() throws java.io.IOException; - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void connect() throws java.io.IOException; + method @FlaggedApi("com.android.bluetooth.flags.socket_settings_api") @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}, conditional=true) public void connect() throws java.io.IOException; method public int getConnectionType(); method public java.io.InputStream getInputStream() throws java.io.IOException; method public int getMaxReceivePacketSize(); diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index 06f611fcaa..8368f676b8 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -127,7 +127,9 @@ package android.bluetooth { method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean isAutoOnSupported(); method public boolean isBleScanAlwaysAvailable(); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int isDistanceMeasurementSupported(); + method @FlaggedApi("com.android.bluetooth.flags.socket_settings_api") @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean isLeCocSocketOffloadSupported(); method public boolean isLeEnabled(); + method @FlaggedApi("com.android.bluetooth.flags.socket_settings_api") @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean isRfcommSocketOffloadSupported(); method @NonNull public static String nameForState(int); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int notifyActiveDeviceChangeApplied(@NonNull android.bluetooth.BluetoothDevice); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean registerBluetoothConnectionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothAdapter.BluetoothConnectionCallback); @@ -1152,6 +1154,25 @@ package android.bluetooth { public final class BluetoothSocket implements java.io.Closeable { method @FlaggedApi("com.android.bluetooth.flags.bt_socket_api_l2cap_cid") @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getL2capLocalChannelId() throws java.io.IOException; method @FlaggedApi("com.android.bluetooth.flags.bt_socket_api_l2cap_cid") @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getL2capRemoteChannelId() throws java.io.IOException; + method @FlaggedApi("com.android.bluetooth.flags.socket_settings_api") public long getSocketId() throws java.io.IOException; + } + + @FlaggedApi("com.android.bluetooth.flags.socket_settings_api") public final class BluetoothSocketSettings { + method public int getDataPath(); + method public long getEndpointId(); + method public long getHubId(); + method public int getRequestedMaximumPacketSize(); + method @NonNull public String getSocketName(); + field public static final int DATA_PATH_HARDWARE_OFFLOAD = 1; // 0x1 + field public static final int DATA_PATH_NO_OFFLOAD = 0; // 0x0 + } + + @FlaggedApi("com.android.bluetooth.flags.socket_settings_api") public static final class BluetoothSocketSettings.Builder { + method @NonNull public android.bluetooth.BluetoothSocketSettings.Builder setDataPath(int); + method @NonNull public android.bluetooth.BluetoothSocketSettings.Builder setEndpointId(long); + method @NonNull public android.bluetooth.BluetoothSocketSettings.Builder setHubId(long); + method @NonNull public android.bluetooth.BluetoothSocketSettings.Builder setRequestedMaximumPacketSize(int); + method @NonNull public android.bluetooth.BluetoothSocketSettings.Builder setSocketName(@NonNull String); } public final class BluetoothStatusCodes { diff --git a/framework/java/android/bluetooth/BluetoothAdapter.java b/framework/java/android/bluetooth/BluetoothAdapter.java index 2d26bf3e1d..2ffc178e6e 100644 --- a/framework/java/android/bluetooth/BluetoothAdapter.java +++ b/framework/java/android/bluetooth/BluetoothAdapter.java @@ -4564,6 +4564,14 @@ public final class BluetoothAdapter { * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming connections from a listening * {@link BluetoothServerSocket}. * + * <p>Use {@link BluetoothDevice#createUsingSocketSettings(BluetoothSocketSettings)} to connect + * to this server socket from another Android device using the L2cap protocol/service + * multiplexer(PSM) value or the RFCOMM service UUID as input. + * + * <p>This API requires the {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} permission + * only when {@code settings.getDataPath()} is different from {@link + * BluetoothSocketSettings#DATA_PATH_NO_OFFLOAD}. + * * <p>This API supports {@link BluetoothSocket#TYPE_RFCOMM} and {{@link BluetoothSocket#TYPE_LE} * only, which can be set using {@link BluetoothSocketSettings#setSocketType()}. * <li>For `BluetoothSocket.TYPE_RFCOMM`: The RFCOMM UUID must be provided using {@link @@ -4574,10 +4582,6 @@ public final class BluetoothAdapter { * application exits unexpectedly. The mechanism for disclosing the PSM value to the client * is application-defined. * - * <p>Use {@link BluetoothDevice#createUsingSocketSettings(BluetoothSocketSettings)} to - * connect to this server socket from another Android device using the L2cap - * protocol/service multiplexer(PSM) value or the RFCOMM service UUID as input. - * * @param settings Bluetooth socket settings {@link BluetoothSocketSettings}. * @return a {@link BluetoothServerSocket} * @throws IllegalArgumentException if BluetoothSocket#TYPE_RFCOMM socket is requested with no @@ -4586,7 +4590,9 @@ public final class BluetoothAdapter { * Connection-oriented Channel (CoC). */ @RequiresBluetoothConnectPermission - @RequiresPermission(BLUETOOTH_CONNECT) + @RequiresPermission( + allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}, + conditional = true) @FlaggedApi(Flags.FLAG_SOCKET_SETTINGS_API) public @NonNull BluetoothServerSocket listenUsingSocketSettings( @NonNull BluetoothSocketSettings settings) throws IOException { @@ -4597,24 +4603,64 @@ public final class BluetoothAdapter { if (settings.getRfcommUuid() == null) { throw new IllegalArgumentException("RFCOMM server missing UUID"); } - return createNewRfcommSocketAndRecord( - settings.getRfcommServiceName(), - settings.getRfcommUuid(), - settings.isAuthenticationRequired(), - settings.isEncryptionRequired()); + if (settings.getDataPath() == BluetoothSocketSettings.DATA_PATH_NO_OFFLOAD) { + socket = + new BluetoothServerSocket( + settings.getSocketType(), + settings.isAuthenticationRequired(), + settings.isEncryptionRequired(), + new ParcelUuid(settings.getRfcommUuid())); + } else { + socket = + new BluetoothServerSocket( + settings.getSocketType(), + settings.isAuthenticationRequired(), + settings.isEncryptionRequired(), + -1, + new ParcelUuid(settings.getRfcommUuid()), + false, + false, + settings.getDataPath(), + settings.getSocketName(), + settings.getHubId(), + settings.getEndpointId(), + settings.getRequestedMaximumPacketSize()); + } + socket.setServiceName(settings.getRfcommServiceName()); } else if (type == BluetoothSocket.TYPE_LE) { - socket = - new BluetoothServerSocket( - settings.getSocketType(), - settings.isAuthenticationRequired(), - settings.isEncryptionRequired(), - SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, - false, - false); + if (settings.getDataPath() == BluetoothSocketSettings.DATA_PATH_NO_OFFLOAD) { + socket = + new BluetoothServerSocket( + settings.getSocketType(), + settings.isAuthenticationRequired(), + settings.isEncryptionRequired(), + SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, + false, + false); + } else { + socket = + new BluetoothServerSocket( + settings.getSocketType(), + settings.isAuthenticationRequired(), + settings.isEncryptionRequired(), + SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, + null, + false, + false, + settings.getDataPath(), + settings.getSocketName(), + settings.getHubId(), + settings.getEndpointId(), + settings.getRequestedMaximumPacketSize()); + } } else { - throw new IOException("Error: Invalid socket type: " + type); + throw new IllegalArgumentException("Error: Invalid socket type: " + type); } - int errno = socket.mSocket.bindListen(); + int errno; + errno = + (settings.getDataPath() == BluetoothSocketSettings.DATA_PATH_NO_OFFLOAD) + ? socket.mSocket.bindListen() + : socket.mSocket.bindListenWithOffload(); if (errno != 0) { throw new IOException("Error: " + errno); } @@ -5916,4 +5962,98 @@ public final class BluetoothAdapter { mServiceLock.readLock().unlock(); } } + + /** + * Returns whether LE CoC socket hardware offload is supported. + * + * <p>Bluetooth socket hardware offload allows the system to handle Bluetooth communication on a + * low-power processor, improving efficiency and reducing power consumption. This is achieved by + * providing channel information of an already connected {@link BluetoothSocket} to offload + * endpoints (e.g., offload stacks and applications). The offload stack can then decode received + * packets and pass them to the appropriate offload application without waking up the main + * application processor. This API allows offload endpoints to utilize Bluetooth sockets while + * the host stack retains control over the connection. + * + * <p>To configure a socket for hardware offload, use the following {@link + * BluetoothSocketSettings} methods: + * + * <ul> + * <li>{@link BluetoothSocketSettings#setDataPath(int)} with {@link + * BluetoothSocketSettings#DATA_PATH_HARDWARE_OFFLOAD} + * <li>{@link BluetoothSocketSettings#setHubId(long)} + * <li>{@link BluetoothSocketSettings#setEndpointId(long)} + * </ul> + * + * <p>This functionality is provided as a System API because only OEM specific system + * applications can be offloaded as endpoints in the low-power processor. + * + * @return {@code true} if LE CoC socket hardware offload is supported, {@code false} otherwise. + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_SOCKET_SETTINGS_API) + @RequiresPermission(BLUETOOTH_PRIVILEGED) + public boolean isLeCocSocketOffloadSupported() { + if (!isEnabled()) { + return false; + } + mServiceLock.readLock().lock(); + try { + if (mService != null) { + return mService.isLeCocSocketOffloadSupported(mAttributionSource); + } + } catch (RemoteException e) { + Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); + } finally { + mServiceLock.readLock().unlock(); + } + return false; + } + + /** + * Returns whether RFCOMM socket hardware offload is supported. + * + * <p>Bluetooth socket hardware offload allows the system to handle Bluetooth communication on a + * low-power processor, improving efficiency and reducing power consumption. This is achieved by + * providing channel information of an already connected {@link BluetoothSocket} to offload + * endpoints (e.g., offload stacks and applications). The offload stack can then decode received + * packets and pass them to the appropriate offload application without waking up the main + * application processor. This API allows offload endpoints to utilize Bluetooth sockets while + * the host stack retains control over the connection. + * + * <p>To configure a socket for hardware offload, use the following {@link + * BluetoothSocketSettings} methods: + * + * <ul> + * <li>{@link BluetoothSocketSettings#setDataPath(int)} with {@link + * BluetoothSocketSettings#DATA_PATH_HARDWARE_OFFLOAD} + * <li>{@link BluetoothSocketSettings#setHubId(long)} + * <li>{@link BluetoothSocketSettings#setEndpointId(long)} + * </ul> + * + * <p>This functionality is provided as a System API because only OEM specific system + * applications can be offloaded as endpoints in the low-power processor. + * + * @return {@code true} if RFCOMM socket hardware offload is supported, {@code false} otherwise. + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_SOCKET_SETTINGS_API) + @RequiresPermission(BLUETOOTH_PRIVILEGED) + public boolean isRfcommSocketOffloadSupported() { + if (!isEnabled()) { + return false; + } + mServiceLock.readLock().lock(); + try { + if (mService != null) { + return mService.isRfcommSocketOffloadSupported(mAttributionSource); + } + } catch (RemoteException e) { + Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); + } finally { + mServiceLock.readLock().unlock(); + } + return false; + } } diff --git a/framework/java/android/bluetooth/BluetoothDevice.java b/framework/java/android/bluetooth/BluetoothDevice.java index d200e6a149..795e911501 100644 --- a/framework/java/android/bluetooth/BluetoothDevice.java +++ b/framework/java/android/bluetooth/BluetoothDevice.java @@ -3366,13 +3366,30 @@ public final class BluetoothDevice implements Parcelable, Attributable { throw new IllegalArgumentException("Invalid PSM/Channel value: " + psm); } } - return new BluetoothSocket( - this, - settings.getSocketType(), - settings.isAuthenticationRequired(), - settings.isEncryptionRequired(), - psm, - uuid); + if (settings.getDataPath() == BluetoothSocketSettings.DATA_PATH_NO_OFFLOAD) { + return new BluetoothSocket( + this, + settings.getSocketType(), + settings.isAuthenticationRequired(), + settings.isEncryptionRequired(), + psm, + uuid); + } else { + return new BluetoothSocket( + this, + settings.getSocketType(), + settings.isAuthenticationRequired(), + settings.isEncryptionRequired(), + psm, + uuid, + false, + false, + settings.getDataPath(), + settings.getSocketName(), + settings.getHubId(), + settings.getEndpointId(), + settings.getRequestedMaximumPacketSize()); + } } /** diff --git a/framework/java/android/bluetooth/BluetoothServerSocket.java b/framework/java/android/bluetooth/BluetoothServerSocket.java index b1a0188626..f20a03d1cc 100644 --- a/framework/java/android/bluetooth/BluetoothServerSocket.java +++ b/framework/java/android/bluetooth/BluetoothServerSocket.java @@ -16,6 +16,7 @@ package android.bluetooth; +import android.annotation.NonNull; import android.annotation.SuppressLint; import android.compat.annotation.UnsupportedAppUsage; import android.os.Handler; @@ -154,6 +155,60 @@ public final class BluetoothServerSocket implements Closeable { } /** + * Construct a socket for incoming connections. + * + * @param type type of socket + * @param auth require the remote device to be authenticated + * @param encrypt require the connection to be encrypted + * @param port remote port + * @param uuid uuid + * @param pitm enforce person-in-the-middle protection for authentication. + * @param min16DigitPin enforce a minimum length of 16 digits for a sec mode 2 connection + * @param dataPath data path used for this socket + * @param socketName user-friendly name for this socket + * @param hubId ID of the hub to which the end point belongs + * @param endpointId ID of the endpoint within the hub that is associated with this socket + * @param maximumPacketSize The maximum size (in bytes) of a single data packet + * @throws IOException On error, for example Bluetooth not available, or insufficient privileges + */ + /*package*/ BluetoothServerSocket( + int type, + boolean auth, + boolean encrypt, + int port, + ParcelUuid uuid, + boolean pitm, + boolean min16DigitPin, + int dataPath, + @NonNull String socketName, + long hubId, + long endpointId, + int maximumPacketSize) + throws IOException { + mSocketCreationTimeMillis = System.currentTimeMillis(); + mType = type; + mChannel = port; + mSocket = + new BluetoothSocket( + type, + auth, + encrypt, + port, + uuid, + pitm, + min16DigitPin, + dataPath, + socketName, + hubId, + endpointId, + maximumPacketSize); + if (port == BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) { + mSocket.setExcludeSdp(true); + } + mSocketCreationLatencyMillis = System.currentTimeMillis() - mSocketCreationTimeMillis; + } + + /** * Block until a connection is established. * * <p>Returns a connected {@link BluetoothSocket} on successful connection. diff --git a/framework/java/android/bluetooth/BluetoothSocket.java b/framework/java/android/bluetooth/BluetoothSocket.java index 901407c00b..97bdf595a0 100644 --- a/framework/java/android/bluetooth/BluetoothSocket.java +++ b/framework/java/android/bluetooth/BluetoothSocket.java @@ -157,6 +157,8 @@ public final class BluetoothSocket implements Closeable { /*package*/ static final int SEC_FLAG_AUTH_PITM = 1 << 3; /*package*/ static final int SEC_FLAG_AUTH_16_DIGIT = 1 << 4; + /*package*/ static final String DEFAULT_SOCKET_NAME = "default_name"; + private final int mType; /* one of TYPE_RFCOMM etc */ private BluetoothDevice mDevice; /* remote device */ private String mAddress; /* remote address */ @@ -165,6 +167,11 @@ public final class BluetoothSocket implements Closeable { private final BluetoothInputStream mInputStream; private final BluetoothOutputStream mOutputStream; private final ParcelUuid mUuid; + private final int mDataPath; + private final String mSocketName; + private final long mHubId; + private final long mEndpointId; + private final int mMaximumPacketSize; /** when true no SPP SDP record will be created */ private boolean mExcludeSdp = false; @@ -184,12 +191,15 @@ public final class BluetoothSocket implements Closeable { @UnsupportedAppUsage private int mPort; /* RFCOMM channel or L2CAP psm */ private String mServiceName; - private static final int SOCK_SIGNAL_SIZE = 36; + private static final int SOCK_CONNECTION_SIGNAL_SIZE = 44; + private static final long INVALID_SOCKET_ID = 0; + private static final int SOCK_ACCEPT_SIGNAL_SIZE = 4; private ByteBuffer mL2capBuffer = null; private int mMaxTxPacketSize = 0; // The l2cap maximum packet size supported by the peer. private int mMaxRxPacketSize = 0; // The l2cap maximum packet size that can be received. private ParcelUuid mConnectionUuid; + private long mSocketId; // Socket ID in connected state. private long mSocketCreationTimeNanos = 0; private long mSocketCreationLatencyNanos = 0; @@ -245,6 +255,41 @@ public final class BluetoothSocket implements Closeable { boolean pitm, boolean min16DigitPin) throws IOException { + this(type, auth, encrypt, port, uuid, pitm, min16DigitPin, 0, DEFAULT_SOCKET_NAME, 0, 0, 0); + } + + /** + * Construct a BluetoothSocket. + * + * @param type type of socket + * @param auth require the remote device to be authenticated + * @param encrypt require the connection to be encrypted + * @param port remote port + * @param uuid SDP uuid + * @param pitm enforce person-in-the-middle protection. + * @param min16DigitPin enforce a minimum length of 16 digits for a sec mode 2 connection + * @param dataPath data path used for this socket + * @param socketName user-friendly name for this socket + * @param hubId ID of the hub to which the end point belongs + * @param endpointId ID of the endpoint within the hub that is associated with this socket + * @param maximumPacketSize The maximum size (in bytes) of a single data packet + * @throws IOException On error, for example Bluetooth not available, or insufficient privileges + */ + @RequiresPermission(allOf = {BLUETOOTH_CONNECT, LOCAL_MAC_ADDRESS}) + /*package*/ BluetoothSocket( + int type, + boolean auth, + boolean encrypt, + int port, + ParcelUuid uuid, + boolean pitm, + boolean min16DigitPin, + int dataPath, + @NonNull String socketName, + long hubId, + long endpointId, + int maximumPacketSize) + throws IOException { if (VDBG) Log.d(TAG, "Creating new BluetoothSocket of type: " + type); mSocketCreationTimeNanos = System.nanoTime(); if (type == BluetoothSocket.TYPE_RFCOMM @@ -267,6 +312,11 @@ public final class BluetoothSocket implements Closeable { mPort = port; // this constructor to be called only from BluetoothServerSocket mDevice = null; + mDataPath = dataPath; + mSocketName = socketName; + mHubId = hubId; + mEndpointId = endpointId; + mMaximumPacketSize = maximumPacketSize; mSocketState = SocketState.INIT; @@ -322,6 +372,55 @@ public final class BluetoothSocket implements Closeable { boolean pitm, boolean min16DigitPin) throws IOException { + this( + device, + type, + auth, + encrypt, + port, + uuid, + pitm, + min16DigitPin, + 0, + DEFAULT_SOCKET_NAME, + 0, + 0, + 0); + } + + /** + * Construct a BluetoothSocket. + * + * @param device remote device that this socket can connect to + * @param type type of socket + * @param auth require the remote device to be authenticated + * @param encrypt require the connection to be encrypted + * @param port remote port + * @param uuid SDP uuid + * @param pitm enforce person-in-the-middle protection. + * @param min16DigitPin enforce a minimum length of 16 digits for a sec mode 2 connection + * @param dataPath data path used for this socket + * @param socketName user-friendly name for this socket + * @param hubId ID of the hub to which the end point belongs + * @param endpointId ID of the endpoint within the hub that is associated with this socket + * @param maximumPacketSize The maximum size (in bytes) of a single data packet + * @throws IOException On error, for example Bluetooth not available, or insufficient privileges + */ + /*package*/ BluetoothSocket( + @NonNull BluetoothDevice device, + int type, + boolean auth, + boolean encrypt, + int port, + ParcelUuid uuid, + boolean pitm, + boolean min16DigitPin, + int dataPath, + @NonNull String socketName, + long hubId, + long endpointId, + int maximumPacketSize) + throws IOException { if (VDBG) Log.d(TAG, "Creating new BluetoothSocket of type: " + type); mSocketCreationTimeNanos = System.nanoTime(); if (type == BluetoothSocket.TYPE_RFCOMM @@ -343,6 +442,11 @@ public final class BluetoothSocket implements Closeable { mEncrypt = encrypt; mDevice = device; mPort = port; + mDataPath = dataPath; + mSocketName = socketName; + mHubId = hubId; + mEndpointId = endpointId; + mMaximumPacketSize = maximumPacketSize; mSocketState = SocketState.INIT; @@ -393,11 +497,17 @@ public final class BluetoothSocket implements Closeable { mMaxRxPacketSize = s.mMaxRxPacketSize; mMaxTxPacketSize = s.mMaxTxPacketSize; mConnectionUuid = s.mConnectionUuid; + mSocketId = s.mSocketId; mServiceName = s.mServiceName; mExcludeSdp = s.mExcludeSdp; mAuthPitm = s.mAuthPitm; mMin16DigitPin = s.mMin16DigitPin; + mDataPath = s.mDataPath; + mSocketName = s.mSocketName; + mHubId = s.mHubId; + mEndpointId = s.mEndpointId; + mMaximumPacketSize = s.mMaximumPacketSize; mSocketCreationTimeNanos = s.mSocketCreationTimeNanos; mSocketCreationLatencyNanos = s.mSocketCreationLatencyNanos; } @@ -524,11 +634,17 @@ public final class BluetoothSocket implements Closeable { * * <p>{@link #close} can be used to abort this call from another thread. * + * <p>Requires the {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} permission only when + * {@code mDataPath} is different from {@link BluetoothSocketSettings#DATA_PATH_NO_OFFLOAD}. + * * @throws BluetoothSocketException in case of failure, with the corresponding error code. * @throws IOException for other errors (eg: InputStream read failures etc.). */ + @FlaggedApi(Flags.FLAG_SOCKET_SETTINGS_API) @RequiresBluetoothConnectPermission - @RequiresPermission(BLUETOOTH_CONNECT) + @RequiresPermission( + allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}, + conditional = true) public void connect() throws IOException { IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService(); long socketConnectionTimeNanos = System.nanoTime(); @@ -547,7 +663,30 @@ public final class BluetoothSocket implements Closeable { if (socketManager == null) { throw new BluetoothSocketException(BluetoothSocketException.SOCKET_MANAGER_FAILURE); } - mPfd = socketManager.connectSocket(mDevice, mType, mUuid, mPort, getSecurityFlags()); + if (Flags.socketSettingsApi()) { + if (mDataPath == BluetoothSocketSettings.DATA_PATH_NO_OFFLOAD) { + mPfd = + socketManager.connectSocket( + mDevice, mType, mUuid, mPort, getSecurityFlags()); + } else { + mPfd = + socketManager.connectSocketwithOffload( + mDevice, + mType, + mUuid, + mPort, + getSecurityFlags(), + mDataPath, + mSocketName, + mHubId, + mEndpointId, + mMaximumPacketSize); + } + } else { + mPfd = + socketManager.connectSocket( + mDevice, mType, mUuid, mPort, getSecurityFlags()); + } synchronized (this) { Log.i(TAG, "connect(), SocketState: " + mSocketState + ", mPfd: " + mPfd); if (mSocketState == SocketState.CLOSED) { @@ -675,7 +814,7 @@ public final class BluetoothSocket implements Closeable { if (DBG) Log.d(TAG, "bindListen(): channel=" + channel + ", mPort=" + mPort); if (mPort <= -1) { mPort = channel; - } // else ASSERT(mPort == channel) + } ret = 0; } catch (IOException e) { if (mPfd != null) { @@ -692,6 +831,98 @@ public final class BluetoothSocket implements Closeable { return ret; } + /** + * Currently returns unix errno instead of throwing IOException, so that BluetoothAdapter can + * check the error code for EADDRINUSE + */ + @RequiresBluetoothConnectPermission + @RequiresPermission( + allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}, + conditional = true) + /*package*/ int bindListenWithOffload() { + int ret; + if (mSocketState == SocketState.CLOSED) return EBADFD; + IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService(); + if (bluetoothProxy == null) { + Log.e(TAG, "bindListenWithOffload() fail, reason: bluetooth is off"); + return -1; + } + try { + if (DBG) Log.d(TAG, "bindListenWithOffload(): mPort=" + mPort + ", mType=" + mType); + IBluetoothSocketManager socketManager = bluetoothProxy.getSocketManager(); + if (socketManager == null) { + Log.e(TAG, "bindListenWithOffload() bt get socket manager failed"); + return -1; + } + mPfd = + socketManager.createSocketChannelWithOffload( + mType, + mServiceName, + mUuid, + mPort, + getSecurityFlags(), + mDataPath, + mSocketName, + mHubId, + mEndpointId, + mMaximumPacketSize); + } catch (RemoteException e) { + Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); + return -1; + } + + // read out port number + try { + synchronized (this) { + if (DBG) { + Log.d( + TAG, + "bindListenWithOffload(), SocketState: " + + mSocketState + + ", mPfd: " + + mPfd); + } + if (mSocketState != SocketState.INIT) return EBADFD; + if (mPfd == null) return -1; + FileDescriptor fd = mPfd.getFileDescriptor(); + if (fd == null) { + Log.e(TAG, "bindListenWithOffload(), null file descriptor"); + return -1; + } + + if (DBG) Log.d(TAG, "bindListenWithOffload(), Create LocalSocket"); + mSocket = new LocalSocket(fd); + if (DBG) Log.d(TAG, "bindListenWithOffload(), new LocalSocket.getInputStream()"); + mSocketIS = mSocket.getInputStream(); + mSocketOS = mSocket.getOutputStream(); + } + if (DBG) Log.d(TAG, "bindListenWithOffload(), readInt mSocketIS: " + mSocketIS); + int channel = readInt(mSocketIS); + synchronized (this) { + if (mSocketState == SocketState.INIT) { + mSocketState = SocketState.LISTENING; + } + } + if (DBG) Log.d(TAG, "bindListenWithOffload(): channel=" + channel + ", mPort=" + mPort); + if (mPort <= -1) { + mPort = channel; + } + ret = 0; + } catch (IOException e) { + if (mPfd != null) { + try { + mPfd.close(); + } catch (IOException e1) { + Log.e(TAG, "bindListenWithOffload, close mPfd: " + e1); + } + mPfd = null; + } + Log.e(TAG, "bindListenWithOffload, fail to get port number, exception: " + e); + return -1; + } + return ret; + } + /*package*/ BluetoothSocket accept(int timeout) throws IOException { BluetoothSocket acceptedSocket; if (mSocketState != SocketState.LISTENING) { @@ -701,7 +932,13 @@ public final class BluetoothSocket implements Closeable { if (timeout > 0) { mSocket.setSoTimeout(timeout); } - String RemoteAddr = waitSocketSignal(mSocketIS); + sendSocketAcceptSignal(mSocketOS, true); + String RemoteAddr; + try { + RemoteAddr = waitSocketSignal(mSocketIS); + } finally { + sendSocketAcceptSignal(mSocketOS, false); + } if (timeout > 0) { mSocket.setSoTimeout(0); } @@ -836,6 +1073,7 @@ public final class BluetoothSocket implements Closeable { mPfd = null; } mConnectionUuid = null; + mSocketId = INVALID_SOCKET_ID; } } } @@ -1016,6 +1254,25 @@ public final class BluetoothSocket implements Closeable { return cid; } + /** + * Returns the socket ID assigned to the open connection on this BluetoothSocket. This socket ID + * is a unique identifier for the socket. It is valid only while the socket is connected. + * + * @return The socket ID in connected state. + * @throws BluetoothSocketException If the socket is not connected or an error occurs while + * retrieving the socket ID. + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_SOCKET_SETTINGS_API) + @RequiresNoPermission + public long getSocketId() throws IOException { + if (mSocketState != SocketState.CONNECTED || mSocketId == INVALID_SOCKET_ID) { + throw new BluetoothSocketException(BluetoothSocketException.SOCKET_CLOSED); + } + return mSocketId; + } + /** @hide */ @RequiresNoPermission public ParcelFileDescriptor getParcelFileDescriptor() { @@ -1034,17 +1291,53 @@ public final class BluetoothSocket implements Closeable { addr[5]); } + /** + * Sends a socket accept signal to the host stack. + * + * <p>This method is used to notify the host stack whether the host application is actively + * accepting a new connection or not. It sends a signal containing the acceptance status to the + * output stream associated with the socket. + * + * <p>This method is only effective when the data path is not {@link + * BluetoothSocketSettings#DATA_PATH_NO_OFFLOAD}. + * + * @param os The output stream to write the signal to. + * @param isAccepting {@code true} if the socket connection is being accepted, {@code false} + * otherwise. + * @throws IOException If an I/O error occurs while writing to the output stream. + * @hide + */ + private void sendSocketAcceptSignal(OutputStream os, boolean isAccepting) throws IOException { + if (Flags.socketSettingsApi()) { + if (mDataPath == BluetoothSocketSettings.DATA_PATH_NO_OFFLOAD) { + return; + } + Log.d(TAG, "sendSocketAcceptSignal" + " isAccepting " + isAccepting); + byte[] sig = new byte[SOCK_ACCEPT_SIGNAL_SIZE]; + ByteBuffer bb = ByteBuffer.wrap(sig); + bb.order(ByteOrder.nativeOrder()); + bb.putShort((short) SOCK_ACCEPT_SIGNAL_SIZE); + bb.putShort((short) (isAccepting ? 1 : 0)); + os.write(sig, 0, SOCK_ACCEPT_SIGNAL_SIZE); + } + } + private String waitSocketSignal(InputStream is) throws IOException { - byte[] sig = new byte[SOCK_SIGNAL_SIZE]; + byte[] sig = new byte[SOCK_CONNECTION_SIGNAL_SIZE]; int ret = readAll(is, sig); if (VDBG) { - Log.d(TAG, "waitSocketSignal read " + SOCK_SIGNAL_SIZE + " bytes signal ret: " + ret); + Log.d( + TAG, + "waitSocketSignal read " + + SOCK_CONNECTION_SIGNAL_SIZE + + " bytes signal ret: " + + ret); } ByteBuffer bb = ByteBuffer.wrap(sig); /* the struct in native is decorated with __attribute__((packed)), hence this is possible */ bb.order(ByteOrder.nativeOrder()); int size = bb.getShort(); - if (size != SOCK_SIGNAL_SIZE) { + if (size != SOCK_CONNECTION_SIGNAL_SIZE) { throw new IOException("Connection failure, wrong signal size: " + size); } byte[] addr = new byte[6]; @@ -1056,6 +1349,7 @@ public final class BluetoothSocket implements Closeable { long uuidLsb = bb.getLong(); long uuidMsb = bb.getLong(); mConnectionUuid = new ParcelUuid(new UUID(uuidMsb, uuidLsb)); + mSocketId = bb.getLong(); String RemoteAddr = convertAddr(addr); if (VDBG) { Log.d( @@ -1073,7 +1367,9 @@ public final class BluetoothSocket implements Closeable { + " MaxTxPktSize: " + mMaxTxPacketSize + " mConnectionUuid: " - + mConnectionUuid.toString()); + + mConnectionUuid.toString() + + " mSocketId: " + + mSocketId); } if (status != 0) { throw new IOException("Connection failure, status: " + status); diff --git a/framework/java/android/bluetooth/BluetoothSocketException.java b/framework/java/android/bluetooth/BluetoothSocketException.java index aba95a375a..4bf84ad982 100644 --- a/framework/java/android/bluetooth/BluetoothSocketException.java +++ b/framework/java/android/bluetooth/BluetoothSocketException.java @@ -31,8 +31,8 @@ import java.lang.annotation.Retention; /** * Thrown when an error occurs during a Bluetooth Socket related exception. * - * <p>This is currently only intended to be thrown for a failure during {@link - * BluetoothSocket#connect()} operation. + * <p>This is currently intended to be thrown for a failure during {@link BluetoothSocket} + * operations. */ public class BluetoothSocketException extends IOException { @@ -70,8 +70,8 @@ public class BluetoothSocketException extends IOException { public static final int UNSPECIFIED = 0; /** - * Error code during connect when socket connection fails for unknown reason during L2CAP - * connection. + * Error code returned by {@link BluetoothSocket} during a L2CAP-related socket operation that + * failed for an unknown reason. */ public static final int L2CAP_UNKNOWN = 1; @@ -141,13 +141,22 @@ public class BluetoothSocketException extends IOException { /** Error code during connect when L2CAP connection timeout. */ public static final int L2CAP_TIMEOUT = 14; - /** Error code during connect when Bluetooth is off and socket connection is triggered. */ + /** + * Error code returned by {@link BluetoothSocket} during a socket operation that failed because + * Bluetooth is turned off. + */ public static final int BLUETOOTH_OFF_FAILURE = 15; - /** Error code during connect when socket manager is not available. */ + /** + * Error code returned by {@link BluetoothSocket} during a socket operation that failed because + * socket manager is not available. + */ public static final int SOCKET_MANAGER_FAILURE = 16; - /** Error code during connect when socket is closed. */ + /** + * Error code returned by {@link BluetoothSocket} during a socket operation that failed because + * the socket has been closed. + */ public static final int SOCKET_CLOSED = 17; /** Error code during connect for generic socket connection failures. */ diff --git a/framework/java/android/bluetooth/BluetoothSocketSettings.java b/framework/java/android/bluetooth/BluetoothSocketSettings.java index 6fa8b0dc8b..b7706d50fe 100644 --- a/framework/java/android/bluetooth/BluetoothSocketSettings.java +++ b/framework/java/android/bluetooth/BluetoothSocketSettings.java @@ -18,14 +18,21 @@ package android.bluetooth; import static android.bluetooth.BluetoothSocket.SocketType; +import static java.util.Objects.requireNonNull; + import android.annotation.FlaggedApi; +import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresNoPermission; +import android.annotation.SystemApi; import com.android.bluetooth.flags.Flags; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.nio.charset.StandardCharsets; import java.util.UUID; /** @@ -42,6 +49,64 @@ public final class BluetoothSocketSettings { private static final int L2CAP_PSM_UNSPECIFIED = -1; + /** + * Annotation to define the data path used for Bluetooth socket communication. This determines + * how data flows between the application and the Bluetooth controller. + * + * @hide + */ + @IntDef( + prefix = {"DATA_PATH_"}, + value = {DATA_PATH_NO_OFFLOAD, DATA_PATH_HARDWARE_OFFLOAD}) + @Retention(RetentionPolicy.SOURCE) + public @interface SocketDataPath {} + + /** + * Non-offload data path where the application's socket data is processed by the main Bluetooth + * stack. + * + * @hide + */ + @SystemApi public static final int DATA_PATH_NO_OFFLOAD = 0; + + /** + * Hardware offload data path where the application's socket data is processed by a offloaded + * application running on the low-power processor. + * + * <p>Using this data path requires the {@code BLUETOOTH_PRIVILEGED} permission, which will be + * checked when a socket connection or channel is created. + * + * @hide + */ + @SystemApi public static final int DATA_PATH_HARDWARE_OFFLOAD = 1; + + /** + * Maximum size (in bytes) of a data packet that can be received from the endpoint when using + * {@link #DATA_PATH_HARDWARE_OFFLOAD}. + */ + @SystemApi private static final int HARDWARE_OFFLOAD_PACKET_MAX_SIZE = 65535; + + /** + * Maximum length (in bytes) of a socket name when using {@link #DATA_PATH_HARDWARE_OFFLOAD}. + */ + @SystemApi private static final int HARDWARE_OFFLOAD_SOCKET_NAME_MAX_LENGTH = 127; + + /** + * Constant representing an invalid hub ID. This value indicates that a hub ID has not been + * assigned or is not valid. + * + * @hide + */ + private static final long INVALID_HUB_ID = 0; + + /** + * Constant representing an invalid hub endpoint ID. This value indicates that an endpoint ID + * has not been assigned or is not valid. + * + * @hide + */ + private static final long INVALID_ENDPOINT_ID = 0; + /** Type of the Bluetooth socket */ @SocketType private int mSocketType; @@ -61,6 +126,50 @@ public final class BluetoothSocketSettings { private UUID mRfcommUuid; /** + * Specifies the data path used for this socket, influencing how data is transmitted and + * processed. Select the appropriate data path based on performance and power consumption + * requirements: + * + * <ul> + * <li>{@link #DATA_PATH_NO_OFFLOAD}: Suitable for applications that require the full + * processing capabilities of the main Bluetooth stack. + * <li>{@link #DATA_PATH_HARDWARE_OFFLOAD}: Optimized for lower power consumption by utilizing + * an offloaded application running on a dedicated low-power processor. + * </ul> + */ + @SocketDataPath private int mDataPath; + + /** + * A user-friendly name for this socket, primarily for debugging and logging. This name should + * be descriptive and can help identify the socket during development and troubleshooting. + * + * <p>When using {@link #DATA_PATH_HARDWARE_OFFLOAD}, this name is also passed to the offloaded + * application running on the low-power processor. This allows the offloaded application to + * identify and manage the socket. + */ + private String mSocketName; + + /** + * When using {@link #DATA_PATH_HARDWARE_OFFLOAD}, this identifies the hub hosting the endpoint. + * + * <p>Hub represents a logical/physical representation of multiple endpoints. A pair of {@code + * mHubId} and {@code mEndpointId} uniquely identifies the endpoint globally. + */ + private long mHubId; + + /** + * When using {@link #DATA_PATH_HARDWARE_OFFLOAD}, this identifies the specific endpoint within + * the hub that is associated with this socket. + */ + private long mEndpointId; + + /** + * The maximum size (in bytes) of a single data packet that can be received from the endpoint + * when using {@link #DATA_PATH_HARDWARE_OFFLOAD}. + */ + private int mMaximumPacketSize; + + /** * Returns the type of the Bluetooth socket. * * <p>Defaults to {@code BluetoothSocket#TYPE_RFCOMM}. @@ -120,35 +229,137 @@ public final class BluetoothSocketSettings { } /** + * Returns the data path used for this socket. The data path determines how data is routed and + * processed for the socket connection. + * + * <p>Defaults to {@link #DATA_PATH_NO_OFFLOAD}. + * + * <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only + * available through the System API. + * + * @hide + */ + @SystemApi + @RequiresNoPermission + public @SocketDataPath int getDataPath() { + return mDataPath; + } + + /** + * Returns the user-friendly name assigned to this socket. This name is primarily used for + * debugging and logging purposes. + * + * <p>When using {@link #DATA_PATH_HARDWARE_OFFLOAD}, this name is also passed to the offloaded + * application running on the low-power processor. + * + * <p>Defaults to {@code null} if no name was explicitly set. + * + * <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only + * available through the System API. + * + * @hide + */ + @SystemApi + @NonNull + @RequiresNoPermission + public String getSocketName() { + return mSocketName; + } + + /** + * Returns the ID of the hub associated with this socket when using {@link + * #DATA_PATH_HARDWARE_OFFLOAD}. + * + * <p>If the data path is not set to {@link #DATA_PATH_HARDWARE_OFFLOAD}, this method returns + * {@link #INVALID_HUB_ID}. + * + * <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only + * available through the System API. + * + * @hide + */ + @SystemApi + @RequiresNoPermission + public long getHubId() { + if (mDataPath != DATA_PATH_HARDWARE_OFFLOAD) { + return INVALID_HUB_ID; + } + return mHubId; + } + + /** + * Returns the ID of the endpoint within the hub associated with this socket when using {@link + * #DATA_PATH_HARDWARE_OFFLOAD}. An endpoint represents a specific point of communication within + * the hub. + * + * <p>If the data path is not set to {@link #DATA_PATH_HARDWARE_OFFLOAD}, this method returns + * {@link #INVALID_ENDPOINT_ID}. + * + * <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only + * available through the System API. + * + * @hide + */ + @SystemApi + @RequiresNoPermission + public long getEndpointId() { + if (mDataPath != DATA_PATH_HARDWARE_OFFLOAD) { + return INVALID_ENDPOINT_ID; + } + return mEndpointId; + } + + /** + * Returns the requested maximum size (in bytes) of a data packet that can be received from the + * endpoint associated with this socket when using {@link #DATA_PATH_HARDWARE_OFFLOAD}. + * + * <p>Defaults to {@link #HARDWARE_OFFLOAD_PACKET_MAX_SIZE}. + * + * <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only + * available through the System API. + * + * @hide + */ + @SystemApi + @RequiresNoPermission + public int getRequestedMaximumPacketSize() { + return mMaximumPacketSize; + } + + /** * Returns a {@link String} that describes each BluetoothSocketSettings parameter current value. */ @Override public String toString() { + StringBuilder builder = new StringBuilder("BluetoothSocketSettings{"); + builder.append("mSocketType=") + .append(mSocketType) + .append(", mEncryptionRequired=") + .append(mEncryptionRequired) + .append(", mAuthenticationRequired=") + .append(mAuthenticationRequired); if (mSocketType == BluetoothSocket.TYPE_RFCOMM) { - return "BluetoothSocketSettings{" - + "mSocketType=" - + mSocketType - + ", mEncryptionRequired=" - + mEncryptionRequired - + ", mAuthenticationRequired=" - + mAuthenticationRequired - + ", mRfcommServiceName=" - + mRfcommServiceName - + ", mRfcommUuid=" - + mRfcommUuid - + "}"; + builder.append(", mRfcommServiceName=") + .append(mRfcommServiceName) + .append(", mRfcommUuid=") + .append(mRfcommUuid); } else { - return "BluetoothSocketSettings{" - + "mSocketType=" - + mSocketType - + ", mL2capPsm=" - + mL2capPsm - + ", mEncryptionRequired=" - + mEncryptionRequired - + ", mAuthenticationRequired=" - + mAuthenticationRequired - + "}"; + builder.append(", mL2capPsm=").append(mL2capPsm); + } + if (mDataPath == DATA_PATH_HARDWARE_OFFLOAD) { + builder.append(", mDataPath=") + .append(mDataPath) + .append(", mSocketName=") + .append(mSocketName) + .append(", mHubId=") + .append(mHubId) + .append(", mEndpointId=") + .append(mEndpointId) + .append(", mMaximumPacketSize=") + .append(mMaximumPacketSize); } + builder.append("}"); + return builder.toString(); } private BluetoothSocketSettings( @@ -157,13 +368,23 @@ public final class BluetoothSocketSettings { boolean encryptionRequired, boolean authenticationRequired, String rfcommServiceName, - UUID rfcommUuid) { + UUID rfcommUuid, + int dataPath, + String socketName, + long hubId, + long endpointId, + int maximumPacketSize) { mSocketType = socketType; mL2capPsm = l2capPsm; mEncryptionRequired = encryptionRequired; mAuthenticationRequired = authenticationRequired; mRfcommUuid = rfcommUuid; mRfcommServiceName = rfcommServiceName; + mDataPath = dataPath; + mSocketName = socketName; + mHubId = hubId; + mEndpointId = endpointId; + mMaximumPacketSize = maximumPacketSize; } /** Builder for {@link BluetoothSocketSettings}. */ @@ -175,6 +396,11 @@ public final class BluetoothSocketSettings { private boolean mAuthenticationRequired = false; private String mRfcommServiceName = null; private UUID mRfcommUuid = null; + private int mDataPath = DATA_PATH_NO_OFFLOAD; + private String mSocketName = BluetoothSocket.DEFAULT_SOCKET_NAME; + private long mHubId = INVALID_HUB_ID; + private long mEndpointId = INVALID_ENDPOINT_ID; + private int mMaximumPacketSize = HARDWARE_OFFLOAD_PACKET_MAX_SIZE; public Builder() {} @@ -303,6 +529,126 @@ public final class BluetoothSocketSettings { } /** + * Sets the data path for this socket. The data path determines how data is routed and + * processed for the socket connection. + * + * <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only + * available through the System API. + * + * @param dataPath The desired data path for the socket. + * @return This Builder object to allow for method chaining. + * @throws IllegalArgumentException If {@code dataPath} is an invalid value. + * @hide + */ + @SystemApi + @NonNull + @RequiresNoPermission + public Builder setDataPath(@SocketDataPath int dataPath) { + if (dataPath < DATA_PATH_NO_OFFLOAD || dataPath > DATA_PATH_HARDWARE_OFFLOAD) { + throw new IllegalArgumentException("Invalid dataPath - " + dataPath); + } + mDataPath = dataPath; + return this; + } + + /** + * Sets a user-friendly name for this socket. This name is primarily used for debugging and + * logging purposes. + * + * <p>When using {@link #DATA_PATH_HARDWARE_OFFLOAD}, this name is also passed to the + * offloaded application running on low-power processor. + * + * <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only + * available through the System API. + * + * @param socketName The desired name for the socket. This should be a descriptive name that + * helps identify the socket during development and troubleshooting. The socket name + * cannot exceed {@link #HARDWARE_OFFLOAD_SOCKET_NAME_MAX_LENGTH} bytes in length when + * encoded in UTF-8. + * @return This Builder object to allow for method chaining. + * @throws IllegalArgumentException if the provided `socketName` exceeds {@link + * #HARDWARE_OFFLOAD_SOCKET_NAME_MAX_LENGTH} bytes when encoded in UTF-8. + * @hide + */ + @SystemApi + @NonNull + @RequiresNoPermission + public Builder setSocketName(@NonNull String socketName) { + byte[] socketNameBytes = requireNonNull(socketName).getBytes(StandardCharsets.UTF_8); + if (socketNameBytes.length > HARDWARE_OFFLOAD_SOCKET_NAME_MAX_LENGTH) { + throw new IllegalArgumentException( + "Socket name cannot exceed " + + HARDWARE_OFFLOAD_SOCKET_NAME_MAX_LENGTH + + " bytes in length when encoded in UTF-8."); + } + mSocketName = requireNonNull(socketName); + return this; + } + + /** + * Sets the ID of the hub to be associated with this socket when using {@link + * #DATA_PATH_HARDWARE_OFFLOAD}. + * + * <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only + * available through the System API. + * + * @param hubId The ID of the hub. + * @return This Builder object to allow for method chaining. + * @hide + */ + @SystemApi + @NonNull + @RequiresNoPermission + public Builder setHubId(long hubId) { + mHubId = hubId; + return this; + } + + /** + * Sets the ID of the endpoint within the hub to be associated with this socket when using + * {@link #DATA_PATH_HARDWARE_OFFLOAD}. An endpoint represents a specific point of + * communication within the hub. + * + * <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only + * available through the System API. + * + * @param endpointId The ID of the endpoint within the hub. + * @return This Builder object to allow for method chaining. + * @hide + */ + @SystemApi + @NonNull + @RequiresNoPermission + public Builder setEndpointId(long endpointId) { + mEndpointId = endpointId; + return this; + } + + /** + * Sets the requested maximum size (in bytes) of a data packet that can be received from the + * endpoint associated with this socket when using {@link #DATA_PATH_HARDWARE_OFFLOAD}. + * + * <p>The main Bluetooth stack may adjust this value based on the actual capabilities + * negotiated with the peer device during connection establishment. To get the final + * negotiated value, use {@link BluetoothSocket#getMaxReceivePacketSize()} after the socket + * is connected. + * + * <p>This API is part of the System API because {@link #DATA_PATH_HARDWARE_OFFLOAD} is only + * available through the System API. + * + * @param maximumPacketSize The maximum packet size in bytes. + * @return This Builder object to allow for method chaining. + * @hide + */ + @SystemApi + @NonNull + @RequiresNoPermission + public Builder setRequestedMaximumPacketSize(int maximumPacketSize) { + mMaximumPacketSize = maximumPacketSize; + return this; + } + + /** * Builds a {@link BluetoothSocketSettings} object. * * @return A new {@link BluetoothSocketSettings} object with the configured parameters. @@ -336,14 +682,32 @@ public final class BluetoothSocketSettings { + mRfcommUuid); } } - + if (mDataPath == DATA_PATH_HARDWARE_OFFLOAD) { + if (mHubId == INVALID_HUB_ID || mEndpointId == INVALID_ENDPOINT_ID) { + throw new IllegalArgumentException( + "Hub ID and endpoint ID must be set for hardware data path"); + } + if (mMaximumPacketSize < 0) { + throw new IllegalArgumentException("invalid packet size " + mMaximumPacketSize); + } + } else { + if (mHubId != INVALID_HUB_ID || mEndpointId != INVALID_ENDPOINT_ID) { + throw new IllegalArgumentException( + "Hub ID and endpoint ID may not be set for software data path"); + } + } return new BluetoothSocketSettings( mSocketType, mL2capPsm, mEncryptionRequired, mAuthenticationRequired, mRfcommServiceName, - mRfcommUuid); + mRfcommUuid, + mDataPath, + mSocketName, + mHubId, + mEndpointId, + mMaximumPacketSize); } } } diff --git a/system/bta/dm/bta_dm_act.cc b/system/bta/dm/bta_dm_act.cc index ec3ca882bc..6b65525050 100644 --- a/system/bta/dm/bta_dm_act.cc +++ b/system/bta/dm/bta_dm_act.cc @@ -269,6 +269,13 @@ void BTA_dm_on_hw_on() { } } + if (com::android::bluetooth::flags::socket_settings_api()) { + /* Read low power processor offload features */ + if (bta_dm_acl_cb.p_acl_cback) { + bta_dm_acl_cb.p_acl_cback(BTA_DM_LPP_OFFLOAD_FEATURES_READ, NULL); + } + } + btm_ble_scanner_init(); // Synchronize with the controller before continuing diff --git a/system/bta/include/bta_api.h b/system/bta/include/bta_api.h index 90f1508c50..51533f8069 100644 --- a/system/bta/include/bta_api.h +++ b/system/bta/include/bta_api.h @@ -157,10 +157,11 @@ typedef struct { typedef uint8_t tBTA_DM_BLE_RSSI_ALERT_TYPE; typedef enum : uint8_t { - BTA_DM_LINK_UP_EVT = 5, /* Connection UP event */ - BTA_DM_LINK_DOWN_EVT = 6, /* Connection DOWN event */ - BTA_DM_LE_FEATURES_READ = 27, /* Controller specific LE features are read */ - BTA_DM_LINK_UP_FAILED_EVT = 34, /* Create connection failed event */ + BTA_DM_LINK_UP_EVT = 5, /* Connection UP event */ + BTA_DM_LINK_DOWN_EVT = 6, /* Connection DOWN event */ + BTA_DM_LE_FEATURES_READ = 27, /* Controller specific LE features are read */ + BTA_DM_LPP_OFFLOAD_FEATURES_READ = 28, /* Low power processor offload features are read */ + BTA_DM_LINK_UP_FAILED_EVT = 34, /* Create connection failed event */ } tBTA_DM_ACL_EVT; /* Structure associated with BTA_DM_LINK_UP_EVT */ diff --git a/system/bta/include/bta_jv_api.h b/system/bta/include/bta_jv_api.h index 4b43cbc485..1416f61ae0 100644 --- a/system/bta/include/bta_jv_api.h +++ b/system/bta/include/bta_jv_api.h @@ -260,6 +260,11 @@ typedef struct { int32_t tx_mtu; /* The transmit MTU */ uint16_t local_cid; /* The local CID */ uint16_t remote_cid; /* The remote CID */ + uint16_t local_coc_mps; /* The local COC MPS */ + uint16_t remote_coc_mps; /* The remote COC MPS */ + uint16_t local_coc_credit; /* The local COC credit */ + uint16_t remote_coc_credit; /* The remote COC credit */ + uint16_t acl_handle; /* The ACL handle */ } tBTA_JV_L2CAP_OPEN; /* data associated with BTA_JV_L2CAP_OPEN_EVT for LE sockets */ diff --git a/system/bta/jv/bta_jv_act.cc b/system/bta/jv/bta_jv_act.cc index 9d4715269c..343e2430cc 100644 --- a/system/bta/jv/bta_jv_act.cc +++ b/system/bta/jv/bta_jv_act.cc @@ -982,11 +982,31 @@ static void bta_jv_l2cap_client_cback(uint16_t gap_handle, uint16_t event, tGAP_ switch (event) { case GAP_EVT_CONN_OPENED: - evt_data.l2c_open.rem_bda = *GAP_ConnGetRemoteAddr(gap_handle); - evt_data.l2c_open.tx_mtu = GAP_ConnGetRemMtuSize(gap_handle); - if (data != nullptr) { - evt_data.l2c_open.local_cid = data->l2cap_cids.local_cid; - evt_data.l2c_open.remote_cid = data->l2cap_cids.remote_cid; + if (!com::android::bluetooth::flags::socket_settings_api() || + !GAP_IsTransportLe(gap_handle)) { + evt_data.l2c_open.rem_bda = *GAP_ConnGetRemoteAddr(gap_handle); + evt_data.l2c_open.tx_mtu = GAP_ConnGetRemMtuSize(gap_handle); + if (data != nullptr) { + evt_data.l2c_open.local_cid = data->l2cap_cids.local_cid; + evt_data.l2c_open.remote_cid = data->l2cap_cids.remote_cid; + } + } else { + uint16_t remote_mtu, local_mps, remote_mps, local_credit, remote_credit; + uint16_t local_cid, remote_cid, acl_handle; + evt_data.l2c_open.rem_bda = *GAP_ConnGetRemoteAddr(gap_handle); + if (GAP_GetLeChannelInfo(gap_handle, &remote_mtu, &local_mps, &remote_mps, &local_credit, + &remote_credit, &local_cid, &remote_cid, + &acl_handle) != PORT_SUCCESS) { + log::warn("Unable to get GAP channel info handle:{}", gap_handle); + } + evt_data.l2c_open.tx_mtu = remote_mtu; + evt_data.l2c_open.local_coc_mps = local_mps; + evt_data.l2c_open.remote_coc_mps = remote_mps; + evt_data.l2c_open.local_coc_credit = local_credit; + evt_data.l2c_open.remote_coc_credit = remote_credit; + evt_data.l2c_open.local_cid = local_cid; + evt_data.l2c_open.remote_cid = remote_cid; + evt_data.l2c_open.acl_handle = acl_handle; } p_cb->state = BTA_JV_ST_CL_OPEN; p_cb->p_cback(BTA_JV_L2CAP_OPEN_EVT, &evt_data, p_cb->l2cap_socket_id); @@ -1142,11 +1162,31 @@ static void bta_jv_l2cap_server_cback(uint16_t gap_handle, uint16_t event, tGAP_ switch (event) { case GAP_EVT_CONN_OPENED: - evt_data.l2c_open.rem_bda = *GAP_ConnGetRemoteAddr(gap_handle); - evt_data.l2c_open.tx_mtu = GAP_ConnGetRemMtuSize(gap_handle); - if (data != nullptr) { - evt_data.l2c_open.local_cid = data->l2cap_cids.local_cid; - evt_data.l2c_open.remote_cid = data->l2cap_cids.remote_cid; + if (!com::android::bluetooth::flags::socket_settings_api() || + !GAP_IsTransportLe(gap_handle)) { + evt_data.l2c_open.rem_bda = *GAP_ConnGetRemoteAddr(gap_handle); + evt_data.l2c_open.tx_mtu = GAP_ConnGetRemMtuSize(gap_handle); + if (data != nullptr) { + evt_data.l2c_open.local_cid = data->l2cap_cids.local_cid; + evt_data.l2c_open.remote_cid = data->l2cap_cids.remote_cid; + } + } else { + uint16_t remote_mtu, local_mps, remote_mps, local_credit, remote_credit; + uint16_t local_cid, remote_cid, acl_handle; + evt_data.l2c_open.rem_bda = *GAP_ConnGetRemoteAddr(gap_handle); + if (GAP_GetLeChannelInfo(gap_handle, &remote_mtu, &local_mps, &remote_mps, &local_credit, + &remote_credit, &local_cid, &remote_cid, + &acl_handle) != PORT_SUCCESS) { + log::warn("Unable to get GAP channel info handle:{}", gap_handle); + } + evt_data.l2c_open.tx_mtu = remote_mtu; + evt_data.l2c_open.local_coc_mps = local_mps; + evt_data.l2c_open.remote_coc_mps = remote_mps; + evt_data.l2c_open.local_coc_credit = local_credit; + evt_data.l2c_open.remote_coc_credit = remote_credit; + evt_data.l2c_open.local_cid = local_cid; + evt_data.l2c_open.remote_cid = remote_cid; + evt_data.l2c_open.acl_handle = acl_handle; } p_cb->state = BTA_JV_ST_SR_OPEN; p_cb->p_cback(BTA_JV_L2CAP_OPEN_EVT, &evt_data, p_cb->l2cap_socket_id); diff --git a/system/btif/Android.bp b/system/btif/Android.bp index 85b937b26d..c8de253527 100644 --- a/system/btif/Android.bp +++ b/system/btif/Android.bp @@ -198,6 +198,7 @@ cc_library_static { "src/btif_sdp.cc", "src/btif_sdp_server.cc", "src/btif_sock.cc", + "src/btif_sock_hal.cc", "src/btif_sock_l2cap.cc", "src/btif_sock_logging.cc", "src/btif_sock_rfc.cc", diff --git a/system/btif/BUILD.gn b/system/btif/BUILD.gn index 519237997a..38b8fa9c6a 100644 --- a/system/btif/BUILD.gn +++ b/system/btif/BUILD.gn @@ -75,6 +75,7 @@ static_library("btif") { "src/btif_sdp.cc", "src/btif_sdp_server.cc", "src/btif_sock.cc", + "src/btif_sock_hal.cc", "src/btif_sock_l2cap.cc", "src/btif_sock_logging.cc", "src/btif_sock_rfc.cc", diff --git a/system/btif/include/btif_sock_hal.h b/system/btif/include/btif_sock_hal.h new file mode 100644 index 0000000000..9588110920 --- /dev/null +++ b/system/btif/include/btif_sock_hal.h @@ -0,0 +1,21 @@ +/* + * Copyright 2024 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. + */ + +#pragma once + +#include <hardware/bluetooth.h> + +bt_status_t btsock_hal_init(); diff --git a/system/btif/include/btif_sock_l2cap.h b/system/btif/include/btif_sock_l2cap.h index c08ef532d7..272436efe9 100644 --- a/system/btif/include/btif_sock_l2cap.h +++ b/system/btif/include/btif_sock_l2cap.h @@ -22,20 +22,25 @@ #define BTIF_SOCK_L2CAP_H #include <hardware/bluetooth.h> +#include <hardware/bt_sock.h> #include "btif_uid.h" #include "types/raw_address.h" bt_status_t btsock_l2cap_init(int handle, uid_set_t* set); bt_status_t btsock_l2cap_cleanup(); -bt_status_t btsock_l2cap_listen(const char* name, int channel, int* sock_fd, int flags, - int app_uid); +bt_status_t btsock_l2cap_listen(const char* name, int channel, int* sock_fd, int flags, int app_uid, + btsock_data_path_t data_path, const char* socket_name, + uint64_t hub_id, uint64_t endpoint_id, int max_rx_packet_size); bt_status_t btsock_l2cap_connect(const RawAddress* bd_addr, int channel, int* sock_fd, int flags, - int app_uid); + int app_uid, btsock_data_path_t data_path, const char* socket_name, + uint64_t hub_id, uint64_t endpoint_id, int max_rx_packet_size); void btsock_l2cap_signaled(int fd, int flags, uint32_t user_id); void on_l2cap_psm_assigned(int id, int psm); bt_status_t btsock_l2cap_disconnect(const RawAddress* bd_addr); bt_status_t btsock_l2cap_get_l2cap_local_cid(bluetooth::Uuid& conn_uuid, uint16_t* cid); bt_status_t btsock_l2cap_get_l2cap_remote_cid(bluetooth::Uuid& conn_uuid, uint16_t* cid); +void on_btsocket_l2cap_opened_complete(uint64_t socket_id, bool success); +void on_btsocket_l2cap_close(uint64_t socket_id); #endif diff --git a/system/btif/src/btif_core.cc b/system/btif/src/btif_core.cc index 73fe810e23..30361defa2 100644 --- a/system/btif/src/btif_core.cc +++ b/system/btif/src/btif_core.cc @@ -53,6 +53,7 @@ #include "device/include/device_iot_config.h" #include "hci/controller_interface.h" #include "internal_include/bt_target.h" +#include "lpp/lpp_offload_interface.h" #include "main/shim/entry.h" #include "main/shim/helpers.h" #include "osi/include/allocator.h" @@ -510,6 +511,14 @@ void btif_get_adapter_property(bt_property_type_t type) { log::verbose("Don't support Dynamic Audio Buffer"); } } + } else if (prop.type == BT_PROPERTY_LPP_OFFLOAD_FEATURES) { + bt_lpp_offload_features_t lpp_offload_features; + hal::SocketCapabilities socket_offload_capabilities = + bluetooth::shim::GetLppOffloadManager()->GetSocketCapabilities(); + lpp_offload_features.number_of_supported_offloaded_le_coc_sockets = + socket_offload_capabilities.le_coc_capabilities.number_of_supported_sockets; + prop.len = sizeof(bt_lpp_offload_features_t); + memcpy(prop.val, &lpp_offload_features, prop.len); } else { status = btif_storage_get_adapter_property(&prop); } diff --git a/system/btif/src/btif_dm.cc b/system/btif/src/btif_dm.cc index 8128e861f0..8be2d87394 100644 --- a/system/btif/src/btif_dm.cc +++ b/system/btif/src/btif_dm.cc @@ -2257,6 +2257,9 @@ void btif_dm_acl_evt(tBTA_DM_ACL_EVT event, tBTA_DM_ACL* p_data) { case BTA_DM_LE_FEATURES_READ: btif_get_adapter_property(BT_PROPERTY_LOCAL_LE_FEATURES); break; + case BTA_DM_LPP_OFFLOAD_FEATURES_READ: + btif_get_adapter_property(BT_PROPERTY_LPP_OFFLOAD_FEATURES); + break; default: { log::error("Unexpected tBTA_DM_ACL_EVT:{}", event); diff --git a/system/btif/src/btif_sock.cc b/system/btif/src/btif_sock.cc index 04b43dda0e..fdeac4b77c 100644 --- a/system/btif/src/btif_sock.cc +++ b/system/btif/src/btif_sock.cc @@ -22,12 +22,14 @@ #include <base/functional/callback.h> #include <bluetooth/log.h> +#include <com_android_bluetooth_flags.h> #include <hardware/bluetooth.h> #include <hardware/bt_sock.h> #include <atomic> #include "bta/include/bta_api.h" +#include "btif_sock_hal.h" #include "btif_sock_l2cap.h" #include "btif_sock_logging.h" #include "btif_sock_rfc.h" @@ -43,9 +45,13 @@ using bluetooth::Uuid; using namespace bluetooth; static bt_status_t btsock_listen(btsock_type_t type, const char* service_name, const Uuid* uuid, - int channel, int* sock_fd, int flags, int app_uid); + int channel, int* sock_fd, int flags, int app_uid, + btsock_data_path_t data_path, const char* socket_name, + uint64_t hub_id, uint64_t endpoint_id, int max_rx_packet_size); static bt_status_t btsock_connect(const RawAddress* bd_addr, btsock_type_t type, const Uuid* uuid, - int channel, int* sock_fd, int flags, int app_uid); + int channel, int* sock_fd, int flags, int app_uid, + btsock_data_path_t data_path, const char* socket_name, + uint64_t hub_id, uint64_t endpoint_id, int max_rx_packet_size); static void btsock_request_max_tx_data_length(const RawAddress& bd_addr); static bt_status_t btsock_control_req(uint8_t dlci, const RawAddress& bd_addr, uint8_t modem_signal, uint8_t break_signal, uint8_t discard_buffers, @@ -112,6 +118,13 @@ bt_status_t btif_sock_init(uid_set_t* uid_set) { goto error; } + if (com::android::bluetooth::flags::socket_settings_api()) { + status = btsock_hal_init(); + if (status != BT_STATUS_SUCCESS) { + log::warn("error initializing socket hal: {}", status); + } + } + return BT_STATUS_SUCCESS; error: @@ -148,7 +161,8 @@ static bt_status_t btsock_control_req(uint8_t dlci, const RawAddress& bd_addr, u static bt_status_t btsock_listen(btsock_type_t type, const char* service_name, const Uuid* service_uuid, int channel, int* sock_fd, int flags, - int app_uid) { + int app_uid, btsock_data_path_t data_path, const char* socket_name, + uint64_t hub_id, uint64_t endpoint_id, int max_rx_packet_size) { if ((flags & BTSOCK_FLAG_NO_SDP) == 0) { log::assert_that(sock_fd != NULL, "assert failed: sock_fd != NULL"); } @@ -158,8 +172,10 @@ static bt_status_t btsock_listen(btsock_type_t type, const char* service_name, log::info( "Attempting listen for socket connections for device: {}, type: {}, " - "channel: {}, app_uid: {}", - RawAddress::kEmpty, type, channel, app_uid); + "channel: {}, app_uid: {}, data_path: {}, hub_id: {}, endpoint_id: {}, " + "max_rx_packet_size: {}", + RawAddress::kEmpty, type, channel, app_uid, data_path, hub_id, endpoint_id, + max_rx_packet_size); btif_sock_connection_logger(RawAddress::kEmpty, 0, type, SOCKET_CONNECTION_STATE_LISTENING, SOCKET_ROLE_LISTEN, app_uid, channel, 0, 0, service_name); switch (type) { @@ -167,11 +183,13 @@ static bt_status_t btsock_listen(btsock_type_t type, const char* service_name, status = btsock_rfc_listen(service_name, service_uuid, channel, sock_fd, flags, app_uid); break; case BTSOCK_L2CAP: - status = btsock_l2cap_listen(service_name, channel, sock_fd, flags, app_uid); + status = btsock_l2cap_listen(service_name, channel, sock_fd, flags, app_uid, data_path, + socket_name, hub_id, endpoint_id, max_rx_packet_size); break; case BTSOCK_L2CAP_LE: status = btsock_l2cap_listen(service_name, channel, sock_fd, flags | BTSOCK_FLAG_LE_COC, - app_uid); + app_uid, data_path, socket_name, hub_id, endpoint_id, + max_rx_packet_size); break; case BTSOCK_SCO: status = btsock_sco_listen(sock_fd, flags); @@ -194,14 +212,16 @@ static bt_status_t btsock_listen(btsock_type_t type, const char* service_name, } static bt_status_t btsock_connect(const RawAddress* bd_addr, btsock_type_t type, const Uuid* uuid, - int channel, int* sock_fd, int flags, int app_uid) { + int channel, int* sock_fd, int flags, int app_uid, + btsock_data_path_t data_path, const char* socket_name, + uint64_t hub_id, uint64_t endpoint_id, int max_rx_packet_size) { log::assert_that(bd_addr != NULL, "assert failed: bd_addr != NULL"); log::assert_that(sock_fd != NULL, "assert failed: sock_fd != NULL"); log::info( "Attempting socket connection for device: {}, type: {}, channel: {}, " - "app_uid: {}", - *bd_addr, type, channel, app_uid); + "app_uid: {}, data_path: {}, hub_id: {}, endpoint_id: {}, max_rx_packet_size: {}", + *bd_addr, type, channel, app_uid, data_path, hub_id, endpoint_id, max_rx_packet_size); *sock_fd = INVALID_FD; bt_status_t status = BT_STATUS_SOCKET_ERROR; @@ -215,11 +235,13 @@ static bt_status_t btsock_connect(const RawAddress* bd_addr, btsock_type_t type, break; case BTSOCK_L2CAP: - status = btsock_l2cap_connect(bd_addr, channel, sock_fd, flags, app_uid); + status = btsock_l2cap_connect(bd_addr, channel, sock_fd, flags, app_uid, data_path, + socket_name, hub_id, endpoint_id, max_rx_packet_size); break; case BTSOCK_L2CAP_LE: - status = btsock_l2cap_connect(bd_addr, channel, sock_fd, (flags | BTSOCK_FLAG_LE_COC), - app_uid); + status = + btsock_l2cap_connect(bd_addr, channel, sock_fd, (flags | BTSOCK_FLAG_LE_COC), app_uid, + data_path, socket_name, hub_id, endpoint_id, max_rx_packet_size); break; case BTSOCK_SCO: status = btsock_sco_connect(bd_addr, sock_fd, flags); diff --git a/system/btif/src/btif_sock_hal.cc b/system/btif/src/btif_sock_hal.cc new file mode 100644 index 0000000000..48b40c541c --- /dev/null +++ b/system/btif/src/btif_sock_hal.cc @@ -0,0 +1,56 @@ +/* + * Copyright 2024 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. + */ + +#define LOG_TAG "bt_btif_sock_hal" + +#include "btif/include/btif_sock_hal.h" + +#include "btif/include/btif_sock_l2cap.h" +#include "lpp/lpp_offload_interface.h" +#include "main/shim/entry.h" +#include "stack/include/main_thread.h" + +using namespace bluetooth; + +class BtifSocketHalCallback : public hal::SocketHalCallback { +public: + void SocketOpenedComplete(uint64_t socket_id, hal::SocketStatus status) const override { + log::info("socket_id: {}, status: {}", socket_id, static_cast<int>(status)); + do_in_main_thread(base::BindOnce(on_btsocket_l2cap_opened_complete, socket_id, + (status == hal::SocketStatus::SUCCESS))); + } + + void SocketClose(uint64_t socket_id) const override { + log::info("socket_id: {}", socket_id); + do_in_main_thread(base::BindOnce(on_btsocket_l2cap_close, socket_id)); + } +}; + +static BtifSocketHalCallback btif_socket_hal_cb; + +bt_status_t btsock_hal_init() { + log::info(""); + auto lpp_offload_manager_interface = bluetooth::shim::GetLppOffloadManager(); + if (lpp_offload_manager_interface == nullptr) { + log::warn("GetLppOffloadManager() returned nullptr!"); + return BT_STATUS_FAIL; + } + if (!lpp_offload_manager_interface->RegisterSocketHalCallback(&btif_socket_hal_cb)) { + log::warn("RegisterSocketHalCallback() failed!"); + return BT_STATUS_FAIL; + } + return BT_STATUS_SUCCESS; +} diff --git a/system/btif/src/btif_sock_l2cap.cc b/system/btif/src/btif_sock_l2cap.cc index 3aa7162b78..e3c23a5647 100644 --- a/system/btif/src/btif_sock_l2cap.cc +++ b/system/btif/src/btif_sock_l2cap.cc @@ -16,11 +16,11 @@ */ #include <bluetooth/log.h> +#include <com_android_bluetooth_flags.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> -#include <com_android_bluetooth_flags.h> #include <cstdint> #include <cstring> @@ -36,6 +36,8 @@ #include "gd/os/rand.h" #include "include/hardware/bluetooth.h" #include "internal_include/bt_target.h" +#include "lpp/lpp_offload_interface.h" +#include "main/shim/entry.h" #include "osi/include/allocator.h" #include "osi/include/osi.h" #include "stack/include/bt_hdr.h" @@ -65,6 +67,7 @@ typedef struct l2cap_socket { int channel; // PSM int our_fd; // fd from our side int app_fd; // fd from app's side + int listen_fd; // listen socket fd from our side unsigned bytes_buffered; struct packet* first_packet; // fist packet to be delivered to app @@ -84,9 +87,18 @@ typedef struct l2cap_socket { uint16_t local_cid; // The local CID uint16_t remote_cid; // The remote CID Uuid conn_uuid; // The connection uuid + uint64_t socket_id; // Socket ID in connected state + btsock_data_path_t data_path; // socket data path + char socket_name[128]; // descriptive socket name + uint64_t hub_id; // ID of the hub to which the end point belongs + uint64_t endpoint_id; // ID of the hub end point + bool is_accepting; // is app accepting on server socket? } l2cap_socket; static void btsock_l2cap_server_listen(l2cap_socket* sock); +static uint64_t btif_l2cap_sock_generate_socket_id(); +static void on_cl_l2cap_psm_connect_offload_l(tBTA_JV_L2CAP_OPEN* p_open, l2cap_socket* sock); +static void on_srv_l2cap_psm_connect_offload_l(tBTA_JV_L2CAP_OPEN* p_open, l2cap_socket* sock); static std::mutex state_lock; @@ -246,8 +258,8 @@ static void btsock_l2cap_free_l(l2cap_socket* sock) { log::info( "Disconnected L2CAP connection for device: {}, channel: {}, app_uid: {}, " - "id: {}, is_le: {}", - sock->addr, sock->channel, sock->app_uid, sock->id, sock->is_le_coc); + "id: {}, is_le: {}, socket_id: {}", + sock->addr, sock->channel, sock->app_uid, sock->id, sock->is_le_coc, sock->socket_id); btif_sock_connection_logger( sock->addr, sock->id, sock->is_le_coc ? BTSOCK_L2CAP_LE : BTSOCK_L2CAP, SOCKET_CONNECTION_STATE_DISCONNECTED, @@ -334,12 +346,19 @@ static l2cap_socket* btsock_l2cap_alloc_l(const char* name, const RawAddress* ad sock->our_fd = fds[0]; sock->app_fd = fds[1]; + sock->listen_fd = -1; sock->security = security; sock->server = is_server; sock->connected = false; sock->handle = 0; sock->server_psm_sent = false; sock->app_uid = -1; + sock->conn_uuid = Uuid::kEmpty; + sock->socket_id = 0; + sock->data_path = BTSOCK_DATA_PATH_NO_OFFLOAD; + sock->hub_id = 0; + sock->endpoint_id = 0; + sock->is_accepting = false; if (name) { strncpy(sock->name, name, sizeof(sock->name) - 1); @@ -446,7 +465,7 @@ static uint64_t uuid_msb(const Uuid& uuid) { static bool send_app_connect_signal(int fd, const RawAddress* addr, int channel, int status, int send_fd, uint16_t rx_mtu, uint16_t tx_mtu, - const Uuid& conn_uuid) { + const Uuid& conn_uuid, uint64_t socket_id) { sock_connect_signal_t cs; cs.size = sizeof(cs); cs.bd_addr = *addr; @@ -456,6 +475,7 @@ static bool send_app_connect_signal(int fd, const RawAddress* addr, int channel, cs.max_tx_packet_size = tx_mtu; cs.conn_uuid_lsb = uuid_lsb(conn_uuid); cs.conn_uuid_msb = uuid_msb(conn_uuid); + cs.socket_id = socket_id; if (send_fd != -1) { if (sock_send_fd(fd, (const uint8_t*)&cs, sizeof(cs), send_fd) == sizeof(cs)) { return true; @@ -531,7 +551,7 @@ static void on_cl_l2cap_init(tBTA_JV_L2CAP_CL_INIT* p_init, uint32_t id) { * will be a clone of the sock representing the BluetoothServerSocket. * */ static void on_srv_l2cap_psm_connect_l(tBTA_JV_L2CAP_OPEN* p_open, l2cap_socket* sock) { - // std::mutex locked by caller + // state_lock taken by caller l2cap_socket* accept_rs = btsock_l2cap_alloc_l(sock->name, &p_open->rem_bda, false, 0); accept_rs->connected = true; accept_rs->security = sock->security; @@ -541,10 +561,18 @@ static void on_srv_l2cap_psm_connect_l(tBTA_JV_L2CAP_OPEN* p_open, l2cap_socket* sock->handle = -1; /* We should no longer associate this handle with the server socket */ accept_rs->is_le_coc = sock->is_le_coc; accept_rs->tx_mtu = sock->tx_mtu = p_open->tx_mtu; + accept_rs->rx_mtu = sock->rx_mtu; accept_rs->local_cid = p_open->local_cid; accept_rs->remote_cid = p_open->remote_cid; + // TODO(b/342012881) Remove connection uuid when offload socket API is landed. Uuid uuid = Uuid::From128BitBE(bluetooth::os::GenerateRandom<Uuid::kNumBytes128>()); accept_rs->conn_uuid = uuid; + accept_rs->socket_id = btif_l2cap_sock_generate_socket_id(); + accept_rs->data_path = sock->data_path; + strncpy(accept_rs->socket_name, sock->socket_name, sizeof(accept_rs->socket_name) - 1); + accept_rs->socket_name[sizeof(accept_rs->socket_name) - 1] = '\0'; + accept_rs->hub_id = sock->hub_id; + accept_rs->endpoint_id = sock->endpoint_id; /* Swap IDs to hand over the GAP connection to the accepted socket, and start a new server on the newly create socket ID. */ @@ -554,8 +582,9 @@ static void on_srv_l2cap_psm_connect_l(tBTA_JV_L2CAP_OPEN* p_open, l2cap_socket* log::info( "Connected to L2CAP connection for device: {}, channel: {}, app_uid: {}, " - "id: {}, is_le: {}", - sock->addr, sock->channel, sock->app_uid, sock->id, sock->is_le_coc); + "id: {}, is_le: {}, socket_id: {}, rx_mtu: {}", + sock->addr, sock->channel, sock->app_uid, sock->id, sock->is_le_coc, accept_rs->socket_id, + accept_rs->rx_mtu); btif_sock_connection_logger(accept_rs->addr, accept_rs->id, accept_rs->is_le_coc ? BTSOCK_L2CAP_LE : BTSOCK_L2CAP, SOCKET_CONNECTION_STATE_CONNECTED, @@ -566,11 +595,13 @@ static void on_srv_l2cap_psm_connect_l(tBTA_JV_L2CAP_OPEN* p_open, l2cap_socket* btsock_thread_add_fd(pth, sock->our_fd, BTSOCK_L2CAP, SOCK_THREAD_FD_EXCEPTION, sock->id); btsock_thread_add_fd(pth, accept_rs->our_fd, BTSOCK_L2CAP, SOCK_THREAD_FD_RD, accept_rs->id); send_app_connect_signal(sock->our_fd, &accept_rs->addr, sock->channel, 0, accept_rs->app_fd, - sock->rx_mtu, p_open->tx_mtu, accept_rs->conn_uuid); + sock->rx_mtu, p_open->tx_mtu, accept_rs->conn_uuid, accept_rs->socket_id); accept_rs->app_fd = -1; // The fd is closed after sent to app in send_app_connect_signal() // But for some reason we still leak a FD - either the server socket // one or the accept socket one. btsock_l2cap_server_listen(sock); + // start monitoring the socketpair to get call back when app is accepting on server socket + btsock_thread_add_fd(pth, sock->our_fd, BTSOCK_L2CAP, SOCK_THREAD_FD_RD, sock->id); } static void on_cl_l2cap_psm_connect_l(tBTA_JV_L2CAP_OPEN* p_open, l2cap_socket* sock) { @@ -578,8 +609,10 @@ static void on_cl_l2cap_psm_connect_l(tBTA_JV_L2CAP_OPEN* p_open, l2cap_socket* sock->tx_mtu = p_open->tx_mtu; sock->local_cid = p_open->local_cid; sock->remote_cid = p_open->remote_cid; + // TODO(b/342012881) Remove connection uuid when offload socket API is landed. Uuid uuid = Uuid::From128BitBE(bluetooth::os::GenerateRandom<Uuid::kNumBytes128>()); sock->conn_uuid = uuid; + sock->socket_id = btif_l2cap_sock_generate_socket_id(); if (!send_app_psm_or_chan_l(sock)) { log::error("Unable to send l2cap socket to application socket_id:{}", sock->id); @@ -587,15 +620,16 @@ static void on_cl_l2cap_psm_connect_l(tBTA_JV_L2CAP_OPEN* p_open, l2cap_socket* } if (!send_app_connect_signal(sock->our_fd, &sock->addr, sock->channel, 0, -1, sock->rx_mtu, - p_open->tx_mtu, sock->conn_uuid)) { + p_open->tx_mtu, sock->conn_uuid, sock->socket_id)) { log::error("Unable to connect l2cap socket to application socket_id:{}", sock->id); return; } log::info( "Connected to L2CAP connection for device: {}, channel: {}, app_uid: {}, " - "id: {}, is_le: {}", - sock->addr, sock->channel, sock->app_uid, sock->id, sock->is_le_coc); + "id: {}, is_le: {}, socket_id: {}, rx_mtu: {}", + sock->addr, sock->channel, sock->app_uid, sock->id, sock->is_le_coc, sock->socket_id, + sock->rx_mtu); btif_sock_connection_logger(sock->addr, sock->id, sock->is_le_coc ? BTSOCK_L2CAP_LE : BTSOCK_L2CAP, SOCKET_CONNECTION_STATE_CONNECTED, @@ -621,10 +655,18 @@ static void on_l2cap_connect(tBTA_JV* p_data, uint32_t id) { sock->tx_mtu = le_open->tx_mtu; if (psm_open->status == tBTA_JV_STATUS::SUCCESS) { - if (!sock->server) { - on_cl_l2cap_psm_connect_l(psm_open, sock); + if (sock->data_path == BTSOCK_DATA_PATH_NO_OFFLOAD) { + if (!sock->server) { + on_cl_l2cap_psm_connect_l(psm_open, sock); + } else { + on_srv_l2cap_psm_connect_l(psm_open, sock); + } } else { - on_srv_l2cap_psm_connect_l(psm_open, sock); + if (!sock->server) { + on_cl_l2cap_psm_connect_offload_l(psm_open, sock); + } else { + on_srv_l2cap_psm_connect_offload_l(psm_open, sock); + } } } else { log::error("Unable to open socket after receiving connection socket_id:{}", sock->id); @@ -825,6 +867,13 @@ static void btsock_l2cap_server_listen(l2cap_socket* sock) { /* Setup ETM settings: mtu will be set below */ std::unique_ptr<tL2CAP_CFG_INFO> cfg = std::make_unique<tL2CAP_CFG_INFO>( tL2CAP_CFG_INFO{.fcr_present = true, .fcr = kDefaultErtmOptions}); + /* For hardware offload data path, host stack sets the initial credits to 0. The offload stack + * should send initial credits to peer device through L2CAP signaling command when the data path + * is switched successfully. */ + if (sock->data_path == BTSOCK_DATA_PATH_HARDWARE_OFFLOAD) { + cfg->init_credit_present = true; + cfg->init_credit = 0; + } std::unique_ptr<tL2CAP_ERTM_INFO> ertm_info; if (!sock->is_le_coc) { @@ -835,9 +884,38 @@ static void btsock_l2cap_server_listen(l2cap_socket* sock) { sock->rx_mtu, std::move(cfg), btsock_l2cap_cbk, sock->id); } +/* + * Determine the local MTU for the offloaded L2CAP connection. + * + * The local MTU is selected as the minimum of: + * - The socket hal's offload capabilities (socket_cap.leCocCapabilities.mtu) + * - The application's requested maximum RX packet size (app_max_rx_packet_size) + * + * However, the MTU must be at least the minimum required by the L2CAP LE + * specification (L2CAP_SDU_LENGTH_LE_MIN). + */ + +static bool btsock_l2cap_get_offload_mtu(uint16_t* rx_mtu, uint16_t app_max_rx_packet_size) { + hal::SocketCapabilities socket_cap = + bluetooth::shim::GetLppOffloadManager()->GetSocketCapabilities(); + if (!socket_cap.le_coc_capabilities.number_of_supported_sockets) { + return false; + } + /* Socket HAL client has already verified that the MTU is in a valid range. */ + uint16_t mtu = static_cast<uint16_t>(socket_cap.le_coc_capabilities.mtu); + mtu = std::min(mtu, app_max_rx_packet_size); + if (mtu < L2CAP_SDU_LENGTH_LE_MIN) { + mtu = L2CAP_SDU_LENGTH_LE_MIN; + } + *rx_mtu = mtu; + return true; +} + static bt_status_t btsock_l2cap_listen_or_connect(const char* name, const RawAddress* addr, int channel, int* sock_fd, int flags, char listen, - int app_uid) { + int app_uid, btsock_data_path_t data_path, + const char* socket_name, uint64_t hub_id, + uint64_t endpoint_id, int max_rx_packet_size) { if (!is_inited()) { return BT_STATUS_NOT_READY; } @@ -875,11 +953,26 @@ static bt_status_t btsock_l2cap_listen_or_connect(const char* name, const RawAdd sock->channel = channel; sock->app_uid = app_uid; sock->is_le_coc = is_le_coc; - sock->rx_mtu = is_le_coc ? L2CAP_SDU_LENGTH_LE_MAX : L2CAP_SDU_LENGTH_MAX; + if (data_path == BTSOCK_DATA_PATH_HARDWARE_OFFLOAD) { + if (!btsock_l2cap_get_offload_mtu(&sock->rx_mtu, static_cast<uint16_t>(max_rx_packet_size))) { + return BT_STATUS_UNSUPPORTED; + } + } else { + sock->rx_mtu = is_le_coc ? L2CAP_SDU_LENGTH_LE_MAX : L2CAP_SDU_LENGTH_MAX; + } + sock->data_path = data_path; + if (socket_name) { + strncpy(sock->socket_name, socket_name, sizeof(sock->socket_name) - 1); + sock->socket_name[sizeof(sock->socket_name) - 1] = '\0'; + } + sock->hub_id = hub_id; + sock->endpoint_id = endpoint_id; /* "role" is never initialized in rfcomm code */ if (listen) { btsock_l2cap_server_listen(sock); + // start monitoring the socketpair to get call back when app is accepting on server socket + btsock_thread_add_fd(pth, sock->our_fd, BTSOCK_L2CAP, SOCK_THREAD_FD_RD, sock->id); } else { tBTA_JV_CONN_TYPE connection_type = sock->is_le_coc ? tBTA_JV_CONN_TYPE::L2CAP_LE : tBTA_JV_CONN_TYPE::L2CAP; @@ -887,6 +980,13 @@ static bt_status_t btsock_l2cap_listen_or_connect(const char* name, const RawAdd /* Setup ETM settings: mtu will be set below */ std::unique_ptr<tL2CAP_CFG_INFO> cfg = std::make_unique<tL2CAP_CFG_INFO>( tL2CAP_CFG_INFO{.fcr_present = true, .fcr = kDefaultErtmOptions}); + /* For hardware offload data path, host stack sets the initial credits to 0. The offload stack + * should send initial credits to peer device through L2CAP signaling command when the data path + * is switched successfully. */ + if (sock->data_path == BTSOCK_DATA_PATH_HARDWARE_OFFLOAD) { + cfg->init_credit_present = true; + cfg->init_credit = 0; + } std::unique_ptr<tL2CAP_ERTM_INFO> ertm_info; if (!sock->is_le_coc) { @@ -909,14 +1009,19 @@ static bt_status_t btsock_l2cap_listen_or_connect(const char* name, const RawAdd return BT_STATUS_SUCCESS; } -bt_status_t btsock_l2cap_listen(const char* name, int channel, int* sock_fd, int flags, - int app_uid) { - return btsock_l2cap_listen_or_connect(name, NULL, channel, sock_fd, flags, 1, app_uid); +bt_status_t btsock_l2cap_listen(const char* name, int channel, int* sock_fd, int flags, int app_uid, + btsock_data_path_t data_path, const char* socket_name, + uint64_t hub_id, uint64_t endpoint_id, int max_rx_packet_size) { + return btsock_l2cap_listen_or_connect(name, NULL, channel, sock_fd, flags, 1, app_uid, data_path, + socket_name, hub_id, endpoint_id, max_rx_packet_size); } bt_status_t btsock_l2cap_connect(const RawAddress* bd_addr, int channel, int* sock_fd, int flags, - int app_uid) { - return btsock_l2cap_listen_or_connect(NULL, bd_addr, channel, sock_fd, flags, 0, app_uid); + int app_uid, btsock_data_path_t data_path, const char* socket_name, + uint64_t hub_id, uint64_t endpoint_id, int max_rx_packet_size) { + return btsock_l2cap_listen_or_connect(NULL, bd_addr, channel, sock_fd, flags, 0, app_uid, + data_path, socket_name, hub_id, endpoint_id, + max_rx_packet_size); } /* return true if we have more to send and should wait for user readiness, false @@ -962,6 +1067,69 @@ inline uint8_t* get_l2cap_sdu_start_ptr(BT_HDR* msg) { return (uint8_t*)(msg) + BT_HDR_SIZE + msg->offset; } +// state_lock taken by caller +bool btsock_l2cap_read_signaled_on_connected_socket(int fd, int flags, uint32_t user_id, + l2cap_socket* sock) { + if (!sock->connected) { + return false; + } + int size = 0; + bool ioctl_success = ioctl(sock->our_fd, FIONREAD, &size) == 0; + if (!(flags & SOCK_THREAD_FD_EXCEPTION) || (ioctl_success && size)) { + /* FIONREAD return number of bytes that are immediately available for + reading, might be bigger than awaiting packet. + + BluetoothSocket.write(...) guarantees that any packet send to this + socket is broken into pieces no bigger than MTU bytes (as requested + by BT spec). */ + size = std::min(size, (int)sock->tx_mtu); + + BT_HDR* buffer = malloc_l2cap_buf(size); + /* The socket is created with SOCK_SEQPACKET, hence we read one message + * at the time. */ + ssize_t count; + OSI_NO_INTR(count = recv(fd, get_l2cap_sdu_start_ptr(buffer), size, + MSG_NOSIGNAL | MSG_DONTWAIT | MSG_TRUNC)); + if (count > sock->tx_mtu) { + /* This can't happen thanks to check in BluetoothSocket.java but leave + * this in case this socket is ever used anywhere else*/ + log::error("recv more than MTU. Data will be lost: {}", count); + count = sock->tx_mtu; + } + + /* When multiple packets smaller than MTU are flushed to the socket, the + size of the single packet read could be smaller than the ioctl + reported total size of awaiting packets. Hence, we adjust the buffer + length. */ + buffer->len = count; + + // will take care of freeing buffer + BTA_JvL2capWrite(sock->handle, PTR_TO_UINT(buffer), buffer, user_id); + } + return true; +} + +// state_lock taken by caller +bool btsock_l2cap_read_signaled_on_listen_socket(int fd, int /* flags */, uint32_t /* user_id */, + l2cap_socket* sock) { + int size = 0; + bool ioctl_success = ioctl(sock->our_fd, FIONREAD, &size) == 0; + if (ioctl_success && size) { + sock_accept_signal_t accept_signal = {}; + ssize_t count; + OSI_NO_INTR(count = recv(fd, reinterpret_cast<uint8_t*>(&accept_signal), sizeof(accept_signal), + MSG_NOSIGNAL | MSG_DONTWAIT | MSG_TRUNC)); + if (count != sizeof(accept_signal) || count != accept_signal.size) { + log::error("Unexpected count {} sizeof(accept_signal) {} accept_signal.size {}", count, + sizeof(accept_signal), accept_signal.size); + return false; + } + sock->is_accepting = accept_signal.is_accepting; + log::info("Server socket {} is_accepting {}", sock->id, sock->is_accepting); + } + return true; +} + void btsock_l2cap_signaled(int fd, int flags, uint32_t user_id) { char drop_it = false; @@ -972,45 +1140,17 @@ void btsock_l2cap_signaled(int fd, int flags, uint32_t user_id) { if (!sock) { return; } - - if ((flags & SOCK_THREAD_FD_RD) && !sock->server) { - // app sending data - if (sock->connected) { - int size = 0; - bool ioctl_success = ioctl(sock->our_fd, FIONREAD, &size) == 0; - if (!(flags & SOCK_THREAD_FD_EXCEPTION) || (ioctl_success && size)) { - /* FIONREAD return number of bytes that are immediately available for - reading, might be bigger than awaiting packet. - - BluetoothSocket.write(...) guarantees that any packet send to this - socket is broken into pieces no bigger than MTU bytes (as requested - by BT spec). */ - size = std::min(size, (int)sock->tx_mtu); - - BT_HDR* buffer = malloc_l2cap_buf(size); - /* The socket is created with SOCK_SEQPACKET, hence we read one message - * at the time. */ - ssize_t count; - OSI_NO_INTR(count = recv(fd, get_l2cap_sdu_start_ptr(buffer), size, - MSG_NOSIGNAL | MSG_DONTWAIT | MSG_TRUNC)); - if (count > sock->tx_mtu) { - /* This can't happen thanks to check in BluetoothSocket.java but leave - * this in case this socket is ever used anywhere else*/ - log::error("recv more than MTU. Data will be lost: {}", count); - count = sock->tx_mtu; - } - - /* When multiple packets smaller than MTU are flushed to the socket, the - size of the single packet read could be smaller than the ioctl - reported total size of awaiting packets. Hence, we adjust the buffer - length. */ - buffer->len = count; - - // will take care of freeing buffer - BTA_JvL2capWrite(sock->handle, PTR_TO_UINT(buffer), buffer, user_id); + if (flags & SOCK_THREAD_FD_RD) { + if (!sock->server) { + // app sending data on connection socket + if (!btsock_l2cap_read_signaled_on_connected_socket(fd, flags, user_id, sock)) { + drop_it = true; } } else { - drop_it = true; + // app sending signal on listen socket + if (!btsock_l2cap_read_signaled_on_listen_socket(fd, flags, user_id, sock)) { + drop_it = true; + } } } if (flags & SOCK_THREAD_FD_WR) { @@ -1074,3 +1214,198 @@ bt_status_t btsock_l2cap_get_l2cap_remote_cid(Uuid& conn_uuid, uint16_t* cid) { *cid = sock->remote_cid; return BT_STATUS_SUCCESS; } + +// TODO(b/380189525): Replace the randomized socket ID with static counter when we don't have +// security concerns about using static counter. +static uint64_t btif_l2cap_sock_generate_socket_id() { + uint64_t socket_id; + do { + socket_id = bluetooth::os::GenerateRandomUint64(); + } while (!socket_id); + return socket_id; +} + +/* only call with state_lock taken */ +static l2cap_socket* btsock_l2cap_find_by_socket_id_l(uint64_t socket_id) { + l2cap_socket* sock = socks; + + while (sock) { + if (sock->socket_id == socket_id) { + return sock; + } + sock = sock->next; + } + + return nullptr; +} + +void on_btsocket_l2cap_opened_complete(uint64_t socket_id, bool success) { + l2cap_socket* sock; + + std::unique_lock<std::mutex> lock(state_lock); + sock = btsock_l2cap_find_by_socket_id_l(socket_id); + if (!sock) { + log::error("Unable to find l2cap socket with socket_id:{}", socket_id); + return; + } + if (!success) { + log::error("L2CAP opened complete failed with socket_id:{}", socket_id); + btsock_l2cap_free_l(sock); + return; + } + // If the socket was accepted from listen socket, use listen_fd. + if (sock->listen_fd != -1) { + send_app_connect_signal(sock->listen_fd, &sock->addr, sock->channel, 0, sock->app_fd, + sock->rx_mtu, sock->tx_mtu, sock->conn_uuid, sock->socket_id); + // The fd is closed after sent to app in send_app_connect_signal() + sock->app_fd = -1; + } else { + if (!send_app_psm_or_chan_l(sock)) { + log::error("Unable to send l2cap socket to application socket_id:{}", sock->id); + return; + } + if (!send_app_connect_signal(sock->our_fd, &sock->addr, sock->channel, 0, -1, sock->rx_mtu, + sock->tx_mtu, sock->conn_uuid, sock->socket_id)) { + log::error("Unable to connect l2cap socket to application socket_id:{}", sock->id); + return; + } + + log::info( + "Connected to L2CAP connection for device: {}, channel: {}, app_uid: {}, id: {}, " + "is_le: {}, socket_id: {}, rx_mtu: {}", + sock->addr, sock->channel, sock->app_uid, sock->id, sock->is_le_coc, sock->socket_id, + sock->rx_mtu); + btif_sock_connection_logger(sock->addr, sock->id, + sock->is_le_coc ? BTSOCK_L2CAP_LE : BTSOCK_L2CAP, + SOCKET_CONNECTION_STATE_CONNECTED, + sock->server ? SOCKET_ROLE_LISTEN : SOCKET_ROLE_CONNECTION, + sock->app_uid, sock->channel, 0, 0, sock->name); + + log::info("Connected l2cap socket socket_id:{}", sock->id); + sock->connected = true; + } +} + +void on_btsocket_l2cap_close(uint64_t socket_id) { + l2cap_socket* sock; + + std::unique_lock<std::mutex> lock(state_lock); + sock = btsock_l2cap_find_by_socket_id_l(socket_id); + if (!sock) { + log::error("Unable to find l2cap socket with socket_id:{}", socket_id); + return; + } + log::info("L2CAP close request for socket_id:{}", socket_id); + btsock_l2cap_free_l(sock); +} + +static void on_cl_l2cap_psm_connect_offload_l(tBTA_JV_L2CAP_OPEN* p_open, l2cap_socket* sock) { + sock->addr = p_open->rem_bda; + sock->tx_mtu = p_open->tx_mtu; + sock->local_cid = p_open->local_cid; + sock->remote_cid = p_open->remote_cid; + // TODO(b/342012881) Remove connection uuid when offload socket API is landed. + Uuid uuid = Uuid::From128BitBE(bluetooth::os::GenerateRandom<Uuid::kNumBytes128>()); + sock->conn_uuid = uuid; + sock->socket_id = btif_l2cap_sock_generate_socket_id(); + + log::info( + "Connected to L2CAP connection for device: {}, channel: {}, app_uid: {}, " + "id: {}, is_le: {}, socket_id: {}, rx_mtu: {}", + sock->addr, sock->channel, sock->app_uid, sock->id, sock->is_le_coc, sock->socket_id, + sock->rx_mtu); + btif_sock_connection_logger(sock->addr, sock->id, + sock->is_le_coc ? BTSOCK_L2CAP_LE : BTSOCK_L2CAP, + SOCKET_CONNECTION_STATE_CONNECTED, + sock->server ? SOCKET_ROLE_LISTEN : SOCKET_ROLE_CONNECTION, + sock->app_uid, sock->channel, 0, 0, sock->name); + + bluetooth::hal::SocketContext socket_context = { + .socket_id = sock->socket_id, + .name = sock->socket_name, + .acl_connection_handle = p_open->acl_handle, + .channel_info = bluetooth::hal::LeCocChannelInfo( + p_open->local_cid, p_open->remote_cid, static_cast<uint16_t>(sock->channel), + sock->rx_mtu, sock->tx_mtu, p_open->local_coc_mps, p_open->remote_coc_mps, + p_open->local_coc_credit, p_open->remote_coc_credit), + .endpoint_info.hub_id = sock->hub_id, + .endpoint_info.endpoint_id = sock->endpoint_id, + }; + if (!bluetooth::shim::GetLppOffloadManager()->SocketOpened(socket_context)) { + log::warn("L2CAP socket opened failed. Disconnect the incoming connection."); + btsock_l2cap_free_l(sock); + } else { + log::info( + "L2CAP socket opened successful. Will send connect signal in " + "on_btsocket_l2cap_opened_complete() asynchronously."); + } +} + +static void on_srv_l2cap_psm_connect_offload_l(tBTA_JV_L2CAP_OPEN* p_open, l2cap_socket* sock) { + // std::mutex locked by caller + l2cap_socket* accept_rs = btsock_l2cap_alloc_l(sock->name, &p_open->rem_bda, false, 0); + accept_rs->connected = true; + accept_rs->security = sock->security; + accept_rs->channel = sock->channel; + accept_rs->handle = sock->handle; + accept_rs->app_uid = sock->app_uid; + sock->handle = -1; /* We should no longer associate this handle with the server socket */ + accept_rs->is_le_coc = sock->is_le_coc; + accept_rs->tx_mtu = sock->tx_mtu = p_open->tx_mtu; + accept_rs->rx_mtu = sock->rx_mtu; + accept_rs->local_cid = p_open->local_cid; + accept_rs->remote_cid = p_open->remote_cid; + // TODO(b/342012881) Remove connection uuid when offload socket API is landed. + Uuid uuid = Uuid::From128BitBE(bluetooth::os::GenerateRandom<Uuid::kNumBytes128>()); + accept_rs->conn_uuid = uuid; + accept_rs->socket_id = btif_l2cap_sock_generate_socket_id(); + accept_rs->data_path = sock->data_path; + strncpy(accept_rs->socket_name, sock->socket_name, sizeof(accept_rs->socket_name) - 1); + accept_rs->socket_name[sizeof(accept_rs->socket_name) - 1] = '\0'; + accept_rs->hub_id = sock->hub_id; + accept_rs->endpoint_id = sock->endpoint_id; + accept_rs->listen_fd = sock->our_fd; + + /* Swap IDs to hand over the GAP connection to the accepted socket, and start + a new server on the newly create socket ID. */ + uint32_t new_listen_id = accept_rs->id; + accept_rs->id = sock->id; + sock->id = new_listen_id; + + log::info( + "Connected to L2CAP connection for device: {}, channel: {}, app_uid: {}, " + "id: {}, is_le: {}, socket_id: {}, rx_mtu: {}", + sock->addr, sock->channel, sock->app_uid, sock->id, sock->is_le_coc, accept_rs->socket_id, + accept_rs->rx_mtu); + btif_sock_connection_logger(accept_rs->addr, accept_rs->id, + accept_rs->is_le_coc ? BTSOCK_L2CAP_LE : BTSOCK_L2CAP, + SOCKET_CONNECTION_STATE_CONNECTED, + accept_rs->server ? SOCKET_ROLE_LISTEN : SOCKET_ROLE_CONNECTION, + accept_rs->app_uid, accept_rs->channel, 0, 0, accept_rs->name); + + bluetooth::hal::SocketContext socket_context = { + .socket_id = accept_rs->socket_id, + .name = accept_rs->socket_name, + .acl_connection_handle = p_open->acl_handle, + .channel_info = bluetooth::hal::LeCocChannelInfo( + p_open->local_cid, p_open->remote_cid, static_cast<uint16_t>(accept_rs->channel), + accept_rs->rx_mtu, accept_rs->tx_mtu, p_open->local_coc_mps, + p_open->remote_coc_mps, p_open->local_coc_credit, p_open->remote_coc_credit), + .endpoint_info.hub_id = accept_rs->hub_id, + .endpoint_info.endpoint_id = accept_rs->endpoint_id, + }; + if (!sock->is_accepting) { + log::warn("Server socket is not accepting. Disconnect the incoming connection."); + btsock_l2cap_free_l(accept_rs); + } else if (!bluetooth::shim::GetLppOffloadManager()->SocketOpened(socket_context)) { + log::warn("L2CAP socket opened failed. Disconnect the incoming connection."); + btsock_l2cap_free_l(accept_rs); + } else { + log::info("L2CAP socket opened successful. Will send connect signal in async callback."); + } + // start monitor the socket + btsock_thread_add_fd(pth, sock->our_fd, BTSOCK_L2CAP, SOCK_THREAD_FD_EXCEPTION, sock->id); + btsock_l2cap_server_listen(sock); + // start monitoring the socketpair to get call back when app is accepting on server socket + btsock_thread_add_fd(pth, sock->our_fd, BTSOCK_L2CAP, SOCK_THREAD_FD_RD, sock->id); +} diff --git a/system/btif/src/btif_sock_rfc.cc b/system/btif/src/btif_sock_rfc.cc index e8bf6b42fe..0f9daa8f51 100644 --- a/system/btif/src/btif_sock_rfc.cc +++ b/system/btif/src/btif_sock_rfc.cc @@ -510,6 +510,7 @@ static bool send_app_connect_signal(int fd, const RawAddress* addr, int channel, cs.max_tx_packet_size = 0; // not used for RFCOMM cs.conn_uuid_lsb = 0; // not used for RFCOMM cs.conn_uuid_msb = 0; // not used for RFCOMM + cs.socket_id = 0; // not used for RFCOMM if (send_fd == INVALID_FD) { return sock_send_all(fd, (const uint8_t*)&cs, sizeof(cs)) == sizeof(cs); } diff --git a/system/btif/test/btif_core_test.cc b/system/btif/test/btif_core_test.cc index 8d78ce68cf..66881a92d8 100644 --- a/system/btif/test/btif_core_test.cc +++ b/system/btif/test/btif_core_test.cc @@ -1022,9 +1022,14 @@ TEST_F(BtifCoreSocketTest, CreateRfcommServerSocket) { static constexpr int kAppUid = 3; const Uuid server_uuid = Uuid::From16Bit(UUID_SERVCLASS_SERIAL_PORT); int socket_number = 0; + btsock_data_path_t data_path = BTSOCK_DATA_PATH_NO_OFFLOAD; + uint64_t hub_id = 0; + uint64_t endpoint_id = 0; + int max_rx_packet_size = 0; ASSERT_EQ(BT_STATUS_SUCCESS, - btif_sock_get_interface()->listen(BTSOCK_RFCOMM, "TestService", &server_uuid, - kChannelOne, &socket_number, kFlags, kAppUid)); + btif_sock_get_interface()->listen( + BTSOCK_RFCOMM, "TestService", &server_uuid, kChannelOne, &socket_number, kFlags, + kAppUid, data_path, "TestSocket", hub_id, endpoint_id, max_rx_packet_size)); } TEST_F(BtifCoreSocketTest, CreateTwoRfcommServerSockets) { @@ -1033,9 +1038,14 @@ TEST_F(BtifCoreSocketTest, CreateTwoRfcommServerSockets) { static constexpr int kAppUid = 3; const Uuid server_uuid = Uuid::From16Bit(UUID_SERVCLASS_SERIAL_PORT); int socket_number = 0; + btsock_data_path_t data_path = BTSOCK_DATA_PATH_NO_OFFLOAD; + uint64_t hub_id = 0; + uint64_t endpoint_id = 0; + int max_rx_packet_size = 0; ASSERT_EQ(BT_STATUS_SUCCESS, - btif_sock_get_interface()->listen(BTSOCK_RFCOMM, "TestService", &server_uuid, - kChannelOne, &socket_number, kFlags, kAppUid)); + btif_sock_get_interface()->listen( + BTSOCK_RFCOMM, "TestService", &server_uuid, kChannelOne, &socket_number, kFlags, + kAppUid, data_path, "TestSocket", hub_id, endpoint_id, max_rx_packet_size)); static constexpr int kChannelTwo = 2; static constexpr int kFlagsTwo = 4; static constexpr int kAppUidTwo = 6; @@ -1043,7 +1053,8 @@ TEST_F(BtifCoreSocketTest, CreateTwoRfcommServerSockets) { int socket_number_two = 1; ASSERT_EQ(BT_STATUS_SUCCESS, btif_sock_get_interface()->listen( BTSOCK_RFCOMM, "ServiceTwo", &server_uuid_two, kChannelTwo, - &socket_number_two, kFlagsTwo, kAppUidTwo)); + &socket_number_two, kFlagsTwo, kAppUidTwo, data_path, + "TestSocket", hub_id, endpoint_id, max_rx_packet_size)); } TEST_F(BtifCoreSocketTest, CreateManyRfcommServerSockets) { @@ -1059,9 +1070,14 @@ TEST_F(BtifCoreSocketTest, CreateManyRfcommServerSockets) { server_uuid_str[1] = (i / 100) % 10 + '0'; server_uuid_str[0] = (i / 1000) % 10 + '0'; Uuid server_uuid = Uuid::FromString(server_uuid_str); + btsock_data_path_t data_path = BTSOCK_DATA_PATH_NO_OFFLOAD; + uint64_t hub_id = 0; + uint64_t endpoint_id = 0; + int max_rx_packet_size = 0; ASSERT_EQ(BT_STATUS_SUCCESS, - btif_sock_get_interface()->listen(BTSOCK_RFCOMM, "TestService", &server_uuid, channel, - &socket_number, flags, app_uuid)); + btif_sock_get_interface()->listen( + BTSOCK_RFCOMM, "TestService", &server_uuid, channel, &socket_number, flags, + app_uuid, data_path, "TestSocket", hub_id, endpoint_id, max_rx_packet_size)); ASSERT_EQ(0, close(socket_number)); } } diff --git a/system/gd/Android.bp b/system/gd/Android.bp index d128823eed..a4c2120ddd 100644 --- a/system/gd/Android.bp +++ b/system/gd/Android.bp @@ -87,6 +87,7 @@ cc_defaults { srcs: [ ":BluetoothHalSources_hci_host", ":BluetoothHalSources_ranging_host", + ":BluetoothHalSources_socket_host", ":BluetoothOsSources_host", ":BluetoothSyspropsSources", ], @@ -95,6 +96,7 @@ cc_defaults { srcs: [ ":BluetoothHalSources_hci_android_hidl", ":BluetoothHalSources_ranging_android", + ":BluetoothHalSources_socket_android", ":BluetoothOsSources_android", ], shared_libs: [ @@ -117,6 +119,7 @@ cc_defaults { whole_static_libs: [ "android.hardware.bluetooth-V1-ndk", "android.hardware.bluetooth.ranging-V2-ndk", + "android.hardware.bluetooth.socket-V1-ndk", ], }, }, @@ -124,6 +127,7 @@ cc_defaults { ":BluetoothCommonSources", ":BluetoothHalSources", ":BluetoothHciSources", + ":BluetoothLppOffloadSources", ":BluetoothMetricsSources", ":BluetoothNeighborSources", ":BluetoothOsSources", diff --git a/system/gd/BUILD.gn b/system/gd/BUILD.gn index fc4b808fb7..fc51ba806f 100644 --- a/system/gd/BUILD.gn +++ b/system/gd/BUILD.gn @@ -64,6 +64,7 @@ static_library("libbluetooth_gd") { "//bt/system/gd/hal:BluetoothHalSources", "//bt/system/gd/hal:BluetoothHalSources_hci_host", "//bt/system/gd/hal:BluetoothHalSources_ranging_host", + "//bt/system/gd/hal:BluetoothHalSources_socket_host", "//bt/system/gd/metrics:BluetoothMetricsSources", "//bt/system/gd/neighbor:BluetoothNeighborSources", "//bt/system/gd/shim:BluetoothShimSources", diff --git a/system/gd/hal/Android.bp b/system/gd/hal/Android.bp index 91e1573c0e..49683afbd1 100644 --- a/system/gd/hal/Android.bp +++ b/system/gd/hal/Android.bp @@ -43,6 +43,20 @@ filegroup { } filegroup { + name: "BluetoothHalSources_socket_android", + srcs: [ + "socket_hal_android.cc", + ], +} + +filegroup { + name: "BluetoothHalSources_socket_host", + srcs: [ + "socket_hal_host.cc", + ], +} + +filegroup { name: "BluetoothHalSources_hci_android_hidl", srcs: [ "hci_backend_aidl.cc", diff --git a/system/gd/hal/BUILD.gn b/system/gd/hal/BUILD.gn index 46be6e9d8a..ebf15255af 100644 --- a/system/gd/hal/BUILD.gn +++ b/system/gd/hal/BUILD.gn @@ -53,3 +53,12 @@ source_set("BluetoothHalSources_ranging_host") { configs += [ "//bt/system/gd:gd_defaults" ] deps = [ "//bt/system/gd:gd_default_deps" ] } + +source_set("BluetoothHalSources_socket_host") { + sources = [ + "socket_hal_host.cc", + ] + + configs += [ "//bt/system/gd:gd_defaults" ] + deps = [ "//bt/system/gd:gd_default_deps" ] +} diff --git a/system/gd/hal/socket_hal.h b/system/gd/hal/socket_hal.h new file mode 100644 index 0000000000..2f3d583d30 --- /dev/null +++ b/system/gd/hal/socket_hal.h @@ -0,0 +1,174 @@ +/* + * Copyright 2024 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. + */ + +#pragma once + +#include <cstdint> + +#include "module.h" + +namespace bluetooth::hal { + +enum SocketStatus { + SUCCESS = 0, + FAILURE, +}; + +struct EndpointInfo { + // The ID of the Hub to which the end point belongs for hardware offload data path. + uint64_t hub_id; + + // The ID of the Hub endpoint for hardware offload data path. + uint64_t endpoint_id; +}; + +struct LeCocCapabilities { + // Maximum number of LE COC sockets supported. If not supported, the value must be zero. + int number_of_supported_sockets; + + // Local Maximum Transmission Unit size in octets. + uint16_t mtu; +}; + +struct SocketCapabilities { + LeCocCapabilities le_coc_capabilities; +}; + +struct LeCocChannelInfo { + // L2cap local channel ID. + uint16_t local_cid; + + // L2cap remote channel ID. + uint16_t remote_cid; + + // PSM for L2CAP LE CoC. + uint16_t psm; + + // Local Maximum Transmission Unit for LE COC specifying the maximum SDU size in bytes that the + // local L2CAP layer can receive. + uint16_t local_mtu; + + // Remote Maximum Transmission Unit for LE COC specifying the maximum SDU size in bytes that the + // remote L2CAP layer can receive. + uint16_t remote_mtu; + + // Local Maximum PDU payload Size in bytes that the local L2CAP layer can receive. + uint16_t local_mps; + + // Remote Maximum PDU payload Size in bytes that the remote L2CAP layer can receive. + uint16_t remote_mps; + + // Protocol initial credits at Rx path. + uint16_t initial_rx_credits; + + // Protocol initial credits at Tx path. + uint16_t initial_tx_credits; +}; + +struct SocketContext { + // Identifier assigned to the socket by the host stack when the socket is connected. + uint64_t socket_id; + + // Descriptive socket name provided by the host app when it created this socket. + std::string name; + + // ACL connection handle for the socket. + uint16_t acl_connection_handle; + + // Channel information of different protocol used for the socket. + std::variant<LeCocChannelInfo> channel_info; + + // Endpoint information. + EndpointInfo endpoint_info; +}; + +/** + * SocketHalCallback provides an interface for receiving asynchronous events from socket HAL. + * Implementations of this class can be registered with the stack to receive these callbacks. + * + * Callback methods in this interface are invoked from the binder thread. This means that + * implementations must be thread-safe and handle any necessary synchronization to avoid race + * conditions or other concurrency issues. The callee is solely responsible for ensuring thread + * safety within the callback methods. + */ +class SocketHalCallback { +public: + virtual ~SocketHalCallback() = default; + + /** + * Invoked when IBluetoothSocket.opened() has been completed. + * + * @param socket_id Identifier assigned to the socket by the host stack + * @param status Status indicating success or failure + */ + virtual void SocketOpenedComplete(uint64_t socket_id, SocketStatus status) const = 0; + + /** + * Invoked when offload app or stack requests host stack to close the socket. + * + * @param socket_id Identifier assigned to the socket by the host stack + */ + virtual void SocketClose(uint64_t socket_id) const = 0; +}; + +/** + * SocketHal provides an interface to low-power processors, enabling Bluetooth Offload Socket + * functionality. + * + * Bluetooth Offload Socket allows the transfer of channel information from an established + * BluetoothSocket to a low-power processor. This enables the offload stack on the low-power + * processor to handle packet reception, processing, and transmission independently. This offloading + * process prevents the need to wake the main application processor, improving power efficiency. + */ +class SocketHal : public ::bluetooth::Module { +public: + static const ModuleFactory Factory; + + virtual ~SocketHal() = default; + + /** + * Registers a socket hal callback function to receive asynchronous events from socket HAL. + * + * @param callback A pointer to the callback function. Must not be nullptr and must have static + * lifetime. + * @return True if the callback was successfully registered, false otherwise. + */ + virtual bool RegisterCallback(hal::SocketHalCallback const* callback) = 0; + + /** + * Retrieves the supported offloaded socket capabilities. + * + * @return Supported socket capabilities + */ + virtual hal::SocketCapabilities GetSocketCapabilities() const = 0; + + /** + * Notifies the socket HAL that the socket has been opened. + * + * @param context Socket context including socket ID, channel, hub, and endpoint info + * @return Result of calling this method + */ + virtual bool Opened(const hal::SocketContext& context) const = 0; + + /** + * Notifies the socket HAL that the socket has been closed. + * + * @param socket_id Identifier assigned to the socket by the host stack + */ + virtual void Closed(uint64_t socket_id) const = 0; +}; + +} // namespace bluetooth::hal diff --git a/system/gd/hal/socket_hal_android.cc b/system/gd/hal/socket_hal_android.cc new file mode 100644 index 0000000000..5e23974897 --- /dev/null +++ b/system/gd/hal/socket_hal_android.cc @@ -0,0 +1,236 @@ +/* + * Copyright 2024 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. + */ + +#include <aidl/android/hardware/bluetooth/socket/BnBluetoothSocketCallback.h> +#include <aidl/android/hardware/bluetooth/socket/IBluetoothSocket.h> +#include <aidl/android/hardware/bluetooth/socket/IBluetoothSocketCallback.h> +#include <android/binder_manager.h> +#include <bluetooth/log.h> + +// syslog.h conflicts with libchrome/base/logging.h +#undef LOG_DEBUG +#undef LOG_INFO +#undef LOG_WARNING + +#include "hal/socket_hal.h" + +using ::aidl::android::hardware::bluetooth::socket::BnBluetoothSocketCallback; +using ::aidl::android::hardware::bluetooth::socket::IBluetoothSocket; + +namespace bluetooth::hal { + +constexpr uint16_t kLeCocMtuMin = 23; +constexpr uint16_t kLeCocMtuMax = 65535; + +class SocketAidlCallback : public BnBluetoothSocketCallback { + class : public hal::SocketHalCallback { + public: + void SocketOpenedComplete(uint64_t /* socket_id */, + hal::SocketStatus /* status */) const override { + log::warn("Dropping SocketOpenedComplete event, since callback is not set"); + } + + void SocketClose(uint64_t /* socket_id */) const override { + log::warn("Dropping SocketClose event, since callback is not set"); + } + } kNullCallbacks; + +public: + SocketAidlCallback() = default; + + void SetCallback(hal::SocketHalCallback const* callback) { + log::assert_that(callback != nullptr, "callback != nullptr"); + socket_hal_cb_ = callback; + } + + ::ndk::ScopedAStatus openedComplete(int64_t socket_id, + ::aidl::android::hardware::bluetooth::socket::Status status, + const std::string& reason) override { + log::info("socket_id: {} status: {} reason: {}", socket_id, static_cast<int>(status), reason); + socket_hal_cb_->SocketOpenedComplete( + socket_id, status == ::aidl::android::hardware::bluetooth::socket::Status::SUCCESS + ? hal::SocketStatus::SUCCESS + : hal::SocketStatus::FAILURE); + return ::ndk::ScopedAStatus::ok(); + } + + ::ndk::ScopedAStatus close(int64_t socket_id, const std::string& reason) override { + log::info("socket_id: {} reason: {}", socket_id, reason); + socket_hal_cb_->SocketClose(socket_id); + return ::ndk::ScopedAStatus::ok(); + } + +private: + hal::SocketHalCallback const* socket_hal_cb_ = &kNullCallbacks; +}; + +class SocketHalAndroid : public SocketHal { +public: + bool IsBound() const { return socket_hal_instance_ != nullptr; } + +protected: + void ListDependencies(ModuleList* /*list*/) const {} + + void Start() override { + std::string instance = std::string() + IBluetoothSocket::descriptor + "/default"; + if (!AServiceManager_isDeclared(instance.c_str())) { + log::error("The service {} is not declared", instance); + return; + } + + ::ndk::SpAIBinder binder(AServiceManager_waitForService(instance.c_str())); + socket_hal_instance_ = IBluetoothSocket::fromBinder(binder); + + if (socket_hal_instance_ == nullptr) { + log::error("Failed to bind to the service {}", instance); + return; + } + + socket_aidl_cb_ = ndk::SharedRefBase::make<SocketAidlCallback>(); + ::ndk::ScopedAStatus status = socket_hal_instance_->registerCallback(socket_aidl_cb_); + if (!status.isOk()) { + log::error("registerCallback failure: {}", status.getDescription()); + socket_hal_instance_ = nullptr; + return; + } + + death_recipient_ = + ::ndk::ScopedAIBinder_DeathRecipient(AIBinder_DeathRecipient_new([](void* /* cookie*/) { + log::error("The Socket HAL service died."); + // At shutdown, sometimes the HAL service gets killed before Bluetooth. + std::this_thread::sleep_for(std::chrono::seconds(1)); + log::fatal("Restarting Bluetooth after the socket HAL has died."); + })); + + auto death_link = AIBinder_linkToDeath(socket_hal_instance_->asBinder().get(), + death_recipient_.get(), this); + log::assert_that(death_link == STATUS_OK, + "Unable to set the death recipient for the Socket HAL"); + } + + void Stop() override { + if (IsBound()) { + auto death_unlink = AIBinder_unlinkToDeath(socket_hal_instance_->asBinder().get(), + death_recipient_.get(), this); + if (death_unlink != STATUS_OK) { + log::error("Error unlinking death recipient from the Socket HAL"); + } + socket_hal_instance_ = nullptr; + } + } + + std::string ToString() const override { return std::string("SocketHalAndroid"); } + + hal::SocketCapabilities GetSocketCapabilities() const override { + if (!IsBound()) { + return {}; + } + ::aidl::android::hardware::bluetooth::socket::SocketCapabilities socket_capabilities; + ::ndk::ScopedAStatus status = socket_hal_instance_->getSocketCapabilities(&socket_capabilities); + if (!status.isOk()) { + log::info("Failed to get socket capabilities"); + return {}; + } + if (socket_capabilities.leCocCapabilities.numberOfSupportedSockets < 0) { + log::error("Invalid leCocCapabilities.numberOfSupportedSockets: {}", + socket_capabilities.leCocCapabilities.numberOfSupportedSockets); + return {}; + } + if (socket_capabilities.leCocCapabilities.numberOfSupportedSockets) { + if (socket_capabilities.leCocCapabilities.mtu < kLeCocMtuMin || + socket_capabilities.leCocCapabilities.mtu > kLeCocMtuMax) { + log::error("Invalid leCocCapabilities.mtu: {}", socket_capabilities.leCocCapabilities.mtu); + return {}; + } + } + log::info("le_coc_capabilities number_of_supported_sockets: {}, mtu: {}", + socket_capabilities.leCocCapabilities.numberOfSupportedSockets, + socket_capabilities.leCocCapabilities.mtu); + return hal::SocketCapabilities{ + .le_coc_capabilities.number_of_supported_sockets = + socket_capabilities.leCocCapabilities.numberOfSupportedSockets, + .le_coc_capabilities.mtu = + static_cast<uint16_t>(socket_capabilities.leCocCapabilities.mtu)}; + } + + bool RegisterCallback(hal::SocketHalCallback const* callback) override { + if (!IsBound()) { + return false; + } + socket_aidl_cb_->SetCallback(callback); + return true; + } + + bool Opened(const hal::SocketContext& context) const override { + if (!IsBound()) { + return false; + } + log::info("socket_id: {}, name: {}, acl_connection_handle: {}, hub_id: {}, endpoint_id: {}", + context.socket_id, context.name, context.acl_connection_handle, + context.endpoint_info.hub_id, context.endpoint_info.endpoint_id); + ::aidl::android::hardware::bluetooth::socket::SocketContext hal_context = { + .socketId = static_cast<int64_t>(context.socket_id), + .name = context.name, + .aclConnectionHandle = context.acl_connection_handle, + .hubId = static_cast<int64_t>(context.endpoint_info.hub_id), + .endpointId = static_cast<int64_t>(context.endpoint_info.endpoint_id), + }; + if (std::holds_alternative<hal::LeCocChannelInfo>(context.channel_info)) { + auto& le_coc_context = std::get<hal::LeCocChannelInfo>(context.channel_info); + hal_context.channelInfo = ::aidl::android::hardware::bluetooth::socket::LeCocChannelInfo( + le_coc_context.local_cid, le_coc_context.remote_cid, le_coc_context.psm, + le_coc_context.local_mtu, le_coc_context.remote_mtu, le_coc_context.local_mps, + le_coc_context.remote_mps, le_coc_context.initial_rx_credits, + le_coc_context.initial_tx_credits); + log::info( + "le_coc local_cid: {}, remote_cid: {}, psm: {}, local_mtu: {}, remote_mtu: {}, " + "local_mps: {}, remote_mps: {}, initial_rx_credits: {}, initial_tx_credits: {}", + le_coc_context.local_cid, le_coc_context.remote_cid, le_coc_context.psm, + le_coc_context.local_mtu, le_coc_context.remote_mtu, le_coc_context.local_mps, + le_coc_context.remote_mps, le_coc_context.initial_rx_credits, + le_coc_context.initial_tx_credits); + } else { + log::error("Unsupported protocol"); + return false; + } + ::ndk::ScopedAStatus status = socket_hal_instance_->opened(hal_context); + if (!status.isOk()) { + log::error("Opened failure: {}", status.getDescription()); + return false; + } + return true; + } + + void Closed(uint64_t socket_id) const override { + if (!IsBound()) { + return; + } + log::info("socket_id: {}", socket_id); + ::ndk::ScopedAStatus status = socket_hal_instance_->closed(socket_id); + if (!status.isOk()) { + log::info("Closed failure: {}", status.getDescription()); + } + } + +private: + std::shared_ptr<IBluetoothSocket> socket_hal_instance_; + std::shared_ptr<SocketAidlCallback> socket_aidl_cb_; + ::ndk::ScopedAIBinder_DeathRecipient death_recipient_; +}; + +const ModuleFactory SocketHal::Factory = ModuleFactory([]() { return new SocketHalAndroid(); }); + +} // namespace bluetooth::hal diff --git a/system/gd/hal/socket_hal_host.cc b/system/gd/hal/socket_hal_host.cc new file mode 100644 index 0000000000..292428a821 --- /dev/null +++ b/system/gd/hal/socket_hal_host.cc @@ -0,0 +1,44 @@ +/* + * Copyright 2024 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. + */ +#include "hal/socket_hal.h" + +namespace bluetooth::hal { + +class SocketHalHost : public SocketHal { +protected: + void ListDependencies(ModuleList* /*list*/) const {} + + void Start() override {} + + void Stop() override { socket_hal_cb_ = nullptr; } + + std::string ToString() const override { return std::string("SocketHalHost"); } + + hal::SocketCapabilities GetSocketCapabilities() const override { return {}; } + + bool RegisterCallback(hal::SocketHalCallback const* /*callback*/) override { return false; } + + bool Opened(const hal::SocketContext& /*context*/) const override { return false; } + + void Closed(uint64_t /*socket_id*/) const override {} + +private: + hal::SocketHalCallback* socket_hal_cb_; +}; + +const ModuleFactory SocketHal::Factory = ModuleFactory([]() { return new SocketHalHost(); }); + +} // namespace bluetooth::hal diff --git a/system/gd/lpp/Android.bp b/system/gd/lpp/Android.bp new file mode 100644 index 0000000000..2be7b85d9e --- /dev/null +++ b/system/gd/lpp/Android.bp @@ -0,0 +1,15 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "system_bt_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["system_bt_license"], +} + +filegroup { + name: "BluetoothLppOffloadSources", + srcs: [ + "lpp_offload_manager.cc", + ], +} diff --git a/system/gd/lpp/BUILD.gn b/system/gd/lpp/BUILD.gn new file mode 100644 index 0000000000..b773001ce8 --- /dev/null +++ b/system/gd/lpp/BUILD.gn @@ -0,0 +1,31 @@ +# +# Copyright 2024 Google, Inc. +# +# 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. + +source_set("BluetoothLppOffloadSources") { + sources = [ + "lpp_offload_manager.cc", + ] + + include_dirs = [ "//bt/system/gd" ] + + deps = [ + "//bt/system/gd:gd_default_deps", + ] + + configs += [ + "//bt/system:target_defaults", + "//bt/system/log:log_defaults", + ] +} diff --git a/system/gd/lpp/lpp_offload_interface.h b/system/gd/lpp/lpp_offload_interface.h new file mode 100644 index 0000000000..388466f2e5 --- /dev/null +++ b/system/gd/lpp/lpp_offload_interface.h @@ -0,0 +1,75 @@ +/* + * Copyright 2024 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. + */ +#pragma once + +#include "hal/socket_hal.h" + +namespace bluetooth::lpp { + +/** + * Interface to low-power processors (LPPs) for supporting LPP offload features. + * + * This interface allows inheritance from multiple offload HAL interfaces, enabling a unified + * offload function management approach through a single interface accessible from the upper layer. + */ +class LppOffloadInterface { +public: + LppOffloadInterface() = default; + + virtual ~LppOffloadInterface() = default; + + LppOffloadInterface(const LppOffloadInterface&) = delete; + + LppOffloadInterface& operator=(const LppOffloadInterface&) = delete; + + /** + * Registers a socket hal callback function to receive asynchronous events from socket HAL. + * + * The provided callback function must be executed on the main thread. + * + * @param callback A pointer to the callback function. Must not be nullptr and must have static + * lifetime. + * @return True if the callback was successfully registered, false otherwise. + */ + virtual bool RegisterSocketHalCallback(hal::SocketHalCallback* callbacks) = 0; + + /** + * Retrieves the supported offload socket capabilities. + * + * @return Supported socket capabilities + */ + virtual hal::SocketCapabilities GetSocketCapabilities() const = 0; + + /** + * Notifies the socket HAL that the socket has been opened. + * + * If this method returns true, SocketHalCallback.SocketOpenedComplete() shall be called to + * indicate the result of this operation. + * + * @param context Socket context including socket ID, channel, hub, and endpoint info + * @return True if calling this method was successful, false otherwise + */ + virtual bool SocketOpened(const hal::SocketContext& context) = 0; + + /** + * Notifies the socket HAL that the socket has been closed. + * + * @param socket_id Identifier assigned to the socket by the host stack + */ + virtual void SocketClosed(uint64_t socket_id) = 0; +}; + +} // namespace bluetooth::lpp diff --git a/system/gd/lpp/lpp_offload_interface_mock.h b/system/gd/lpp/lpp_offload_interface_mock.h new file mode 100644 index 0000000000..03cd5f4f52 --- /dev/null +++ b/system/gd/lpp/lpp_offload_interface_mock.h @@ -0,0 +1,33 @@ +/* + * Copyright 2024 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. + */ +#pragma once + +#include <gmock/gmock.h> + +#include "lpp/lpp_offload_interface.h" + +// Unit test interfaces +namespace bluetooth::lpp::testing { + +class MockLppOffloadInterface : public LppOffloadInterface { +public: + MOCK_METHOD(bool, RegisterSocketHalCallback, (hal::SocketHalCallback*), (override)); + MOCK_METHOD(hal::SocketCapabilities, GetSocketCapabilities, (), (const override)); + MOCK_METHOD(bool, SocketOpened, (const hal::SocketContext&), (override)); + MOCK_METHOD(void, SocketClosed, (uint64_t), (override)); +}; + +} // namespace bluetooth::lpp::testing diff --git a/system/gd/lpp/lpp_offload_manager.cc b/system/gd/lpp/lpp_offload_manager.cc new file mode 100644 index 0000000000..c5c1fb247e --- /dev/null +++ b/system/gd/lpp/lpp_offload_manager.cc @@ -0,0 +1,100 @@ +/* + * Copyright 2024 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. + */ +#include "lpp_offload_manager.h" + +#include <bluetooth/log.h> + +#include <string> + +#include "hal/socket_hal.h" +#include "module.h" +#include "os/handler.h" +#include "os/system_properties.h" + +namespace bluetooth::lpp { + +const ModuleFactory LppOffloadManager::Factory = + ModuleFactory([]() { return new LppOffloadManager(); }); + +struct LppOffloadManager::impl { + ~impl() {} + + void start(os::Handler* handler, hal::SocketHal* socket_hal) { + log::info(""); + handler_ = handler; + socket_hal_ = socket_hal; + socket_capabilities_ = socket_hal_->GetSocketCapabilities(); + } + + void stop() { + log::info(""); + socket_capabilities_ = {}; + } + + bool register_socket_hal_callbacks(hal::SocketHalCallback* callbacks) { + log::info(""); + return socket_hal_->RegisterCallback(callbacks); + } + + hal::SocketCapabilities get_socket_capabilities() const { + log::info(""); + return socket_capabilities_; + } + + bool socket_opened(const hal::SocketContext& context) { + log::info("socket_id: {}", context.socket_id); + return socket_hal_->Opened(context); + } + + void socket_closed(uint64_t socket_id) { + log::info("socket_id: {}", socket_id); + return socket_hal_->Closed(socket_id); + } + + os::Handler* handler_; + hal::SocketHal* socket_hal_; + hal::SocketCapabilities socket_capabilities_; +}; + +LppOffloadManager::LppOffloadManager() { pimpl_ = std::make_unique<impl>(); } + +LppOffloadManager::~LppOffloadManager() = default; + +void LppOffloadManager::ListDependencies(ModuleList* list) const { list->add<hal::SocketHal>(); } + +void LppOffloadManager::Start() { pimpl_->start(GetHandler(), GetDependency<hal::SocketHal>()); } + +void LppOffloadManager::Stop() { pimpl_->stop(); } + +std::string LppOffloadManager::ToString() const { return "Low Power Processor Offload Manager"; } + +bool LppOffloadManager::RegisterSocketHalCallback(hal::SocketHalCallback* callbacks) { + return pimpl_->register_socket_hal_callbacks(callbacks); +} + +hal::SocketCapabilities LppOffloadManager::GetSocketCapabilities() const { + return pimpl_->get_socket_capabilities(); +} + +bool LppOffloadManager::SocketOpened(const hal::SocketContext& context) { + return pimpl_->socket_opened(context); +} + +void LppOffloadManager::SocketClosed(uint64_t socket_id) { + CallOn(pimpl_.get(), &impl::socket_closed, socket_id); +} + +} // namespace bluetooth::lpp diff --git a/system/gd/lpp/lpp_offload_manager.h b/system/gd/lpp/lpp_offload_manager.h new file mode 100644 index 0000000000..90097f77bd --- /dev/null +++ b/system/gd/lpp/lpp_offload_manager.h @@ -0,0 +1,62 @@ +/* + * Copyright 2024 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. + */ +#pragma once + +#include <bluetooth/log.h> + +#include <memory> +#include <string> + +#include "lpp_offload_interface.h" +#include "module.h" + +namespace bluetooth::lpp { + +class LppOffloadManager : public bluetooth::Module, public LppOffloadInterface { +public: + LppOffloadManager(); + + LppOffloadManager(const LppOffloadManager&) = delete; + + LppOffloadManager& operator=(const LppOffloadManager&) = delete; + + ~LppOffloadManager(); + + bool RegisterSocketHalCallback(hal::SocketHalCallback* callbacks) override; + + hal::SocketCapabilities GetSocketCapabilities() const override; + + bool SocketOpened(const hal::SocketContext& context) override; + + void SocketClosed(uint64_t socket_id) override; + + static const ModuleFactory Factory; + +protected: + void ListDependencies(ModuleList* list) const override; + + void Start() override; + + void Stop() override; + + std::string ToString() const override; + +private: + struct impl; + std::unique_ptr<impl> pimpl_; +}; + +} // namespace bluetooth::lpp diff --git a/system/gd/os/rand.h b/system/gd/os/rand.h index 04e29d30f4..03f29bc815 100644 --- a/system/gd/os/rand.h +++ b/system/gd/os/rand.h @@ -40,5 +40,12 @@ inline uint32_t GenerateRandom() { return ret; } +inline uint64_t GenerateRandomUint64() { + uint64_t ret{}; + log::assert_that(RAND_bytes((uint8_t*)(&ret), sizeof(uint64_t)) == 1, + "assert failed: RAND_bytes((uint8_t*)(&ret), sizeof(uint64_t)) == 1"); + return ret; +} + } // namespace os } // namespace bluetooth diff --git a/system/gd/rust/topshim/src/profiles/socket.rs b/system/gd/rust/topshim/src/profiles/socket.rs index 49a0f15cde..d8cfd4590a 100644 --- a/system/gd/rust/topshim/src/profiles/socket.rs +++ b/system/gd/rust/topshim/src/profiles/socket.rs @@ -164,6 +164,12 @@ impl BtSocket { let name = CString::new(service_name).expect("Service name has null in it."); let name_ptr = LTCheckedPtr::from(&name); + let data_path: u32 = 0; + let sock_name = CString::new("test").expect("Socket name has null in it"); + let hub_id: u64 = 0; + let endpoint_id: u64 = 0; + let max_rx_packet_size: i32 = 0; + let status: BtStatus = ccall!( self, listen, @@ -173,7 +179,12 @@ impl BtSocket { channel, sockfd_ptr.into(), flags, - calling_uid + calling_uid, + data_path, + sock_name.as_ptr(), + hub_id, + endpoint_id, + max_rx_packet_size ) .into(); @@ -194,6 +205,12 @@ impl BtSocket { let uuid_ptr = LTCheckedPtr::from(&service_uuid); let addr_ptr = LTCheckedPtr::from_ref(&addr); + let data_path: u32 = 0; + let sock_name = CString::new("test").expect("Socket name has null in it"); + let hub_id: u64 = 0; + let endpoint_id: u64 = 0; + let max_rx_packet_size: i32 = 0; + let status: BtStatus = ccall!( self, connect, @@ -203,7 +220,12 @@ impl BtSocket { channel, sockfd_ptr.into(), flags, - calling_uid + calling_uid, + data_path, + sock_name.as_ptr(), + hub_id, + endpoint_id, + max_rx_packet_size ) .into(); @@ -274,6 +296,7 @@ mod tests { max_rx_packet_size: 17_u16, conn_uuid_lsb: 0x0000113500001135_u64, conn_uuid_msb: 0x1135000011350000_u64, + socket_id: 0x1135113511351135_u64, }; // SAFETY: The sock_connect_signal_t type has size CONNECT_COMPLETE_SIZE, // and has no padding, so it's safe to convert it to a byte array. diff --git a/system/include/hardware/bluetooth.h b/system/include/hardware/bluetooth.h index 02a8f51778..1be80e144b 100644 --- a/system/include/hardware/bluetooth.h +++ b/system/include/hardware/bluetooth.h @@ -238,6 +238,10 @@ typedef struct { bool le_channel_sounding_supported; } bt_local_le_features_t; +typedef struct { + uint8_t number_of_supported_offloaded_le_coc_sockets; +} bt_lpp_offload_features_t; + /** Bluetooth Vendor and Product ID info */ typedef struct { uint8_t vendor_id_src; @@ -415,6 +419,13 @@ typedef enum { */ BT_PROPERTY_REMOTE_MAX_SESSION_KEY_SIZE, + /** + * Description - Low power processor offload features + * Access mode - GET. + * Data Type - bt_lpp_offload_features_t. + */ + BT_PROPERTY_LPP_OFFLOAD_FEATURES, + BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP = 0xFF, } bt_property_type_t; diff --git a/system/include/hardware/bt_sock.h b/system/include/hardware/bt_sock.h index c7fff44daf..0436fad9cb 100644 --- a/system/include/hardware/bt_sock.h +++ b/system/include/hardware/bt_sock.h @@ -38,6 +38,18 @@ typedef enum { BTSOCK_L2CAP_LE = 4 } btsock_type_t; +/** + * Data path used for Bluetooth socket communication. + * + * NOTE: The values must be same as: + * - BluetoothSocketSettings.DATA_PATH_NO_OFFLOAD = 0 + * - BluetoothSocketSettings.DATA_PATH_HARDWARE_OFFLOAD = 1 + */ +typedef enum { + BTSOCK_DATA_PATH_NO_OFFLOAD = 0, + BTSOCK_DATA_PATH_HARDWARE_OFFLOAD = 1, +} btsock_data_path_t; + /** Represents the standard BT SOCKET interface. */ typedef struct { int16_t size; @@ -56,9 +68,17 @@ typedef struct { // The connection uuid. (L2CAP only) uint64_t conn_uuid_lsb; uint64_t conn_uuid_msb; + + // Socket ID in connected state + uint64_t socket_id; } __attribute__((packed)) sock_connect_signal_t; typedef struct { + uint16_t size; + uint16_t is_accepting; +} __attribute__((packed)) sock_accept_signal_t; + +typedef struct { /** set to size of this struct*/ size_t size; @@ -73,7 +93,8 @@ typedef struct { */ bt_status_t (*listen)(btsock_type_t type, const char* service_name, const bluetooth::Uuid* service_uuid, int channel, int* sock_fd, int flags, - int callingUid); + int callingUid, btsock_data_path_t data_path, const char* socket_name, + uint64_t hub_id, uint64_t endpoint_id, int max_rx_packet_size); /** * Connect to a RFCOMM UUID channel of remote device, It returns the socket fd @@ -83,7 +104,9 @@ typedef struct { * purposes. */ bt_status_t (*connect)(const RawAddress* bd_addr, btsock_type_t type, const bluetooth::Uuid* uuid, - int channel, int* sock_fd, int flags, int callingUid); + int channel, int* sock_fd, int flags, int callingUid, + btsock_data_path_t data_path, const char* socket_name, uint64_t hub_id, + uint64_t endpoint_id, int max_rx_packet_size); /** * Set the LE Data Length value to this connected peer to the @@ -128,6 +151,9 @@ __END_DECLS namespace std { template <> struct formatter<btsock_type_t> : enum_formatter<btsock_type_t> {}; + +template <> +struct formatter<btsock_data_path_t> : enum_formatter<btsock_data_path_t> {}; } // namespace std #endif // __has_include(<bluetooth/log.h>) diff --git a/system/main/shim/BUILD.gn b/system/main/shim/BUILD.gn index 5e73e0f22c..ff7bdae78a 100644 --- a/system/main/shim/BUILD.gn +++ b/system/main/shim/BUILD.gn @@ -31,6 +31,7 @@ source_set("BluetoothStackManagerSources") { deps = [ "//bt/system/gd/common:BluetoothCommonSources", "//bt/system/gd/hci:BluetoothHciSources", + "//bt/system/gd/lpp:BluetoothLppOffloadSources", "//bt/system/gd/os:BluetoothOsSources_linux_generic", "//bt/system/gd/packet:BluetoothPacketSources", "//bt/system/gd/rust/topshim:libbluetooth_topshim", @@ -75,6 +76,7 @@ source_set("LibBluetoothShimSources") { "//bt/sysprop:libcom.android.sysprop.bluetooth", "//bt/system/gd/common:BluetoothCommonSources", "//bt/system/gd/hci:BluetoothHciSources", + "//bt/system/gd/lpp:BluetoothLppOffloadSources", "//bt/system/gd/os:BluetoothOsSources_linux_generic", "//bt/system/gd/packet:BluetoothPacketSources", "//bt/system/gd/rust/topshim:libbluetooth_topshim", diff --git a/system/main/shim/entry.cc b/system/main/shim/entry.cc index a1c61b7ab0..8f0c6c6020 100644 --- a/system/main/shim/entry.cc +++ b/system/main/shim/entry.cc @@ -26,6 +26,7 @@ #include "hci/le_scanning_manager.h" #include "hci/msft.h" #include "hci/remote_name_request.h" +#include "lpp/lpp_offload_manager.h" #include "main/shim/stack.h" #include "metrics/counter_metrics.h" #include "os/handler.h" @@ -67,6 +68,10 @@ hal::SnoopLogger* GetSnoopLogger() { return Stack::GetInstance()->GetStackManager()->GetInstance<hal::SnoopLogger>(); } +lpp::LppOffloadInterface* GetLppOffloadManager() { + return Stack::GetInstance()->GetStackManager()->GetInstance<lpp::LppOffloadManager>(); +} + storage::StorageModule* GetStorage() { return Stack::GetInstance()->GetStackManager()->GetInstance<storage::StorageModule>(); } diff --git a/system/main/shim/entry.h b/system/main/shim/entry.h index e50cbc4d91..7e97c87716 100644 --- a/system/main/shim/entry.h +++ b/system/main/shim/entry.h @@ -48,6 +48,10 @@ class LeScanningManager; class MsftExtensionManager; } // namespace hci +namespace lpp { +class LppOffloadInterface; +} + namespace metrics { class CounterMetrics; } @@ -69,6 +73,7 @@ hci::HciInterface* GetHciLayer(); hci::RemoteNameRequestModule* GetRemoteNameRequest(); hci::DistanceMeasurementManager* GetDistanceMeasurementManager(); hci::LeScanningManager* GetScanning(); +lpp::LppOffloadInterface* GetLppOffloadManager(); hal::SnoopLogger* GetSnoopLogger(); storage::StorageModule* GetStorage(); hci::AclManager* GetAclManager(); diff --git a/system/main/shim/stack.cc b/system/main/shim/stack.cc index 73526bc4dd..3f99837a1d 100644 --- a/system/main/shim/stack.cc +++ b/system/main/shim/stack.cc @@ -36,6 +36,7 @@ #include "hci/le_scanning_manager.h" #include "hci/msft.h" #include "hci/remote_name_request.h" +#include "lpp/lpp_offload_manager.h" #include "main/shim/acl.h" #include "main/shim/acl_interface.h" #include "main/shim/distance_measurement_manager.h" @@ -74,6 +75,8 @@ void Stack::StartEverything() { #if TARGET_FLOSS modules.add<sysprops::SyspropsModule>(); +#else + modules.add<lpp::LppOffloadManager>(); #endif modules.add<metrics::CounterMetrics>(); modules.add<hal::HciHal>(); diff --git a/system/stack/gap/gap_conn.cc b/system/stack/gap/gap_conn.cc index fd988c8fd3..aad35dedfd 100644 --- a/system/stack/gap/gap_conn.cc +++ b/system/stack/gap/gap_conn.cc @@ -55,7 +55,9 @@ typedef struct { uint8_t service_id; /* Used by BTM */ uint16_t gap_handle; /* GAP handle */ - uint16_t connection_id; /* L2CAP CID */ + uint16_t local_cid; /* Local L2CAP CID */ + uint16_t remote_cid; /* Remote L2CAP CID */ + uint16_t acl_handle; /* ACL handle */ bool rem_addr_specified; uint8_t chan_mode_mask; /* Supported channel modes (FCR) */ RawAddress rem_dev_address; @@ -211,7 +213,8 @@ uint16_t GAP_ConnOpen(const char* /* p_serv_name */, uint8_t service_id, bool is /* Configure L2CAP COC, if transport is LE */ if (transport == BT_TRANSPORT_LE) { - p_ccb->local_coc_cfg.credits = L2CA_LeCreditDefault(); + p_ccb->local_coc_cfg.credits = + (p_ccb->cfg.init_credit_present) ? p_ccb->cfg.init_credit : L2CA_LeCreditDefault(); p_ccb->local_coc_cfg.mtu = p_cfg->mtu; uint16_t max_mps = bluetooth::shim::GetController()->GetLeBufferSize().le_data_packet_length_; @@ -284,7 +287,7 @@ uint16_t GAP_ConnOpen(const char* /* p_serv_name */, uint8_t service_id, bool is cid = stack::l2cap::get_interface().L2CA_ConnectReqWithSecurity(p_ccb->psm, *p_rem_bda, security); if (cid != 0) { - p_ccb->connection_id = cid; + p_ccb->local_cid = cid; return p_ccb->gap_handle; } log::warn("Unable to initiate connection peer:{} psm:{} transport:{}", *p_rem_bda, p_ccb->psm, @@ -295,7 +298,7 @@ uint16_t GAP_ConnOpen(const char* /* p_serv_name */, uint8_t service_id, bool is cid = stack::l2cap::get_interface().L2CA_ConnectLECocReq(p_ccb->psm, *p_rem_bda, &p_ccb->local_coc_cfg, security); if (cid != 0) { - p_ccb->connection_id = cid; + p_ccb->local_cid = cid; return p_ccb->gap_handle; } log::warn("Unable to initiate connection peer:{} psm:{} transport:{}", *p_rem_bda, p_ccb->psm, @@ -329,14 +332,14 @@ uint16_t GAP_ConnClose(uint16_t gap_handle) { /* Check if we have a connection ID */ if (p_ccb->con_state != GAP_CCB_STATE_LISTENING) { if (p_ccb->transport == BT_TRANSPORT_LE) { - if (!stack::l2cap::get_interface().L2CA_DisconnectLECocReq(p_ccb->connection_id)) { + if (!stack::l2cap::get_interface().L2CA_DisconnectLECocReq(p_ccb->local_cid)) { log::warn("Unable to request L2CAP disconnect le_coc peer:{} cid:{}", - p_ccb->rem_dev_address, p_ccb->connection_id); + p_ccb->rem_dev_address, p_ccb->local_cid); } } else { - if (!stack::l2cap::get_interface().L2CA_DisconnectReq(p_ccb->connection_id)) { + if (!stack::l2cap::get_interface().L2CA_DisconnectReq(p_ccb->local_cid)) { log::warn("Unable to request L2CAP disconnect peer:{} cid:{}", p_ccb->rem_dev_address, - p_ccb->connection_id); + p_ccb->local_cid); } } } @@ -451,9 +454,9 @@ static bool gap_try_write_queued_data(tGAP_CCB* p_ccb) { while ((p_buf = (BT_HDR*)fixed_queue_try_dequeue(p_ccb->tx_queue)) != NULL) { tL2CAP_DW_RESULT status; if (p_ccb->transport == BT_TRANSPORT_LE) { - status = stack::l2cap::get_interface().L2CA_LECocDataWrite(p_ccb->connection_id, p_buf); + status = stack::l2cap::get_interface().L2CA_LECocDataWrite(p_ccb->local_cid, p_buf); } else { - status = stack::l2cap::get_interface().L2CA_DataWrite(p_ccb->connection_id, p_buf); + status = stack::l2cap::get_interface().L2CA_DataWrite(p_ccb->local_cid, p_buf); } if (status == tL2CAP_DW_RESULT::CONGESTED) { @@ -576,7 +579,67 @@ uint16_t GAP_ConnGetL2CAPCid(uint16_t gap_handle) { return 0; } - return p_ccb->connection_id; + return p_ccb->local_cid; +} + +/******************************************************************************* + * + * Function GAP_GetLeChannelInfo + * + * Description This function is called to get LE L2CAP channel information + * by the gap handle. All OUT parameters must NOT be nullptr. + * + * Parameters: handle - Handle of the port returned in the Open + * remote_mtu - OUT remote L2CAP MTU + * local_mps - OUT local L2CAP COC MPS + * remote_mps - OUT remote L2CAP COC MPS + * local_credit - OUT local L2CAP COC credit + * remote_credit - OUT remote L2CAP COC credit + * local_cid - OUT local L2CAP CID + * remote_cid - OUT remote L2CAP CID + * acl_handle - OUT ACL handle + * + * Returns true if request accepted + * + ******************************************************************************/ +bool GAP_GetLeChannelInfo(uint16_t gap_handle, uint16_t* remote_mtu, uint16_t* local_mps, + uint16_t* remote_mps, uint16_t* local_credit, uint16_t* remote_credit, + uint16_t* local_cid, uint16_t* remote_cid, uint16_t* acl_handle) { + tGAP_CCB* p_ccb = gap_find_ccb_by_handle(gap_handle); + if (p_ccb == NULL || p_ccb->transport != BT_TRANSPORT_LE || + p_ccb->con_state != GAP_CCB_STATE_CONNECTED) { + return false; + } + + *remote_mtu = p_ccb->peer_coc_cfg.mtu; + *local_mps = p_ccb->local_coc_cfg.mps; + *remote_mps = p_ccb->peer_coc_cfg.mps; + *local_credit = p_ccb->local_coc_cfg.credits; + *remote_credit = p_ccb->peer_coc_cfg.credits; + *local_cid = p_ccb->local_cid; + *remote_cid = p_ccb->remote_cid; + *acl_handle = p_ccb->acl_handle; + return true; +} + +/******************************************************************************* + * + * Function GAP_IsTransportLe + * + * Description This function returns if the transport is LE by the gap handle. + * + * Parameters: handle - Handle of the port returned in the Open + * + * Returns true if transport is LE, else false + * + ******************************************************************************/ +bool GAP_IsTransportLe(uint16_t gap_handle) { + tGAP_CCB* p_ccb = gap_find_ccb_by_handle(gap_handle); + if (p_ccb == NULL || p_ccb->transport != BT_TRANSPORT_LE || + p_ccb->con_state != GAP_CCB_STATE_CONNECTED) { + return false; + } + return true; } /******************************************************************************* @@ -649,7 +712,7 @@ static void gap_connect_ind(const RawAddress& bd_addr, uint16_t l2cap_cid, uint1 /* Save the BD Address and Channel ID. */ p_ccb->rem_dev_address = bd_addr; - p_ccb->connection_id = l2cap_cid; + p_ccb->local_cid = l2cap_cid; if (p_ccb->transport == BT_TRANSPORT_LE) { /* get the remote coc configuration */ @@ -683,12 +746,14 @@ static void gap_checks_con_flags(tGAP_CCB* p_ccb) { tGAP_CB_DATA cb_data; uint16_t l2cap_remote_cid; if (com::android::bluetooth::flags::bt_socket_api_l2cap_cid() && - stack::l2cap::get_interface().L2CA_GetRemoteChannelId(p_ccb->connection_id, + stack::l2cap::get_interface().L2CA_GetRemoteChannelId(p_ccb->local_cid, &l2cap_remote_cid)) { - cb_data.l2cap_cids.local_cid = p_ccb->connection_id; + cb_data.l2cap_cids.local_cid = p_ccb->local_cid; cb_data.l2cap_cids.remote_cid = l2cap_remote_cid; cb_data_ptr = &cb_data; } + stack::l2cap::get_interface().L2CA_GetRemoteChannelId(p_ccb->local_cid, &p_ccb->remote_cid); + stack::l2cap::get_interface().L2CA_GetAclHandle(p_ccb->local_cid, &p_ccb->acl_handle); p_ccb->con_state = GAP_CCB_STATE_CONNECTED; p_ccb->p_callback(p_ccb->gap_handle, GAP_EVT_CONN_OPENED, cb_data_ptr); @@ -926,7 +991,7 @@ static void gap_congestion_ind(uint16_t lcid, bool is_congested) { * Function gap_find_ccb_by_cid * * Description This function searches the CCB table for an entry with the - * passed CID. + * passed local CID. * * Returns the CCB address, or NULL if not found. * @@ -937,7 +1002,7 @@ static tGAP_CCB* gap_find_ccb_by_cid(uint16_t cid) { /* Look through each connection control block */ for (xx = 0, p_ccb = conn.ccb_pool; xx < GAP_MAX_CONNECTIONS; xx++, p_ccb++) { - if ((p_ccb->con_state != GAP_CCB_STATE_IDLE) && (p_ccb->connection_id == cid)) { + if ((p_ccb->con_state != GAP_CCB_STATE_IDLE) && (p_ccb->local_cid == cid)) { return p_ccb; } } diff --git a/system/stack/include/gap_api.h b/system/stack/include/gap_api.h index e4a185eefe..f216b12f3b 100644 --- a/system/stack/include/gap_api.h +++ b/system/stack/include/gap_api.h @@ -226,6 +226,43 @@ uint16_t GAP_ConnGetL2CAPCid(uint16_t gap_handle); /******************************************************************************* * + * Function GAP_GetLeChannelInfo + * + * Description This function is called to get LE L2CAP channel information + * by the gap handle. All OUT parameters must NOT be nullptr. + * + * Parameters: handle - Handle of the port returned in the Open + * remote_mtu - OUT remote L2CAP MTU + * local_mps - OUT local L2CAP COC MPS + * remote_mps - OUT remote L2CAP COC MPS + * local_credit - OUT local L2CAP COC credit + * remote_credit - OUT remote L2CAP COC credit + * local_cid - OUT local L2CAP CID + * remote_cid - OUT remote L2CAP CID + * acl_handle - OUT ACL handle + * + * Returns true if request accepted + * + ******************************************************************************/ +bool GAP_GetLeChannelInfo(uint16_t gap_handle, uint16_t* remote_mtu, uint16_t* local_mps, + uint16_t* remote_mps, uint16_t* local_credit, uint16_t* remote_credit, + uint16_t* local_cid, uint16_t* remote_cid, uint16_t* acl_handle); + +/******************************************************************************* + * + * Function GAP_IsTransportLe + * + * Description This function returns if the transport is LE by the gap handle. + * + * Parameters: handle - Handle of the port returned in the Open + * + * Returns true if transport is LE, else false + * + ******************************************************************************/ +bool GAP_IsTransportLe(uint16_t gap_handle); + +/******************************************************************************* + * * Function GAP_Init * * Description Initializes the control blocks used by GAP. diff --git a/system/stack/include/l2cap_interface.h b/system/stack/include/l2cap_interface.h index cfd88153f7..c3e32dcf36 100644 --- a/system/stack/include/l2cap_interface.h +++ b/system/stack/include/l2cap_interface.h @@ -916,6 +916,23 @@ public: ** *******************************************************************************/ virtual bool L2CA_isMediaChannel(uint16_t handle, uint16_t channel_id, bool is_local_cid) = 0; + + /******************************************************************************* + ** + ** Function L2CA_GetAclHandle + ** + ** Description Given a local channel identifier, |lcid|, this function + ** returns the handle of the corresponding ACL connection, |acl_handle|. If + ** |lcid| is not known or is invalid, this function returns false and does not + ** modify the value pointed at by |acl_handle|. + ** + ** Parameters: lcid: Local CID + ** acl_handle: Pointer to ACL handle must NOT be nullptr + ** + ** Returns true if acl_handle lookup was successful + ** + ******************************************************************************/ + virtual bool L2CA_GetAclHandle(uint16_t lcid, uint16_t* acl_handle) = 0; }; Interface& get_interface(); diff --git a/system/stack/include/l2cap_types.h b/system/stack/include/l2cap_types.h index 311dab7528..d2dd74bad7 100644 --- a/system/stack/include/l2cap_types.h +++ b/system/stack/include/l2cap_types.h @@ -129,6 +129,8 @@ typedef struct { uint8_t fcs; /* '0' if desire is to bypass FCS, otherwise '1' */ bool ext_flow_spec_present; tHCI_EXT_FLOW_SPEC ext_flow_spec; + bool init_credit_present; + uint16_t init_credit; uint16_t flags; /* bit 0: 0-no continuation, 1-continuation */ } tL2CAP_CFG_INFO; diff --git a/system/stack/include/l2cdefs.h b/system/stack/include/l2cdefs.h index 88ed7a76d3..be101711da 100644 --- a/system/stack/include/l2cdefs.h +++ b/system/stack/include/l2cdefs.h @@ -487,6 +487,7 @@ inline std::string l2cap_cfg_result_text(const tL2CAP_CFG_RESULT& result) { */ #define L2CAP_SDU_LENGTH_MAX (8080 + 26 - (L2CAP_MIN_OFFSET + 6)) constexpr uint16_t L2CAP_SDU_LENGTH_LE_MAX = 0xffff; +constexpr uint16_t L2CAP_SDU_LENGTH_LE_MIN = 23; /* SAR bits in the control word */ diff --git a/system/stack/l2cap/internal/l2c_api.h b/system/stack/l2cap/internal/l2c_api.h index 5ed3403cff..99c1f57781 100644 --- a/system/stack/l2cap/internal/l2c_api.h +++ b/system/stack/l2cap/internal/l2c_api.h @@ -777,3 +777,20 @@ void L2CA_SetMediaStreamChannel(uint16_t local_media_cid, bool status); ** *******************************************************************************/ [[nodiscard]] bool L2CA_isMediaChannel(uint16_t handle, uint16_t channel_id, bool is_local_cid); + +/******************************************************************************* +** +** Function L2CA_GetAclHandle +** +** Description Given a local channel identifier, |lcid|, this function +** returns the handle of the corresponding ACL connection, |acl_handle|. If +** |lcid| is not known or is invalid, this function returns false and does not +** modify the value pointed at by |acl_handle|. +** +** Parameters: lcid: Local CID +** acl_handle: Pointer to ACL handle must NOT be nullptr +** +** Returns true if acl_handle lookup was successful +** +******************************************************************************/ +[[nodiscard]] bool L2CA_GetAclHandle(uint16_t lcid, uint16_t* acl_handle); diff --git a/system/stack/l2cap/l2c_api.cc b/system/stack/l2cap/l2c_api.cc index 2b67132edb..68aacece21 100644 --- a/system/stack/l2cap/l2c_api.cc +++ b/system/stack/l2cap/l2c_api.cc @@ -1731,6 +1731,38 @@ bool L2CA_isMediaChannel(uint16_t handle, uint16_t channel_id, bool is_local_cid return ret; } +/******************************************************************************* + * + * Function L2CA_GetAclHandle + * + * Description Given a local channel identifier, |lcid|, this function + * returns the bound ACL handle, |acl_handle|. If |acl_handle| + * is not known or is invalid, this function returns false and + * does not modify the value pointed at by |acl_handle|. + * + * Parameters: lcid: Local CID + * rcid: Pointer to ACL handle must NOT be nullptr + * + * Return value: true if acl_handle lookup was successful + * + ******************************************************************************/ +bool L2CA_GetAclHandle(uint16_t lcid, uint16_t* acl_handle) { + log::assert_that(acl_handle != nullptr, "assert failed: acl_handle != nullptr"); + + tL2C_CCB* p_ccb = l2cu_find_ccb_by_cid(nullptr, lcid); + if (p_ccb == nullptr) { + log::error("No CCB for CID:0x{:04x}", lcid); + return false; + } + uint16_t handle = p_ccb->p_lcb->Handle(); + if (handle == HCI_INVALID_HANDLE) { + log::error("Invalid ACL handle"); + return false; + } + *acl_handle = handle; + return true; +} + using namespace bluetooth; #define DUMPSYS_TAG "shim::legacy::l2cap" diff --git a/system/stack/l2cap/l2c_api.h b/system/stack/l2cap/l2c_api.h index f645803dad..f89a5cc6ca 100644 --- a/system/stack/l2cap/l2c_api.h +++ b/system/stack/l2cap/l2c_api.h @@ -133,6 +133,8 @@ public: void L2CA_SetMediaStreamChannel(uint16_t local_media_cid, bool status) override; [[nodiscard]] bool L2CA_isMediaChannel(uint16_t handle, uint16_t channel_id, bool is_local_cid) override; + + [[nodiscard]] bool L2CA_GetAclHandle(uint16_t lcid, uint16_t* acl_handle) override; }; } // namespace l2cap diff --git a/system/stack/l2cap/l2c_ble.cc b/system/stack/l2cap/l2c_ble.cc index 9d69e5ab36..56b50f809f 100644 --- a/system/stack/l2cap/l2c_ble.cc +++ b/system/stack/l2cap/l2c_ble.cc @@ -777,11 +777,11 @@ void l2cble_process_sig_cmd(tL2C_LCB* p_lcb, uint8_t* p, uint16_t pkt_len) { p_ccb->p_rcb = p_rcb; p_ccb->remote_cid = rcid; - p_ccb->local_conn_cfg.mtu = L2CAP_SDU_LENGTH_LE_MAX; - p_ccb->local_conn_cfg.mps = - bluetooth::shim::GetController()->GetLeBufferSize().le_data_packet_length_; - p_ccb->local_conn_cfg.credits = L2CA_LeCreditDefault(); - p_ccb->remote_credit_count = L2CA_LeCreditDefault(); + p_ccb->local_conn_cfg.mtu = p_rcb->coc_cfg.mtu; + p_ccb->local_conn_cfg.mps = p_rcb->coc_cfg.mps; + p_ccb->local_conn_cfg.credits = p_rcb->coc_cfg.credits; + + p_ccb->remote_credit_count = p_rcb->coc_cfg.credits; p_ccb->peer_conn_cfg.mtu = mtu; p_ccb->peer_conn_cfg.mps = mps; diff --git a/system/stack/l2cap/l2c_utils.cc b/system/stack/l2cap/l2c_utils.cc index c04379bf89..c18ba8c5a9 100644 --- a/system/stack/l2cap/l2c_utils.cc +++ b/system/stack/l2cap/l2c_utils.cc @@ -3331,6 +3331,9 @@ void l2cu_send_peer_ble_credit_based_conn_res(tL2C_CCB* p_ccb, tL2CAP_LE_RESULT_ p = (uint8_t*)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + log::verbose("local cid: {}, mtu: {}, mps: {}, initial credits: {}", p_ccb->local_cid, + p_ccb->local_conn_cfg.mtu, p_ccb->local_conn_cfg.mps, p_ccb->local_conn_cfg.credits); + UINT16_TO_STREAM(p, p_ccb->local_cid); /* Local CID */ UINT16_TO_STREAM(p, p_ccb->local_conn_cfg.mtu); /* MTU */ UINT16_TO_STREAM(p, p_ccb->local_conn_cfg.mps); /* MPS */ diff --git a/system/stack/l2cap/l2cap_api.cc b/system/stack/l2cap/l2cap_api.cc index 94466c90e2..2e1b12c7de 100644 --- a/system/stack/l2cap/l2cap_api.cc +++ b/system/stack/l2cap/l2cap_api.cc @@ -257,3 +257,8 @@ void bluetooth::stack::l2cap::Impl::L2CA_SetMediaStreamChannel(uint16_t local_me uint16_t* rcid) { return ::L2CA_GetRemoteChannelId(lcid, rcid); } + +[[nodiscard]] bool bluetooth::stack::l2cap::Impl::L2CA_GetAclHandle(uint16_t lcid, + uint16_t* acl_handle) { + return ::L2CA_GetAclHandle(lcid, acl_handle); +} diff --git a/system/test/mock/mock_btif_sock_l2cap.cc b/system/test/mock/mock_btif_sock_l2cap.cc new file mode 100644 index 0000000000..41f19f8cd0 --- /dev/null +++ b/system/test/mock/mock_btif_sock_l2cap.cc @@ -0,0 +1,66 @@ +/* + * Copyright 2023 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:13 + * + * mockcify.pl ver 0.7.1 + */ + +// Mock include file to share data between tests and mock +#include "test/mock/mock_btif_sock_l2cap.h" + +#include <cstdint> + +#include "test/common/mock_functions.h" + +// TODO(b/369381361) Enfore -Wmissing-prototypes +#pragma GCC diagnostic ignored "-Wmissing-prototypes" + +// Original usings + +// Mocked internal structures, if any + +namespace test { +namespace mock { +namespace btif_sock_l2cap { + +// Function state capture and return values, if needed +struct on_btsocket_l2cap_close on_btsocket_l2cap_close; +struct on_btsocket_l2cap_opened_complete on_btsocket_l2cap_opened_complete; + +} // namespace btif_sock_l2cap +} // namespace mock +} // namespace test + +// Mocked function return values, if any +namespace test { +namespace mock { +namespace btif_sock_l2cap {} // namespace btif_sock_l2cap +} // namespace mock +} // namespace test + +// Mocked functions, if any +void on_btsocket_l2cap_close(uint64_t socket_id) { + inc_func_call_count(__func__); + test::mock::btif_sock_l2cap::on_btsocket_l2cap_close(socket_id); +} +void on_btsocket_l2cap_opened_complete(uint64_t socket_id, bool success) { + inc_func_call_count(__func__); + test::mock::btif_sock_l2cap::on_btsocket_l2cap_opened_complete(socket_id, success); +} +// Mocked functions complete +// END mockcify generation diff --git a/system/test/mock/mock_btif_sock_l2cap.h b/system/test/mock/mock_btif_sock_l2cap.h new file mode 100644 index 0000000000..f883f8bf1f --- /dev/null +++ b/system/test/mock/mock_btif_sock_l2cap.h @@ -0,0 +1,63 @@ +/* + * Copyright 2023 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. + */ +#pragma once + +/* + * Generated mock file from original source file + * Functions generated:13 + * + * mockcify.pl ver 0.7.1 + */ + +#include <cstdint> +#include <functional> + +// Original included files, if any +#include <cstring> + +// Original usings + +// Mocked compile conditionals, if any + +namespace test { +namespace mock { +namespace btif_sock_l2cap { + +// Shared state between mocked functions and tests +// Name: on_btsocket_l2cap_close +// Params: uint64_t socket_id +// Return: void +struct on_btsocket_l2cap_close { + std::function<void(uint64_t socket_id)> body{[](uint64_t /* socket_id */) {}}; + void operator()(uint64_t socket_id) { body(socket_id); } +}; +extern struct on_btsocket_l2cap_close on_btsocket_l2cap_close; + +// Name: on_btsocket_l2cap_opened_complete +// Params: uint64_t socket_id, bool success +// Return: void +struct on_btsocket_l2cap_opened_complete { + std::function<void(uint64_t socket_id, bool success)> body{ + [](uint64_t /* socket_id */, bool /* success */) {}}; + void operator()(uint64_t socket_id, bool success) { body(socket_id, success); } +}; +extern struct on_btsocket_l2cap_opened_complete on_btsocket_l2cap_opened_complete; + +} // namespace btif_sock_l2cap +} // namespace mock +} // namespace test + +// END mockcify generation diff --git a/system/test/mock/mock_main_shim_entry.cc b/system/test/mock/mock_main_shim_entry.cc index 72054dad81..d48a382727 100644 --- a/system/test/mock/mock_main_shim_entry.cc +++ b/system/test/mock/mock_main_shim_entry.cc @@ -20,6 +20,7 @@ #include "hci/hci_interface.h" #include "hci/le_advertising_manager_mock.h" #include "hci/le_scanning_manager_mock.h" +#include "lpp/lpp_offload_interface_mock.h" #include "main/shim/entry.h" #include "os/handler.h" #include "storage/storage_module.h" @@ -40,6 +41,10 @@ MockDistanceMeasurementManager* mock_distance_measurement_manager_{nullptr}; } // namespace testing } // namespace hci +namespace lpp::testing { +MockLppOffloadInterface* mock_lpp_offload_interface_{nullptr}; +} // namespace lpp::testing + class Dumpsys; namespace shim { @@ -59,6 +64,9 @@ storage::StorageModule* GetStorage() { return nullptr; } metrics::CounterMetrics* GetCounterMetrics() { return nullptr; } hci::MsftExtensionManager* GetMsftExtensionManager() { return nullptr; } hci::RemoteNameRequestModule* GetRemoteNameRequest() { return nullptr; } +lpp::LppOffloadInterface* GetLppOffloadManager() { + return lpp::testing::mock_lpp_offload_interface_; +} } // namespace shim } // namespace bluetooth diff --git a/system/test/mock/mock_stack_gap_conn.cc b/system/test/mock/mock_stack_gap_conn.cc index 19034a9fec..07193c9130 100644 --- a/system/test/mock/mock_stack_gap_conn.cc +++ b/system/test/mock/mock_stack_gap_conn.cc @@ -61,4 +61,16 @@ uint16_t GAP_ConnWriteData(uint16_t /* gap_handle */, BT_HDR* /* msg */) { inc_func_call_count(__func__); return 0; } +bool GAP_GetLeChannelInfo(uint16_t /* gap_handle */, uint16_t* /*remote_mtu */, + uint16_t* /* local_mps */, uint16_t* /* remote_mps */, + uint16_t* /* local_credit */, uint16_t* /* remote_credit */, + uint16_t* /* local_cid */, uint16_t* /* remote_cid */, + uint16_t* /* acl_handle */) { + inc_func_call_count(__func__); + return false; +} +bool GAP_IsTransportLe(uint16_t /* gap_handle */) { + inc_func_call_count(__func__); + return false; +} void GAP_Init(void) { inc_func_call_count(__func__); } diff --git a/system/test/mock/mock_stack_l2cap_api.cc b/system/test/mock/mock_stack_l2cap_api.cc index b8f3f4e227..3dd0ef991c 100644 --- a/system/test/mock/mock_stack_l2cap_api.cc +++ b/system/test/mock/mock_stack_l2cap_api.cc @@ -75,6 +75,7 @@ struct L2CA_SetMediaStreamChannel L2CA_SetMediaStreamChannel; struct L2CA_isMediaChannel L2CA_isMediaChannel; struct L2CA_LeCreditDefault L2CA_LeCreditDefault; struct L2CA_LeCreditThreshold L2CA_LeCreditThreshold; +struct L2CA_GetAclHandle L2CA_GetAclHandle; } // namespace stack_l2cap_api } // namespace mock @@ -250,6 +251,10 @@ uint16_t L2CA_LeCreditThreshold() { inc_func_call_count(__func__); return test::mock::stack_l2cap_api::L2CA_LeCreditThreshold(); } +bool L2CA_GetAclHandle(uint16_t lcid, uint16_t* acl_handle) { + inc_func_call_count(__func__); + return test::mock::stack_l2cap_api::L2CA_GetAclHandle(lcid, acl_handle); +} // END mockcify generation diff --git a/system/test/mock/mock_stack_l2cap_api.h b/system/test/mock/mock_stack_l2cap_api.h index 40d746ecd3..ed20c3438d 100644 --- a/system/test/mock/mock_stack_l2cap_api.h +++ b/system/test/mock/mock_stack_l2cap_api.h @@ -457,6 +457,16 @@ struct L2CA_LeCreditThreshold { }; extern struct L2CA_LeCreditThreshold L2CA_LeCreditThreshold; +// Name: L2CA_GetAclHandle +// Params: uint16_t lcid, uint16_t* acl_handle +// Returns: bool +struct L2CA_GetAclHandle { + std::function<bool(uint16_t lcid, uint16_t* acl_handle)> body{ + [](uint16_t /* lcid */, uint16_t* /* acl_handle */) { return false; }}; + bool operator()(uint16_t lcid, uint16_t* acl_handle) { return body(lcid, acl_handle); } +}; +extern struct L2CA_GetAclHandle L2CA_GetAclHandle; + } // namespace stack_l2cap_api } // namespace mock } // namespace test diff --git a/system/test/mock/mock_stack_l2cap_interface.h b/system/test/mock/mock_stack_l2cap_interface.h index c87bf164bb..7756f4a4ed 100644 --- a/system/test/mock/mock_stack_l2cap_interface.h +++ b/system/test/mock/mock_stack_l2cap_interface.h @@ -106,6 +106,7 @@ public: MOCK_METHOD(uint16_t, L2CA_LeCreditThreshold, ()); MOCK_METHOD(void, L2CA_Consolidate, (const RawAddress& identity_addr, const RawAddress& rpa)); + MOCK_METHOD(bool, L2CA_GetAclHandle, (uint16_t lcid, uint16_t* acl_handle)); // Disconnect methods an active connection for both BR/EDR and BLE MOCK_METHOD(bool, L2CA_DisconnectReq, (uint16_t cid)); |