diff options
245 files changed, 4582 insertions, 2883 deletions
diff --git a/Android.bp b/Android.bp index 7be2e6ec9e..aef08f38e9 100644 --- a/Android.bp +++ b/Android.bp @@ -76,6 +76,8 @@ cc_defaults { // there are too many unused parameters in all the code. "-Wno-unused-parameter", ], + c_std: "c99", + cpp_std: "c++17", } // Address Sanitizer is flaky on Android x86_64 binaries but it's not on x86 diff --git a/android/app/Android.bp b/android/app/Android.bp index 2bf58974cb..ae6f315ddd 100644 --- a/android/app/Android.bp +++ b/android/app/Android.bp @@ -55,7 +55,7 @@ java_library { cc_library_shared { name: "libbluetooth_jni", - defaults: ["fluoride_basic_defaults"], + defaults: ["fluoride_defaults"], srcs: ["jni/**/*.cpp"], version_script: "libbluetooth_jni.map", header_libs: [ @@ -77,7 +77,7 @@ cc_library_shared { // is required to maintain FIPS compliance. stl: "libc++_static", static_libs: [ - "android.hardware.audio.common-V1-ndk", + "android.hardware.audio.common-V2-ndk", "android.hardware.audio.common@5.0", "android.hardware.bluetooth.audio-V3-ndk", "android.hardware.bluetooth.audio@2.0", @@ -118,6 +118,7 @@ cc_library_shared { "libchrome", "libcutils", "libevent", + "libflatbuffers-cpp", "libfmq", "libg722codec", "libhidlbase", @@ -125,6 +126,7 @@ cc_library_shared { "libmodpb64", "libopus", "libosi", + "libprotobuf-cpp-lite", "libudrv-uipc", "libutils", ], @@ -148,7 +150,7 @@ cc_library_shared { cc_library { name: "libbluetooth-core", - defaults: ["fluoride_basic_defaults"], + defaults: ["fluoride_defaults"], header_libs: [ "jni_headers", "libbluetooth_headers", @@ -199,8 +201,12 @@ android_app { srcs: [ ":statslog-bluetooth-java-gen", ":statslog-bt-restricted-java-gen", + "proto/keystore.proto", "src/**/*.java", ], + proto: { + type: "lite", + }, aaptflags: [ "--custom-package", "com.android.bluetooth", diff --git a/android/app/AndroidManifest.xml b/android/app/AndroidManifest.xml index 929cc0e42b..7a398344c0 100644 --- a/android/app/AndroidManifest.xml +++ b/android/app/AndroidManifest.xml @@ -221,17 +221,6 @@ </intent-filter> </service> - <!-- Generic Attribute (GATT) Profile Service --> - <service android:process="@string/process" - android:name="com.android.bluetooth.gatt.GattService" - android:enabled="true" - android:exported="true" - android:permission="android.permission.ACCESS_BLUETOOTH_SHARE"> - <intent-filter> - <action android:name="android.bluetooth.IBluetoothGatt"/> - </intent-filter> - </service> - <!-- Hearing Aid Profile (HAP) client Profile Service --> <service android:process="@string/process" android:name="com.android.bluetooth.hap.HapClientService" diff --git a/android/app/jni/com_android_bluetooth_btservice_ActivityAttribution.cpp b/android/app/jni/com_android_bluetooth_btservice_ActivityAttribution.cpp index 4e0247dccc..2790b00903 100644 --- a/android/app/jni/com_android_bluetooth_btservice_ActivityAttribution.cpp +++ b/android/app/jni/com_android_bluetooth_btservice_ActivityAttribution.cpp @@ -24,138 +24,10 @@ #include "com_android_bluetooth.h" #include "hardware/bt_activity_attribution.h" -using bluetooth::activity_attribution::ActivityAttributionCallbacks; using bluetooth::activity_attribution::ActivityAttributionInterface; namespace android { -static jmethodID method_onWakeup; -static jmethodID method_onActivityLogsReady; - static ActivityAttributionInterface* sActivityAttributionInterface = nullptr; -static std::shared_timed_mutex interface_mutex; - -static jobject mCallbacksObj = nullptr; -static std::shared_timed_mutex callbacks_mutex; - -class ActivityAttributionCallbacksImpl : public ActivityAttributionCallbacks { - public: - ~ActivityAttributionCallbacksImpl() = default; - - void OnWakeup(const Activity activity, const RawAddress& bd_addr) override { - LOG(INFO) << __func__; - - std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); - CallbackEnv sCallbackEnv(__func__); - if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; - - ScopedLocalRef<jbyteArray> addr( - sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress))); - if (!addr.get()) { - LOG(ERROR) - << "Failed to allocate jbyteArray for bd_addr of wakeup callback"; - return; - } - - sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), - (jbyte*)&bd_addr); - sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onWakeup, (jint)activity, - addr.get()); - } - - void OnActivityLogsReady( - const std::vector<BtaaAggregationEntry> logs) override { - LOG(INFO) << __func__; - - std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); - CallbackEnv sCallbackEnv(__func__); - if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; - - jsize logs_size = logs.size() * sizeof(BtaaAggregationEntry); - ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), - sCallbackEnv->NewByteArray(logs_size)); - if (!addr.get()) { - LOG(ERROR) << "Failed to allocate jbyteArray for logs from activity " - "logging callback"; - return; - } - - sCallbackEnv->SetByteArrayRegion(addr.get(), 0, logs_size, - (jbyte*)logs.data()); - sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onActivityLogsReady, - addr.get()); - } -}; - -static ActivityAttributionCallbacksImpl sActivityAttributionCallbacks; - -static void classInitNative(JNIEnv* env, jclass clazz) { - method_onWakeup = env->GetMethodID(clazz, "onWakeup", "(I[B)V"); - method_onActivityLogsReady = - env->GetMethodID(clazz, "onActivityLogsReady", "([B)V"); - - LOG(INFO) << __func__ << ": succeeds"; -} - -static void initNative(JNIEnv* env, jobject object) { - std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex); - std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex); - const bt_interface_t* btInf = getBluetoothInterface(); - if (btInf == nullptr) { - LOG(ERROR) << "Bluetooth module is not loaded"; - return; - } - - if (sActivityAttributionInterface != nullptr) { - LOG(INFO) - << "Cleaning up ActivityAttribution Interface before initializing..."; - sActivityAttributionInterface->Cleanup(); - sActivityAttributionInterface = nullptr; - } - - if (mCallbacksObj != nullptr) { - LOG(INFO) << "Cleaning up ActivityAttribution callback object"; - env->DeleteGlobalRef(mCallbacksObj); - mCallbacksObj = nullptr; - } - - if ((mCallbacksObj = env->NewGlobalRef(object)) == nullptr) { - LOG(ERROR) - << "Failed to allocate Global Ref for ActivityAttribution Callbacks"; - return; - } - - sActivityAttributionInterface = - (ActivityAttributionInterface*)btInf->get_profile_interface( - BT_ACTIVITY_ATTRIBUTION_ID); - if (sActivityAttributionInterface == nullptr) { - LOG(ERROR) << "Failed to get ActivityAttribution Interface"; - return; - } - - sActivityAttributionInterface->RegisterCallbacks( - &sActivityAttributionCallbacks); -} - -static void cleanupNative(JNIEnv* env, jobject object) { - std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex); - std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex); - - const bt_interface_t* btInf = getBluetoothInterface(); - if (btInf == nullptr) { - LOG(ERROR) << "Bluetooth module is not loaded"; - return; - } - - if (sActivityAttributionInterface != nullptr) { - sActivityAttributionInterface->Cleanup(); - sActivityAttributionInterface = nullptr; - } - - if (mCallbacksObj != nullptr) { - env->DeleteGlobalRef(mCallbacksObj); - mCallbacksObj = nullptr; - } -} static void notifyActivityAttributionInfoNative(JNIEnv* env, jobject object, jint uid, jstring packageName, @@ -186,9 +58,6 @@ static void notifyActivityAttributionInfoNative(JNIEnv* env, jobject object, } static JNINativeMethod sMethods[] = { - {"classInitNative", "()V", (void*)classInitNative}, - {"initNative", "()V", (void*)initNative}, - {"cleanupNative", "()V", (void*)cleanupNative}, {"notifyActivityAttributionInfoNative", "(ILjava/lang/String;Ljava/lang/String;)V", (void*)notifyActivityAttributionInfoNative}, diff --git a/android/app/jni/com_android_bluetooth_gatt.cpp b/android/app/jni/com_android_bluetooth_gatt.cpp index 1290aae868..1c329d68b2 100644 --- a/android/app/jni/com_android_bluetooth_gatt.cpp +++ b/android/app/jni/com_android_bluetooth_gatt.cpp @@ -2682,7 +2682,7 @@ static void stopDistanceMeasurementNative(JNIEnv* env, jobject object, * JNI function definitinos */ -// JNI functions defined in AdvertiseManager class. +// JNI functions defined in AdvertiseManagerNativeInterface class. static JNINativeMethod sAdvertiseMethods[] = { {"classInitNative", "()V", (void*)advertiseClassInitNative}, {"initializeNative", "()V", (void*)advertiseInitializeNative}, @@ -2691,8 +2691,8 @@ static JNINativeMethod sAdvertiseMethods[] = { "(Landroid/bluetooth/le/AdvertisingSetParameters;[B[BLandroid/bluetooth/" "le/PeriodicAdvertisingParameters;[BIIII)V", (void*)startAdvertisingSetNative}, - {"getOwnAddressNative", "(I)V", (void*)getOwnAddressNative}, {"stopAdvertisingSetNative", "(I)V", (void*)stopAdvertisingSetNative}, + {"getOwnAddressNative", "(I)V", (void*)getOwnAddressNative}, {"enableAdvertisingSetNative", "(IZII)V", (void*)enableAdvertisingSetNative}, {"setAdvertisingDataNative", "(I[B)V", (void*)setAdvertisingDataNative}, @@ -2848,8 +2848,8 @@ int register_com_android_bluetooth_gatt(JNIEnv* env) { env, "com/android/bluetooth/gatt/ScanNativeInterface", sScanMethods, NELEM(sScanMethods)); register_success &= jniRegisterNativeMethods( - env, "com/android/bluetooth/gatt/AdvertiseManager", sAdvertiseMethods, - NELEM(sAdvertiseMethods)); + env, "com/android/bluetooth/gatt/AdvertiseManagerNativeInterface", + sAdvertiseMethods, NELEM(sAdvertiseMethods)); register_success &= jniRegisterNativeMethods( env, "com/android/bluetooth/gatt/PeriodicScanManager", sPeriodicScanMethods, NELEM(sPeriodicScanMethods)); diff --git a/android/app/jni/com_android_bluetooth_le_audio.cpp b/android/app/jni/com_android_bluetooth_le_audio.cpp index 5751231146..08f859c404 100644 --- a/android/app/jni/com_android_bluetooth_le_audio.cpp +++ b/android/app/jni/com_android_bluetooth_le_audio.cpp @@ -789,11 +789,15 @@ jobject prepareLeAudioContentMetadataObject( JNIEnv* env, const std::map<uint8_t, std::vector<uint8_t>>& metadata) { jstring program_info_str = nullptr; if (metadata.count(bluetooth::le_audio::kLeAudioMetadataTypeProgramInfo)) { - program_info_str = env->NewStringUTF( - (const char*)(metadata - .at(bluetooth::le_audio:: - kLeAudioMetadataTypeProgramInfo) - .data())); + // Convert the metadata vector to string with null terminator + std::string p_str( + (const char*)metadata + .at(bluetooth::le_audio::kLeAudioMetadataTypeProgramInfo) + .data(), + metadata.at(bluetooth::le_audio::kLeAudioMetadataTypeProgramInfo) + .size()); + + program_info_str = env->NewStringUTF(p_str.c_str()); if (!program_info_str) { LOG(ERROR) << "Failed to create new preset name String for preset name"; return nullptr; @@ -802,10 +806,14 @@ jobject prepareLeAudioContentMetadataObject( jstring language_str = nullptr; if (metadata.count(bluetooth::le_audio::kLeAudioMetadataTypeLanguage)) { - language_str = env->NewStringUTF( - (const char*)(metadata - .at(bluetooth::le_audio::kLeAudioMetadataTypeLanguage) - .data())); + // Convert the metadata vector to string with null terminator + std::string l_str( + (const char*)metadata + .at(bluetooth::le_audio::kLeAudioMetadataTypeLanguage) + .data(), + metadata.at(bluetooth::le_audio::kLeAudioMetadataTypeLanguage).size()); + + language_str = env->NewStringUTF(l_str.c_str()); if (!language_str) { LOG(ERROR) << "Failed to create new preset name String for language"; return nullptr; diff --git a/system/gd/proto/bluetooth/bluetoothKeystore/keystore.proto b/android/app/proto/keystore.proto index d0e418bd28..f0641ff6fa 100644 --- a/system/gd/proto/bluetooth/bluetoothKeystore/keystore.proto +++ b/android/app/proto/keystore.proto @@ -16,9 +16,6 @@ syntax = "proto2"; -// C++ namespace: bluetooth::metrics::BluetoothMetricsProto -package bluetooth.keystore.BluetoothKeystoreProto; - option java_package = "com.android.bluetooth"; option java_outer_classname = "BluetoothKeystoreProto"; diff --git a/android/app/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java b/android/app/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java index e7b0920476..02e8a5ace4 100644 --- a/android/app/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java +++ b/android/app/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java @@ -134,6 +134,7 @@ public class A2dpSinkService extends ProfileService { * Set the device that should be allowed to actively stream */ public boolean setActiveDevice(BluetoothDevice device) { + Log.i(TAG, "setActiveDevice(device=" + device + ")"); synchronized (mActiveDeviceLock) { if (mNativeInterface.setActiveDevice(device)) { mActiveDevice = device; diff --git a/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java b/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java index 25c7f9e7a1..a43594df76 100644 --- a/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java +++ b/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java @@ -208,8 +208,12 @@ public class AvrcpControllerService extends ProfileService { */ @VisibleForTesting boolean setActiveDevice(BluetoothDevice device) { + if (DBG) { + Log.d(TAG, "setActiveDevice(device=" + device + ")"); + } A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService(); if (a2dpSinkService == null) { + Log.w(TAG, "setActiveDevice(device=" + device + "): A2DP Sink not available"); return false; } @@ -242,6 +246,8 @@ public class AvrcpControllerService extends ProfileService { return true; } } + + Log.w(TAG, "setActiveDevice(device=" + device + "): A2DP Sink request failed"); return false; } @@ -333,15 +339,21 @@ public class AvrcpControllerService extends ProfileService { for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) { requestedNode = stateMachine.findNode(parentMediaId); if (requestedNode != null) { - Log.d(TAG, "Found a node"); break; } } } + + if (DBG) { + Log.d(TAG, "getContents(" + parentMediaId + "): " + + (requestedNode == null + ? "Failed to find node" + : "node=" + requestedNode + ", device=" + requestedNode.getDevice())); + } + // If we don't find a node in the tree then do not have any way to browse for the contents. // Return an empty list instead. if (requestedNode == null) { - if (DBG) Log.d(TAG, "Didn't find a node"); return new BrowseResult(new ArrayList(0), BrowseResult.ERROR_MEDIA_ID_INVALID); } if (parentMediaId.equals(BrowseTree.ROOT) && requestedNode.getChildrenCount() == 0) { @@ -355,9 +367,8 @@ public class AvrcpControllerService extends ProfileService { List<MediaItem> contents = requestedNode.getContents(); - if (DBG) Log.d(TAG, "Returning contents"); if (!requestedNode.isCached()) { - if (DBG) Log.d(TAG, "node is not cached"); + if (DBG) Log.d(TAG, "getContents(" + parentMediaId + "): node download pending"); refreshContents(requestedNode); /* Ongoing downloads can have partial results and we want to make sure they get sent * to the client. If a download gets kicked off as a result of this request, the @@ -365,6 +376,10 @@ public class AvrcpControllerService extends ProfileService { */ return new BrowseResult(contents, BrowseResult.DOWNLOAD_PENDING); } + if (DBG) { + Log.d(TAG, "getContents(" + parentMediaId + "): return node, contents=" + + requestedNode.getContents()); + } return new BrowseResult(contents, BrowseResult.SUCCESS); } diff --git a/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java b/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java index 290dc35cb4..9f43d8199f 100644 --- a/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java +++ b/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java @@ -581,6 +581,8 @@ class AvrcpControllerStateMachine extends StateMachine { mAddressedPlayer.updateCurrentTrack(track); if (isActive()) { BluetoothMediaBrowserService.trackChanged(track); + BluetoothMediaBrowserService.notifyChanged( + mAddressedPlayer.getPlaybackState()); } if (previousTrack != null) { removeUnusedArtwork(previousTrack.getCoverArtUuid()); diff --git a/android/app/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java b/android/app/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java index 25df4cd613..8d68409fb2 100644 --- a/android/app/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java +++ b/android/app/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java @@ -86,6 +86,7 @@ public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(Intent.ACTION_LOCALE_CHANGED)) { + if (DBG) Log.d(TAG, "Locale has updated"); if (sBluetoothMediaBrowserService == null) return; MediaSessionCompat session = sBluetoothMediaBrowserService.getSession(); MediaControllerCompat controller = session.getController(); @@ -106,7 +107,7 @@ public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat { */ @Override public void onCreate() { - if (DBG) Log.d(TAG, "onCreate"); + if (DBG) Log.d(TAG, "Service Created"); super.onCreate(); // Create and configure the MediaSessionCompat @@ -128,6 +129,7 @@ public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat { @Override public void onDestroy() { + if (DBG) Log.d(TAG, "Service Destroyed"); unregisterReceiver(mReceiver); mReceiver = null; } @@ -191,6 +193,7 @@ public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat { AvrcpControllerService avrcpControllerService = AvrcpControllerService.getAvrcpControllerService(); if (avrcpControllerService == null) { + Log.w(TAG, "getContents(id=" + parentMediaId + "): AVRCP Controller Service not ready"); return new BrowseResult(new ArrayList(0), BrowseResult.ERROR_NO_AVRCP_SERVICE); } else { return avrcpControllerService.getContents(parentMediaId); @@ -227,7 +230,7 @@ public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat { @Override public synchronized void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) { - if (DBG) Log.d(TAG, "onLoadChildren parentMediaId= " + parentMediaId); + if (DBG) Log.d(TAG, "Request for contents, id= " + parentMediaId); BrowseResult contents = getContents(parentMediaId); byte status = contents.getStatus(); List<MediaItem> results = contents.getResults(); @@ -236,8 +239,8 @@ public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat { result.detach(); } else { if (DBG) { - Log.d(TAG, "id= " + parentMediaId + ", status= " + contents.getStatusString() - + ", results=" + results); + Log.d(TAG, "Received Contents, id= " + parentMediaId + ", status= " + + contents.getStatusString() + ", results=" + results); } result.sendResult(results); } @@ -245,7 +248,7 @@ public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat { @Override public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) { - if (DBG) Log.d(TAG, "onGetRoot"); + Log.i(TAG, "Browser Client Connection Request, client='" + clientPackageName + "')"); Bundle style = getDefaultStyle(); return new BrowserRoot(BrowseTree.ROOT, style); } @@ -263,6 +266,7 @@ public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat { } else { mSession.setQueue(null); } + if (DBG) Log.d(TAG, "Now Playing List Changed, queue=" + mMediaQueue); } private void clearNowPlayingQueue() { @@ -275,6 +279,7 @@ public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat { if (node.getScope() == AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING) { sBluetoothMediaBrowserService.updateNowPlayingQueue(node); } else { + if (DBG) Log.d(TAG, "Browse Node contents changed, node=" + node); sBluetoothMediaBrowserService.notifyChildrenChanged(node.getID()); } } @@ -293,7 +298,7 @@ public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat { } static synchronized void trackChanged(AvrcpItem track) { - if (DBG) Log.d(TAG, "trackChanged setMetadata=" + track); + if (DBG) Log.d(TAG, "Track Changed, track=" + track); if (sBluetoothMediaBrowserService != null) { if (track != null) { sBluetoothMediaBrowserService.mSession.setMetadata(track.toMediaMetadata()); @@ -307,7 +312,7 @@ public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat { } static synchronized void notifyChanged(PlaybackStateCompat playbackState) { - Log.d(TAG, "notifyChanged PlaybackState" + playbackState); + if (DBG) Log.d(TAG, "Playback State Changed, state=" + playbackState); if (sBluetoothMediaBrowserService != null) { sBluetoothMediaBrowserService.mSession.setPlaybackState(playbackState); } else { @@ -340,15 +345,16 @@ public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat { /** * Get playback state */ - public static synchronized int getPlaybackState() { + public static synchronized PlaybackStateCompat getPlaybackState() { if (sBluetoothMediaBrowserService != null) { - PlaybackStateCompat currentPlaybackState = - sBluetoothMediaBrowserService.mSession.getController().getPlaybackState(); - if (currentPlaybackState != null) { - return currentPlaybackState.getState(); - } + MediaSessionCompat session = sBluetoothMediaBrowserService.getSession(); + if (session == null) return null; + MediaControllerCompat controller = session.getController(); + PlaybackStateCompat playbackState = + controller == null ? null : controller.getPlaybackState(); + return playbackState; } - return PlaybackStateCompat.STATE_ERROR; + return null; } /** diff --git a/android/app/src/com/android/bluetooth/bas/BatteryService.java b/android/app/src/com/android/bluetooth/bas/BatteryService.java index d9a70479f3..cc57d631c7 100644 --- a/android/app/src/com/android/bluetooth/bas/BatteryService.java +++ b/android/app/src/com/android/bluetooth/bas/BatteryService.java @@ -53,8 +53,8 @@ import java.util.Objects; * A profile service that connects to the Battery service (BAS) of BLE devices */ public class BatteryService extends ProfileService { - private static final boolean DBG = false; private static final String TAG = "BatteryService"; + private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); // Timeout for state machine thread join, to prevent potential ANR. private static final int SM_THREAD_JOIN_TIMEOUT_MS = 1_000; diff --git a/android/app/src/com/android/bluetooth/bass_client/BassClientService.java b/android/app/src/com/android/bluetooth/bass_client/BassClientService.java index 1d489f7974..f540051a96 100644 --- a/android/app/src/com/android/bluetooth/bass_client/BassClientService.java +++ b/android/app/src/com/android/bluetooth/bass_client/BassClientService.java @@ -50,6 +50,7 @@ import android.sysprop.BluetoothProperties; import android.util.Log; import android.util.Pair; +import com.android.bluetooth.BluetoothEventLogger; import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.ProfileService; @@ -60,6 +61,7 @@ import com.android.bluetooth.le_audio.LeAudioService; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -107,6 +109,11 @@ public class BassClientService extends ProfileService { private Callbacks mCallbacks; private BroadcastReceiver mIntentReceiver; + private static final int LOG_NB_EVENTS = 100; + private static final BluetoothEventLogger sEventLogger = + new BluetoothEventLogger(LOG_NB_EVENTS, TAG + " event log"); + ; + @VisibleForTesting ServiceFactory mServiceFactory = new ServiceFactory(); @@ -299,7 +306,7 @@ public class BassClientService extends ProfileService { } } }; - registerReceiver(mIntentReceiver, filter, Context.RECEIVER_NOT_EXPORTED); + registerReceiver(mIntentReceiver, filter, Context.RECEIVER_EXPORTED); setBassClientService(this); // Saving PSync stuff for future addition @@ -645,6 +652,14 @@ public class BassClientService extends ProfileService { return; } + sEventLogger.logd( + DBG, + TAG, + "connectionStateChanged: fromState= " + + BluetoothProfile.getConnectionStateName(fromState) + + ", toState= " + + BluetoothProfile.getConnectionStateName(toState)); + // Check if the device is disconnected - if unbond, remove the state machine if (toState == BluetoothProfile.STATE_DISCONNECTED) { mPendingGroupOp.remove(device); @@ -946,6 +961,10 @@ public class BassClientService extends ProfileService { int broadcastId = (int)(((broadcastIdArray[2] & 0xff) << 16) | ((broadcastIdArray[1] & 0xff) << 8) | (broadcastIdArray[0] & 0xff)); + + sEventLogger.logd(DBG, TAG, "Broadcast Source Found: Broadcast ID: " + + broadcastId); + if (mScanBroadcasts.get(broadcastId) == null) { log("selectBroadcastSource: broadcastId " + broadcastId); mScanBroadcasts.put(broadcastId, result); @@ -981,6 +1000,7 @@ public class BassClientService extends ProfileService { serviceData, serviceDataMask).build()); } scanner.startScan(filters, settings, mSearchScanCallback); + sEventLogger.logd(DBG, TAG, "startSearchingForSources"); mCallbacks.notifySearchStarted(BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST); } } @@ -1008,6 +1028,7 @@ public class BassClientService extends ProfileService { } scanner.stopScan(mSearchScanCallback); mSearchScanCallback = null; + sEventLogger.logd(DBG, TAG, "stopSearchingForSources"); mCallbacks.notifySearchStopped(BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST); mScanBroadcasts.clear(); } @@ -1030,6 +1051,8 @@ public class BassClientService extends ProfileService { } synchronized (mStateMachines) { + sEventLogger.logd(DBG, TAG, "Select Broadcast Source"); + BassClientStateMachine stateMachine = getOrCreateStateMachine(sink); Message message = stateMachine.obtainMessage( BassClientStateMachine.SELECT_BCAST_SOURCE); @@ -1112,10 +1135,22 @@ public class BassClientService extends ProfileService { sourceMetadata); } + sEventLogger.logd( + DBG, + TAG, + "Add Broadcast Source: device: " + + sink + + ", sourceMetadata: " + + sourceMetadata + + ", isGroupOp: " + + isGroupOp); + Message message = stateMachine.obtainMessage(BassClientStateMachine.ADD_BCAST_SOURCE); message.obj = sourceMetadata; stateMachine.sendMessage(message); if (code != null && code.length != 0) { + sEventLogger.logd(DBG, TAG, "Set Broadcast Code (Add Source context)"); + message = stateMachine.obtainMessage(BassClientStateMachine.SET_BCAST_CODE); message.obj = sourceMetadata; message.arg1 = BassClientStateMachine.ARGTYPE_METADATA; @@ -1183,6 +1218,16 @@ public class BassClientService extends ProfileService { throw new IllegalStateException("modifySource: source operation already pending"); } + sEventLogger.logd( + DBG, + TAG, + "Modify Broadcast Source: device: " + + sink + + ", sourceId: " + + sourceId + + ", updatedMetadata: " + + updatedMetadata); + Message message = stateMachine.obtainMessage(BassClientStateMachine.UPDATE_BCAST_SOURCE); message.arg1 = deviceSourceId; @@ -1190,6 +1235,7 @@ public class BassClientService extends ProfileService { message.obj = updatedMetadata; stateMachine.sendMessage(message); if (code != null && code.length != 0) { + sEventLogger.logd(DBG, TAG, "Set Broadcast Code (Modify Source context)"); message = stateMachine.obtainMessage(BassClientStateMachine.SET_BCAST_CODE); message.obj = updatedMetadata; message.arg1 = BassClientStateMachine.ARGTYPE_METADATA; @@ -1238,6 +1284,14 @@ public class BassClientService extends ProfileService { stateMachine.getCurrentBroadcastMetadata(sourceId); if (metaData != null && recvState != null && recvState.getPaSyncState() == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED) { + sEventLogger.logd( + DBG, + TAG, + "Remove Broadcast Source(Force lost PA sync): device: " + + sink + + ", sourceId: " + + sourceId); + log("Force source to lost PA sync"); Message message = stateMachine.obtainMessage( BassClientStateMachine.UPDATE_BCAST_SOURCE); @@ -1250,6 +1304,11 @@ public class BassClientService extends ProfileService { continue; } + sEventLogger.logd( + DBG, + TAG, + "Remove Broadcast Source: device: " + sink + ", sourceId: " + sourceId); + Message message = stateMachine.obtainMessage(BassClientStateMachine.REMOVE_BCAST_SOURCE); message.arg1 = deviceSourceId; @@ -1479,60 +1538,177 @@ public class BassClientService extends ProfileService { } void notifySearchStarted(int reason) { + sEventLogger.logd(DBG, TAG, "notifySearchStarted: " + ", reason: " + reason); obtainMessage(MSG_SEARCH_STARTED, reason, 0).sendToTarget(); } void notifySearchStartFailed(int reason) { + sEventLogger.loge(TAG, "notifySearchStartFailed: " + ", reason: " + reason); obtainMessage(MSG_SEARCH_STARTED_FAILED, reason, 0).sendToTarget(); } void notifySearchStopped(int reason) { + sEventLogger.logd(DBG, TAG, "notifySearchStopped: " + ", reason: " + reason); obtainMessage(MSG_SEARCH_STOPPED, reason, 0).sendToTarget(); } void notifySearchStopFailed(int reason) { + sEventLogger.loge(TAG, "notifySearchStopFailed: " + ", reason: " + reason); obtainMessage(MSG_SEARCH_STOPPED_FAILED, reason, 0).sendToTarget(); } void notifySourceFound(BluetoothLeBroadcastMetadata source) { + sEventLogger.logd( + DBG, + TAG, + "invokeCallback: MSG_SOURCE_FOUND" + + ", source: " + + source.getSourceDevice() + + ", broadcastId: " + + source.getBroadcastId() + + ", broadcastName: " + + source.getBroadcastName() + + ", isPublic: " + + source.isPublicBroadcast() + + ", isEncrypted: " + + source.isEncrypted()); obtainMessage(MSG_SOURCE_FOUND, 0, 0, source).sendToTarget(); } void notifySourceAdded(BluetoothDevice sink, BluetoothLeBroadcastReceiveState recvState, int reason) { + sEventLogger.logd( + DBG, + TAG, + "notifySourceAdded: " + + ", source: " + + sink + + ", sourceId: " + + recvState.getSourceId() + + ", reason: " + + reason); + ObjParams param = new ObjParams(sink, recvState); obtainMessage(MSG_SOURCE_ADDED, reason, recvState.getSourceId(), param).sendToTarget(); } void notifySourceAddFailed(BluetoothDevice sink, BluetoothLeBroadcastMetadata source, int reason) { + sEventLogger.loge( + TAG, "notifySourceAddFailed: " + ", source: " + sink + ", reason: " + reason); ObjParams param = new ObjParams(sink, source); obtainMessage(MSG_SOURCE_ADDED_FAILED, reason, 0, param).sendToTarget(); } void notifySourceModified(BluetoothDevice sink, int sourceId, int reason) { + sEventLogger.logd( + DBG, + TAG, + "notifySourceModified: " + + ", source: " + + sink + + ", sourceId: " + + sourceId + + ", reason: " + + reason); obtainMessage(MSG_SOURCE_MODIFIED, reason, sourceId, sink).sendToTarget(); } void notifySourceModifyFailed(BluetoothDevice sink, int sourceId, int reason) { + sEventLogger.loge( + TAG, + "notifySourceModifyFailed: " + ", source: " + sink + ", reason: " + reason); obtainMessage(MSG_SOURCE_MODIFIED_FAILED, reason, sourceId, sink).sendToTarget(); } void notifySourceRemoved(BluetoothDevice sink, int sourceId, int reason) { + sEventLogger.logd( + DBG, + TAG, + "notifySourceRemoved: " + + ", source: " + + sink + + ", sourceId: " + + sourceId + + ", reason: " + + reason); obtainMessage(MSG_SOURCE_REMOVED, reason, sourceId, sink).sendToTarget(); } void notifySourceRemoveFailed(BluetoothDevice sink, int sourceId, int reason) { + sEventLogger.loge( + TAG, + "notifySourceRemoveFailed: " + + ", source: " + + sink + + ", sourceId: " + + sourceId + + ", reason: " + + reason); obtainMessage(MSG_SOURCE_REMOVED_FAILED, reason, sourceId, sink).sendToTarget(); } void notifyReceiveStateChanged(BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state) { ObjParams param = new ObjParams(sink, state); + String subgroupState = " / SUB GROUPS: "; + for (int i = 0; i < state.getNumSubgroups(); i++) { + subgroupState += "IDX: " + i + ", SYNC: " + state.getBisSyncState().get(i); + } + + sEventLogger.logd( + TAG, + "notifyReceiveStateChanged: " + + ", source: " + + sink + + ", state: SRC ID: " + + state.getSourceId() + + " / ADDR TYPE: " + + state.getSourceAddressType() + + " / SRC DEV: " + + state.getSourceDevice() + + " / ADV SID: " + + state.getSourceAdvertisingSid() + + " / BID: " + + state.getBroadcastId() + + " / PA STATE: " + + state.getPaSyncState() + + " / BENC STATE: " + + state.getBigEncryptionState() + + " / BAD CODE: " + + Arrays.toString(state.getBadCode()) + + subgroupState); obtainMessage(MSG_RECEIVESTATE_CHANGED, 0, sourceId, param).sendToTarget(); } } + @Override + public void dump(StringBuilder sb) { + super.dump(sb); + + sb.append("Broadcast Assistant Service instance:\n"); + + /* Dump first connected state machines */ + for (Map.Entry<BluetoothDevice, BassClientStateMachine> entry : mStateMachines.entrySet()) { + BassClientStateMachine sm = entry.getValue(); + if (sm.getConnectionState() == BluetoothProfile.STATE_CONNECTED) { + sm.dump(sb); + } + } + + /* Dump at least all other than connected state machines */ + for (Map.Entry<BluetoothDevice, BassClientStateMachine> entry : mStateMachines.entrySet()) { + BassClientStateMachine sm = entry.getValue(); + if (sm.getConnectionState() != BluetoothProfile.STATE_CONNECTED) { + sm.dump(sb); + } + } + + sb.append("\n"); + sEventLogger.dump(sb); + sb.append("\n"); + } + /** Binder object: must be a static class or memory leak may occur */ @VisibleForTesting static class BluetoothLeBroadcastAssistantBinder extends IBluetoothLeBroadcastAssistant.Stub diff --git a/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java b/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java index 2bb0923ac4..d625d92f8c 100644 --- a/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java +++ b/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java @@ -457,6 +457,14 @@ public class BassClientStateMachine extends StateMachine { mNoStopScanOffload = false; } + private void resetBluetoothGatt() { + // cleanup mBluetoothGatt + if (mBluetoothGatt != null) { + mBluetoothGatt.close(); + mBluetoothGatt = null; + } + } + private BluetoothLeBroadcastMetadata getBroadcastMetadataFromBaseData(BaseData baseData, BluetoothDevice device) { return getBroadcastMetadataFromBaseData(baseData, device, false); @@ -1261,6 +1269,7 @@ public class BassClientStateMachine extends StateMachine { transitionTo(mConnected); } else { Log.w(TAG, "Connection failed to " + mDevice); + resetBluetoothGatt(); transitionTo(mDisconnected); } break; @@ -1271,6 +1280,7 @@ public class BassClientStateMachine extends StateMachine { Log.e(TAG, "Unknown device timeout " + device); break; } + resetBluetoothGatt(); transitionTo(mDisconnected); break; case PSYNC_ACTIVE_TIMEOUT: @@ -1370,24 +1380,7 @@ public class BassClientStateMachine extends StateMachine { log("no existing SI for update source op"); return null; } - BluetoothDevice broadcastSource = metaData.getSourceDevice(); - PeriodicAdvertisementResult paRes = - mService.getPeriodicAdvertisementResult(broadcastSource); - if (paRes == null) { - Log.e(TAG, "No matching psync, scan res for update"); - mService.getCallbacks().notifySourceRemoveFailed( - mDevice, sourceId, BluetoothStatusCodes.ERROR_UNKNOWN); - return null; - } - // populate metadata from BASE levelOne - BaseData base = mService.getBase(paRes.getSyncHandle()); - if (base == null) { - Log.e(TAG, "No valid base data populated for this device"); - mService.getCallbacks().notifySourceRemoveFailed( - mDevice, sourceId, BluetoothStatusCodes.ERROR_UNKNOWN); - return null; - } - byte numSubGroups = base.getNumberOfSubgroupsofBIG(); + byte numSubGroups = (byte) metaData.getSubgroups().size(); byte[] res = new byte[UPDATE_SOURCE_FIXED_LENGTH + numSubGroups * 5]; int offset = 0; // Opcode @@ -1544,11 +1537,7 @@ public class BassClientStateMachine extends StateMachine { Log.w(TAG, "device is already connected to Bass" + mDevice); } else { Log.w(TAG, "unexpected disconnected from " + mDevice); - // cleanup mBluetoothGatt - if (mBluetoothGatt != null) { - mBluetoothGatt.close(); - mBluetoothGatt = null; - } + resetBluetoothGatt(); cancelActiveSync(null); transitionTo(mDisconnected); } @@ -1829,6 +1818,8 @@ public class BassClientStateMachine extends StateMachine { Log.w(TAG, "should never happen from this state"); } else { Log.w(TAG, "Unexpected disconnection " + mDevice); + resetBluetoothGatt(); + cancelActiveSync(null); transitionTo(mDisconnected); } break; @@ -1980,6 +1971,11 @@ public class BassClientStateMachine extends StateMachine { ProfileService.println(sb, " " + line); } scanner.close(); + for (Map.Entry<Integer, BluetoothLeBroadcastReceiveState> entry : + mBluetoothLeBroadcastReceiveStates.entrySet()) { + BluetoothLeBroadcastReceiveState state = entry.getValue(); + sb.append(state); + } } @Override diff --git a/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java b/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java index a871bdd229..73530ed150 100644 --- a/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java +++ b/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java @@ -23,9 +23,7 @@ import android.annotation.SuppressLint; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothHapClient; import android.bluetooth.BluetoothHearingAid; -import android.bluetooth.BluetoothLeAudio; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothSinkAudioPolicy; import android.content.BroadcastReceiver; @@ -69,13 +67,9 @@ import java.util.Set; * devices is more than one, the rules below will apply. * 2) The selected A2DP active device is the one used for AVRCP as well. * 3) The HFP active device might be different from the A2DP active device. - * 4) The Active Device Manager always listens for ACTION_ACTIVE_DEVICE_CHANGED - * broadcasts for each profile: - * - BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED for HearingAid - * - BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED for LE audio - * If such broadcast is received (e.g., triggered indirectly by user - * action on the UI), the device in the received broadcast is marked - * as the current active device for that profile. + * 4) The Active Device Manager always listens for the change of active devices. + * When it changed (e.g., triggered indirectly by user action on the UI), + * the new active device is marked as the current active device for that profile. * 5) If there is a HearingAid active device, then A2DP, HFP and LE audio active devices * must be set to null (i.e., A2DP, HFP and LE audio cannot have active devices). * The reason is that A2DP, HFP or LE audio cannot be used together with HearingAid. @@ -90,11 +84,8 @@ import java.util.Set; * 7) If the currently active device (per profile) is disconnected, the * Active Device Manager just marks that the profile has no active device, * and the lastly activated BT device that is still connected would be selected. - * 8) If there is already an active device, and the corresponding - * ACTION_ACTIVE_DEVICE_CHANGED broadcast is received, the device - * contained in the broadcast is marked as active. However, if - * the contained device is null, the corresponding profile is marked - * as having no active device. + * 8) If there is already an active device, however, if active device change notified + * with a null device, the corresponding profile is marked as having no active device. * 9) If a wired audio device is connected, the audio output is switched * by the Audio Framework itself to that device. We detect this here, * and the active device for each profile (A2DP/HFP/HearingAid/LE audio) is set @@ -153,7 +144,6 @@ public class ActiveDeviceManager { private BluetoothDevice mClassicDeviceToBeActivated = null; private BluetoothDevice mClassicDeviceNotToBeActivated = null; - // Broadcast receiver for all changes private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -170,55 +160,12 @@ public class ActiveDeviceManager { if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { int currentState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); mHandler.post(() -> handleAdapterStateChanged(currentState)); - return; - } - - final BluetoothDevice device = intent.getParcelableExtra( - BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class); - final int previousState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1); - final int currentState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); - - if (currentState != -1 && previousState == currentState) { - return; - } - - switch (action) { - case BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED: - if (currentState == BluetoothProfile.STATE_CONNECTED) { - mHandler.post(() -> handleHearingAidConnected(device)); - } else if (previousState == BluetoothProfile.STATE_CONNECTED) { - mHandler.post(() -> handleHearingAidDisconnected(device)); - } - break; - case BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED: - if (currentState == BluetoothProfile.STATE_CONNECTED) { - mHandler.post(() -> handleLeAudioConnected(device)); - } else if (previousState == BluetoothProfile.STATE_CONNECTED) { - mHandler.post(() -> handleLeAudioDisconnected(device)); - } - break; - case BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED: - if (currentState == BluetoothProfile.STATE_CONNECTED) { - mHandler.post(() -> handleHapConnected(device)); - } else if (previousState == BluetoothProfile.STATE_CONNECTED) { - mHandler.post(() -> handleHapDisconnected(device)); - } - break; - case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED: - mHandler.post(() -> handleHearingAidActiveDeviceChanged(device)); - break; - case BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED: - mHandler.post(() -> handleLeAudioActiveDeviceChanged(device)); - break; - default: - Log.e(TAG, "Received unexpected intent, action=" + action); - break; } } }; /** - * Called when A2DP connection state changed by A2dpStateMachine + * Called when A2DP connection state changed by A2dpService * * @param device The device of which connection state was changed * @param fromState The previous connection state of the device @@ -242,7 +189,7 @@ public class ActiveDeviceManager { } /** - * Called when HFP connection state changed by HeadsetStateMachine + * Called when HFP connection state changed by HeadsetService * * @param device The device of which connection state was changed * @param prevState The previous connection state of the device @@ -257,14 +204,78 @@ public class ActiveDeviceManager { } /** - * Called when HFP active state changed by HeadsetStateMachine + * Called when HFP active state changed by HeadsetService * - * @param device The device currently activated. {@code null} if no A2DP device activated + * @param device The device currently activated. {@code null} if no HFP device activated */ public void hfpActiveStateChanged(BluetoothDevice device) { mHandler.post(() -> handleHfpActiveDeviceChanged(device)); } + /** + * Called when LE audio connection state changed by LeAudioService + * + * @param device The device of which connection state was changed + * @param prevState The previous connection state of the device + * @param newState The new connection state of the device + */ + public void leAudioConnectionStateChanged(BluetoothDevice device, int prevState, int newState) { + if (newState == BluetoothProfile.STATE_CONNECTED) { + mHandler.post(() -> handleLeAudioConnected(device)); + } else if (prevState == BluetoothProfile.STATE_CONNECTED) { + mHandler.post(() -> handleLeAudioDisconnected(device)); + } + } + + /** + * Called when LE audio active state changed by LeAudioService + * + * @param device The device currently activated. {@code null} if no LE audio device activated + */ + public void leAudioActiveStateChanged(BluetoothDevice device) { + mHandler.post(() -> handleLeAudioActiveDeviceChanged(device)); + } + + /** + * Called when HearingAid connection state changed by HearingAidService + * + * @param device The device of which connection state was changed + * @param prevState The previous connection state of the device + * @param newState The new connection state of the device + */ + public void hearingAidConnectionStateChanged( + BluetoothDevice device, int prevState, int newState) { + if (newState == BluetoothProfile.STATE_CONNECTED) { + mHandler.post(() -> handleHearingAidConnected(device)); + } else if (prevState == BluetoothProfile.STATE_CONNECTED) { + mHandler.post(() -> handleHearingAidDisconnected(device)); + } + } + + /** + * Called when HearingAid active state changed by HearingAidService + * + * @param device The device currently activated. {@code null} if no HearingAid device activated + */ + public void hearingAidActiveStateChanged(BluetoothDevice device) { + mHandler.post(() -> handleHearingAidActiveDeviceChanged(device)); + } + + /** + * Called when HAP connection state changed by HapClientService + * + * @param device The device of which connection state was changed + * @param prevState The previous connection state of the device + * @param newState The new connection state of the device + */ + public void hapConnectionStateChanged(BluetoothDevice device, int prevState, int newState) { + if (newState == BluetoothProfile.STATE_CONNECTED) { + mHandler.post(() -> handleHapConnected(device)); + } else if (prevState == BluetoothProfile.STATE_CONNECTED) { + mHandler.post(() -> handleHapDisconnected(device)); + } + } + private void handleAdapterStateChanged(int currentState) { if (DBG) { Log.d(TAG, "handleAdapterStateChanged: currentState=" + currentState); @@ -827,11 +838,6 @@ public class ActiveDeviceManager { IntentFilter filter = new IntentFilter(); filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); - filter.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED); - filter.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED); - filter.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED); - filter.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED); - filter.addAction(BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED); mAdapterService.registerReceiver(mReceiver, filter, Context.RECEIVER_EXPORTED); mAudioManager.registerAudioDeviceCallback(mAudioManagerAudioDeviceCallback, mHandler); diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterService.java b/android/app/src/com/android/bluetooth/btservice/AdapterService.java index 53382e3ff6..f84cc69ff4 100644 --- a/android/app/src/com/android/bluetooth/btservice/AdapterService.java +++ b/android/app/src/com/android/bluetooth/btservice/AdapterService.java @@ -79,11 +79,9 @@ import android.bluetooth.UidTraffic; import android.companion.CompanionDeviceManager; import android.content.AttributionSource; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.os.AsyncTask; @@ -122,6 +120,7 @@ import com.android.bluetooth.bass_client.BassClientService; import com.android.bluetooth.btservice.InteropUtil.InteropFeature; import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties; import com.android.bluetooth.btservice.activityattribution.ActivityAttributionService; +import com.android.bluetooth.btservice.bluetoothkeystore.BluetoothKeystoreNativeInterface; import com.android.bluetooth.btservice.bluetoothkeystore.BluetoothKeystoreService; import com.android.bluetooth.btservice.storage.DatabaseManager; import com.android.bluetooth.btservice.storage.MetadataDatabase; @@ -382,7 +381,7 @@ public class AdapterService extends Service { private BassClientService mBassClientService; private BatteryService mBatteryService; private BluetoothQualityReportNativeInterface mBluetoothQualityReportNativeInterface; - private IBluetoothGatt mBluetoothGatt; + private GattService mGattService; private volatile boolean mTestModeEnabled = false; @@ -436,8 +435,6 @@ public class AdapterService extends Service { private static final int MESSAGE_PROFILE_SERVICE_REGISTERED = 2; private static final int MESSAGE_PROFILE_SERVICE_UNREGISTERED = 3; private static final int MESSAGE_PREFERRED_AUDIO_PROFILES_AUDIO_FRAMEWORK_TIMEOUT = 4; - private static final int MESSAGE_ON_PROFILE_SERVICE_BIND = 5; - private static final int MESSAGE_ON_PROFILE_SERVICE_UNBIND = 6; class AdapterServiceHandler extends Handler { AdapterServiceHandler(Looper looper) { @@ -461,14 +458,6 @@ public class AdapterService extends Service { verboseLog("handleMessage() - MESSAGE_PROFILE_SERVICE_UNREGISTERED"); unregisterProfileService((ProfileService) msg.obj); break; - case MESSAGE_ON_PROFILE_SERVICE_BIND: - verboseLog("handleMessage() - MESSAGE_ON_PROFILE_SERVICE_BIND"); - onGattBind((IBinder) msg.obj); - break; - case MESSAGE_ON_PROFILE_SERVICE_UNBIND: - verboseLog("handleMessage() - MESSAGE_ON_PROFILE_SERVICE_UNBIND"); - onGattUnbind(); - break; case MESSAGE_PREFERRED_AUDIO_PROFILES_AUDIO_FRAMEWORK_TIMEOUT: errorLog( "handleMessage() - " @@ -508,22 +497,6 @@ public class AdapterService extends Service { mRegisteredProfiles.remove(profile); } - private void onGattBind(IBinder service) { - mBluetoothGatt = IBluetoothGatt.Stub.asInterface(service); - try { - mBluetoothGatt.startService(); - } catch (RemoteException e) { - Log.e(TAG, "onGattBind: RemoteException", e); - } - } - - private void onGattUnbind() { - mBluetoothGatt = null; - Log.e( - TAG, - "onGattUnbind: Gatt service has disconnected from AdapterService unexpectedly"); - } - private void processProfileServiceStateChanged(ProfileService profile, int state) { switch (state) { case BluetoothAdapter.STATE_ON: @@ -651,7 +624,9 @@ public class AdapterService extends Service { mAdapterProperties = new AdapterProperties(this); mAdapterStateMachine = new AdapterState(this, mLooper); mJniCallbacks = new JniCallbacks(this, mAdapterProperties); - mBluetoothKeystoreService = new BluetoothKeystoreService(isCommonCriteriaMode()); + mBluetoothKeystoreService = + new BluetoothKeystoreService( + new BluetoothKeystoreNativeInterface(), isCommonCriteriaMode()); mBluetoothKeystoreService.start(); int configCompareResult = mBluetoothKeystoreService.getCompareResult(); @@ -728,7 +703,6 @@ public class AdapterService extends Service { mBluetoothSocketManagerBinder = new BluetoothSocketManagerBinder(this); mActivityAttributionService = new ActivityAttributionService(); - mActivityAttributionService.start(); setAdapterService(this); @@ -1015,47 +989,11 @@ public class AdapterService extends Service { } } - class GattServiceConnection implements ServiceConnection { - public void onServiceConnected(ComponentName componentName, IBinder service) { - String name = componentName.getClassName(); - if (DBG) { - Log.d(TAG, "GattServiceConnection.onServiceConnected: " + name); - } - if (!name.equals(GattService.class.getName())) { - Log.e(TAG, "Unknown service connected: " + name); - return; - } - mHandler.obtainMessage(MESSAGE_ON_PROFILE_SERVICE_BIND, service).sendToTarget(); - } - - public void onServiceDisconnected(ComponentName componentName) { - // Called if we unexpectedly disconnect. This should never happen. - String name = componentName.getClassName(); - Log.e(TAG, "GattServiceConnection.onServiceDisconnected: " + name); - if (!name.equals(GattService.class.getName())) { - Log.e(TAG, "Unknown service disconnected: " + name); - return; - } - mHandler.sendEmptyMessage(MESSAGE_ON_PROFILE_SERVICE_UNBIND); - } - } - - private GattServiceConnection mGattConnection = new GattServiceConnection(); - private void startGattProfileService() { mStartedProfiles.add(GattService.class.getSimpleName()); - Intent intent = new Intent(this, GattService.class); - if (!bindServiceAsUser( - intent, - mGattConnection, - Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, - UserHandle.CURRENT)) { - // This should never happen - // unbindService will be called during stopGattProfileService triggered by AdapterState - Log.e(TAG, "Error while binding to gatt. This Bluetooth session will timeout"); - unbindService(mGattConnection); - } + mGattService = new GattService(this); + ((ProfileService) mGattService).doStart(); } private void stopGattProfileService() { @@ -1066,15 +1004,10 @@ public class AdapterService extends Service { } mStartedProfiles.remove(GattService.class.getSimpleName()); - - try { - if (mBluetoothGatt != null) { - mBluetoothGatt.stopService(); - } - } catch (RemoteException e) { - Log.e(TAG, "stopGattProfileService: RemoteException", e); + if (mGattService != null) { + ((ProfileService) mGattService).doStop(); + mGattService = null; } - unbindService(mGattConnection); } private void invalidateBluetoothGetStateCache() { @@ -1372,10 +1305,6 @@ public class AdapterService extends Service { mSdpManager = null; } - if (mActivityAttributionService != null) { - mActivityAttributionService.cleanup(); - } - if (mNativeAvailable) { debugLog("cleanup() - Cleaning up adapter native"); cleanupNative(); @@ -5026,7 +4955,7 @@ public class AdapterService extends Service { if (service == null) { return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; } - if (!callerIsSystem(TAG, "setPreferredAudioProfiles")) { + if (!callerIsSystem(TAG, "notifyActiveDeviceChangeApplied")) { return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED; } requireNonNull(device); @@ -5258,6 +5187,7 @@ public class AdapterService extends Service { try { AdapterService service = getService(); if (service != null) { + enforceBluetoothPrivilegedPermission(service); service.unregAllGattClient(source); } receiver.send(null); @@ -6089,7 +6019,10 @@ public class AdapterService extends Service { if (leAudioSupported) { Log.i(TAG, "setActiveDevice: Setting active Le Audio device " + device); if (device == null) { - mLeAudioService.removeActiveDevice(false); + /* If called by BluetoothAdapter it means Audio should not be stopped. + * For this reason let's say that fallback device exists + */ + mLeAudioService.removeActiveDevice(true /* hasFallbackDevice */); } else { mLeAudioService.setActiveDevice(device); } @@ -6894,16 +6827,15 @@ public class AdapterService extends Service { } IBluetoothGatt getBluetoothGatt() { - return mBluetoothGatt; + if (mGattService == null) { + return null; + } + return IBluetoothGatt.Stub.asInterface(((ProfileService) mGattService).getBinder()); } void unregAllGattClient(AttributionSource source) { - if (mBluetoothGatt != null) { - try { - mBluetoothGatt.unregAll(source); - } catch (RemoteException e) { - Log.e(TAG, "Unable to disconnect all apps.", e); - } + if (mGattService != null) { + mGattService.unregAll(source); } } diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterState.java b/android/app/src/com/android/bluetooth/btservice/AdapterState.java index 87134e4673..ae395bf173 100644 --- a/android/app/src/com/android/bluetooth/btservice/AdapterState.java +++ b/android/app/src/com/android/bluetooth/btservice/AdapterState.java @@ -77,10 +77,14 @@ final class AdapterState extends StateMachine { static final String BLE_STOP_TIMEOUT_DELAY_PROPERTY = "ro.bluetooth.ble_stop_timeout_delay"; - static final int BLE_START_TIMEOUT_DELAY = 4000; - static final int BLE_STOP_TIMEOUT_DELAY = 4000; - static final int BREDR_START_TIMEOUT_DELAY = 4000; - static final int BREDR_STOP_TIMEOUT_DELAY = 4000; + static final int BLE_START_TIMEOUT_DELAY = + 4000 * SystemProperties.getInt("ro.hw_timeout_multiplier", 1); + static final int BLE_STOP_TIMEOUT_DELAY = + 4000 * SystemProperties.getInt("ro.hw_timeout_multiplier", 1); + static final int BREDR_START_TIMEOUT_DELAY = + 4000 * SystemProperties.getInt("ro.hw_timeout_multiplier", 1); + static final int BREDR_STOP_TIMEOUT_DELAY = + 4000 * SystemProperties.getInt("ro.hw_timeout_multiplier", 1); private AdapterService mAdapterService; private TurningOnState mTurningOnState = new TurningOnState(); diff --git a/android/app/src/com/android/bluetooth/btservice/BondStateMachine.java b/android/app/src/com/android/bluetooth/btservice/BondStateMachine.java index a1af22f3c3..2bd4abb39b 100644 --- a/android/app/src/com/android/bluetooth/btservice/BondStateMachine.java +++ b/android/app/src/com/android/bluetooth/btservice/BondStateMachine.java @@ -250,6 +250,11 @@ final class BondStateMachine extends StateMachine { } break; case SSP_REQUEST: + if (devProp == null) { + errorLog("devProp is null, maybe the device is disconnected"); + break; + } + int passkey = msg.arg1; int variant = msg.arg2; boolean displayPasskey = @@ -262,6 +267,11 @@ final class BondStateMachine extends StateMachine { variant); break; case PIN_REQUEST: + if (devProp == null) { + errorLog("devProp is null, maybe the device is disconnected"); + break; + } + BluetoothClass btClass = dev.getBluetoothClass(); int btDeviceClass = btClass.getDeviceClass(); if (btDeviceClass == BluetoothClass.Device.PERIPHERAL_KEYBOARD || btDeviceClass diff --git a/android/app/src/com/android/bluetooth/btservice/Config.java b/android/app/src/com/android/bluetooth/btservice/Config.java index bc0ed8408c..caa1a0004a 100644 --- a/android/app/src/com/android/bluetooth/btservice/Config.java +++ b/android/app/src/com/android/bluetooth/btservice/Config.java @@ -56,6 +56,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Set; public class Config { @@ -171,9 +172,14 @@ public class Config { profile.mSupported = enabled; } } + if (enabled) { + sSupportedProfiles.add(profileClass); + } else { + sSupportedProfiles.remove(profileClass); + } } - private static Class[] sSupportedProfiles = new Class[0]; + private static List<Class> sSupportedProfiles = new ArrayList<>(); private static boolean sIsGdEnabledUptoScanningLayer = false; @@ -211,15 +217,20 @@ public class Config { setProfileEnabled(HearingAidService.class, false); } - ArrayList<Class> profiles = new ArrayList<>(PROFILE_SERVICES_AND_FLAGS.length); - for (ProfileConfig config : PROFILE_SERVICES_AND_FLAGS) { - Log.i(TAG, "init: profile=" + config.mClass.getSimpleName() + ", enabled=" - + config.mSupported); - if (config.mSupported) { - profiles.add(config.mClass); + synchronized (sSupportedProfiles) { + sSupportedProfiles.clear(); + for (ProfileConfig config : PROFILE_SERVICES_AND_FLAGS) { + Log.i( + TAG, + "init: profile=" + + config.mClass.getSimpleName() + + ", enabled=" + + config.mSupported); + if (config.mSupported) { + sSupportedProfiles.add(config.mClass); + } } } - sSupportedProfiles = profiles.toArray(new Class[profiles.size()]); if (ctx == null) { return; @@ -253,19 +264,17 @@ public class Config { * Remove the input profiles from the supported list. */ static void removeProfileFromSupportedList(HashSet<Class> nonSupportedProfiles) { - ArrayList<Class> profilesList = new ArrayList<Class>(Arrays.asList(sSupportedProfiles)); - Iterator<Class> iter = profilesList.iterator(); - - while (iter.hasNext()) { - Class profileClass = iter.next(); - - if (nonSupportedProfiles.contains(profileClass)) { - iter.remove(); - Log.v(TAG, "Remove " + profileClass.getSimpleName() + " from supported list."); + synchronized (sSupportedProfiles) { + Iterator<Class> iter = sSupportedProfiles.iterator(); + while (iter.hasNext()) { + Class profileClass = iter.next(); + + if (nonSupportedProfiles.contains(profileClass)) { + iter.remove(); + Log.v(TAG, "Remove " + profileClass.getSimpleName() + " from supported list."); + } } } - - sSupportedProfiles = profilesList.toArray(new Class[profilesList.size()]); } static void updateSupportedProfileMask(Boolean enable, Class profile, int supportedProfile) { @@ -286,7 +295,9 @@ public class Config { } static Class[] getSupportedProfiles() { - return sSupportedProfiles; + synchronized (sSupportedProfiles) { + return sSupportedProfiles.toArray(new Class[0]); + } } static boolean isGdEnabledUpToScanningLayer() { diff --git a/android/app/src/com/android/bluetooth/btservice/ProfileService.java b/android/app/src/com/android/bluetooth/btservice/ProfileService.java index 5795847d1b..acd72c7c69 100644 --- a/android/app/src/com/android/bluetooth/btservice/ProfileService.java +++ b/android/app/src/com/android/bluetooth/btservice/ProfileService.java @@ -18,6 +18,8 @@ package com.android.bluetooth.btservice; import static android.Manifest.permission.BLUETOOTH_CONNECT; +import static java.util.Objects.requireNonNull; + import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.app.Service; @@ -180,6 +182,11 @@ public abstract class ProfileService extends Service { return mBinder; } + IBinder getBinder() { + requireNonNull(mBinder, "Binder is null. onCreate need to be called first"); + return mBinder; + } + @Override // Suppressed since this is called from framework @SuppressLint("AndroidFrameworkRequiresPermission") @@ -295,7 +302,7 @@ public abstract class ProfileService extends Service { android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS }) - protected void doStart() { + void doStart() { Log.v(mName, "doStart"); if (mAdapter == null) { Log.w(mName, "Can't start profile service: device does not have BT"); @@ -321,7 +328,7 @@ public abstract class ProfileService extends Service { mAdapterService.onProfileServiceStateChanged(this, BluetoothAdapter.STATE_ON); } - protected void doStop() { + void doStop() { Log.v(mName, "doStop"); if (mAdapterService == null || mAdapterService.isStartedProfile(mName)) { Log.w(mName, "Unexpectedly do Stop, don't stop."); diff --git a/android/app/src/com/android/bluetooth/btservice/activityAttribution/ActivityAttributionNativeInterface.java b/android/app/src/com/android/bluetooth/btservice/activityAttribution/ActivityAttributionNativeInterface.java index b9cb9d778d..7755ce2d2c 100644 --- a/android/app/src/com/android/bluetooth/btservice/activityAttribution/ActivityAttributionNativeInterface.java +++ b/android/app/src/com/android/bluetooth/btservice/activityAttribution/ActivityAttributionNativeInterface.java @@ -21,26 +21,15 @@ */ package com.android.bluetooth.btservice.activityattribution; -import android.util.Log; - import com.android.internal.annotations.GuardedBy; -import java.util.Arrays; - /** ActivityAttribution Native Interface to/from JNI. */ public class ActivityAttributionNativeInterface { - private static final boolean DBG = false; - private static final String TAG = "ActivityAttributionNativeInterface"; - @GuardedBy("INSTANCE_LOCK") private static ActivityAttributionNativeInterface sInstance; private static final Object INSTANCE_LOCK = new Object(); - static { - classInitNative(); - } - /** Get singleton instance. */ public static ActivityAttributionNativeInterface getInstance() { synchronized (INSTANCE_LOCK) { @@ -51,40 +40,11 @@ public class ActivityAttributionNativeInterface { } } - /** Initializes the native interface. */ - public void init() { - initNative(); - } - - /** Cleanup the native interface. */ - public void cleanup() { - cleanupNative(); - } - /** Notify the UID and package name of the app, and the address of associated active device */ public void notifyActivityAttributionInfo(int uid, String packageName, String deviceAddress) { notifyActivityAttributionInfoNative(uid, packageName, deviceAddress); } - // Callbacks from the native stack back into the Java framework. - // All callbacks are routed via the Service which will disambiguate which - // state machine the message should be routed to. - - private void onWakeup(int activity, byte[] address) { - Log.i(TAG, "onWakeup() BTAA: " + activity); - } - - private void onActivityLogsReady(byte[] logs) { - Log.i(TAG, "onActivityLogsReady() BTAA: " + Arrays.toString(logs)); - } - - // Native methods that call into the JNI interface - private static native void classInitNative(); - - private native void initNative(); - - private native void cleanupNative(); - private native void notifyActivityAttributionInfoNative( - int uid, String packageName, String deviceAddress); + int uid, String packageName, String deviceAddress); } diff --git a/android/app/src/com/android/bluetooth/btservice/activityAttribution/ActivityAttributionService.java b/android/app/src/com/android/bluetooth/btservice/activityAttribution/ActivityAttributionService.java index 033459812a..5194a25313 100644 --- a/android/app/src/com/android/bluetooth/btservice/activityAttribution/ActivityAttributionService.java +++ b/android/app/src/com/android/bluetooth/btservice/activityAttribution/ActivityAttributionService.java @@ -16,108 +16,31 @@ package com.android.bluetooth.btservice.activityattribution; -import android.util.Log; +import static java.util.Objects.requireNonNull; -import java.util.Objects; +import android.util.Log; /** * Service used for attributes wakeup, wakelock and Bluetooth traffic into per-app and per-device * based activities. */ public class ActivityAttributionService { - private boolean mCleaningUp; - private static ActivityAttributionService sActivityAttributionService; - private static final boolean DBG = false; - private static final String TAG = "ActivityAttributionService"; - - ActivityAttributionNativeInterface mActivityAttributionNativeInterface; - - /** Start and initialize the Activity Attribution service. */ - public void start() { - debugLog("start()"); - - if (sActivityAttributionService != null) { - Log.e(TAG, "start() called twice"); - return; - } - - mActivityAttributionNativeInterface = - Objects.requireNonNull( - ActivityAttributionNativeInterface.getInstance(), - "ActivityAttributionNativeInterface " - + "cannot be null when ActivityAttributionService starts"); - - // Mark service as started - setActivityAttributionService(this); - } - - /** Cleans up the Activity Attribution service. */ - public void cleanup() { - debugLog("cleanup"); - if (mCleaningUp) { - debugLog("already doing cleanup"); - return; - } - - mCleaningUp = true; - - if (sActivityAttributionService == null) { - debugLog("cleanup() called before start()"); - return; - } - - // Mark service as stopped - setActivityAttributionService(null); + private static final String TAG = ActivityAttributionService.class.getSimpleName(); - // Cleanup native interface - mActivityAttributionNativeInterface.cleanup(); - mActivityAttributionNativeInterface = null; - } - - /** Get the ActivityAttributionService instance */ - public static synchronized ActivityAttributionService getActivityAttributionService() { - if (sActivityAttributionService == null) { - Log.w(TAG, "getActivityAttributionService(): service is NULL"); - return null; - } - - if (!sActivityAttributionService.isAvailable()) { - Log.w(TAG, "getActivityAttributionService(): service is not available"); - return null; - } - return sActivityAttributionService; - } - - /** Init JNI */ - public void initJni() { - debugLog("initJni()"); - // Initialize native interface - mActivityAttributionNativeInterface.init(); - } + private final ActivityAttributionNativeInterface mActivityAttributionNativeInterface = + requireNonNull( + ActivityAttributionNativeInterface.getInstance(), + "ActivityAttributionNativeInterface cannot be null"); /** Notify the UID and package name of the app, and the address of associated active device */ public void notifyActivityAttributionInfo(int uid, String packageName, String deviceAddress) { - Log.d(TAG, "notifyActivityAttributionInfo" - + " UID=" + uid - + " packageName=" + packageName - + " deviceAddress=" + deviceAddress); + Log.d( + TAG, + "notifyActivityAttributionInfo()" + + (" UID=" + uid) + + (" packageName=" + packageName) + + (" deviceAddress=" + deviceAddress)); mActivityAttributionNativeInterface.notifyActivityAttributionInfo( uid, packageName, deviceAddress); } - - private boolean isAvailable() { - return !mCleaningUp; - } - - private static synchronized void setActivityAttributionService( - ActivityAttributionService instance) { - debugLog("setActivityAttributionService(): set to: " + instance); - sActivityAttributionService = instance; - } - - private static void debugLog(String msg) { - if (DBG) { - Log.d(TAG, msg); - } - } } diff --git a/android/app/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreNativeInterface.java b/android/app/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreNativeInterface.java index ebee407821..0b616accaf 100644 --- a/android/app/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreNativeInterface.java +++ b/android/app/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreNativeInterface.java @@ -18,44 +18,27 @@ package com.android.bluetooth.btservice.bluetoothkeystore; import android.util.Log; -import com.android.internal.annotations.GuardedBy; import java.io.IOException; import java.security.NoSuchAlgorithmException; -final class BluetoothKeystoreNativeInterface { +/** Native interface to be used by BluetoothKeystoreService */ +public class BluetoothKeystoreNativeInterface { + private static final String TAG = BluetoothKeystoreNativeInterface.class.getSimpleName(); - private static final String TAG = "BluetoothKeystoreNativeInterface"; - - @GuardedBy("INSTANCE_LOCK") - private static BluetoothKeystoreNativeInterface sInstance; - private static final Object INSTANCE_LOCK = new Object(); + private BluetoothKeystoreService mBluetoothKeystoreService = null; static { classInitNative(); } - private BluetoothKeystoreNativeInterface() { - } - - /** - * Get singleton instance. - */ - public static BluetoothKeystoreNativeInterface getInstance() { - synchronized (INSTANCE_LOCK) { - if (sInstance == null) { - sInstance = new BluetoothKeystoreNativeInterface(); - } - return sInstance; - } - } - /** * Initializes the native interface. * - * priorities to configure. + * <p>priorities to configure. */ - public void init() { + public void init(BluetoothKeystoreService service) { + mBluetoothKeystoreService = service; initNative(); } @@ -64,6 +47,7 @@ final class BluetoothKeystoreNativeInterface { */ public void cleanup() { cleanupNative(); + mBluetoothKeystoreService = null; } // Callbacks from the native stack back into the Java framework. @@ -71,30 +55,36 @@ final class BluetoothKeystoreNativeInterface { // state machine the message should be routed to. private void setEncryptKeyOrRemoveKeyCallback(String prefixString, String decryptedString) { - BluetoothKeystoreService service = BluetoothKeystoreService.getBluetoothKeystoreService(); - if (service != null) { - try { - service.setEncryptKeyOrRemoveKey(prefixString, decryptedString); - } catch (InterruptedException e) { - Log.e(TAG, "Interrupted while operating."); - } catch (IOException e) { - Log.e(TAG, "IO error while file operating."); - } catch (NoSuchAlgorithmException e) { - Log.e(TAG, "encrypt could not find the algorithm: SHA256"); - } - } else { - Log.e(TAG, "Event ignored, service not available: " + prefixString); + final BluetoothKeystoreService service = mBluetoothKeystoreService; + + if (service == null) { + Log.e( + TAG, + "setEncryptKeyOrRemoveKeyCallback: Event ignored, service not available: " + + prefixString); + return; + } + + try { + service.setEncryptKeyOrRemoveKey(prefixString, decryptedString); + } catch (InterruptedException e) { + Log.e(TAG, "Interrupted while operating."); + } catch (IOException e) { + Log.e(TAG, "IO error while file operating."); + } catch (NoSuchAlgorithmException e) { + Log.e(TAG, "encrypt could not find the algorithm: SHA256"); } } private String getKeyCallback(String prefixString) { - BluetoothKeystoreService service = BluetoothKeystoreService.getBluetoothKeystoreService(); - if (service != null) { - return service.getKey(prefixString); - } else { - Log.e(TAG, "Event ignored, service not available: " + prefixString); + final BluetoothKeystoreService service = mBluetoothKeystoreService; + + if (service == null) { + Log.e(TAG, "getKeyCallback: Event ignored, service not available: " + prefixString); return null; } + + return service.getKey(prefixString); } // Native methods that call into the JNI interface diff --git a/android/app/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreService.java b/android/app/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreService.java index 25b06c3775..743534420b 100644 --- a/android/app/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreService.java +++ b/android/app/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreService.java @@ -46,7 +46,6 @@ import java.util.Base64; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; @@ -99,7 +98,7 @@ public class BluetoothKeystoreService { private static final int CONFIG_BACKUP_COMPARE_PASS = 0b10; private int mCompareResult; - BluetoothKeystoreNativeInterface mBluetoothKeystoreNativeInterface; + private final BluetoothKeystoreNativeInterface mBluetoothKeystoreNativeInterface; private ComputeDataThread mEncryptDataThread; private ComputeDataThread mDecryptDataThread; @@ -113,8 +112,10 @@ public class BluetoothKeystoreService { private Base64.Decoder mDecoder = Base64.getDecoder(); private Base64.Encoder mEncoder = Base64.getEncoder(); - public BluetoothKeystoreService(boolean isCommonCriteriaMode) { + public BluetoothKeystoreService( + BluetoothKeystoreNativeInterface nativeInterface, boolean isCommonCriteriaMode) { debugLog("new BluetoothKeystoreService isCommonCriteriaMode: " + isCommonCriteriaMode); + mBluetoothKeystoreNativeInterface = nativeInterface; mIsCommonCriteriaMode = isCommonCriteriaMode; mCompareResult = CONFIG_COMPARE_INIT; startThread(); @@ -140,13 +141,6 @@ public class BluetoothKeystoreService { return; } - mBluetoothKeystoreNativeInterface = Objects.requireNonNull( - BluetoothKeystoreNativeInterface.getInstance(), - "BluetoothKeystoreNativeInterface cannot be null when BluetoothKeystore starts"); - - // Mark service as started - setBluetoothKeystoreService(this); - try { if (!keyStore.containsAlias(KEYALIAS) && mIsCommonCriteriaMode) { infoLog("Enable Common Criteria mode for the first time, pass hash check."); @@ -187,12 +181,9 @@ public class BluetoothKeystoreService { debugLog("cleanup() called before start()"); return; } - // Mark service as stopped - setBluetoothKeystoreService(null); // Cleanup native interface mBluetoothKeystoreNativeInterface.cleanup(); - mBluetoothKeystoreNativeInterface = null; if (mIsCommonCriteriaMode) { cleanupForCommonCriteriaModeEnable(); @@ -296,9 +287,7 @@ public class BluetoothKeystoreService { stopThread(); startThread(); // Initialize native interface - if (mBluetoothKeystoreNativeInterface != null) { - mBluetoothKeystoreNativeInterface.init(); - } + mBluetoothKeystoreNativeInterface.init(this); } private boolean isAvailable() { @@ -306,28 +295,6 @@ public class BluetoothKeystoreService { } /** - * Get the BluetoothKeystoreService instance - */ - public static synchronized BluetoothKeystoreService getBluetoothKeystoreService() { - if (sBluetoothKeystoreService == null) { - debugLog("getBluetoothKeystoreService(): service is NULL"); - return null; - } - - if (!sBluetoothKeystoreService.isAvailable()) { - debugLog("getBluetoothKeystoreService(): service is not available"); - return null; - } - return sBluetoothKeystoreService; - } - - private static synchronized void setBluetoothKeystoreService( - BluetoothKeystoreService instance) { - debugLog("setBluetoothKeystoreService(): set to: " + instance); - sBluetoothKeystoreService = instance; - } - - /** * Gets result of the checksum comparison */ public int getCompareResult() { diff --git a/android/app/src/com/android/bluetooth/csip/CsipSetCoordinatorService.java b/android/app/src/com/android/bluetooth/csip/CsipSetCoordinatorService.java index c2dbaaffce..2809b08d77 100644 --- a/android/app/src/com/android/bluetooth/csip/CsipSetCoordinatorService.java +++ b/android/app/src/com/android/bluetooth/csip/CsipSetCoordinatorService.java @@ -847,7 +847,7 @@ public class CsipSetCoordinatorService extends ProfileService { intent.addFlags( Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); - sendBroadcast(intent, BLUETOOTH_PRIVILEGED); + sendOrderedBroadcast(intent, BLUETOOTH_PRIVILEGED); /* Notify registered parties */ handleSetMemberAvailable(device, groupId); @@ -890,7 +890,7 @@ public class CsipSetCoordinatorService extends ProfileService { if (intent != null) { intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); - sendBroadcast(intent, BLUETOOTH_PRIVILEGED); + sendOrderedBroadcast(intent, BLUETOOTH_PRIVILEGED); } synchronized (mStateMachines) { diff --git a/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java b/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java index e105abff5b..a632ef9210 100644 --- a/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java +++ b/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java @@ -51,20 +51,23 @@ public class AdvertiseManager { private final GattService mService; private final AdapterService mAdapterService; + private final AdvertiseManagerNativeInterface mNativeInterface; private final AdvertiserMap mAdvertiserMap; private Handler mHandler; Map<IBinder, AdvertiserInfo> mAdvertisers = Collections.synchronizedMap(new HashMap<>()); static int sTempRegistrationId = -1; - /** - * Constructor of {@link AdvertiseManager}. - */ - AdvertiseManager(GattService service, AdapterService adapterService, + /** Constructor of {@link AdvertiseManager}. */ + AdvertiseManager( + GattService service, + AdvertiseManagerNativeInterface nativeInterface, + AdapterService adapterService, AdvertiserMap advertiserMap) { if (DBG) { Log.d(TAG, "advertise manager created"); } mService = service; + mNativeInterface = nativeInterface; mAdapterService = adapterService; mAdvertiserMap = advertiserMap; } @@ -73,7 +76,7 @@ public class AdvertiseManager { * Start a {@link HandlerThread} that handles advertising operations. */ void start() { - initializeNative(); + mNativeInterface.init(this); HandlerThread thread = new HandlerThread("BluetoothAdvertiseManager"); thread.start(); mHandler = new Handler(thread.getLooper()); @@ -83,7 +86,7 @@ public class AdvertiseManager { if (DBG) { Log.d(TAG, "cleanup()"); } - cleanupNative(); + mNativeInterface.cleanup(); mAdvertisers.clear(); sTempRegistrationId = -1; @@ -157,7 +160,7 @@ public class AdvertiseManager { if (entry == null) { Log.i(TAG, "onAdvertisingSetStarted() - no callback found for regId " + regId); // Advertising set was stopped before it was properly registered. - stopAdvertisingSetNative(advertiserId); + mNativeInterface.stopAdvertisingSet(advertiserId); return; } @@ -252,8 +255,15 @@ public class AdvertiseManager { mAdvertiserMap.recordAdvertiseStart(cbId, parameters, advertiseData, scanResponse, periodicParameters, periodicData, duration, maxExtAdvEvents); - startAdvertisingSetNative(parameters, advDataBytes, scanResponseBytes, - periodicParameters, periodicDataBytes, duration, maxExtAdvEvents, cbId, + mNativeInterface.startAdvertisingSet( + parameters, + advDataBytes, + scanResponseBytes, + periodicParameters, + periodicDataBytes, + duration, + maxExtAdvEvents, + cbId, serverIf); } catch (IllegalArgumentException e) { @@ -289,7 +299,7 @@ public class AdvertiseManager { Log.w(TAG, "getOwnAddress() - bad advertiserId " + advertiserId); return; } - getOwnAddressNative(advertiserId); + mNativeInterface.getOwnAddress(advertiserId); } void stopAdvertisingSet(IAdvertisingSetCallback callback) { @@ -313,7 +323,7 @@ public class AdvertiseManager { return; } - stopAdvertisingSetNative(advertiserId); + mNativeInterface.stopAdvertisingSet(advertiserId); try { callback.onAdvertisingSetStopped(advertiserId); @@ -330,7 +340,7 @@ public class AdvertiseManager { Log.w(TAG, "enableAdvertisingSet() - bad advertiserId " + advertiserId); return; } - enableAdvertisingSetNative(advertiserId, enable, duration, maxExtAdvEvents); + mNativeInterface.enableAdvertisingSet(advertiserId, enable, duration, maxExtAdvEvents); mAdvertiserMap.enableAdvertisingSet(advertiserId, enable, duration, maxExtAdvEvents); @@ -344,8 +354,8 @@ public class AdvertiseManager { } String deviceName = AdapterService.getAdapterService().getName(); try { - setAdvertisingDataNative(advertiserId, - AdvertiseHelper.advertiseDataToBytes(data, deviceName)); + mNativeInterface.setAdvertisingData( + advertiserId, AdvertiseHelper.advertiseDataToBytes(data, deviceName)); mAdvertiserMap.setAdvertisingData(advertiserId, data); } catch (IllegalArgumentException e) { @@ -366,8 +376,8 @@ public class AdvertiseManager { } String deviceName = AdapterService.getAdapterService().getName(); try { - setScanResponseDataNative(advertiserId, - AdvertiseHelper.advertiseDataToBytes(data, deviceName)); + mNativeInterface.setScanResponseData( + advertiserId, AdvertiseHelper.advertiseDataToBytes(data, deviceName)); mAdvertiserMap.setScanResponseData(advertiserId, data); } catch (IllegalArgumentException e) { @@ -386,7 +396,7 @@ public class AdvertiseManager { Log.w(TAG, "setAdvertisingParameters() - bad advertiserId " + advertiserId); return; } - setAdvertisingParametersNative(advertiserId, parameters); + mNativeInterface.setAdvertisingParameters(advertiserId, parameters); mAdvertiserMap.setAdvertisingParameters(advertiserId, parameters); } @@ -398,7 +408,7 @@ public class AdvertiseManager { Log.w(TAG, "setPeriodicAdvertisingParameters() - bad advertiserId " + advertiserId); return; } - setPeriodicAdvertisingParametersNative(advertiserId, parameters); + mNativeInterface.setPeriodicAdvertisingParameters(advertiserId, parameters); mAdvertiserMap.setPeriodicAdvertisingParameters(advertiserId, parameters); } @@ -411,8 +421,8 @@ public class AdvertiseManager { } String deviceName = AdapterService.getAdapterService().getName(); try { - setPeriodicAdvertisingDataNative(advertiserId, - AdvertiseHelper.advertiseDataToBytes(data, deviceName)); + mNativeInterface.setPeriodicAdvertisingData( + advertiserId, AdvertiseHelper.advertiseDataToBytes(data, deviceName)); mAdvertiserMap.setPeriodicAdvertisingData(advertiserId, data); } catch (IllegalArgumentException e) { @@ -431,7 +441,7 @@ public class AdvertiseManager { Log.w(TAG, "setPeriodicAdvertisingEnable() - bad advertiserId " + advertiserId); return; } - setPeriodicAdvertisingEnableNative(advertiserId, enable); + mNativeInterface.setPeriodicAdvertisingEnable(advertiserId, enable); } void onAdvertisingDataSet(int advertiserId, int status) throws Exception { @@ -538,40 +548,4 @@ public class AdvertiseManager { stats.onPeriodicAdvertiseEnabled(enable); } } - - static { - classInitNative(); - } - - private static native void classInitNative(); - - private native void initializeNative(); - - private native void cleanupNative(); - - private native void startAdvertisingSetNative(AdvertisingSetParameters parameters, - byte[] advertiseData, byte[] scanResponse, - PeriodicAdvertisingParameters periodicParameters, byte[] periodicData, int duration, - int maxExtAdvEvents, int regId, int serverIf); - - private native void getOwnAddressNative(int advertiserId); - - private native void stopAdvertisingSetNative(int advertiserId); - - private native void enableAdvertisingSetNative(int advertiserId, boolean enable, int duration, - int maxExtAdvEvents); - - private native void setAdvertisingDataNative(int advertiserId, byte[] data); - - private native void setScanResponseDataNative(int advertiserId, byte[] data); - - private native void setAdvertisingParametersNative(int advertiserId, - AdvertisingSetParameters parameters); - - private native void setPeriodicAdvertisingParametersNative(int advertiserId, - PeriodicAdvertisingParameters parameters); - - private native void setPeriodicAdvertisingDataNative(int advertiserId, byte[] data); - - private native void setPeriodicAdvertisingEnableNative(int advertiserId, boolean enable); } diff --git a/android/app/src/com/android/bluetooth/gatt/AdvertiseManagerNativeInterface.java b/android/app/src/com/android/bluetooth/gatt/AdvertiseManagerNativeInterface.java new file mode 100644 index 0000000000..7529bdb86d --- /dev/null +++ b/android/app/src/com/android/bluetooth/gatt/AdvertiseManagerNativeInterface.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 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. + */ + +package com.android.bluetooth.gatt; + +import android.bluetooth.le.AdvertisingSetParameters; +import android.bluetooth.le.PeriodicAdvertisingParameters; + +import androidx.annotation.VisibleForTesting; + +/** Native interface for AdvertiseManager */ +@VisibleForTesting +public class AdvertiseManagerNativeInterface { + AdvertiseManager mManager; + + void init(AdvertiseManager manager) { + mManager = manager; + initializeNative(); + } + + void cleanup() { + cleanupNative(); + mManager = null; + } + + void startAdvertisingSet( + AdvertisingSetParameters parameters, + byte[] advertiseDataBytes, + byte[] scanResponseBytes, + PeriodicAdvertisingParameters periodicParameters, + byte[] periodicDataBytes, + int duration, + int maxExtAdvEvents, + int cbId, + int serverIf) { + startAdvertisingSetNative( + parameters, + advertiseDataBytes, + scanResponseBytes, + periodicParameters, + periodicDataBytes, + duration, + maxExtAdvEvents, + cbId, + serverIf); + } + + void stopAdvertisingSet(int advertiserId) { + stopAdvertisingSetNative(advertiserId); + } + + void getOwnAddress(int advertiserId) { + getOwnAddressNative(advertiserId); + } + + void enableAdvertisingSet(int advertiserId, boolean enable, int duration, int maxExtAdvEvents) { + enableAdvertisingSetNative(advertiserId, enable, duration, maxExtAdvEvents); + } + + void setAdvertisingData(int advertiserId, byte[] advertiseDataBytes) { + setAdvertisingDataNative(advertiserId, advertiseDataBytes); + } + + void setScanResponseData(int advertiserId, byte[] advertiseDataBytes) { + setScanResponseDataNative(advertiserId, advertiseDataBytes); + } + + void setAdvertisingParameters(int advertiserId, AdvertisingSetParameters parameters) { + setAdvertisingParametersNative(advertiserId, parameters); + } + + void setPeriodicAdvertisingParameters( + int advertiserId, PeriodicAdvertisingParameters parameters) { + setPeriodicAdvertisingParametersNative(advertiserId, parameters); + } + + void setPeriodicAdvertisingData(int advertiserId, byte[] advertiseDataBytes) { + setPeriodicAdvertisingDataNative(advertiserId, advertiseDataBytes); + } + + void setPeriodicAdvertisingEnable(int advertiserId, boolean enable) { + setPeriodicAdvertisingEnableNative(advertiserId, enable); + } + + static { + classInitNative(); + } + + void onAdvertisingSetStarted(int regId, int advertiserId, int txPower, int status) + throws Exception { + mManager.onAdvertisingSetStarted(regId, advertiserId, txPower, status); + } + + void onOwnAddressRead(int advertiserId, int addressType, String address) throws Exception { + mManager.onOwnAddressRead(advertiserId, addressType, address); + } + + void onAdvertisingEnabled(int advertiserId, boolean enable, int status) throws Exception { + mManager.onAdvertisingEnabled(advertiserId, enable, status); + } + + void onAdvertisingDataSet(int advertiserId, int status) throws Exception { + mManager.onAdvertisingDataSet(advertiserId, status); + } + + void onScanResponseDataSet(int advertiserId, int status) throws Exception { + mManager.onScanResponseDataSet(advertiserId, status); + } + + void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status) + throws Exception { + mManager.onAdvertisingParametersUpdated(advertiserId, txPower, status); + } + + void onPeriodicAdvertisingParametersUpdated(int advertiserId, int status) throws Exception { + mManager.onPeriodicAdvertisingParametersUpdated(advertiserId, status); + } + + void onPeriodicAdvertisingDataSet(int advertiserId, int status) throws Exception { + mManager.onPeriodicAdvertisingDataSet(advertiserId, status); + } + + void onPeriodicAdvertisingEnabled(int advertiserId, boolean enable, int status) + throws Exception { + mManager.onPeriodicAdvertisingEnabled(advertiserId, enable, status); + } + + private static native void classInitNative(); + + private native void initializeNative(); + + private native void cleanupNative(); + + private native void startAdvertisingSetNative( + AdvertisingSetParameters parameters, + byte[] advertiseData, + byte[] scanResponse, + PeriodicAdvertisingParameters periodicParameters, + byte[] periodicData, + int duration, + int maxExtAdvEvents, + int regId, + int serverIf); + + private native void stopAdvertisingSetNative(int advertiserId); + + private native void getOwnAddressNative(int advertiserId); + + private native void enableAdvertisingSetNative( + int advertiserId, boolean enable, int duration, int maxExtAdvEvents); + + private native void setAdvertisingDataNative(int advertiserId, byte[] data); + + private native void setScanResponseDataNative(int advertiserId, byte[] data); + + private native void setAdvertisingParametersNative( + int advertiserId, AdvertisingSetParameters parameters); + + private native void setPeriodicAdvertisingParametersNative( + int advertiserId, PeriodicAdvertisingParameters parameters); + + private native void setPeriodicAdvertisingDataNative(int advertiserId, byte[] data); + + private native void setPeriodicAdvertisingEnableNative(int advertiserId, boolean enable); +} diff --git a/android/app/src/com/android/bluetooth/gatt/GattObjectsFactory.java b/android/app/src/com/android/bluetooth/gatt/GattObjectsFactory.java index 14eb6c5f89..13a5dca54f 100644 --- a/android/app/src/com/android/bluetooth/gatt/GattObjectsFactory.java +++ b/android/app/src/com/android/bluetooth/gatt/GattObjectsFactory.java @@ -19,6 +19,9 @@ package com.android.bluetooth.gatt; import android.util.Log; import com.android.bluetooth.Utils; +import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.btservice.BluetoothAdapterProxy; + /** * Factory class for object initialization to help with unit testing */ @@ -64,4 +67,18 @@ public class GattObjectsFactory { public ScanNativeInterface getScanNativeInterface() { return ScanNativeInterface.getInstance(); } + + public ScanManager createScanManager(GattService service, AdapterService adapterService, + BluetoothAdapterProxy bluetoothAdapterProxy) { + return new ScanManager(service, adapterService, bluetoothAdapterProxy); + } + + public PeriodicScanManager createPeriodicScanManager(AdapterService adapterService) { + return new PeriodicScanManager(adapterService); + } + + public DistanceMeasurementManager createDistanceMeasurementManager( + AdapterService adapterService) { + return new DistanceMeasurementManager(adapterService); + } } diff --git a/android/app/src/com/android/bluetooth/gatt/GattService.java b/android/app/src/com/android/bluetooth/gatt/GattService.java index a8cad1f127..0156835787 100644 --- a/android/app/src/com/android/bluetooth/gatt/GattService.java +++ b/android/app/src/com/android/bluetooth/gatt/GattService.java @@ -56,6 +56,7 @@ import android.bluetooth.le.ScanSettings; import android.companion.AssociationInfo; import android.companion.CompanionDeviceManager; import android.content.AttributionSource; +import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.PackageInfoFlags; @@ -280,13 +281,9 @@ public class GattService extends ProfileService { private AdapterService mAdapterService; private BluetoothAdapterProxy mBluetoothAdapterProxy; - @VisibleForTesting AdvertiseManager mAdvertiseManager; - @VisibleForTesting PeriodicScanManager mPeriodicScanManager; - @VisibleForTesting DistanceMeasurementManager mDistanceMeasurementManager; - @VisibleForTesting ScanManager mScanManager; private AppOpsManager mAppOps; private CompanionDeviceManager mCompanionManager; @@ -294,6 +291,11 @@ public class GattService extends ProfileService { private Handler mTestModeHandler; private final Object mTestModeLock = new Object(); + public GattService(Context ctx) { + attachBaseContext(ctx); + onCreate(); + } + public static boolean isEnabled() { return BluetoothProperties.isProfileGattEnabled().orElse(true); } @@ -345,16 +347,24 @@ public class GattService extends ProfileService { mBluetoothAdapterProxy = BluetoothAdapterProxy.getInstance(); mCompanionManager = getSystemService(CompanionDeviceManager.class); mAppOps = getSystemService(AppOpsManager.class); - mAdvertiseManager = new AdvertiseManager(this, mAdapterService, mAdvertiserMap); + mAdvertiseManager = + new AdvertiseManager( + this, + new AdvertiseManagerNativeInterface(), + mAdapterService, + mAdvertiserMap); mAdvertiseManager.start(); - mScanManager = new ScanManager(this, mAdapterService, mBluetoothAdapterProxy); + mScanManager = GattObjectsFactory.getInstance() + .createScanManager(this, mAdapterService, mBluetoothAdapterProxy); mScanManager.start(); - mPeriodicScanManager = new PeriodicScanManager(mAdapterService); + mPeriodicScanManager = GattObjectsFactory.getInstance() + .createPeriodicScanManager(mAdapterService); mPeriodicScanManager.start(); - mDistanceMeasurementManager = new DistanceMeasurementManager(mAdapterService); + mDistanceMeasurementManager = GattObjectsFactory.getInstance() + .createDistanceMeasurementManager(mAdapterService); mDistanceMeasurementManager.start(); setGattService(this); @@ -608,34 +618,6 @@ public class GattService extends ProfileService { } @Override - public void startService() { - GattService service = mService; - if (service == null) { - Log.e(TAG, "startService: Service is null"); - return; - } - if (!Utils.checkConnectPermissionForDataDelivery( - service, null, "GattService startService")) { - return; - } - service.doStart(); - } - - @Override - public void stopService() { - GattService service = mService; - if (service == null) { - Log.e(TAG, "stopService: Service is null"); - return; - } - if (!Utils.checkConnectPermissionForDataDelivery( - service, null, "GattService stopService")) { - return; - } - service.doStop(); - } - - @Override public void getDevicesMatchingConnectionStates(int[] states, AttributionSource attributionSource, SynchronousResultReceiver receiver) { try { @@ -1774,15 +1756,6 @@ public class GattService extends ProfileService { } @Override - public void unregAll(AttributionSource attributionSource) { - GattService service = getService(); - if (service == null) { - return; - } - service.unregAll(attributionSource); - } - - @Override public void numHwTrackFiltersAvailable(AttributionSource attributionSource, SynchronousResultReceiver receiver) { try { @@ -3431,7 +3404,7 @@ public class GattService extends ProfileService { } @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - void unregAll(AttributionSource attributionSource) { + public void unregAll(AttributionSource attributionSource) { for (Integer appId : mClientMap.getAllAppsIds()) { if (DBG) { Log.d(TAG, "unreg:" + appId); diff --git a/android/app/src/com/android/bluetooth/hap/HapClientService.java b/android/app/src/com/android/bluetooth/hap/HapClientService.java index 104751275c..54d79205e5 100644 --- a/android/app/src/com/android/bluetooth/hap/HapClientService.java +++ b/android/app/src/com/android/bluetooth/hap/HapClientService.java @@ -18,7 +18,6 @@ package com.android.bluetooth.hap; import static android.Manifest.permission.BLUETOOTH_CONNECT; -import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission; @@ -46,6 +45,7 @@ import android.sysprop.BluetoothProperties; import android.util.Log; import com.android.bluetooth.Utils; +import com.android.bluetooth.btservice.ActiveDeviceManager; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.ProfileService; import com.android.bluetooth.btservice.ServiceFactory; @@ -83,7 +83,6 @@ public class HapClientService extends ProfileService { private DatabaseManager mDatabaseManager; private HandlerThread mStateMachinesThread; private BroadcastReceiver mBondStateChangedReceiver; - private BroadcastReceiver mConnectionStateChangedReceiver; private final Map<BluetoothDevice, Integer> mDeviceCurrentPresetMap = new HashMap<>(); private final Map<BluetoothDevice, Integer> mDeviceFeaturesMap = new HashMap<>(); @@ -175,11 +174,6 @@ public class HapClientService extends ProfileService { filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); mBondStateChangedReceiver = new BondStateChangedReceiver(); registerReceiver(mBondStateChangedReceiver, filter); - filter = new IntentFilter(); - filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); - filter.addAction(BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED); - mConnectionStateChangedReceiver = new ConnectionStateChangedReceiver(); - registerReceiver(mConnectionStateChangedReceiver, filter, Context.RECEIVER_NOT_EXPORTED); mCallbacks = new RemoteCallbackList<IBluetoothHapClientCallback>(); @@ -208,8 +202,6 @@ public class HapClientService extends ProfileService { // Unregister broadcast receivers unregisterReceiver(mBondStateChangedReceiver); mBondStateChangedReceiver = null; - unregisterReceiver(mConnectionStateChangedReceiver); - mConnectionStateChangedReceiver = null; // Destroy state machines and stop handler thread synchronized (mStateMachines) { @@ -453,6 +445,10 @@ public class HapClientService extends ProfileService { removeStateMachine(device); } } + ActiveDeviceManager adManager = mAdapterService.getActiveDeviceManager(); + if (adManager != null) { + adManager.hapConnectionStateChanged(device, fromState, toState); + } } /** @@ -1659,18 +1655,4 @@ public class HapClientService extends ProfileService { bondStateChanged(device, state); } } - - private class ConnectionStateChangedReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - if (!BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED.equals( - intent.getAction())) { - return; - } - BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - int toState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); - int fromState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1); - connectionStateChanged(device, fromState, toState); - } - } } diff --git a/android/app/src/com/android/bluetooth/hap/HapClientStateMachine.java b/android/app/src/com/android/bluetooth/hap/HapClientStateMachine.java index dc85f0aea3..1dfd45dcd9 100644 --- a/android/app/src/com/android/bluetooth/hap/HapClientStateMachine.java +++ b/android/app/src/com/android/bluetooth/hap/HapClientStateMachine.java @@ -179,6 +179,7 @@ final class HapClientStateMachine extends StateMachine { log("Connection state " + mDevice + ": " + profileStateToString(prevState) + "->" + profileStateToString(newState)); + mService.connectionStateChanged(mDevice, prevState, newState); Intent intent = new Intent(BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED); intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); intent.putExtra(BluetoothProfile.EXTRA_STATE, newState); diff --git a/android/app/src/com/android/bluetooth/hearingaid/HearingAidService.java b/android/app/src/com/android/bluetooth/hearingaid/HearingAidService.java index a0ab01e8ee..7618baf145 100644 --- a/android/app/src/com/android/bluetooth/hearingaid/HearingAidService.java +++ b/android/app/src/com/android/bluetooth/hearingaid/HearingAidService.java @@ -155,9 +155,6 @@ public class HearingAidService extends ProfileService { filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); mBondStateChangedReceiver = new BondStateChangedReceiver(); registerReceiver(mBondStateChangedReceiver, filter); - filter = new IntentFilter(); - filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); - filter.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED); // Mark service as started setHearingAidService(this); @@ -733,6 +730,7 @@ public class HearingAidService extends ProfileService { } private void notifyActiveDeviceChanged() { + mAdapterService.getActiveDeviceManager().hearingAidActiveStateChanged(mActiveDevice); Intent intent = new Intent(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mActiveDevice); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT @@ -746,11 +744,11 @@ public class HearingAidService extends ProfileService { public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { for (AudioDeviceInfo deviceInfo : removedDevices) { if (deviceInfo.getType() == AudioDeviceInfo.TYPE_HEARING_AID) { - notifyActiveDeviceChanged(); if (DBG) { Log.d(TAG, " onAudioDevicesRemoved: device type: " + deviceInfo.getType()); } if (mAudioManager != null) { + notifyActiveDeviceChanged(); mAudioManager.unregisterAudioDeviceCallback(this); } else { Log.w(TAG, "onAudioDevicesRemoved: mAudioManager is null"); @@ -766,11 +764,11 @@ public class HearingAidService extends ProfileService { public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { for (AudioDeviceInfo deviceInfo : addedDevices) { if (deviceInfo.getType() == AudioDeviceInfo.TYPE_HEARING_AID) { - notifyActiveDeviceChanged(); if (DBG) { Log.d(TAG, " onAudioDevicesAdded: device type: " + deviceInfo.getType()); } if (mAudioManager != null) { + notifyActiveDeviceChanged(); mAudioManager.unregisterAudioDeviceCallback(this); } else { Log.w(TAG, "onAudioDevicesAdded: mAudioManager is null"); @@ -964,6 +962,9 @@ public class HearingAidService extends ProfileService { removeStateMachine(device); } } + mAdapterService + .getActiveDeviceManager() + .hearingAidConnectionStateChanged(device, fromState, toState); } /** diff --git a/android/app/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java b/android/app/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java index 977f6e966c..d3eb601dbe 100644 --- a/android/app/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java +++ b/android/app/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java @@ -79,6 +79,10 @@ public class HeadsetNativeInterface { } private byte[] getByteAddress(BluetoothDevice device) { + if (device == null) { + // Set bt_stack's active device to default if java layer set active device to null + return Utils.getBytesFromAddress("00:00:00:00:00:00"); + } return mAdapterService.getByteIdentityAddress(device); } @@ -491,10 +495,6 @@ public class HeadsetNativeInterface { */ @VisibleForTesting public boolean setActiveDevice(BluetoothDevice device) { - // Set bt_stack's active device to default if java layer set active device to null - if (device == null) { - return setActiveDeviceNative(Utils.getBytesFromAddress("00:00:00:00:00:00")); - } return setActiveDeviceNative(getByteAddress(device)); } diff --git a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java index 6e4b626266..9c2c7fee48 100644 --- a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +++ b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java @@ -170,6 +170,7 @@ public class LeAudioService extends ProfileService { mDirection = AUDIO_DIRECTION_NONE; mCodecStatus = null; mLostLeadDeviceWhileStreaming = null; + mCurrentLeadDevice = null; mInbandRingtoneEnabled = isInbandRingtonEnabled; mAvailableContexts = 0; } @@ -180,6 +181,7 @@ public class LeAudioService extends ProfileService { public BluetoothLeAudioCodecStatus mCodecStatus; /* This can be non empty only for the streaming time */ BluetoothDevice mLostLeadDeviceWhileStreaming; + BluetoothDevice mCurrentLeadDevice; Boolean mInbandRingtoneEnabled; Integer mAvailableContexts; } @@ -604,31 +606,11 @@ public class LeAudioService extends ProfileService { } BluetoothDevice getConnectedGroupLeadDevice(int groupId) { - BluetoothDevice device = null; - - if (mActiveAudioOutDevice != null - && getGroupId(mActiveAudioOutDevice) == groupId) { - device = mActiveAudioOutDevice; - } else { - device = getFirstDeviceFromGroup(groupId); - } - - if (device == null) { - return device; - } - - LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device); - if (descriptor == null) { - Log.e(TAG, "getConnectedGroupLeadDevice: No valid descriptor for device: " + device); - return null; - } - - LeAudioStateMachine sm = descriptor.mStateMachine; - if (sm != null && sm.getConnectionState() == BluetoothProfile.STATE_CONNECTED) { - return device; + if (getGroupId(mActiveAudioOutDevice) == groupId) { + return mActiveAudioOutDevice; } - return null; + return getLeadDeviceForTheGroup(groupId); } List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { @@ -986,11 +968,23 @@ public class LeAudioService extends ProfileService { return 1; } - private BluetoothDevice getFirstDeviceFromGroup(Integer groupId) { + private BluetoothDevice getLeadDeviceForTheGroup(Integer groupId) { if (groupId == LE_AUDIO_GROUP_ID_INVALID) { return null; } synchronized (mGroupLock) { + LeAudioGroupDescriptor groupDescriptor = getGroupDescriptor(groupId); + if (groupDescriptor == null) { + Log.e(TAG, "Group " + groupId + " does not exist"); + return null; + } + + if (groupDescriptor.mCurrentLeadDevice != null + && getConnectionState(groupDescriptor.mCurrentLeadDevice) + == BluetoothProfile.STATE_CONNECTED) { + return groupDescriptor.mCurrentLeadDevice; + } + for (LeAudioDeviceDescriptor descriptor : mDeviceDescriptors.values()) { if (!descriptor.mGroupId.equals(groupId)) { continue; @@ -1000,7 +994,8 @@ public class LeAudioService extends ProfileService { if (sm == null || sm.getConnectionState() != BluetoothProfile.STATE_CONNECTED) { continue; } - return sm.getDevice(); + groupDescriptor.mCurrentLeadDevice = sm.getDevice(); + return groupDescriptor.mCurrentLeadDevice; } } return null; @@ -1132,6 +1127,37 @@ public class LeAudioService extends ProfileService { } /** + * Send broadcast intent about LeAudio connection state changed. This is called by + * LeAudioStateMachine. + */ + void notifyConnectionStateChanged(BluetoothDevice device, int newState, int prevState) { + if (DBG) { + Log.d( + TAG, + "Notify connection state changed." + + device + + "(" + + prevState + + " -> " + + newState + + ")"); + } + + mAdapterService + .getActiveDeviceManager() + .leAudioConnectionStateChanged(device, prevState, newState); + Intent intent = new Intent(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED); + intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); + intent.putExtra(BluetoothProfile.EXTRA_STATE, newState); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + intent.addFlags( + Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + Utils.sendBroadcast( + this, intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions()); + } + + /** * Send broadcast intent about LeAudio active device. * This is called when AudioManager confirms, LeAudio device * is added or removed. @@ -1143,6 +1169,7 @@ public class LeAudioService extends ProfileService { + ". Currently active device is " + mActiveAudioOutDevice); } + mAdapterService.getActiveDeviceManager().leAudioActiveStateChanged(device); Intent intent = new Intent(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT @@ -1302,7 +1329,7 @@ public class LeAudioService extends ProfileService { BluetoothDevice previousActiveInDevice = mActiveAudioInDevice; if (isActive) { - device = getFirstDeviceFromGroup(groupId); + device = getLeadDeviceForTheGroup(groupId); } boolean isNewActiveOutDevice = updateActiveOutDevice(device, groupId, @@ -3833,7 +3860,6 @@ public class LeAudioService extends ProfileService { ProfileService.println(sb, " isConnected: " + groupDescriptor.mIsConnected); ProfileService.println(sb, " mDirection: " + groupDescriptor.mDirection); ProfileService.println(sb, " group lead: " + leadDevice); - ProfileService.println(sb, " first device: " + getFirstDeviceFromGroup(groupId)); ProfileService.println(sb, " lost lead device: " + groupDescriptor.mLostLeadDeviceWhileStreaming); ProfileService.println(sb, " mInbandRingtoneEnabled: " diff --git a/android/app/src/com/android/bluetooth/le_audio/LeAudioStateMachine.java b/android/app/src/com/android/bluetooth/le_audio/LeAudioStateMachine.java index a1411f6934..0eb7577d72 100644 --- a/android/app/src/com/android/bluetooth/le_audio/LeAudioStateMachine.java +++ b/android/app/src/com/android/bluetooth/le_audio/LeAudioStateMachine.java @@ -47,15 +47,11 @@ package com.android.bluetooth.le_audio; import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothLeAudio; import android.bluetooth.BluetoothProfile; -import android.content.Intent; import android.os.Looper; import android.os.Message; import android.util.Log; -import static android.Manifest.permission.BLUETOOTH_CONNECT; -import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.ProfileService; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.State; @@ -514,15 +510,7 @@ final class LeAudioStateMachine extends StateMachine { private void broadcastConnectionState(int newState, int prevState) { log("Connection state " + mDevice + ": " + profileStateToString(prevState) + "->" + profileStateToString(newState)); - - Intent intent = new Intent(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED); - intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); - intent.putExtra(BluetoothProfile.EXTRA_STATE, newState); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT - | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); - Utils.sendBroadcast(mService, intent, BLUETOOTH_CONNECT, - Utils.getTempAllowlistBroadcastOptions()); + mService.notifyConnectionStateChanged(mDevice, newState, prevState); } private static String messageWhatToString(int what) { diff --git a/android/app/tests/unit/src/com/android/bluetooth/TestUtils.java b/android/app/tests/unit/src/com/android/bluetooth/TestUtils.java index b911669d84..d605ef2f8e 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/TestUtils.java +++ b/android/app/tests/unit/src/com/android/bluetooth/TestUtils.java @@ -39,6 +39,7 @@ import androidx.test.uiautomator.UiDevice; import com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.gatt.GattService; import org.junit.Assert; import org.junit.rules.TestRule; @@ -117,6 +118,21 @@ public class TestUtils { AdapterService.clearAdapterService(adapterService); } + /** Helper function to mock getSystemService calls */ + public static <T> void mockGetSystemService( + Context ctx, String serviceName, Class<T> serviceClass, T mockService) { + when(ctx.getSystemService(eq(serviceName))).thenReturn(mockService); + when(ctx.getSystemServiceName(eq(serviceClass))).thenReturn(serviceName); + } + + /** Helper function to mock getSystemService calls */ + public static <T> T mockGetSystemService( + Context ctx, String serviceName, Class<T> serviceClass) { + T mockedService = mock(serviceClass); + mockGetSystemService(ctx, serviceName, serviceClass, mockedService); + return mockedService; + } + /** * Start a profile service using the given {@link ServiceTestRule} and verify through * {@link AdapterService#getAdapterService()} that the service is actually started within @@ -135,6 +151,9 @@ public class TestUtils { */ public static <T extends ProfileService> void startService(ServiceTestRule serviceTestRule, Class<T> profileServiceClass) throws TimeoutException { + if (profileServiceClass == GattService.class) { + Assert.assertFalse("GattService cannot be started as a service", true); + } AdapterService adapterService = AdapterService.getAdapterService(); Assert.assertNotNull("Adapter service should not be null", adapterService); Assert.assertTrue("AdapterService.getAdapterService() must return a mocked or spied object" diff --git a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java index 80c62f0edc..2fa6849c3e 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java @@ -405,7 +405,7 @@ public class AvrcpControllerStateMachineTest { BluetoothMediaBrowserService.getTransportControls(); Assert.assertNotNull(transportControls); Assert.assertEquals(PlaybackStateCompat.STATE_NONE, - BluetoothMediaBrowserService.getPlaybackState()); + BluetoothMediaBrowserService.getPlaybackState().getState()); mAvrcpStateMachine.disconnect(); numBroadcastsSent += 2; verify(mAvrcpControllerService, @@ -434,7 +434,7 @@ public class AvrcpControllerStateMachineTest { int numBroadcastsSent = setUpConnectedState(false, true); Assert.assertEquals(1, mAvrcpControllerService.sBrowseTree.mRootNode.getChildrenCount()); Assert.assertEquals(PlaybackStateCompat.STATE_NONE, - BluetoothMediaBrowserService.getPlaybackState()); + BluetoothMediaBrowserService.getPlaybackState().getState()); mAvrcpStateMachine.disconnect(); numBroadcastsSent += 2; verify(mAvrcpControllerService, @@ -1268,6 +1268,69 @@ public class AvrcpControllerStateMachineTest { Assert.assertFalse(mAvrcpStateMachine.isActive()); } + @Test + public void testTrackChangedWhileActive_currentTrackAndQueueNumberUpdated() { + setUpConnectedState(true, true); + + // Set track + AvrcpItem track = makeTrack("Song 1", "artist", "album", 1, 2, "none", 10, null); + setCurrentTrack(track); + + // Set current Now Playing list + List<AvrcpItem> nowPlayingList = new ArrayList<AvrcpItem>(); + nowPlayingList.add(makeNowPlayingItem(1, "Song 1")); + nowPlayingList.add(makeNowPlayingItem(2, "Song 2")); + setNowPlayingList(nowPlayingList); + + // Set playing + setPlaybackState(PlaybackStateCompat.STATE_PLAYING); + + // Wait + TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper()); + + // Verify track and playback state + MediaSessionCompat session = BluetoothMediaBrowserService.getSession(); + Assert.assertNotNull(session); + MediaControllerCompat controller = session.getController(); + Assert.assertNotNull(controller); + + MediaMetadataCompat metadata = controller.getMetadata(); + Assert.assertNotNull(metadata); + Assert.assertEquals("Song 1", metadata.getString(MediaMetadataCompat.METADATA_KEY_TITLE)); + Assert.assertEquals("artist", metadata.getString(MediaMetadataCompat.METADATA_KEY_ARTIST)); + Assert.assertEquals("album", metadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM)); + Assert.assertEquals(1, metadata.getLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER)); + Assert.assertEquals(2, metadata.getLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS)); + Assert.assertEquals("none", metadata.getString(MediaMetadataCompat.METADATA_KEY_GENRE)); + Assert.assertEquals(10, metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION)); + + PlaybackStateCompat playbackState = controller.getPlaybackState(); + Assert.assertNotNull(playbackState); + Assert.assertEquals(PlaybackStateCompat.STATE_PLAYING, playbackState.getState()); + Assert.assertEquals(0, playbackState.getActiveQueueItemId()); + + // Track changes, with new metadata and new track number + track = makeTrack("Song 2", "artist", "album", 2, 2, "none", 10, null); + setCurrentTrack(track); + TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper()); + + // Assert new track metadata and active queue item + metadata = controller.getMetadata(); + Assert.assertNotNull(metadata); + Assert.assertEquals("Song 2", metadata.getString(MediaMetadataCompat.METADATA_KEY_TITLE)); + Assert.assertEquals("artist", metadata.getString(MediaMetadataCompat.METADATA_KEY_ARTIST)); + Assert.assertEquals("album", metadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM)); + Assert.assertEquals(2, metadata.getLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER)); + Assert.assertEquals(2, metadata.getLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS)); + Assert.assertEquals("none", metadata.getString(MediaMetadataCompat.METADATA_KEY_GENRE)); + Assert.assertEquals(10, metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION)); + + playbackState = controller.getPlaybackState(); + Assert.assertNotNull(playbackState); + Assert.assertEquals(PlaybackStateCompat.STATE_PLAYING, playbackState.getState()); + Assert.assertEquals(1, playbackState.getActiveQueueItemId()); + } + /** * Test receiving a track change update when we're not the active device */ @@ -1318,7 +1381,7 @@ public class AvrcpControllerStateMachineTest { eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_DOWN)); verify(mA2dpSinkService, never()).requestAudioFocus(mTestDevice, true); Assert.assertEquals(PlaybackStateCompat.STATE_ERROR, - BluetoothMediaBrowserService.getPlaybackState()); + BluetoothMediaBrowserService.getPlaybackState().getState()); } /** diff --git a/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java index 999d74607e..7e0aca04fb 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java @@ -919,7 +919,12 @@ public class BassClientStateMachineTest { Message msg = mBassClientStateMachine.obtainMessage(CONNECTION_STATE_CHANGED); msg.obj = BluetoothProfile.STATE_CONNECTING; + BassClientStateMachine.BluetoothGattTestableWrapper btGatt = + Mockito.mock(BassClientStateMachine.BluetoothGattTestableWrapper.class); + mBassClientStateMachine.mBluetoothGatt = btGatt; sendMessageAndVerifyTransition(msg, BassClientStateMachine.Disconnected.class); + verify(btGatt).close(); + assertNull(mBassClientStateMachine.mBluetoothGatt); } @Test @@ -941,8 +946,13 @@ public class BassClientStateMachineTest { TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any()); + BassClientStateMachine.BluetoothGattTestableWrapper btGatt = + Mockito.mock(BassClientStateMachine.BluetoothGattTestableWrapper.class); + mBassClientStateMachine.mBluetoothGatt = btGatt; Message msg = mBassClientStateMachine.obtainMessage(CONNECT_TIMEOUT, mTestDevice); sendMessageAndVerifyTransition(msg, BassClientStateMachine.Disconnected.class); + verify(btGatt).close(); + assertNull(mBassClientStateMachine.mBluetoothGatt); } @Test @@ -991,9 +1001,14 @@ public class BassClientStateMachineTest { TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any()); + BassClientStateMachine.BluetoothGattTestableWrapper btGatt = + Mockito.mock(BassClientStateMachine.BluetoothGattTestableWrapper.class); + mBassClientStateMachine.mBluetoothGatt = btGatt; Message noneConnectedMsg = mBassClientStateMachine.obtainMessage(CONNECTION_STATE_CHANGED); noneConnectedMsg.obj = BluetoothProfile.STATE_DISCONNECTING; sendMessageAndVerifyTransition(noneConnectedMsg, BassClientStateMachine.Disconnected.class); + verify(btGatt).close(); + assertNull(mBassClientStateMachine.mBluetoothGatt); } @Test @@ -1379,11 +1394,16 @@ public class BassClientStateMachineTest { TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any()); + BassClientStateMachine.BluetoothGattTestableWrapper btGatt = + Mockito.mock(BassClientStateMachine.BluetoothGattTestableWrapper.class); + mBassClientStateMachine.mBluetoothGatt = btGatt; Message msgToNoneConnectedState = mBassClientStateMachine.obtainMessage(CONNECTION_STATE_CHANGED); msgToNoneConnectedState.obj = BluetoothProfile.STATE_DISCONNECTING; sendMessageAndVerifyTransition( msgToNoneConnectedState, BassClientStateMachine.Disconnected.class); + verify(btGatt).close(); + assertNull(mBassClientStateMachine.mBluetoothGatt); } /** diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java index 4e37c265a9..1b444ced73 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java @@ -32,13 +32,9 @@ import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothHapClient; -import android.bluetooth.BluetoothHearingAid; -import android.bluetooth.BluetoothLeAudio; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothSinkAudioPolicy; import android.content.Context; -import android.content.Intent; import android.media.AudioManager; import android.util.ArrayMap; import android.util.SparseIntArray; @@ -51,6 +47,7 @@ import com.android.bluetooth.TestUtils; import com.android.bluetooth.Utils; import com.android.bluetooth.a2dp.A2dpService; import com.android.bluetooth.btservice.storage.DatabaseManager; +import com.android.bluetooth.hap.HapClientService; import com.android.bluetooth.hearingaid.HearingAidService; import com.android.bluetooth.hfp.HeadsetService; import com.android.bluetooth.le_audio.LeAudioService; @@ -101,6 +98,7 @@ public class ActiveDeviceManagerTest { @Mock private HearingAidService mHearingAidService; @Mock private LeAudioService mLeAudioService; @Mock private AudioManager mAudioManager; + @Mock private HapClientService mHapClientService; @Before public void setUp() throws Exception { @@ -1213,13 +1211,11 @@ public class ActiveDeviceManagerTest { * Helper to indicate Hearing Aid connected for a device. */ private void hearingAidConnected(BluetoothDevice device) { + mDeviceConnectionStack.add(device); mMostRecentDevice = device; - Intent intent = new Intent(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); - intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED); - intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED); - mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent); + mActiveDeviceManager.hearingAidConnectionStateChanged( + device, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED); } /** @@ -1230,24 +1226,19 @@ public class ActiveDeviceManagerTest { mMostRecentDevice = (mDeviceConnectionStack.size() > 0) ? mDeviceConnectionStack.get(mDeviceConnectionStack.size() - 1) : null; - Intent intent = new Intent(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); - intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTED); - intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED); - mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent); + mActiveDeviceManager.hearingAidConnectionStateChanged( + device, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); } /** * Helper to indicate Hearing Aid active device changed for a device. */ private void hearingAidActiveDeviceChanged(BluetoothDevice device) { - mMostRecentDevice = device; - - Intent intent = new Intent(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); - mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent); mDeviceConnectionStack.remove(device); mDeviceConnectionStack.add(device); + mMostRecentDevice = device; + + mActiveDeviceManager.hearingAidActiveStateChanged(device); } /** @@ -1256,11 +1247,8 @@ public class ActiveDeviceManagerTest { private void leAudioConnected(BluetoothDevice device) { mMostRecentDevice = device; - Intent intent = new Intent(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); - intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED); - intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED); - mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent); + mActiveDeviceManager.leAudioConnectionStateChanged( + device, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED); } /** @@ -1271,11 +1259,8 @@ public class ActiveDeviceManagerTest { mMostRecentDevice = (mDeviceConnectionStack.size() > 0) ? mDeviceConnectionStack.get(mDeviceConnectionStack.size() - 1) : null; - Intent intent = new Intent(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); - intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTED); - intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED); - mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent); + mActiveDeviceManager.leAudioConnectionStateChanged( + device, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); } /** @@ -1286,9 +1271,7 @@ public class ActiveDeviceManagerTest { mDeviceConnectionStack.add(device); mMostRecentDevice = device; - Intent intent = new Intent(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); - mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent); + mActiveDeviceManager.leAudioActiveStateChanged(device); } /** @@ -1298,39 +1281,18 @@ public class ActiveDeviceManagerTest { mDeviceConnectionStack.add(device); mMostRecentDevice = device; - Intent intent = new Intent(BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); - intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED); - intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED); - mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent); + mActiveDeviceManager.hapConnectionStateChanged( + device, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED); } - /** - * Helper to indicate LE Hearing Aid disconnected for a device. - */ + /** Helper to indicate LE Hearing Aid disconnected for a device. */ private void leHearingAidDisconnected(BluetoothDevice device) { mDeviceConnectionStack.remove(device); mMostRecentDevice = (mDeviceConnectionStack.size() > 0) ? mDeviceConnectionStack.get(mDeviceConnectionStack.size() - 1) : null; - Intent intent = new Intent(BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); - intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTED); - intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED); - mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent); - } - - /** - * Helper to indicate LE Audio Hearing Aid device changed for a device. - */ - private void leHearingAidActiveDeviceChanged(BluetoothDevice device) { - mDeviceConnectionStack.remove(device); - mDeviceConnectionStack.add(device); - mMostRecentDevice = device; - - Intent intent = new Intent(BluetoothHapClient.ACTION_HAP_DEVICE_AVAILABLE); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); - mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent); + mActiveDeviceManager.hapConnectionStateChanged( + device, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); } private class TestDatabaseManager extends DatabaseManager { diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceFactoryResetTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceFactoryResetTest.java index 9d81e88e5f..beb4a4554c 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceFactoryResetTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceFactoryResetTest.java @@ -36,6 +36,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PermissionInfo; import android.content.res.Resources; +import android.hardware.display.DisplayManager; import android.media.AudioManager; import android.os.BatteryStatsManager; import android.os.Binder; @@ -91,16 +92,10 @@ public class AdapterServiceFactoryResetTest { private @Mock android.app.Application mApplication; private @Mock MetricsLogger mMockMetricsLogger; - // Mocked SystemService - private @Mock AlarmManager mMockAlarmManager; - private @Mock AppOpsManager mMockAppOpsManager; - private @Mock AudioManager mMockAudioManager; - private @Mock DevicePolicyManager mMockDevicePolicyManager; - private @Mock UserManager mMockUserManager; - // SystemService that are not mocked private BluetoothManager mBluetoothManager; private CompanionDeviceManager mCompanionDeviceManager; + private DisplayManager mDisplayManager; private PowerManager mPowerManager; private PermissionCheckerManager mPermissionCheckerManager; private PermissionManager mPermissionManager; @@ -131,6 +126,12 @@ public class AdapterServiceFactoryResetTest { when(mMockContext.getSystemServiceName(eq(serviceClass))).thenReturn(serviceName); } + <T> T mockGetSystemService(String serviceName, Class<T> serviceClass) { + T mockedService = mock(serviceClass); + mockGetSystemService(serviceName, serviceClass, mockedService); + return mockedService; + } + @Before public void setUp() throws PackageManager.NameNotFoundException { Log.e(TAG, "setUp()"); @@ -156,23 +157,20 @@ public class AdapterServiceFactoryResetTest { } }); - mPowerManager = InstrumentationRegistry.getTargetContext() - .getSystemService(PowerManager.class); - mPermissionCheckerManager = InstrumentationRegistry.getTargetContext() - .getSystemService(PermissionCheckerManager.class); - - mPermissionManager = InstrumentationRegistry.getTargetContext() - .getSystemService(PermissionManager.class); + Context targetContext = InstrumentationRegistry.getTargetContext(); - mBluetoothManager = InstrumentationRegistry.getTargetContext() - .getSystemService(BluetoothManager.class); - - mCompanionDeviceManager = - InstrumentationRegistry.getTargetContext() - .getSystemService(CompanionDeviceManager.class); + mBluetoothManager = targetContext.getSystemService(BluetoothManager.class); + mCompanionDeviceManager = targetContext.getSystemService(CompanionDeviceManager.class); + mDisplayManager = targetContext.getSystemService(DisplayManager.class); + mPermissionCheckerManager = targetContext.getSystemService(PermissionCheckerManager.class); + mPermissionManager = targetContext.getSystemService(PermissionManager.class); + mPowerManager = targetContext.getSystemService(PowerManager.class); when(mMockContext.getCacheDir()) .thenReturn(InstrumentationRegistry.getTargetContext().getCacheDir()); + when(mMockContext.getUser()) + .thenReturn(InstrumentationRegistry.getTargetContext().getUser()); + when(mMockContext.getPackageName()).thenReturn("com.android.bluetooth"); when(mMockContext.getApplicationInfo()).thenReturn(mMockApplicationInfo); when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver); when(mMockContext.getApplicationContext()).thenReturn(mMockContext); @@ -182,12 +180,13 @@ public class AdapterServiceFactoryResetTest { when(mMockContext.getUserId()).thenReturn(Process.BLUETOOTH_UID); when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); - mockGetSystemService(Context.ALARM_SERVICE, AlarmManager.class, mMockAlarmManager); - mockGetSystemService(Context.APP_OPS_SERVICE, AppOpsManager.class, mMockAppOpsManager); - mockGetSystemService(Context.AUDIO_SERVICE, AudioManager.class, mMockAudioManager); - mockGetSystemService( - Context.DEVICE_POLICY_SERVICE, DevicePolicyManager.class, mMockDevicePolicyManager); - mockGetSystemService(Context.USER_SERVICE, UserManager.class, mMockUserManager); + mockGetSystemService(Context.ALARM_SERVICE, AlarmManager.class); + mockGetSystemService(Context.APP_OPS_SERVICE, AppOpsManager.class); + mockGetSystemService(Context.AUDIO_SERVICE, AudioManager.class); + DevicePolicyManager dpm = + mockGetSystemService(Context.DEVICE_POLICY_SERVICE, DevicePolicyManager.class); + doReturn(false).when(dpm).isCommonCriteriaModeEnabled(any()); + mockGetSystemService(Context.USER_SERVICE, UserManager.class); mockGetSystemService( Context.BATTERY_STATS_SERVICE, BatteryStatsManager.class, mBatteryStatsManager); @@ -196,6 +195,7 @@ public class AdapterServiceFactoryResetTest { Context.COMPANION_DEVICE_SERVICE, CompanionDeviceManager.class, mCompanionDeviceManager); + mockGetSystemService(Context.DISPLAY_SERVICE, DisplayManager.class, mDisplayManager); mockGetSystemService( Context.PERMISSION_CHECKER_SERVICE, PermissionCheckerManager.class, @@ -221,8 +221,6 @@ public class AdapterServiceFactoryResetTest { UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid); Utils.setForegroundUserId(callingUser.getIdentifier()); - when(mMockDevicePolicyManager.isCommonCriteriaModeEnabled(any())).thenReturn(false); - when(mIBluetoothCallback.asBinder()).thenReturn(mBinder); doReturn(Process.BLUETOOTH_UID).when(mMockPackageManager) diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceRestartTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceRestartTest.java index 96ccb11add..e02a34d8d4 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceRestartTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceRestartTest.java @@ -162,6 +162,9 @@ public class AdapterServiceRestartTest { when(mMockContext.getCacheDir()).thenReturn(InstrumentationRegistry.getTargetContext() .getCacheDir()); + when(mMockContext.getUser()) + .thenReturn(InstrumentationRegistry.getTargetContext().getUser()); + when(mMockContext.getPackageName()).thenReturn("com.android.bluetooth"); when(mMockContext.getApplicationInfo()).thenReturn(mMockApplicationInfo); when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver); when(mMockContext.getApplicationContext()).thenReturn(mMockContext); diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java index 61aefd7b9f..db52e36d63 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java @@ -26,6 +26,7 @@ import static android.bluetooth.BluetoothAdapter.STATE_TURNING_OFF; import static android.bluetooth.BluetoothAdapter.STATE_TURNING_ON; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @@ -43,6 +44,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PermissionInfo; import android.content.res.Resources; +import android.hardware.display.DisplayManager; import android.media.AudioManager; import android.os.BatteryStatsManager; import android.os.Binder; @@ -142,16 +144,10 @@ public class AdapterServiceTest { private @Mock android.app.Application mApplication; private @Mock MetricsLogger mMockMetricsLogger; - // Mocked SystemService - private @Mock AlarmManager mMockAlarmManager; - private @Mock AppOpsManager mMockAppOpsManager; - private @Mock AudioManager mMockAudioManager; - private @Mock DevicePolicyManager mMockDevicePolicyManager; - private @Mock UserManager mMockUserManager; - // SystemService that are not mocked private BluetoothManager mBluetoothManager; private CompanionDeviceManager mCompanionDeviceManager; + private DisplayManager mDisplayManager; private PowerManager mPowerManager; private PermissionCheckerManager mPermissionCheckerManager; private PermissionManager mPermissionManager; @@ -222,8 +218,11 @@ public class AdapterServiceTest { } <T> void mockGetSystemService(String serviceName, Class<T> serviceClass, T mockService) { - when(mMockContext.getSystemService(eq(serviceName))).thenReturn(mockService); - when(mMockContext.getSystemServiceName(eq(serviceClass))).thenReturn(serviceName); + TestUtils.mockGetSystemService(mMockContext, serviceName, serviceClass, mockService); + } + + <T> T mockGetSystemService(String serviceName, Class<T> serviceClass) { + return TestUtils.mockGetSystemService(mMockContext, serviceName, serviceClass); } @Before @@ -251,23 +250,20 @@ public class AdapterServiceTest { } }); - mPowerManager = InstrumentationRegistry.getTargetContext() - .getSystemService(PowerManager.class); - mPermissionCheckerManager = InstrumentationRegistry.getTargetContext() - .getSystemService(PermissionCheckerManager.class); - - mPermissionManager = InstrumentationRegistry.getTargetContext() - .getSystemService(PermissionManager.class); + Context targetContext = InstrumentationRegistry.getTargetContext(); - mBluetoothManager = InstrumentationRegistry.getTargetContext() - .getSystemService(BluetoothManager.class); - - mCompanionDeviceManager = - InstrumentationRegistry.getTargetContext() - .getSystemService(CompanionDeviceManager.class); + mBluetoothManager = targetContext.getSystemService(BluetoothManager.class); + mCompanionDeviceManager = targetContext.getSystemService(CompanionDeviceManager.class); + mDisplayManager = targetContext.getSystemService(DisplayManager.class); + mPermissionCheckerManager = targetContext.getSystemService(PermissionCheckerManager.class); + mPermissionManager = targetContext.getSystemService(PermissionManager.class); + mPowerManager = targetContext.getSystemService(PowerManager.class); when(mMockContext.getCacheDir()).thenReturn(InstrumentationRegistry.getTargetContext() .getCacheDir()); + when(mMockContext.getUser()) + .thenReturn(InstrumentationRegistry.getTargetContext().getUser()); + when(mMockContext.getPackageName()).thenReturn("com.android.bluetooth"); when(mMockContext.getApplicationInfo()).thenReturn(mMockApplicationInfo); when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver); when(mMockContext.getApplicationContext()).thenReturn(mMockContext); @@ -277,12 +273,13 @@ public class AdapterServiceTest { when(mMockContext.getUserId()).thenReturn(Process.BLUETOOTH_UID); when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); - mockGetSystemService(Context.ALARM_SERVICE, AlarmManager.class, mMockAlarmManager); - mockGetSystemService(Context.APP_OPS_SERVICE, AppOpsManager.class, mMockAppOpsManager); - mockGetSystemService(Context.AUDIO_SERVICE, AudioManager.class, mMockAudioManager); - mockGetSystemService( - Context.DEVICE_POLICY_SERVICE, DevicePolicyManager.class, mMockDevicePolicyManager); - mockGetSystemService(Context.USER_SERVICE, UserManager.class, mMockUserManager); + mockGetSystemService(Context.ALARM_SERVICE, AlarmManager.class); + mockGetSystemService(Context.APP_OPS_SERVICE, AppOpsManager.class); + mockGetSystemService(Context.AUDIO_SERVICE, AudioManager.class); + DevicePolicyManager dpm = + mockGetSystemService(Context.DEVICE_POLICY_SERVICE, DevicePolicyManager.class); + doReturn(false).when(dpm).isCommonCriteriaModeEnabled(any()); + mockGetSystemService(Context.USER_SERVICE, UserManager.class); mockGetSystemService( Context.BATTERY_STATS_SERVICE, BatteryStatsManager.class, mBatteryStatsManager); @@ -291,6 +288,7 @@ public class AdapterServiceTest { Context.COMPANION_DEVICE_SERVICE, CompanionDeviceManager.class, mCompanionDeviceManager); + mockGetSystemService(Context.DISPLAY_SERVICE, DisplayManager.class, mDisplayManager); mockGetSystemService( Context.PERMISSION_CHECKER_SERVICE, PermissionCheckerManager.class, @@ -303,8 +301,6 @@ public class AdapterServiceTest { .thenReturn(InstrumentationRegistry.getTargetContext() .getSharedPreferences("AdapterServiceTestPrefs", Context.MODE_PRIVATE)); - doReturn(true).when(mMockContext).bindServiceAsUser(any(), any(), anyInt(), any()); - doAnswer(invocation -> { Object[] args = invocation.getArguments(); return InstrumentationRegistry.getTargetContext().getDatabasePath((String) args[0]); @@ -316,8 +312,6 @@ public class AdapterServiceTest { UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid); Utils.setForegroundUserId(callingUser.getIdentifier()); - when(mMockDevicePolicyManager.isCommonCriteriaModeEnabled(any())).thenReturn(false); - when(mIBluetoothCallback.asBinder()).thenReturn(mBinder); doReturn(Process.BLUETOOTH_UID).when(mMockPackageManager) @@ -367,14 +361,23 @@ public class AdapterServiceTest { syncHandler(mLooper, what); } + private void dropNextMessage(int what) { + Message msg = mLooper.nextMessage(); + assertThat(msg).isNotNull(); + assertWithMessage("Not the expected Message:\n" + msg).that(msg.what).isEqualTo(what); + Log.d(TAG, "Message dropped on purpose: " + msg); + } + private static void syncHandler(TestLooper looper, int... what) { IntStream.of(what) .forEach( w -> { Message msg = looper.nextMessage(); assertThat(msg).isNotNull(); - assertThat(msg.what).isEqualTo(w); - Log.d(TAG, "Processing message: " + msg.what); + assertWithMessage("Not the expected Message:\n" + msg) + .that(msg.what) + .isEqualTo(w); + Log.d(TAG, "Processing message: " + msg); msg.getTarget().dispatchMessage(msg); }); } @@ -422,12 +425,9 @@ public class AdapterServiceTest { adapter.enable(false); syncHandler(looper, AdapterState.BLE_TURN_ON); verifyStateChange(callback, STATE_OFF, STATE_BLE_TURNING_ON); - verify(ctx).bindServiceAsUser(any(), any(), anyInt(), any()); - adapter.addProfile(gattService); syncHandler(looper, MESSAGE_PROFILE_SERVICE_REGISTERED); - adapter.onProfileServiceStateChanged(gattService, STATE_ON); syncHandler(looper, MESSAGE_PROFILE_SERVICE_STATE_CHANGED); // Native loop is not in TestLooper and it will send a event later @@ -550,9 +550,7 @@ public class AdapterServiceTest { adapter.stopBle(); syncHandler(looper, AdapterState.BLE_TURN_OFF); verifyStateChange(callback, STATE_BLE_ON, STATE_BLE_TURNING_OFF); - verify(ctx).unbindService(any()); - adapter.onProfileServiceStateChanged(gattService, STATE_OFF); syncHandler(looper, MESSAGE_PROFILE_SERVICE_STATE_CHANGED); // Native loop is not in TestLooper and it will send a event later @@ -630,14 +628,15 @@ public class AdapterServiceTest { mAdapterService.enable(false); syncHandler(AdapterState.BLE_TURN_ON); verifyStateChange(STATE_OFF, STATE_BLE_TURNING_ON); - verify(mMockContext).bindServiceAsUser(any(), any(), anyInt(), any()); - - mAdapterService.addProfile(mMockGattService); + assertThat(mAdapterService.getBluetoothGatt()).isNotNull(); syncHandler(MESSAGE_PROFILE_SERVICE_REGISTERED); + // Fetch next message and never process it to simulate a timeout. + dropNextMessage(MESSAGE_PROFILE_SERVICE_STATE_CHANGED); + mLooper.moveTimeForward(120_000); // Skip time so the timeout fires syncHandler(AdapterState.BLE_START_TIMEOUT); - verify(mMockContext).unbindService(any()); + assertThat(mAdapterService.getBluetoothGatt()).isNull(); // Native loop is not in TestLooper and it will send a event later mLooper.startAutoDispatch(); @@ -666,7 +665,11 @@ public class AdapterServiceTest { mAdapterService.stopBle(); syncHandler(AdapterState.BLE_TURN_OFF); verifyStateChange(STATE_BLE_ON, STATE_BLE_TURNING_OFF, CONTEXT_SWITCH_MS); - verify(mMockContext).unbindService(any()); // Stop GATT + assertThat(mAdapterService.getBluetoothGatt()).isNull(); + + // Fetch Gatt message and never process it to simulate a timeout. + dropNextMessage(MESSAGE_PROFILE_SERVICE_STATE_CHANGED); + dropNextMessage(MESSAGE_PROFILE_SERVICE_UNREGISTERED); mLooper.moveTimeForward(120_000); // Skip time so the timeout fires syncHandler(AdapterState.BLE_STOP_TIMEOUT); @@ -711,7 +714,7 @@ public class AdapterServiceTest { verifyStateChange(STATE_TURNING_OFF, STATE_BLE_ON); // Ensure GATT is still running - verify(mMockContext, times(0)).unbindService(any()); + assertThat(mAdapterService.getBluetoothGatt()).isNotNull(); } /** @@ -735,10 +738,9 @@ public class AdapterServiceTest { mLooper.moveTimeForward(120_000); // Skip time so the timeout fires syncHandler(AdapterState.BREDR_STOP_TIMEOUT); verifyStateChange(STATE_TURNING_OFF, STATE_BLE_TURNING_OFF); - verify(mMockContext).unbindService(any()); - mAdapterService.onProfileServiceStateChanged(mMockGattService, STATE_OFF); syncHandler(MESSAGE_PROFILE_SERVICE_STATE_CHANGED); + syncHandler(MESSAGE_PROFILE_SERVICE_UNREGISTERED); // TODO(b/280518177): The only timeout to fire here should be the BREDR mLooper.moveTimeForward(120_000); // Skip time so the timeout fires @@ -778,10 +780,7 @@ public class AdapterServiceTest { // Do not call stopBle(). The Adapter should turn itself off. syncHandler(AdapterState.BLE_TURN_OFF); verifyStateChange(STATE_BLE_ON, STATE_BLE_TURNING_OFF, CONTEXT_SWITCH_MS); - verify(mMockContext).unbindService(any()); // stop Gatt - - mAdapterService.onProfileServiceStateChanged(mMockGattService, STATE_OFF); - syncHandler(MESSAGE_PROFILE_SERVICE_STATE_CHANGED); + syncHandler(MESSAGE_PROFILE_SERVICE_STATE_CHANGED); // stop GATT // Native loop is not in TestLooper and it will send a event later mLooper.startAutoDispatch(); diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/ConfigTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/ConfigTest.java new file mode 100644 index 0000000000..7b97035ed5 --- /dev/null +++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/ConfigTest.java @@ -0,0 +1,51 @@ +/* + * 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. + */ + +package com.android.bluetooth.btservice; + +import static com.google.common.truth.Truth.assertThat; + +import com.android.bluetooth.csip.CsipSetCoordinatorService; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.Arrays; + +@RunWith(JUnit4.class) +public final class ConfigTest { + @Test + public void setProfileEnabled() { + boolean enabled = + Arrays.stream(Config.getSupportedProfiles()) + .anyMatch(cls -> cls == CsipSetCoordinatorService.class); + + Config.setProfileEnabled(CsipSetCoordinatorService.class, false); + assertThat( + Arrays.stream(Config.getSupportedProfiles()) + .anyMatch(cls -> cls == CsipSetCoordinatorService.class)) + .isFalse(); + + Config.setProfileEnabled(CsipSetCoordinatorService.class, true); + assertThat( + Arrays.stream(Config.getSupportedProfiles()) + .anyMatch(cls -> cls == CsipSetCoordinatorService.class)) + .isTrue(); + + Config.setProfileEnabled(CsipSetCoordinatorService.class, enabled); + } +} diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java index 98a5a199de..ce04cd6215 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java @@ -59,7 +59,7 @@ public class PhonePolicyTest { private static final int ASYNC_CALL_TIMEOUT_MILLIS = 250; private static final int CONNECT_OTHER_PROFILES_TIMEOUT_MILLIS = 1000; private static final int CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS = - CONNECT_OTHER_PROFILES_TIMEOUT_MILLIS * 3 / 2; + CONNECT_OTHER_PROFILES_TIMEOUT_MILLIS * 3; private HandlerThread mHandlerThread; private BluetoothAdapter mAdapter; diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java index 4cc055e35d..15721bb004 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java @@ -16,6 +16,10 @@ package com.android.bluetooth.btservice; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; @@ -32,6 +36,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.TestUtils; import com.android.bluetooth.btservice.storage.DatabaseManager; +import com.android.bluetooth.gatt.GattService; import org.junit.After; import org.junit.Assert; @@ -78,7 +83,13 @@ public class ProfileServiceTest { } private void setAllProfilesState(int state, int invocationNumber) throws TimeoutException { + int profileCount = mProfiles.length; for (Class profile : mProfiles) { + if (profile == GattService.class) { + // GattService is no longer a service to be start independently + profileCount--; + continue; + } setProfileState(profile, state); } if (invocationNumber == 0) { @@ -87,11 +98,15 @@ public class ProfileServiceTest { return; } ArgumentCaptor<ProfileService> argument = ArgumentCaptor.forClass(ProfileService.class); - verify(mMockAdapterService, timeout(PROFILE_START_MILLIS).times( - mProfiles.length * invocationNumber)).onProfileServiceStateChanged( - argument.capture(), eq(state)); + verify( + mMockAdapterService, + timeout(PROFILE_START_MILLIS).times(profileCount * invocationNumber)) + .onProfileServiceStateChanged(argument.capture(), eq(state)); List<ProfileService> argumentProfiles = argument.getAllValues(); for (Class profile : mProfiles) { + if (profile == GattService.class) { + continue; + } int matches = 0; for (ProfileService arg : argumentProfiles) { if (arg.getClass().getName().equals(profile.getName())) { @@ -118,27 +133,41 @@ public class ProfileServiceTest { return mStartedProfileMap.get((String) args[0]); } }); + doReturn(mDatabaseManager).when(mMockAdapterService).getDatabase(); + when(mMockAdapterService.getSystemService(Context.LOCATION_SERVICE)) .thenReturn(mLocationManager); when(mMockAdapterService.getSystemServiceName(LocationManager.class)) .thenReturn(Context.LOCATION_SERVICE); + // Despite calling on the Mock of adapterService, mockito cannot handle native method and + // will call the real method instead, allowing to initialize the native library + // when(mMockAdapterService.initNative(anyBoolean(), anyBoolean(), anyInt(), any(), + // anyBoolean(), anyString())).thenCallRealMethod(); + doCallRealMethod() + .when(mMockAdapterService) + .initNative(anyBoolean(), anyBoolean(), anyInt(), any(), anyBoolean(), anyString()); + doCallRealMethod().when(mMockAdapterService).enableNative(); + doCallRealMethod().when(mMockAdapterService).disableNative(); + doCallRealMethod().when(mMockAdapterService).cleanupNative(); + mProfiles = Config.getSupportedProfiles(); + TestUtils.setAdapterService(mMockAdapterService); + + Assert.assertNotNull(AdapterService.getAdapterService()); mMockAdapterService.initNative(false /* is_restricted */, false /* is_common_criteria_mode */, 0 /* config_compare_result */, new String[0], false, ""); - - TestUtils.setAdapterService(mMockAdapterService); - doReturn(mDatabaseManager).when(mMockAdapterService).getDatabase(); - - Assert.assertNotNull(AdapterService.getAdapterService()); + mMockAdapterService.enableNative(); } @After public void tearDown() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + mMockAdapterService.disableNative(); mMockAdapterService.cleanupNative(); + TestUtils.clearAdapterService(mMockAdapterService); mMockAdapterService = null; mProfiles = null; @@ -172,13 +201,18 @@ public class ProfileServiceTest { */ @Test public void testEnableDisableInterleaved() throws TimeoutException { + int invocationNumber = mProfiles.length; for (Class profile : mProfiles) { + if (profile == GattService.class) { + // GattService is no longer a service to be start independently + invocationNumber--; + continue; + } setProfileState(profile, BluetoothAdapter.STATE_ON); setProfileState(profile, BluetoothAdapter.STATE_OFF); } ArgumentCaptor<ProfileService> starts = ArgumentCaptor.forClass(ProfileService.class); ArgumentCaptor<ProfileService> stops = ArgumentCaptor.forClass(ProfileService.class); - int invocationNumber = mProfiles.length; verify(mMockAdapterService, timeout(PROFILE_START_MILLIS).times(invocationNumber)).onProfileServiceStateChanged( starts.capture(), eq(BluetoothAdapter.STATE_ON)); @@ -204,6 +238,10 @@ public class ProfileServiceTest { public void testRepeatedEnableDisableSingly() throws TimeoutException { int profileNumber = 0; for (Class profile : mProfiles) { + if (profile == GattService.class) { + // GattService is no longer a service to be start independently + continue; + } for (int i = 0; i < NUM_REPEATS; i++) { setProfileState(profile, BluetoothAdapter.STATE_ON); ArgumentCaptor<ProfileService> start = @@ -230,6 +268,10 @@ public class ProfileServiceTest { public void testProfileServiceRegisterUnregister() throws TimeoutException { int profileNumber = 0; for (Class profile : mProfiles) { + if (profile == GattService.class) { + // GattService is no longer a service to be start independently + continue; + } for (int i = 0; i < NUM_REPEATS; i++) { setProfileState(profile, BluetoothAdapter.STATE_ON); ArgumentCaptor<ProfileService> start = diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/activityAttribution/ActivityAttributionServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/activityAttribution/ActivityAttributionServiceTest.java index 7f845b5dc8..8a4db9a7a4 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/btservice/activityAttribution/ActivityAttributionServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/activityAttribution/ActivityAttributionServiceTest.java @@ -16,12 +16,9 @@ package com.android.bluetooth.btservice.activityattribution; -import static com.google.common.truth.Truth.assertThat; - import android.os.Binder; import android.os.Process; -import org.junit.After; import org.junit.Assume; import org.junit.Before; import org.junit.Test; @@ -30,24 +27,12 @@ import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public final class ActivityAttributionServiceTest { - private static final String TAG = "ActivityAttributionServiceTest"; private ActivityAttributionService mActivityAttributionService; @Before public void setUp() { Assume.assumeTrue("Ignore test when the user is not primary.", isPrimaryUser()); mActivityAttributionService = new ActivityAttributionService(); - mActivityAttributionService.start(); - assertThat(mActivityAttributionService).isNotNull(); - } - - @After - public void tearDown() { - if (!isPrimaryUser()) { - return; - } - mActivityAttributionService.cleanup(); - mActivityAttributionService = null; } private boolean isPrimaryUser() { diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreServiceTest.java index f65ab82331..1e78e0c86b 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreServiceTest.java @@ -20,15 +20,6 @@ import android.os.Binder; import android.os.Process; import android.util.Log; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.security.NoSuchAlgorithmException; -import java.util.HashMap; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - import org.junit.After; import org.junit.Assert; import org.junit.Assume; @@ -36,12 +27,25 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; @RunWith(JUnit4.class) public final class BluetoothKeystoreServiceTest { private static final String TAG = "BluetoothKeystoreServiceTest"; private BluetoothKeystoreService mBluetoothKeystoreService; + @Mock private BluetoothKeystoreNativeInterface mMockNativeInterface; + // Please also check bt_stack string configuration if you want to change the content. private static final String CONFIG_FILE_PREFIX = "bt_config-origin"; private static final String CONFIG_BACKUP_PREFIX = "bt_config-backup"; @@ -119,8 +123,9 @@ public final class BluetoothKeystoreServiceTest { @Before public void setUp() { + MockitoAnnotations.initMocks(this); Assume.assumeTrue("Ignore test when the user is not primary.", isPrimaryUser()); - mBluetoothKeystoreService = new BluetoothKeystoreService(true); + mBluetoothKeystoreService = new BluetoothKeystoreService(mMockNativeInterface, true); Assert.assertNotNull(mBluetoothKeystoreService); // backup origin config data. try { @@ -280,7 +285,7 @@ public final class BluetoothKeystoreServiceTest { mBluetoothKeystoreService.cleanupForCommonCriteriaModeEnable(); // new mBluetoothKeystoreService and the Common Criteria mode is false. - mBluetoothKeystoreService = new BluetoothKeystoreService(false); + mBluetoothKeystoreService = new BluetoothKeystoreService(mMockNativeInterface, false); Assert.assertNotNull(mBluetoothKeystoreService); mBluetoothKeystoreService.loadConfigData(); @@ -304,7 +309,7 @@ public final class BluetoothKeystoreServiceTest { mBluetoothKeystoreService.cleanupForCommonCriteriaModeDisable(); // new mBluetoothKeystoreService and the Common Criteria mode is true. - mBluetoothKeystoreService = new BluetoothKeystoreService(true); + mBluetoothKeystoreService = new BluetoothKeystoreService(mMockNativeInterface, true); mBluetoothKeystoreService.loadConfigData(); Assert.assertTrue(mBluetoothKeystoreService.getCompareResult() == 0); diff --git a/android/app/tests/unit/src/com/android/bluetooth/gatt/AdvertiseManagerTest.java b/android/app/tests/unit/src/com/android/bluetooth/gatt/AdvertiseManagerTest.java index 74d0e64365..c7bf403325 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/gatt/AdvertiseManagerTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/gatt/AdvertiseManagerTest.java @@ -16,7 +16,6 @@ package com.android.bluetooth.gatt; -import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -59,6 +58,8 @@ public class AdvertiseManagerTest { @Mock private GattService.AdvertiserMap mAdvertiserMap; + @Mock private AdvertiseManagerNativeInterface mNativeInterface; + @Mock private IAdvertisingSetCallback mCallback; @@ -74,7 +75,9 @@ public class AdvertiseManagerTest { TestUtils.setAdapterService(mAdapterService); - mAdvertiseManager = new AdvertiseManager(mService, mAdapterService, mAdvertiserMap); + mAdvertiseManager = + new AdvertiseManager(mService, mNativeInterface, mAdapterService, mAdvertiserMap); + AdvertisingSetParameters parameters = new AdvertisingSetParameters.Builder().build(); AdvertiseData advertiseData = new AdvertiseData.Builder().build(); AdvertiseData scanResponse = new AdvertiseData.Builder().build(); diff --git a/android/app/tests/unit/src/com/android/bluetooth/gatt/AppScanStatsTest.java b/android/app/tests/unit/src/com/android/bluetooth/gatt/AppScanStatsTest.java index 359ad3104e..b1bed3da63 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/gatt/AppScanStatsTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/gatt/AppScanStatsTest.java @@ -18,9 +18,6 @@ package com.android.bluetooth.gatt; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.when; import android.bluetooth.le.ScanFilter; import android.bluetooth.le.ScanSettings; @@ -28,6 +25,7 @@ import android.content.Context; import android.location.LocationManager; import android.os.WorkSource; +import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.rule.ServiceTestRule; import androidx.test.runner.AndroidJUnit4; @@ -64,28 +62,23 @@ public class AppScanStatsTest { @Mock private AdapterService mAdapterService; - @Mock private LocationManager mLocationManager; - @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); TestUtils.setAdapterService(mAdapterService); - doReturn(true).when(mAdapterService).isStartedProfile(anyString()); - when(mAdapterService.getSystemService(Context.LOCATION_SERVICE)) - .thenReturn(mLocationManager); - when(mAdapterService.getSystemServiceName(LocationManager.class)) - .thenReturn(Context.LOCATION_SERVICE); - - TestUtils.startService(mServiceRule, GattService.class); - mService = GattService.getGattService(); + + TestUtils.mockGetSystemService( + mAdapterService, Context.LOCATION_SERVICE, LocationManager.class); + + mService = new GattService(InstrumentationRegistry.getTargetContext()); + mService.start(); } @After public void tearDown() throws Exception { - doReturn(false).when(mAdapterService).isStartedProfile(anyString()); - TestUtils.stopService(mServiceRule, GattService.class); - mService = GattService.getGattService(); + mService.stop(); + mService = null; TestUtils.clearAdapterService(mAdapterService); } diff --git a/android/app/tests/unit/src/com/android/bluetooth/gatt/ContextMapTest.java b/android/app/tests/unit/src/com/android/bluetooth/gatt/ContextMapTest.java index 3ff84eea8d..e391a38c95 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/gatt/ContextMapTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/gatt/ContextMapTest.java @@ -18,10 +18,8 @@ package com.android.bluetooth.gatt; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import android.bluetooth.le.AdvertiseData; import android.bluetooth.le.AdvertisingSetParameters; @@ -30,6 +28,7 @@ import android.content.Context; import android.location.LocationManager; import android.os.Binder; +import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.rule.ServiceTestRule; import androidx.test.runner.AndroidJUnit4; @@ -64,8 +63,6 @@ public class ContextMapTest { @Mock private AdapterService mAdapterService; - @Mock private LocationManager mLocationManager; - @Mock private AppAdvertiseStats appAdvertiseStats; @@ -78,23 +75,20 @@ public class ContextMapTest { BluetoothMethodProxy.setInstanceForTesting(mMapMethodProxy); TestUtils.setAdapterService(mAdapterService); - doReturn(true).when(mAdapterService).isStartedProfile(anyString()); - when(mAdapterService.getSystemService(Context.LOCATION_SERVICE)) - .thenReturn(mLocationManager); - when(mAdapterService.getSystemServiceName(LocationManager.class)) - .thenReturn(Context.LOCATION_SERVICE); - - TestUtils.startService(mServiceRule, GattService.class); - mService = GattService.getGattService(); + + TestUtils.mockGetSystemService( + mAdapterService, Context.LOCATION_SERVICE, LocationManager.class); + + mService = new GattService(InstrumentationRegistry.getTargetContext()); + mService.start(); } @After public void tearDown() throws Exception { BluetoothMethodProxy.setInstanceForTesting(null); - doReturn(false).when(mAdapterService).isStartedProfile(anyString()); - TestUtils.stopService(mServiceRule, GattService.class); - mService = GattService.getGattService(); + mService.stop(); + mService = null; TestUtils.clearAdapterService(mAdapterService); } diff --git a/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceBinderTest.java b/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceBinderTest.java index 714e911a41..1c1fda0007 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceBinderTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceBinderTest.java @@ -756,13 +756,6 @@ public class GattServiceBinderTest { } @Test - public void unregAll() throws Exception { - mBinder.unregAll(mAttributionSource); - - verify(mService).unregAll(mAttributionSource); - } - - @Test public void numHwTrackFiltersAvailable() throws Exception { mBinder.numHwTrackFiltersAvailable(mAttributionSource, SynchronousResultReceiver.get()); diff --git a/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java index af9fd7dc99..6d092c0787 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java @@ -25,21 +25,17 @@ import static org.mockito.Mockito.verify; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; -import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothStatusCodes; import android.bluetooth.IBluetoothGattCallback; -import android.bluetooth.IBluetoothGattServerCallback; import android.bluetooth.le.AdvertiseData; import android.bluetooth.le.AdvertisingSetParameters; import android.bluetooth.le.DistanceMeasurementMethod; import android.bluetooth.le.DistanceMeasurementParams; -import android.bluetooth.le.IAdvertisingSetCallback; import android.bluetooth.le.IDistanceMeasurementCallback; import android.bluetooth.le.IPeriodicAdvertisingCallback; import android.bluetooth.le.IScannerCallback; import android.bluetooth.le.PeriodicAdvertisingParameters; -import android.bluetooth.le.ScanFilter; import android.bluetooth.le.ScanResult; import android.bluetooth.le.ScanSettings; import android.content.AttributionSource; @@ -47,7 +43,6 @@ import android.content.Context; import android.content.res.Resources; import android.location.LocationManager; import android.os.Binder; -import android.os.ParcelUuid; import android.os.RemoteException; import android.os.WorkSource; @@ -56,14 +51,12 @@ import androidx.test.filters.SmallTest; import androidx.test.rule.ServiceTestRule; import androidx.test.runner.AndroidJUnit4; -import com.android.bluetooth.R; import com.android.bluetooth.TestUtils; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.CompanionManager; import org.junit.After; import org.junit.Assert; -import org.junit.Assume; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; @@ -97,11 +90,11 @@ public class GattServiceTest { @Mock private GattService.ScannerMap mScannerMap; @Mock private GattService.ScannerMap.App mApp; @Mock private GattService.PendingIntentInfo mPiInfo; + @Mock private PeriodicScanManager mPeriodicScanManager; @Mock private ScanManager mScanManager; @Mock private Set<String> mReliableQueue; @Mock private GattService.ServerMap mServerMap; @Mock private DistanceMeasurementManager mDistanceMeasurementManager; - @Mock private LocationManager mLocationManager; @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); @@ -122,10 +115,13 @@ public class GattServiceTest { MockitoAnnotations.initMocks(this); TestUtils.setAdapterService(mAdapterService); - doReturn(true).when(mAdapterService).isStartedProfile(anyString()); GattObjectsFactory.setInstanceForTesting(mFactory); doReturn(mNativeInterface).when(mFactory).getNativeInterface(); + doReturn(mScanManager).when(mFactory).createScanManager(any(), any(), any()); + doReturn(mPeriodicScanManager).when(mFactory).createPeriodicScanManager(any()); + doReturn(mDistanceMeasurementManager).when(mFactory) + .createDistanceMeasurementManager(any()); mAdapter = BluetoothAdapter.getDefaultAdapter(); mAttributionSource = mAdapter.getAttributionSource(); @@ -136,62 +132,43 @@ public class GattServiceTest { when(mAdapterService.getSharedPreferences(anyString(), anyInt())) .thenReturn(InstrumentationRegistry.getTargetContext() .getSharedPreferences("GattServiceTestPrefs", Context.MODE_PRIVATE)); - when(mAdapterService.getSystemService(Context.LOCATION_SERVICE)) - .thenReturn(mLocationManager); - when(mAdapterService.getSystemServiceName(LocationManager.class)) - .thenReturn(Context.LOCATION_SERVICE); + + TestUtils.mockGetSystemService( + mAdapterService, Context.LOCATION_SERVICE, LocationManager.class); mBtCompanionManager = new CompanionManager(mAdapterService, null); doReturn(mBtCompanionManager).when(mAdapterService).getCompanionManager(); - TestUtils.startService(mServiceRule, GattService.class); - mService = GattService.getGattService(); - Assert.assertNotNull(mService); + mService = new GattService(InstrumentationRegistry.getTargetContext()); + mService.start(); mService.mClientMap = mClientMap; mService.mScannerMap = mScannerMap; - mService.mScanManager = mScanManager; mService.mReliableQueue = mReliableQueue; mService.mServerMap = mServerMap; - mService.mDistanceMeasurementManager = mDistanceMeasurementManager; } @After public void tearDown() throws Exception { - doReturn(false).when(mAdapterService).isStartedProfile(anyString()); - TestUtils.stopService(mServiceRule, GattService.class); - mService = GattService.getGattService(); - Assert.assertNull(mService); + mService.stop(); + mService = null; + TestUtils.clearAdapterService(mAdapterService); GattObjectsFactory.setInstanceForTesting(null); } @Test - public void testInitialize() { - Assert.assertEquals(mService, GattService.getGattService()); - verify(mNativeInterface).init(eq(mService)); - } - - @Test public void testServiceUpAndDown() throws Exception { for (int i = 0; i < TIMES_UP_AND_DOWN; i++) { - GattService gattService = GattService.getGattService(); - doReturn(false).when(mAdapterService).isStartedProfile(anyString()); - TestUtils.stopService(mServiceRule, GattService.class); - mService = GattService.getGattService(); - Assert.assertNull(mService); - gattService.cleanup(); + mService.stop(); + mService = null; + TestUtils.clearAdapterService(mAdapterService); reset(mAdapterService); TestUtils.setAdapterService(mAdapterService); - doReturn(true).when(mAdapterService).isStartedProfile(anyString()); - when(mAdapterService.getSystemService(Context.LOCATION_SERVICE)) - .thenReturn(mLocationManager); - when(mAdapterService.getSystemServiceName(LocationManager.class)) - .thenReturn(Context.LOCATION_SERVICE); - TestUtils.startService(mServiceRule, GattService.class); - mService = GattService.getGattService(); - Assert.assertNotNull(mService); + + mService = new GattService(InstrumentationRegistry.getTargetContext()); + mService.start(); } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/gatt/ScanManagerTest.java b/android/app/tests/unit/src/com/android/bluetooth/gatt/ScanManagerTest.java index cb9a42d2d6..42d26f77b0 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/gatt/ScanManagerTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/gatt/ScanManagerTest.java @@ -29,7 +29,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.atMost; import static org.mockito.Mockito.doReturn; @@ -76,6 +75,7 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; /** * Test cases for {@link ScanManager}. @@ -104,7 +104,7 @@ public class ScanManagerTest { @Mock private AdapterService mAdapterService; @Mock private BluetoothAdapterProxy mBluetoothAdapterProxy; @Mock private LocationManager mLocationManager; - @Mock private GattObjectsFactory mFactory; + @Spy private GattObjectsFactory mFactory = GattObjectsFactory.getInstance(); @Mock private GattNativeInterface mNativeInterface; @Mock private ScanNativeInterface mScanNativeInterface; @Mock private MetricsLogger mMetricsLogger; @@ -117,17 +117,15 @@ public class ScanManagerTest { MockitoAnnotations.initMocks(this); TestUtils.setAdapterService(mAdapterService); - doReturn(true).when(mAdapterService).isStartedProfile(anyString()); when(mAdapterService.getScanTimeoutMillis()). thenReturn((long) DELAY_DEFAULT_SCAN_TIMEOUT_MS); when(mAdapterService.getNumOfOffloadedScanFilterSupported()) .thenReturn(DEFAULT_NUM_OFFLOAD_SCAN_FILTER); when(mAdapterService.getOffloadedScanResultStorage()) .thenReturn(DEFAULT_BYTES_OFFLOAD_SCAN_RESULT_STORAGE); - when(mAdapterService.getSystemService(Context.LOCATION_SERVICE)) - .thenReturn(mLocationManager); - when(mAdapterService.getSystemServiceName(LocationManager.class)) - .thenReturn(Context.LOCATION_SERVICE); + + TestUtils.mockGetSystemService( + mAdapterService, Context.LOCATION_SERVICE, LocationManager.class, mLocationManager); doReturn(true).when(mLocationManager).isLocationEnabled(); BluetoothAdapterProxy.setInstanceForTesting(mBluetoothAdapterProxy); @@ -142,9 +140,8 @@ public class ScanManagerTest { MetricsLogger.setInstanceForTesting(mMetricsLogger); - TestUtils.startService(mServiceRule, GattService.class); - mService = GattService.getGattService(); - assertThat(mService).isNotNull(); + mService = new GattService(InstrumentationRegistry.getTargetContext()); + mService.start(); mScanManager = mService.getScanManager(); assertThat(mScanManager).isNotNull(); @@ -160,10 +157,9 @@ public class ScanManagerTest { @After public void tearDown() throws Exception { - doReturn(false).when(mAdapterService).isStartedProfile(anyString()); - TestUtils.stopService(mServiceRule, GattService.class); - mService = GattService.getGattService(); - assertThat(mService).isNull(); + mService.stop(); + mService = null; + TestUtils.clearAdapterService(mAdapterService); BluetoothAdapterProxy.setInstanceForTesting(null); GattObjectsFactory.setInstanceForTesting(null); diff --git a/android/app/tests/unit/src/com/android/bluetooth/hap/HapClientStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/hap/HapClientStateMachineTest.java index 3fb878c7a6..d7c8e61b4b 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/hap/HapClientStateMachineTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/hap/HapClientStateMachineTest.java @@ -28,13 +28,11 @@ import static org.mockito.Mockito.verify; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; -import android.content.Context; import android.content.Intent; import android.os.HandlerThread; import android.os.Message; import android.test.suitebuilder.annotation.MediumTest; -import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.TestUtils; @@ -55,7 +53,6 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public class HapClientStateMachineTest { private BluetoothAdapter mAdapter; - private Context mTargetContext; private HandlerThread mHandlerThread; private HapClientStateMachine mHapClientStateMachine; private BluetoothDevice mTestDevice; @@ -70,7 +67,6 @@ public class HapClientStateMachineTest { @Before public void setUp() throws Exception { - mTargetContext = InstrumentationRegistry.getTargetContext(); // Set up mocks and test assets MockitoAnnotations.initMocks(this); TestUtils.setAdapterService(mAdapterService); diff --git a/android/app/tests/unit/src/com/android/bluetooth/hap/HapClientTest.java b/android/app/tests/unit/src/com/android/bluetooth/hap/HapClientTest.java index 0405a79edc..32e643541b 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/hap/HapClientTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/hap/HapClientTest.java @@ -86,8 +86,7 @@ public class HapClientTest { private final String mFlagDexmarker = System.getProperty("dexmaker.share_classloader", "false"); private static final int TIMEOUT_MS = 1000; - @Rule - public final ServiceTestRule mServiceRule = new ServiceTestRule(); + @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); private BluetoothAdapter mAdapter; private BluetoothDevice mDevice; private BluetoothDevice mDevice2; @@ -99,20 +98,13 @@ public class HapClientTest { private HasIntentReceiver mHasIntentReceiver; private HashMap<BluetoothDevice, LinkedBlockingQueue<Intent>> mIntentQueue; - @Mock - private AdapterService mAdapterService; - @Mock - private DatabaseManager mDatabaseManager; - @Mock - private HapClientNativeInterface mNativeInterface; - @Mock - private ServiceFactory mServiceFactory; - @Mock - private CsipSetCoordinatorService mCsipService; - @Mock - private IBluetoothHapClientCallback mCallback; - @Mock - private Binder mBinder; + @Mock private AdapterService mAdapterService; + @Mock private DatabaseManager mDatabaseManager; + @Mock private HapClientNativeInterface mNativeInterface; + @Mock private ServiceFactory mServiceFactory; + @Mock private CsipSetCoordinatorService mCsipService; + @Mock private IBluetoothHapClientCallback mCallback; + @Mock private Binder mBinder; @Before public void setUp() throws Exception { diff --git a/android/app/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java index 645528b0d1..44b6bfa362 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java @@ -44,6 +44,7 @@ import androidx.test.rule.ServiceTestRule; import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.TestUtils; +import com.android.bluetooth.btservice.ActiveDeviceManager; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.storage.DatabaseManager; import com.android.bluetooth.x.com.android.modules.utils.SynchronousResultReceiver; @@ -80,6 +81,7 @@ public class HearingAidServiceTest { private BroadcastReceiver mHearingAidIntentReceiver; @Mock private AdapterService mAdapterService; + @Mock private ActiveDeviceManager mActiveDeviceManager; @Mock private DatabaseManager mDatabaseManager; @Mock private HearingAidNativeInterface mNativeInterface; @Mock private AudioManager mAudioManager; @@ -97,6 +99,7 @@ public class HearingAidServiceTest { } TestUtils.setAdapterService(mAdapterService); + doReturn(mActiveDeviceManager).when(mAdapterService).getActiveDeviceManager(); doReturn(mDatabaseManager).when(mAdapterService).getDatabase(); doReturn(true, false).when(mAdapterService).isStartedProfile(anyString()); diff --git a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java index d75199350a..185a7aa772 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java @@ -41,6 +41,7 @@ import android.os.ParcelUuid; import android.os.PowerManager; import android.os.RemoteException; import android.telecom.PhoneAccount; +import android.util.Log; import androidx.test.InstrumentationRegistry; import androidx.test.espresso.intent.Intents; @@ -82,9 +83,11 @@ import java.util.concurrent.LinkedBlockingQueue; @MediumTest @RunWith(AndroidJUnit4.class) public class HeadsetServiceAndStateMachineTest { + private static final String TAG = "HeadsetServiceAndStateMachineTest"; private static final int ASYNC_CALL_TIMEOUT_MILLIS = 250; private static final int START_VR_TIMEOUT_MILLIS = 1000; private static final int START_VR_TIMEOUT_WAIT_MILLIS = START_VR_TIMEOUT_MILLIS * 3 / 2; + private static final int STATE_CHANGE_TIMEOUT_MILLIS = 1000; private static final int MAX_HEADSET_CONNECTIONS = 5; private static final ParcelUuid[] FAKE_HEADSET_UUID = {BluetoothUuid.HFP}; private static final String TEST_PHONE_NUMBER = "1234567890"; @@ -470,7 +473,7 @@ public class HeadsetServiceAndStateMachineTest { BluetoothDevice activeDevice = connectedDevices.get(MAX_HEADSET_CONNECTIONS / 2); Assert.assertTrue(mHeadsetService.setActiveDevice(activeDevice)); verify(mNativeInterface).setActiveDevice(activeDevice); - waitAndVerifyActiveDeviceChangedIntent(ASYNC_CALL_TIMEOUT_MILLIS, activeDevice); + verify(mActiveDeviceManager).hfpActiveStateChanged(activeDevice); Assert.assertEquals(activeDevice, mHeadsetService.getActiveDevice()); // Start virtual call Assert.assertTrue(mHeadsetService.startScoUsingVirtualVoiceCall()); @@ -1176,11 +1179,11 @@ public class HeadsetServiceAndStateMachineTest { verify(mObjectsFactory).makeStateMachine(device, mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, mAdapterService, mNativeInterface, mSystemInterface); - // Wait ASYNC_CALL_TIMEOUT_MILLIS for state to settle, timing is also tested here and - // 250ms for processing two messages should be way more than enough. Anything that breaks - // this indicate some breakage in other part of Android OS - waitAndVerifyConnectionStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device, - BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED); + verify(mActiveDeviceManager, timeout(STATE_CHANGE_TIMEOUT_MILLIS)) + .hfpConnectionStateChanged( + device, + BluetoothProfile.STATE_DISCONNECTED, + BluetoothProfile.STATE_CONNECTING); Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, mHeadsetService.getConnectionState(device)); Assert.assertEquals(Collections.singletonList(device), @@ -1191,11 +1194,11 @@ public class HeadsetServiceAndStateMachineTest { new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED, HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED, device); mHeadsetService.messageFromNative(slcConnectedEvent); - // Wait ASYNC_CALL_TIMEOUT_MILLIS for state to settle, timing is also tested here and - // 250ms for processing two messages should be way more than enough. Anything that breaks - // this indicate some breakage in other part of Android OS - waitAndVerifyConnectionStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device, - BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING); + verify(mActiveDeviceManager, timeout(STATE_CHANGE_TIMEOUT_MILLIS)) + .hfpConnectionStateChanged( + device, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_CONNECTED); Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mHeadsetService.getConnectionState(device)); } diff --git a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java index 008fc0d349..b689546d9b 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java @@ -56,6 +56,7 @@ import androidx.test.rule.ServiceTestRule; import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.TestUtils; +import com.android.bluetooth.btservice.ActiveDeviceManager; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.ServiceFactory; import com.android.bluetooth.btservice.storage.DatabaseManager; @@ -109,6 +110,7 @@ public class LeAudioServiceTest { private BroadcastReceiver mLeAudioIntentReceiver; @Mock private AdapterService mAdapterService; + @Mock private ActiveDeviceManager mActiveDeviceManager; @Mock private AudioManager mAudioManager; @Mock private DatabaseManager mDatabaseManager; @Mock private LeAudioNativeInterface mNativeInterface; @@ -169,6 +171,7 @@ public class LeAudioServiceTest { doReturn(MAX_LE_AUDIO_CONNECTIONS).when(mAdapterService).getMaxConnectedAudioDevices(); doReturn(new ParcelUuid[]{BluetoothUuid.LE_AUDIO}).when(mAdapterService) .getRemoteUuids(any(BluetoothDevice.class)); + doReturn(mActiveDeviceManager).when(mAdapterService).getActiveDeviceManager(); doReturn(mDatabaseManager).when(mAdapterService).getDatabase(); doReturn(true, false).when(mAdapterService).isStartedProfile(anyString()); diff --git a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioStateMachineTest.java index e75f2fda45..ac69b84dfa 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioStateMachineTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioStateMachineTest.java @@ -17,15 +17,17 @@ package com.android.bluetooth.le_audio; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Mockito.after; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; -import static com.google.common.truth.Truth.assertThat; - import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; @@ -45,7 +47,6 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -146,12 +147,10 @@ public class LeAudioStateMachineTest { connStCh.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_CONNECTING; mLeAudioStateMachine.sendMessage(LeAudioStateMachine.STACK_EVENT, connStCh); - // Verify that one connection state broadcast is executed - ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class); - verify(mLeAudioService, timeout(TIMEOUT_MS).times(1)).sendBroadcast( - intentArgument1.capture(), anyString(), any(Bundle.class)); - assertThat(BluetoothProfile.STATE_CONNECTING).isEqualTo( - intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); + // Verify that one connection state change is notifyed + verify(mLeAudioService, timeout(TIMEOUT_MS)) + .notifyConnectionStateChanged( + any(), eq(BluetoothProfile.STATE_CONNECTING), anyInt()); // Check that we are in Connecting state assertThat(mLeAudioStateMachine.getCurrentState()) @@ -164,11 +163,14 @@ public class LeAudioStateMachineTest { connCompletedEvent.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_CONNECTED; mLeAudioStateMachine.sendMessage(LeAudioStateMachine.STACK_EVENT, connCompletedEvent); - // Verify that the expected number of broadcasts are executed: - // - two calls to broadcastConnectionState(): Disconnected -> Conecting -> Connected - ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class); - verify(mLeAudioService, timeout(TIMEOUT_MS).times(2)).sendBroadcast( - intentArgument2.capture(), anyString(), any(Bundle.class)); + // Verify that the expected number of notification are called: + // - two calls to notifyConnectionStateChanged(): Disconnected -> Connecting -> Connected + verify(mLeAudioService, timeout(TIMEOUT_MS)) + .notifyConnectionStateChanged( + any(), eq(BluetoothProfile.STATE_CONNECTING), anyInt()); + verify(mLeAudioService, timeout(TIMEOUT_MS)) + .notifyConnectionStateChanged( + any(), eq(BluetoothProfile.STATE_CONNECTED), anyInt()); // Check that we are in Connected state assertThat(mLeAudioStateMachine.getCurrentState()) .isInstanceOf(LeAudioStateMachine.Connected.class); @@ -188,25 +190,19 @@ public class LeAudioStateMachineTest { // Send a connect request mLeAudioStateMachine.sendMessage(LeAudioStateMachine.CONNECT, mTestDevice); - // Verify that one connection state broadcast is executed - ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class); - verify(mLeAudioService, timeout(TIMEOUT_MS).times(1)).sendBroadcast( - intentArgument1.capture(), - anyString(), any(Bundle.class)); - assertThat(BluetoothProfile.STATE_CONNECTING).isEqualTo( - intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); + // Verify that one connection state change is notified + verify(mLeAudioService, timeout(TIMEOUT_MS)) + .notifyConnectionStateChanged( + any(), eq(BluetoothProfile.STATE_CONNECTING), anyInt()); // Check that we are in Connecting state assertThat(mLeAudioStateMachine.getCurrentState()) .isInstanceOf(LeAudioStateMachine.Connecting.class); - // Verify that one connection state broadcast is executed - ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class); - verify(mLeAudioService, timeout(LeAudioStateMachine.sConnectTimeoutMs * 2).times( - 2)).sendBroadcast(intentArgument2.capture(), anyString(), - any(Bundle.class)); - assertThat(BluetoothProfile.STATE_DISCONNECTED).isEqualTo( - intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); + // Verify that one connection state change is notified + verify(mLeAudioService, timeout(LeAudioStateMachine.sConnectTimeoutMs * 2)) + .notifyConnectionStateChanged( + any(), eq(BluetoothProfile.STATE_DISCONNECTED), anyInt()); // Check that we are in Disconnected state assertThat(mLeAudioStateMachine.getCurrentState()) @@ -231,25 +227,19 @@ public class LeAudioStateMachineTest { connStCh.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_CONNECTING; mLeAudioStateMachine.sendMessage(LeAudioStateMachine.STACK_EVENT, connStCh); - // Verify that one connection state broadcast is executed - ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class); - verify(mLeAudioService, timeout(TIMEOUT_MS).times(1)).sendBroadcast( - intentArgument1.capture(), - anyString(), any(Bundle.class)); - assertThat(BluetoothProfile.STATE_CONNECTING).isEqualTo( - intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); + // Verify that one connection state change is notified + verify(mLeAudioService, timeout(TIMEOUT_MS)) + .notifyConnectionStateChanged( + any(), eq(BluetoothProfile.STATE_CONNECTING), anyInt()); // Check that we are in Connecting state assertThat(mLeAudioStateMachine.getCurrentState()) .isInstanceOf(LeAudioStateMachine.Connecting.class); - // Verify that one connection state broadcast is executed - ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class); - verify(mLeAudioService, timeout(LeAudioStateMachine.sConnectTimeoutMs * 2).times( - 2)).sendBroadcast(intentArgument2.capture(), anyString(), - any(Bundle.class)); - assertThat(intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1)) - .isEqualTo(BluetoothProfile.STATE_DISCONNECTED); + // Verify that one connection state change is notified + verify(mLeAudioService, timeout(LeAudioStateMachine.sConnectTimeoutMs * 2)) + .notifyConnectionStateChanged( + any(), eq(BluetoothProfile.STATE_DISCONNECTED), anyInt()); // Check that we are in Disconnected state assertThat(mLeAudioStateMachine.getCurrentState()) diff --git a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java index 953d93249a..d61d3de936 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java @@ -49,7 +49,6 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; - @MediumTest @RunWith(AndroidJUnit4.class) public class BluetoothOppServiceTest { @@ -90,7 +89,11 @@ public class BluetoothOppServiceTest { // Since the update thread is not run (we mocked it), it will not clean itself on interrupt // (normally, the service will wait for the update thread to clean itself after // being interrupted). We clean it manually here - mService.mUpdateThread = null; + BluetoothOppService service = mService; + if (service != null) { + service.mUpdateThread = null; + } + BluetoothMethodProxy.setInstanceForTesting(null); TestUtils.stopService(mServiceRule, BluetoothOppService.class); TestUtils.clearAdapterService(mAdapterService); @@ -187,5 +190,4 @@ public class BluetoothOppServiceTest { eq(BluetoothShare._ID + " < " + 20), any()); } -} - +}
\ No newline at end of file diff --git a/android/pandora/server/configs/pts_bot_tests_config.json b/android/pandora/server/configs/pts_bot_tests_config.json index cadb57dfd0..51f8f39f33 100644 --- a/android/pandora/server/configs/pts_bot_tests_config.json +++ b/android/pandora/server/configs/pts_bot_tests_config.json @@ -127,7 +127,6 @@ "AVRCP/TG/MCN/CB/BV-02-I", "AVRCP/TG/MCN/CB/BV-03-I", "AVRCP/TG/MCN/CB/BV-05-C", - "AVRCP/TG/MCN/CB/BV-06-C", "AVRCP/TG/MCN/CB/BV-06-I", "AVRCP/TG/MCN/CB/BV-08-C", "AVRCP/TG/MCN/NP/BI-01-C", @@ -535,10 +534,6 @@ "MAP/MSE/MSM/BV-06-I", "MAP/MSE/MSM/BV-07-I", "MAP/MSE/MSM/BV-08-I", - "OPP/CL/GOEP/BC/BV-02-I", - "OPP/CL/GOEP/CON/BV-01-C", - "OPP/CL/OPH/BV-01-I", - "OPP/CL/OPH/BV-34-I", "OPP/SR/GOEP/BC/BV-01-I", "OPP/SR/GOEP/CON/BV-02-C", "OPP/SR/GOEP/ROB/BV-01-C", @@ -560,7 +555,6 @@ "PBAP/PSE/GOEP/SRM/BI-03-C", "PBAP/PSE/GOEP/SRM/BI-05-C", "PBAP/PSE/GOEP/SRM/BV-08-C", - "PBAP/PSE/GOEP/SRMP/BI-02-C", "PBAP/PSE/GOEP/SRMP/BV-02-C", "PBAP/PSE/PBB/BI-01-C", "PBAP/PSE/PBB/BI-07-C", @@ -734,6 +728,7 @@ "AVRCP/CT/PTT/BV-01-I", "AVRCP/TG/MCN/CB/BI-02-C", "AVRCP/TG/MCN/CB/BV-04-I", + "AVRCP/TG/MCN/CB/BV-06-C", "AVRCP/TG/MCN/CB/BV-09-C", "AVRCP/TG/NFY/BV-04-C", "BNEP/TX-TYPE-0/BV-20-C", @@ -912,6 +907,10 @@ "MAP/MSE/MMN/BV-07-I", "MAP/MSE/MMN/BV-14-I", "MAP/MSE/MMU/BV-02-I", + "OPP/CL/GOEP/BC/BV-02-I", + "OPP/CL/GOEP/CON/BV-01-C", + "OPP/CL/OPH/BV-01-I", + "OPP/CL/OPH/BV-34-I", "OPP/SR/BCP/BV-02-I", "OPP/SR/GOEP/ROB/BV-02-C", "OPP/SR/OPH/BV-10-I", @@ -924,6 +923,7 @@ "PAN/NAP/MISC/UUID/BV-02-C", "PAN/PANU/IP/APP/BV-04-I", "PAN/PANU/IPv4/AUTONET/BV-01-I", + "PBAP/PSE/GOEP/SRMP/BI-02-C", "PBAP/PSE/SSM/BV-07-C", "RFCOMM/DEVA-DEVB/RFC/BV-04-C", "RFCOMM/DEVA-DEVB/RFC/BV-07-C", diff --git a/android/pandora/server/src/AndroidInternal.kt b/android/pandora/server/src/AndroidInternal.kt index 0418e4e09c..c0392d4308 100644 --- a/android/pandora/server/src/AndroidInternal.kt +++ b/android/pandora/server/src/AndroidInternal.kt @@ -60,8 +60,8 @@ class AndroidInternal(val context: Context) : AndroidImplBase(), Closeable { // PTS does not configure the Extended Inquiry Response with the // device name; the device will be found after the Inquiry Timeout - // (12.8sec) has elapsed. - private val BT_DEVICE_SELECT_WAIT_TIMEOUT = 20000L + // (40 secondes) has elapsed. + private val BT_DEVICE_SELECT_WAIT_TIMEOUT = 40000L private val IMAGE_FILE_NAME = "OPP_TEST_IMAGE.bmp" private val bluetoothManager = context.getSystemService(BluetoothManager::class.java)!! diff --git a/android/pandora/server/src/Gatt.kt b/android/pandora/server/src/Gatt.kt index c683dd705a..6ca2fe7370 100644 --- a/android/pandora/server/src/Gatt.kt +++ b/android/pandora/server/src/Gatt.kt @@ -31,9 +31,7 @@ import java.io.Closeable import java.util.UUID import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async import kotlinx.coroutines.cancel -import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted @@ -276,18 +274,15 @@ class Gatt(private val context: Context) : GATTImplBase(), Closeable { service.addCharacteristic(characteristic) } - val fullService = coroutineScope { - val firstService = mScope.async { serverManager.newServiceFlow.first() } - serverManager.server.addService(service) - firstService.await() - } + serverManager.server.addService(service) + val addedService = serverManager.serviceFlow.first() RegisterServiceResponse.newBuilder() .setService( GattService.newBuilder() - .setHandle(fullService.instanceId) - .setType(fullService.type) - .setUuid(fullService.uuid.toString().uppercase()) + .setHandle(addedService.instanceId) + .setType(addedService.type) + .setUuid(addedService.uuid.toString().uppercase()) .addAllIncludedServices(generateServicesList(service.includedServices, 1)) .addAllCharacteristics(generateCharacteristicsList(service.characteristics)) .build() diff --git a/android/pandora/server/src/GattServerManager.kt b/android/pandora/server/src/GattServerManager.kt index 89f3f110f5..b8085e0f3a 100644 --- a/android/pandora/server/src/GattServerManager.kt +++ b/android/pandora/server/src/GattServerManager.kt @@ -28,16 +28,17 @@ import android.util.Log import java.util.UUID import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.launch class GattServerManager( bluetoothManager: BluetoothManager, context: Context, - globalScope: CoroutineScope + scope: CoroutineScope ) { val TAG = "PandoraGattServerManager" val services = mutableMapOf<UUID, BluetoothGattService>() - val newServiceFlow = MutableSharedFlow<BluetoothGattService>(extraBufferCapacity = 8) + val serviceFlow = MutableSharedFlow<BluetoothGattService>() var negociatedMtu = -1 val callback = @@ -45,7 +46,7 @@ class GattServerManager( override fun onServiceAdded(status: Int, service: BluetoothGattService) { Log.i(TAG, "onServiceAdded status=$status") check(status == BluetoothGatt.GATT_SUCCESS) - check(newServiceFlow.tryEmit(service)) + scope.launch { serviceFlow.emit(service) } } override fun onMtuChanged(device: BluetoothDevice, mtu: Int) { Log.i(TAG, "onMtuChanged mtu=$mtu") diff --git a/android/pandora/test/AndroidTest.xml b/android/pandora/test/AndroidTest.xml index 9906aeac75..29353f27ae 100644 --- a/android/pandora/test/AndroidTest.xml +++ b/android/pandora/test/AndroidTest.xml @@ -23,9 +23,7 @@ <option name="install-arg" value="-r" /> <option name="install-arg" value="-g" /> </target_preparer> - <target_preparer class="com.android.tradefed.targetprep.RunHostCommandTargetPreparer"> - <option name="host-setup-command" value="adb -s $SERIAL forward tcp:6211 vsock:2:7300" /> - <option name="host-teardown-command" value="adb -s $SERIAL forward --remove tcp:6211" /> + <target_preparer class="com.android.tradefed.targetprep.RootcanalForwarderPreparer"> </target_preparer> <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="run-command" value="cmd bluetooth_manager enable" /> diff --git a/android/pandora/test/asha_test.py b/android/pandora/test/asha_test.py index 73f75d82bb..83b8d456d8 100644 --- a/android/pandora/test/asha_test.py +++ b/android/pandora/test/asha_test.py @@ -16,10 +16,7 @@ import asyncio import avatar import enum import grpc -import inspect -import itertools import logging -import math import numpy as np from avatar import BumblePandoraDevice, PandoraDevice, PandoraDevices, asynchronous @@ -34,7 +31,7 @@ from mobly.asserts import assert_in # type: ignore from mobly.asserts import assert_is_not_none # type: ignore from mobly.asserts import assert_not_equal # type: ignore from mobly.asserts import assert_true # type: ignore -from pandora._utils import AioStream, Stream +from pandora._utils import AioStream from pandora.host_pb2 import PUBLIC, RANDOM, AdvertiseResponse, Connection, DataTypes, OwnAddressType, ScanningResponse from pandora.security_pb2 import LE_LEVEL3 from pandora_experimental.asha_grpc_aio import Asha as AioAsha, add_AshaServicer_to_server @@ -868,6 +865,9 @@ class AshaTest(base_test.BaseTestClass): # type: ignore[misc] Verify that DUT sends a correct AudioControlPoint `Stop` command. """ + # TODO(b/290204194) Re-activate this test ASAP + raise signals.TestSkip('TODO(b/290204194) Re-activate this test ASAP') + async def ref_device_connect(ref_device: BumblePandoraDevice, ear: Ear) -> Tuple[Connection, Connection]: advertisement = await self.ref_advertise_asha(ref_device=ref_device, ref_address_type=RANDOM, ear=ear) ref = await self.dut_scan_for_asha(dut_address_type=RANDOM, ear=ear) @@ -1064,6 +1064,9 @@ class AshaTest(base_test.BaseTestClass): # type: ignore[misc] Verify Refs cannot recevice audio data after DUT stops media streaming. """ + # TODO(b/290204194) Re-activate this test ASAP + raise signals.TestSkip('TODO(b/290204194) Re-activate this test ASAP') + async def ref_device_connect(ref_device: BumblePandoraDevice, ear: Ear) -> Tuple[Connection, Connection]: advertisement = await self.ref_advertise_asha(ref_device=ref_device, ref_address_type=RANDOM, ear=ear) ref = await self.dut_scan_for_asha(dut_address_type=RANDOM, ear=ear) diff --git a/android/pandora/test/config.yml b/android/pandora/test/config.yml index 3682981ff2..3dd993b15d 100644 --- a/android/pandora/test/config.yml +++ b/android/pandora/test/config.yml @@ -5,8 +5,8 @@ TestBeds: Controllers: AndroidDevice: '*' BumbleDevice: - - transport: 'tcp-client:127.0.0.1:6211' - - transport: 'tcp-client:127.0.0.1:6211' + - tcp: ${ROOTCANAL_HCI_ADDRESS} + - tcp: ${ROOTCANAL_HCI_ADDRESS} - Name: bumble.bumbles Controllers: BumbleDevice: diff --git a/android/pandora/test/gatt_test.py b/android/pandora/test/gatt_test.py index df8b912cc4..09c4dcf0ef 100644 --- a/android/pandora/test/gatt_test.py +++ b/android/pandora/test/gatt_test.py @@ -14,6 +14,7 @@ import asyncio import avatar +import grpc import logging from avatar import BumblePandoraDevice, PandoraDevice, PandoraDevices @@ -26,6 +27,7 @@ from mobly.asserts import assert_equal # type: ignore from mobly.asserts import assert_in # type: ignore from mobly.asserts import assert_is_not_none # type: ignore from mobly.asserts import assert_not_in # type: ignore +from mobly.asserts import assert_true # type: ignore from pandora.host_pb2 import RANDOM, Connection, DataTypes from pandora.security_pb2 import LE_LEVEL3, PairingEventAnswer, SecureResponse from pandora_experimental.gatt_grpc import GATT @@ -238,6 +240,59 @@ class GattTest(base_test.BaseTestClass): # type: ignore[misc] assert_in(SERVICE_UUID_1, (service.uuid for service in second_discovery.services)) assert_in(SERVICE_UUID_2, (service.uuid for service in second_discovery.services)) + @avatar.asynchronous + async def test_eatt_when_not_encrypted_no_timeout(self) -> None: + if not isinstance(self.ref, BumblePandoraDevice): + raise signals.TestSkip('Test require Bumble as reference device(s)') + advertise = self.dut.aio.host.Advertise( + legacy=True, + connectable=True, + own_address_type=RANDOM, + data=DataTypes(manufacturer_specific_data=b'pause cafe'), + ) + + scan = self.ref.aio.host.Scan() + dut = await anext((x async for x in scan if b'pause cafe' in x.data.manufacturer_specific_data)) + scan.cancel() + + ref_dut = (await self.ref.aio.host.ConnectLE(own_address_type=RANDOM, **dut.address_asdict())).connection + assert_is_not_none(ref_dut) + assert ref_dut + advertise.cancel() + + connection = self.ref.device.lookup_connection(int.from_bytes(ref_dut.cookie.value, 'big')) + assert connection + + connection_request = ( + b"\x17" # code of L2CAP_CREDIT_BASED_CONNECTION_REQ + b"\x01" # identifier + b"\x0a\x00" # data length + b"\x27\x00" # psm(EATT) + b"\x64\x00" # MTU + b"\x64\x00" # MPS + b"\x64\x00" # initial credit + b"\x40\x00" # source cid[0] + ) + + fut = asyncio.get_running_loop().create_future() + setattr(self.ref.device.l2cap_channel_manager, "on_[0x18]", lambda _, _1, frame: fut.set_result(frame)) + self.ref.device.l2cap_channel_manager.send_control_frame( # type:ignore + connection, 0x05, connection_request + ) + control_frame = await fut + + assert_equal(bytes(control_frame)[10], 0x05) # All connections refused – insufficient authentication + assert_true(await is_connected(self.ref, ref_dut), "Device is no longer connected") + + +async def is_connected(device: PandoraDevice, connection: Connection) -> bool: + try: + await device.aio.host.WaitDisconnection(connection=connection, timeout=5) + return False + except grpc.RpcError as e: + assert_equal(e.code(), grpc.StatusCode.DEADLINE_EXCEEDED) # type: ignore + return True + if __name__ == '__main__': logging.basicConfig(level=logging.DEBUG) diff --git a/android/pandora/test/hfpclient_test.py b/android/pandora/test/hfpclient_test.py index e32e539bd7..d655590529 100644 --- a/android/pandora/test/hfpclient_test.py +++ b/android/pandora/test/hfpclient_test.py @@ -24,32 +24,32 @@ from bumble.core import ( BT_L2CAP_PROTOCOL_ID, BT_RFCOMM_PROTOCOL_ID, ) -from bumble.rfcomm import Server as RfcommServer, DLC +from bumble.hfp import HfpProtocol +from bumble.rfcomm import DLC, Server as RfcommServer from bumble.sdp import ( - DataElement, - ServiceAttribute, + SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID, SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID, SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID, - SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID, SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID, + DataElement, + ServiceAttribute, ) -from bumble.hfp import HfpProtocol from mobly import base_test, test_runner from mobly.asserts import assert_equal # type: ignore -from mobly.asserts import assert_not_equal # type: ignore from mobly.asserts import assert_in # type: ignore +from mobly.asserts import assert_not_equal # type: ignore from mobly.asserts import assert_not_in # type: ignore from pandora.host_pb2 import Connection as PandoraConnection from pandora.security_pb2 import LEVEL2 -from typing import Optional, Tuple, List, Dict +from typing import Dict, List, Optional, Tuple SDP_PROFILE_SUPPORTED_FEATURES_ID = 0x0311 -HFP_AG_FEATURE_HF_INDICATORS = (1 << 10) +HFP_AG_FEATURE_HF_INDICATORS = 1 << 10 HFP_AG_FEATURE_DEFAULT = HFP_AG_FEATURE_HF_INDICATORS -HFP_HF_FEATURE_HF_INDICATORS = (1 << 8) -HFP_HF_FEATURE_DEFAULT = hex(0x01b5) +HFP_HF_FEATURE_HF_INDICATORS = 1 << 8 +HFP_HF_FEATURE_DEFAULT = hex(0x01B5) PROPERTY_HF_ENABLED = 'bluetooth.profile.hfp.hf.enabled' PROPERTY_HF_FEATURES = 'bluetooth.hfp.hf_client_features.config' @@ -128,8 +128,8 @@ class HfpClientTest(base_test.BaseTestClass): # type: ignore[misc] def on_dlc(dlc: DLC) -> None: dlc_connected.set_result(dlc) - rfcomm_server = RfcommServer(self.ref.device) - channel_number = rfcomm_server.listen(on_dlc) + rfcomm_server = RfcommServer(self.ref.device) # type: ignore + channel_number = rfcomm_server.listen(on_dlc) # type: ignore # Setup SDP records self.ref.device.sdp_service_records = make_bumble_ag_sdp_records(HFP_VERSION_1_7, channel_number, 0) @@ -142,7 +142,7 @@ class HfpClientTest(base_test.BaseTestClass): # type: ignore[misc] dlc = await dlc_connected assert isinstance(dlc, DLC) - return HfpProtocol(dlc) + return HfpProtocol(dlc) # type: ignore @avatar.parameterized((True,), (False,)) # type: ignore[misc] @avatar.asynchronous @@ -155,7 +155,6 @@ class HfpClientTest(base_test.BaseTestClass): # type: ignore[misc] ref_dut_hfp_protocol = await self.make_hfp_connection() class TestAgServer(HfpAgServer): - def on_brsf(self, hf_features: int) -> None: # HF indicators should be enabled assert_not_equal(hf_features & HFP_HF_FEATURE_HF_INDICATORS, 0) @@ -172,8 +171,9 @@ class HfpClientTest(base_test.BaseTestClass): # type: ignore[misc] await server.serve() -def make_bumble_ag_sdp_records(hfp_version: int, rfcomm_channel: int, - ag_sdp_features: int) -> Dict[int, List[ServiceAttribute]]: +def make_bumble_ag_sdp_records( + hfp_version: int, rfcomm_channel: int, ag_sdp_features: int +) -> Dict[int, List[ServiceAttribute]]: return { 0x00010001: [ ServiceAttribute( @@ -182,29 +182,39 @@ def make_bumble_ag_sdp_records(hfp_version: int, rfcomm_channel: int, ), ServiceAttribute( SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID, - DataElement.sequence([ - DataElement.uuid(BT_HANDSFREE_AUDIO_GATEWAY_SERVICE), - DataElement.uuid(BT_GENERIC_AUDIO_SERVICE), - ]), + DataElement.sequence( + [ + DataElement.uuid(BT_HANDSFREE_AUDIO_GATEWAY_SERVICE), + DataElement.uuid(BT_GENERIC_AUDIO_SERVICE), + ] + ), ), ServiceAttribute( SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID, - DataElement.sequence([ - DataElement.sequence([DataElement.uuid(BT_L2CAP_PROTOCOL_ID)]), - DataElement.sequence([ - DataElement.uuid(BT_RFCOMM_PROTOCOL_ID), - DataElement.unsigned_integer_8(rfcomm_channel), - ]), - ]), + DataElement.sequence( + [ + DataElement.sequence([DataElement.uuid(BT_L2CAP_PROTOCOL_ID)]), + DataElement.sequence( + [ + DataElement.uuid(BT_RFCOMM_PROTOCOL_ID), + DataElement.unsigned_integer_8(rfcomm_channel), + ] + ), + ] + ), ), ServiceAttribute( SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID, - DataElement.sequence([ - DataElement.sequence([ - DataElement.uuid(BT_HANDSFREE_AUDIO_GATEWAY_SERVICE), - DataElement.unsigned_integer_16(hfp_version), - ]) - ]), + DataElement.sequence( + [ + DataElement.sequence( + [ + DataElement.uuid(BT_HANDSFREE_AUDIO_GATEWAY_SERVICE), + DataElement.unsigned_integer_16(hfp_version), + ] + ) + ] + ), ), ServiceAttribute( SDP_PROFILE_SUPPORTED_FEATURES_ID, @@ -224,17 +234,20 @@ class HfpAgServer: self.terminated = False self.hf_features = 0 # Unknown + def send_response_line(self, response: str) -> None: + self.protocol.send_response_line(response) # type: ignore + async def serve(self) -> None: while not self.terminated: - line = await self.protocol.next_line() + line = await self.protocol.next_line() # type: ignore if line.startswith('AT+BRSF='): - hf_features = int(line[len('AT+BRSF='):]) + hf_features = int(line[len('AT+BRSF=') :]) self.on_brsf(hf_features) elif line.startswith('AT+BIND=?'): self.on_bind_read_capabilities() elif line.startswith('AT+BIND='): - indicators = [int(i) for i in line[len('AT+BIND='):].split(',')] + indicators = [int(i) for i in line[len('AT+BIND=') :].split(',')] self.on_bind_list(indicators) elif line.startswith('AT+BIND?'): self.on_bind_read_configuration() @@ -243,50 +256,54 @@ class HfpAgServer: elif line.startswith('AT+CIND?'): self.on_cind_test() # TODO(b/286226902): Implement handlers for these commands - elif line.startswith(( + elif line.startswith( + ( 'AT+CLIP=', 'AT+VGS=', 'AT+BIA=', 'AT+CMER=', 'AT+XEVENT=', 'AT+XAPL=', - )): - self.protocol.send_response_line('OK') + ) + ): + self.send_response_line('OK') else: - self.protocol.send_response_line('ERROR') + self.send_response_line('ERROR') def on_brsf(self, hf_features: int) -> None: self.hf_features = hf_features - self.protocol.send_response_line(f'+BRSF: {self.ag_features}') - self.protocol.send_response_line('OK') + self.send_response_line(f'+BRSF: {self.ag_features}') + self.send_response_line('OK') # AT+CIND? def on_cind_read(self) -> None: - self.protocol.send_response_line('+CIND: 0,0,1,4,1,5,0') - self.protocol.send_response_line('OK') + self.send_response_line('+CIND: 0,0,1,4,1,5,0') + self.send_response_line('OK') # AT+CIND=? def on_cind_test(self) -> None: - self.protocol.send_response_line('+CIND: ("call",(0,1)),("callsetup",(0-3)),("service",(0-1)),' - '("signal",(0-5)),("roam",(0,1)),("battchg",(0-5)),' - '("callheld",(0-2))') - self.protocol.send_response_line('OK') + self.send_response_line( + '+CIND: ("call",(0,1)),("callsetup",(0-3)),("service",(0-1)),' + '("signal",(0-5)),("roam",(0,1)),("battchg",(0-5)),' + '("callheld",(0-2))' + ) + self.send_response_line('OK') # AT+BIND= def on_bind_list(self, indicators: list[int]) -> None: self.enabled_hf_indicators = indicators[:] - self.protocol.send_response_line('OK') + self.send_response_line('OK') # AT+BIND=? def on_bind_read_capabilities(self) -> None: - self.protocol.send_response_line('+BIND: ' + ','.join(map(str, self.enabled_hf_indicators))) - self.protocol.send_response_line('OK') + self.send_response_line('+BIND: ' + ','.join(map(str, self.enabled_hf_indicators))) + self.send_response_line('OK') # AT+BIND? def on_bind_read_configuration(self) -> None: for i in self.enabled_hf_indicators: - self.protocol.send_response_line(f'+BIND: {i},1') - self.protocol.send_response_line('OK') + self.send_response_line(f'+BIND: {i},1') + self.send_response_line('OK') if __name__ == '__main__': diff --git a/android/pandora/test/main.py b/android/pandora/test/main.py index b62f64d106..a544aac85a 100644 --- a/android/pandora/test/main.py +++ b/android/pandora/test/main.py @@ -20,9 +20,9 @@ import cases.le_host_test import cases.le_security_test import cases.security_test import gatt_test +import hfpclient_test import sdp_test import smp_test -import hfpclient_test _TEST_CLASSES_LIST = [ cases.host_test.HostTest, @@ -49,7 +49,7 @@ if __name__ == "__main__": # This is a hack for `tradefed` because of `b/166468397`. if '--' in sys.argv: index = sys.argv.index('--') - sys.argv = sys.argv[:1] + sys.argv[index + 1:] + sys.argv = sys.argv[:1] + sys.argv[index + 1 :] # Enable bumble snoop logger. ns, argv = _parse_cli_args() diff --git a/android/pandora/test/pyrightconfig.json b/android/pandora/test/pyrightconfig.json index fdee8e8031..af18d50edc 100644 --- a/android/pandora/test/pyrightconfig.json +++ b/android/pandora/test/pyrightconfig.json @@ -5,4 +5,13 @@ "reportMissingTypeStubs": false, "reportUnknownLambdaType": false, "reportImportCycles": false, + "extraPaths": [ + "../../../pandora/server", + "../../../../../../external/pandora/avatar", + "../../../../../../external/python/bumble", + "../../../../../../external/python/mobly", + "../../../../../../external/python/pyee", + "../../../../../../out/soong/.intermediates/external/pandora/bt-test-interfaces/python/pandora-python-gen-src/gen/", + "../../../../../../out/soong/.intermediates/packages/modules/Bluetooth/pandora/interfaces/python/pandora_experimental-python-gen-src/gen/" + ] }
\ No newline at end of file diff --git a/android/pandora/test/sdp_test.py b/android/pandora/test/sdp_test.py index be78f1384c..d4d9db309d 100644 --- a/android/pandora/test/sdp_test.py +++ b/android/pandora/test/sdp_test.py @@ -14,30 +14,25 @@ import asyncio import avatar -import itertools import logging -import sys -import os from avatar import BumblePandoraDevice, PandoraDevice, PandoraDevices from bumble.colors import color from bumble.core import BT_AUDIO_SOURCE_SERVICE from bumble.sdp import ( - Client as SDP_Client, - SDP_PUBLIC_BROWSE_ROOT, SDP_ALL_ATTRIBUTES_RANGE, + SDP_PUBLIC_BROWSE_ROOT, SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID, - DataElement, + Client as SDP_Client, ServiceAttribute, ) -from mobly import base_test, signals, test_runner +from mobly import base_test, test_runner from mobly.asserts import assert_equal # type: ignore from mobly.asserts import assert_in # type: ignore -from mobly.asserts import assert_is_not_none # type: ignore from mobly.asserts import assert_is_none # type: ignore +from mobly.asserts import assert_is_not_none # type: ignore from mobly.asserts import fail # type: ignore -from pandora.host_pb2 import Connection -from typing import Any, Literal, Optional, Tuple, Union +from typing import Optional class SdpTest(base_test.BaseTestClass): # type: ignore[misc] @@ -136,8 +131,9 @@ class SdpTest(base_test.BaseTestClass): # type: ignore[misc] # For each service in the root browse group, get all its attributes for service_record_handle in service_record_handles: - attributes = await sdp_client.get_attributes(service_record_handle, - [SDP_ALL_ATTRIBUTES_RANGE]) # type: ignore + attributes = await sdp_client.get_attributes( + service_record_handle, [SDP_ALL_ATTRIBUTES_RANGE] + ) # type: ignore print(color(f'SERVICE {service_record_handle:04X} attributes:', 'yellow')) for attribute in attributes: print(' ', attribute.to_string(with_colors=True)) @@ -179,11 +175,12 @@ class SdpTest(base_test.BaseTestClass): # type: ignore[misc] # Verify Audio Source service is present service_found = False for service_record_handle in service_record_handles: - attributes = await sdp_client.get_attributes(service_record_handle, - [SDP_ALL_ATTRIBUTES_RANGE]) # type: ignore + attributes = await sdp_client.get_attributes( + service_record_handle, [SDP_ALL_ATTRIBUTES_RANGE] + ) # type: ignore for attribute in attributes: - if (attribute.id == SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID): - if (ServiceAttribute.is_uuid_in_value(BT_AUDIO_SOURCE_SERVICE, attribute.value)): # type: ignore + if attribute.id == SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID: + if ServiceAttribute.is_uuid_in_value(BT_AUDIO_SOURCE_SERVICE, attribute.value): # type: ignore service_found = True self.ref.log.info(f'Service found') assert service_found diff --git a/floss/hcidoc/src/groups/connections.rs b/floss/hcidoc/src/groups/connections.rs index d1216be279..7d600b3191 100644 --- a/floss/hcidoc/src/groups/connections.rs +++ b/floss/hcidoc/src/groups/connections.rs @@ -292,29 +292,24 @@ impl OddDisconnectionsRule { EventChild::DisconnectionComplete(dsc) => { let handle = dsc.get_connection_handle(); - match self.active_handles.remove(&handle) { - Some(_) => { - // Check if this is a NOCP type disconnection and flag it. - match self.nocp_by_handle.get_mut(&handle) { - Some(nocp_data) => { - if let Some(acl_front_ts) = nocp_data.inflight_acl_ts.pop_front() { - self.signals.push(Signal { - index: packet.index, - ts: packet.ts.clone(), - tag: ConnectionSignal::NocpDisconnect.into(), - }); - - self.reportable.push(( - packet.ts, - format!("DisconnectionComplete for handle({}) showed incomplete in-flight ACL at {}", - handle, acl_front_ts))); - } - } - None => (), + self.active_handles.remove(&handle); + + // Check if this is a NOCP type disconnection and flag it. + match self.nocp_by_handle.get_mut(&handle) { + Some(nocp_data) => { + if let Some(acl_front_ts) = nocp_data.inflight_acl_ts.pop_front() { + self.signals.push(Signal { + index: packet.index, + ts: packet.ts.clone(), + tag: ConnectionSignal::NocpDisconnect.into(), + }); + + self.reportable.push(( + packet.ts, + format!("DisconnectionComplete for handle({}) showed incomplete in-flight ACL at {}", + handle, acl_front_ts))); } } - - // No issue if none, probably device is connected before snoop started. None => (), } diff --git a/floss/hcidoc/src/groups/informational.rs b/floss/hcidoc/src/groups/informational.rs index 803c3c38bc..f3f38afb2d 100644 --- a/floss/hcidoc/src/groups/informational.rs +++ b/floss/hcidoc/src/groups/informational.rs @@ -1,8 +1,10 @@ ///! Rule group for general information. use chrono::NaiveDateTime; +use std::cmp::Ordering; use std::collections::{HashMap, HashSet}; use std::convert::Into; use std::fmt; +use std::hash::Hash; use std::io::Write; use crate::engine::{Rule, RuleGroup, Signal}; @@ -17,7 +19,21 @@ type ConnectionHandle = u16; const INVALID_TS: NaiveDateTime = NaiveDateTime::MAX; -#[derive(Copy, Clone)] +fn print_start_end_timestamps(start: NaiveDateTime, end: NaiveDateTime) -> String { + fn print_time(ts: NaiveDateTime) -> String { + if ts == INVALID_TS { + return "N/A".to_owned(); + } + return format!("{}", ts.time()); + } + + if start == end && start != INVALID_TS { + return format!("{} - Failed", start.time()); + } + return format!("{} to {}", print_time(start), print_time(end)); +} + +#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)] enum AddressType { None, BREDR, @@ -108,24 +124,51 @@ impl DeviceInformation { acl_state: AclState::None, } } + + fn is_connection_active(&self) -> bool { + // not empty and last connection's end time is not set. + return !self.acls.is_empty() && self.acls.last().unwrap().end_time == INVALID_TS; + } + + fn get_or_allocate_connection(&mut self, handle: &ConnectionHandle) -> &mut AclInformation { + if !self.is_connection_active() { + let acl = AclInformation::new(*handle); + self.acls.push(acl); + } + return self.acls.last_mut().unwrap(); + } + + fn report_connection_start(&mut self, handle: ConnectionHandle, ts: NaiveDateTime) { + let mut acl = AclInformation::new(handle); + let initiator = self.acl_state.into(); + acl.report_start(initiator, ts); + self.acls.push(acl); + self.acl_state = AclState::Connected; + } + + fn report_connection_end(&mut self, handle: ConnectionHandle, ts: NaiveDateTime) { + let acl = self.get_or_allocate_connection(&handle); + acl.report_end(ts); + self.acl_state = AclState::None; + } + + fn print_names(names: &HashSet<String>) -> String { + if names.len() > 1 { + format!("{:?}", names) + } else { + names.iter().next().unwrap_or(&String::from("<Unknown name>")).to_owned() + } + } } impl fmt::Display for DeviceInformation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fn print_names(names: &HashSet<String>) -> String { - if names.len() > 1 { - format!("{:?}", names) - } else { - names.iter().next().unwrap_or(&String::from("<Unknown name>")).to_owned() - } - } - let _ = writeln!( f, "{address} ({address_type}, {device_names}), {num_connections} connections", address = self.address, address_type = self.address_type, - device_names = print_names(&self.names), + device_names = DeviceInformation::print_names(&self.names), num_connections = self.acls.len() ); for acl in &self.acls { @@ -142,29 +185,138 @@ struct AclInformation { end_time: NaiveDateTime, handle: ConnectionHandle, initiator: InitiatorType, + profiles: HashMap<ProfileType, Vec<ProfileInformation>>, +} + +impl AclInformation { + pub fn new(handle: ConnectionHandle) -> Self { + AclInformation { + start_time: INVALID_TS, + end_time: INVALID_TS, + handle: handle, + initiator: InitiatorType::Unknown, + profiles: HashMap::new(), + } + } + + fn get_or_allocate_profile(&mut self, profile_type: &ProfileType) -> &mut ProfileInformation { + if !self.profiles.contains_key(profile_type) + || self.profiles.get(profile_type).unwrap().last().unwrap().end_time != INVALID_TS + { + self.profiles.insert(*profile_type, vec![ProfileInformation::new(*profile_type)]); + } + + return self.profiles.get_mut(profile_type).unwrap().last_mut().unwrap(); + } + + fn report_start(&mut self, initiator: InitiatorType, ts: NaiveDateTime) { + self.initiator = initiator; + self.start_time = ts; + } + + fn report_end(&mut self, ts: NaiveDateTime) { + // disconnect the active profiles + let profile_types: Vec<ProfileType> = self.profiles.keys().cloned().collect(); + for profile_type in profile_types { + if let Some(profile) = self.profiles.get(&profile_type).unwrap().last() { + if profile.end_time != INVALID_TS { + self.report_profile_end(profile_type, ts); + } + } + } + self.end_time = ts; + } + + fn report_profile_start( + &mut self, + profile_type: ProfileType, + initiator: InitiatorType, + ts: NaiveDateTime, + ) { + let mut profile = ProfileInformation::new(profile_type); + profile.report_start(initiator, ts); + if !self.profiles.contains_key(&profile_type) { + self.profiles.insert(profile_type, vec![]); + } + self.profiles.get_mut(&profile_type).unwrap().push(profile); + } + + fn report_profile_end(&mut self, profile_type: ProfileType, ts: NaiveDateTime) { + let profile = self.get_or_allocate_profile(&profile_type); + profile.report_end(ts); + } } impl fmt::Display for AclInformation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fn print_time(ts: NaiveDateTime) -> String { - if ts == INVALID_TS { - return "N/A".to_owned(); + let _ = writeln!( + f, + " Handle: {handle}, {initiator}, {timestamp_info}", + handle = self.handle, + initiator = self.initiator, + timestamp_info = print_start_end_timestamps(self.start_time, self.end_time) + ); + + for (_profile_type, profiles) in self.profiles.iter() { + for profile in profiles { + let _ = write!(f, "{}", profile); } - return format!("{}", ts.time()); } - fn print_timestamps(start: NaiveDateTime, end: NaiveDateTime) -> String { - if start == end { - return format!("{} - Failed", start.time()); - } - return format!("{} to {}", print_time(start), print_time(end)); + + Ok(()) + } +} + +// Currently only HFP is possible to be detected. Other profiles needs us to parse L2CAP packets. +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +enum ProfileType { + HFP, +} + +impl fmt::Display for ProfileType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let str = match self { + ProfileType::HFP => "HFP", + }; + write!(f, "{}", str) + } +} + +struct ProfileInformation { + start_time: NaiveDateTime, + end_time: NaiveDateTime, + profile_type: ProfileType, + initiator: InitiatorType, +} + +impl ProfileInformation { + pub fn new(profile_type: ProfileType) -> Self { + ProfileInformation { + start_time: INVALID_TS, + end_time: INVALID_TS, + profile_type: profile_type, + initiator: InitiatorType::Unknown, } + } + + fn report_start(&mut self, initiator: InitiatorType, ts: NaiveDateTime) { + self.initiator = initiator; + self.start_time = ts; + } + + fn report_end(&mut self, ts: NaiveDateTime) { + self.end_time = ts; + } +} +impl fmt::Display for ProfileInformation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!( f, - "> Handle: {handle}, {initiator}, {timestamp_info}", - handle = self.handle, + " {profile}, {initiator}, {timestamp_info}", + profile = self.profile_type, initiator = self.initiator, - timestamp_info = print_timestamps(self.start_time, self.end_time) + timestamp_info = print_start_end_timestamps(self.start_time, self.end_time) ) } } @@ -173,11 +325,19 @@ impl fmt::Display for AclInformation { struct InformationalRule { devices: HashMap<Address, DeviceInformation>, handles: HashMap<ConnectionHandle, Address>, + sco_handles: HashMap<ConnectionHandle, ConnectionHandle>, + // unknownConnections store connections which is initiated before btsnoop starts. + unknown_connections: HashMap<ConnectionHandle, AclInformation>, } impl InformationalRule { pub fn new() -> Self { - InformationalRule { devices: HashMap::new(), handles: HashMap::new() } + InformationalRule { + devices: HashMap::new(), + handles: HashMap::new(), + sco_handles: HashMap::new(), + unknown_connections: HashMap::new(), + } } fn get_or_allocate_device(&mut self, address: &Address) -> &mut DeviceInformation { @@ -187,19 +347,40 @@ impl InformationalRule { return self.devices.get_mut(address).unwrap(); } + fn get_or_allocate_unknown_connection( + &mut self, + handle: &ConnectionHandle, + ) -> &mut AclInformation { + if !self.unknown_connections.contains_key(handle) { + self.unknown_connections.insert(*handle, AclInformation::new(*handle)); + } + return self.unknown_connections.get_mut(handle).unwrap(); + } + + fn get_or_allocate_connection(&mut self, handle: &ConnectionHandle) -> &mut AclInformation { + if !self.handles.contains_key(&handle) { + let conn = self.get_or_allocate_unknown_connection(&handle); + return conn; + } + + let address = &self.handles.get(handle).unwrap().clone(); + let device = self.get_or_allocate_device(address); + return device.get_or_allocate_connection(handle); + } + fn report_address_type(&mut self, address: &Address, address_type: AddressType) { - let info = self.get_or_allocate_device(address); - info.address_type.update(address_type); + let device = self.get_or_allocate_device(address); + device.address_type.update(address_type); } fn report_name(&mut self, address: &Address, name: &String) { - let info = self.get_or_allocate_device(address); - info.names.insert(name.into()); + let device = self.get_or_allocate_device(address); + device.names.insert(name.into()); } fn report_acl_state(&mut self, address: &Address, state: AclState) { - let info = self.get_or_allocate_device(address); - info.acl_state = state; + let device = self.get_or_allocate_device(address); + device.acl_state = state; } fn report_connection_start( @@ -208,37 +389,62 @@ impl InformationalRule { handle: ConnectionHandle, ts: NaiveDateTime, ) { - let info = self.get_or_allocate_device(address); - info.acls.push(AclInformation { - start_time: ts, - end_time: INVALID_TS, - handle: handle, - initiator: info.acl_state.into(), - }); - info.acl_state = AclState::Connected; + let device = self.get_or_allocate_device(address); + device.report_connection_start(handle, ts); self.handles.insert(handle, *address); } + fn report_sco_connection_start( + &mut self, + address: &Address, + handle: ConnectionHandle, + ts: NaiveDateTime, + ) { + if !self.devices.contains_key(address) { + // To simplify things, let's not process unknown devices + return; + } + + let device = self.devices.get_mut(address).unwrap(); + if !device.is_connection_active() { + // SCO is connected, but ACL is not. This is weird, but let's ignore for simplicity. + eprintln!("[{}] SCO is connected, but ACL is not.", address); + return; + } + + // Whatever handle value works here - we aren't allocating a new one. + let acl = device.get_or_allocate_connection(&0); + let acl_handle = acl.handle; + // We need to listen the HCI commands to determine the correct initiator. + // Here we just assume host for simplicity. + acl.report_profile_start(ProfileType::HFP, InitiatorType::Host, ts); + + self.sco_handles.insert(handle, acl_handle); + } + fn report_connection_end(&mut self, handle: ConnectionHandle, ts: NaiveDateTime) { - if !self.handles.contains_key(&handle) { - // For simplicity we can't process unknown handle. This probably can be improved. + // This might be a SCO disconnection event, so check that first + if self.sco_handles.contains_key(&handle) { + let acl_handle = self.sco_handles[&handle]; + let conn = self.get_or_allocate_connection(&acl_handle); + conn.report_profile_end(ProfileType::HFP, ts); return; } - let info = self.get_or_allocate_device(&self.handles.get(&handle).unwrap().clone()); - - // If we can't find the matching acl connection, create one. - if info.acls.is_empty() || info.acls.last().unwrap().end_time != INVALID_TS { - info.acls.push(AclInformation { - start_time: INVALID_TS, - end_time: ts, - handle: handle, - initiator: InitiatorType::Unknown, - }); + + // Not recognized as SCO, assume it's an ACL handle. + if let Some(address) = self.handles.get(&handle) { + // This device is known + let device = self.devices.get_mut(address).unwrap(); + device.report_connection_end(handle, ts); + self.handles.remove(&handle); + + // remove the associated SCO handle, if any + self.sco_handles.retain(|_sco_handle, acl_handle| *acl_handle != handle); } else { - info.acls.last_mut().unwrap().end_time = ts; + // Unknown device. + let conn = self.get_or_allocate_unknown_connection(&handle); + conn.report_end(ts); } - info.acl_state = AclState::None; - self.handles.remove(&handle); } fn report_reset(&mut self, ts: NaiveDateTime) { @@ -247,6 +453,28 @@ impl InformationalRule { for handle in handles { self.report_connection_end(handle, ts); } + self.sco_handles.clear(); + } + + fn _report_profile_start( + &mut self, + handle: ConnectionHandle, + profile_type: ProfileType, + initiator: InitiatorType, + ts: NaiveDateTime, + ) { + let conn = self.get_or_allocate_connection(&handle); + conn.report_profile_start(profile_type, initiator, ts); + } + + fn _report_profile_end( + &mut self, + handle: ConnectionHandle, + profile_type: ProfileType, + ts: NaiveDateTime, + ) { + let conn = self.get_or_allocate_connection(&handle); + conn.report_profile_end(profile_type, ts); } fn process_gap_data(&mut self, address: &Address, data: &GapData) { @@ -296,6 +524,18 @@ impl Rule for InformationalRule { } } + EventChild::SynchronousConnectionComplete(ev) => { + self.report_sco_connection_start( + &ev.get_bd_addr(), + ev.get_connection_handle(), + packet.ts, + ); + // If failed, assume it's the end of connection. + if ev.get_status() != ErrorCode::Success { + self.report_connection_end(ev.get_connection_handle(), packet.ts); + } + } + EventChild::DisconnectionComplete(ev) => { self.report_connection_end(ev.get_connection_handle(), packet.ts); } @@ -416,13 +656,68 @@ impl Rule for InformationalRule { } fn report(&self, writer: &mut dyn Write) { - if self.devices.is_empty() { + /* Sort when displaying the addresses, from the most to the least important: + * (1) Device with connections > Device without connections + * (2) Device with known name > Device with unknown name + * (3) BREDR > LE > Dual + * (4) Name, lexicographically (case sensitive) + * (5) Address, alphabetically + */ + fn sort_addresses(a: &DeviceInformation, b: &DeviceInformation) -> Ordering { + let connection_order = a.acls.is_empty().cmp(&b.acls.is_empty()); + if connection_order != Ordering::Equal { + return connection_order; + } + + let known_name_order = a.names.is_empty().cmp(&b.names.is_empty()); + if known_name_order != Ordering::Equal { + return known_name_order; + } + + let address_type_order = a.address_type.cmp(&b.address_type); + if address_type_order != Ordering::Equal { + return address_type_order; + } + + let a_name = format!("{}", DeviceInformation::print_names(&a.names)); + let b_name = format!("{}", DeviceInformation::print_names(&b.names)); + let name_order = a_name.cmp(&b_name); + if name_order != Ordering::Equal { + return name_order; + } + + let a_address = <[u8; 6]>::from(a.address); + let b_address = <[u8; 6]>::from(b.address); + for i in (0..6).rev() { + let address_order = a_address[i].cmp(&b_address[i]); + if address_order != Ordering::Equal { + return address_order; + } + } + // This shouldn't be executed + return Ordering::Equal; + } + + if self.devices.is_empty() && self.unknown_connections.is_empty() { return; } + let mut addresses: Vec<Address> = self.devices.keys().cloned().collect(); + addresses.sort_unstable_by(|a, b| sort_addresses(&self.devices[a], &self.devices[b])); + let _ = writeln!(writer, "InformationalRule report:"); - for (_, info) in &self.devices { - let _ = write!(writer, "{}", info); + if !self.unknown_connections.is_empty() { + let _ = writeln!( + writer, + "Connections initiated before snoop start, {} connections", + self.unknown_connections.len() + ); + for (_, acl) in &self.unknown_connections { + let _ = write!(writer, "{}", acl); + } + } + for address in addresses { + let _ = write!(writer, "{}", self.devices[&address]); } } diff --git a/floss/pandora/floss/advertising_client.py b/floss/pandora/floss/advertising_client.py index df02a91951..c9d8090501 100644 --- a/floss/pandora/floss/advertising_client.py +++ b/floss/pandora/floss/advertising_client.py @@ -501,6 +501,30 @@ class FlossAdvertisingClient(BluetoothAdvertisingCallbacks): self.proxy().UnregisterAdvertiserCallback(self.callback_id) return True + def register_callback_observer(self, name, observer): + """Add an observer for all callbacks. + + Args: + name: + Name of the observer. + observer: + Observer that implements all callback classes. + """ + if isinstance(observer, BluetoothAdvertisingCallbacks): + self.callbacks.add_observer(name, observer) + + def unregister_callback_observer(self, name, observer): + """Remove an observer for all callbacks. + + Args: + name: + Name of the observer. + observer: + Observer that implements all callback classes. + """ + if isinstance(observer, BluetoothAdvertisingCallbacks): + self.callbacks.remove_observer(name, observer) + @utils.glib_call(None) def start_advertising_set(self, parameters, advertise_data, scan_response, periodic_parameters, periodic_data, duration, max_ext_adv_events): diff --git a/floss/pandora/floss/floss_enums.py b/floss/pandora/floss/floss_enums.py index afb3411ffe..86169b1d2f 100644 --- a/floss/pandora/floss/floss_enums.py +++ b/floss/pandora/floss/floss_enums.py @@ -154,4 +154,26 @@ class SspVariant(enum.IntEnum): PASSKEY_CONFIRMATION = 0 PASSKEY_ENTRY = 1 CONSENT = 2 - PASSKEY_NOTIFICATION = 3
\ No newline at end of file + PASSKEY_NOTIFICATION = 3 + + +class BleAddressType(enum.IntEnum): + BLE_ADDR_PUBLIC = 0x00 + BLE_ADDR_RANDOM = 0x01 + BLE_ADDR_PUBLIC_ID = 0x02 + BLE_ADDR_RANDOM_ID = 0x03 + BLE_ADDR_ANONYMOUS = 0xFF + + +class OwnAddressType(enum.IntEnum): + DEFAULT = -1 + PUBLIC = 0 + RANDOM = 1 + + +class CompanyIdentifiers(enum.IntEnum): + """Bluetooth SIG Company ID values. + + Bluetooth SIG official document: https://www.bluetooth.com/specifications/assigned-numbers/ + """ + GOOGLE = 0x00E0 diff --git a/floss/pandora/floss/scanner_client.py b/floss/pandora/floss/scanner_client.py index 6331c5de8e..293526fa11 100644 --- a/floss/pandora/floss/scanner_client.py +++ b/floss/pandora/floss/scanner_client.py @@ -432,6 +432,30 @@ class FlossScannerClient(BluetoothScannerCallbacks): self.callback_id = self.proxy().RegisterScannerCallback(objpath) return True + def register_callback_observer(self, name, observer): + """Add an observer for all callbacks. + + Args: + name: + Name of the observer. + observer: + Observer that implements all callback classes. + """ + if isinstance(observer, BluetoothScannerCallbacks): + self.callbacks.add_observer(name, observer) + + def unregister_callback_observer(self, name, observer): + """Remove an observer for all callbacks. + + Args: + name: + Name of the observer. + observer: + Observer that implements all callback classes. + """ + if isinstance(observer, BluetoothScannerCallbacks): + self.callbacks.remove_observer(name, observer) + def wait_for_on_scanner_registered(self, uuid): """Waits for register scanner. diff --git a/floss/pandora/floss/utils.py b/floss/pandora/floss/utils.py index 0fbab48c79..47b4d5fef3 100644 --- a/floss/pandora/floss/utils.py +++ b/floss/pandora/floss/utils.py @@ -18,7 +18,9 @@ import logging import threading import time +from floss.pandora.floss import floss_enums from gi.repository import GLib +from pandora import host_pb2 # All GLIB method calls should wait this many seconds by default GLIB_METHOD_CALL_TIMEOUT = 2 @@ -337,3 +339,116 @@ class PropertySet: raise self.PropertySetterMissing('{} has no getter.'.format(prop_name)) return setter(*args) + + +def address_from(request_address: bytes): + """Converts address from grpc server format to floss format.""" + address = request_address.hex() + address = f'{address[:2]}:{address[2:4]}:{address[4:6]}:{address[6:8]}:{address[8:10]}:{address[10:12]}' + return address.upper() + + +def address_to(address: str): + """Converts address from floss format to grpc server format.""" + request_address = bytes.fromhex(address.replace(':', '')) + return request_address + + +def uuid16_to_uuid128(uuid16: str): + return f'0000{uuid16}-0000-1000-8000-00805f9b34fb' + + +def uuid32_to_uuid128(uuid32: str): + return f'{uuid32}-0000-1000-8000-00805f9b34fb' + + +def advertise_data_from(request_data: host_pb2.DataTypes): + """Mapping DataTypes to a dict. + + The dict content follows the format of floss AdvertiseData. + + Args: + request_data : advertising data. + + Raises: + NotImplementedError: if request data is not implemented. + + Returns: + dict: advertising data. + """ + advertise_data = { + 'service_uuids': [], + 'solicit_uuids': [], + 'transport_discovery_data': [], + 'manufacturer_data': {}, + 'service_data': {}, + 'include_tx_power_level': False, + 'include_device_name': False, + } + + # incomplete_service_class_uuids + if (request_data.incomplete_service_class_uuids16 or request_data.incomplete_service_class_uuids32 or + request_data.incomplete_service_class_uuids128): + raise NotImplementedError('Incomplete service class uuid not supported') + + # service_uuids + for uuid16 in request_data.complete_service_class_uuids16: + advertise_data['service_uuids'].append(uuid16_to_uuid128(uuid16)) + + for uuid32 in request_data.complete_service_class_uuids32: + advertise_data['service_uuids'].append(uuid32_to_uuid128(uuid32)) + + for uuid128 in request_data.complete_service_class_uuids128: + advertise_data['service_uuids'].append(uuid128) + + # solicit_uuids + for uuid16 in request_data.service_solicitation_uuids16: + advertise_data['solicit_uuids'].append(uuid16_to_uuid128(uuid16)) + + for uuid32 in request_data.service_solicitation_uuids32: + advertise_data['solicit_uuids'].append(uuid32_to_uuid128(uuid32)) + + for uuid128 in request_data.service_solicitation_uuids128: + advertise_data['solicit_uuids'].append(uuid128) + + # service_data + for (uuid16, data) in request_data.service_data_uuid16: + advertise_data['service_data'][uuid16_to_uuid128(uuid16)] = data + + for (uuid32, data) in request_data.service_data_uuid32: + advertise_data['service_data'][uuid32_to_uuid128(uuid32)] = data + + for (uuid128, data) in request_data.service_data_uuid128: + advertise_data['service_data'][uuid128] = data + + advertise_data['manufacturer_data'][hex( + floss_enums.CompanyIdentifiers.GOOGLE)] = request_data.manufacturer_specific_data + + # The name is derived from adapter directly in floss. + if request_data.WhichOneof('shortened_local_name_oneof') in ('include_shortened_local_name', + 'include_complete_local_name'): + advertise_data['include_device_name'] = getattr(request_data, + request_data.WhichOneof('shortened_local_name_oneof')).value + + # The tx power level is decided by the lower layers. + if request_data.WhichOneof('tx_power_level_oneof') == 'include_tx_power_level': + advertise_data['include_tx_power_level'] = request_data.include_tx_power_level + return advertise_data + + +def create_observer_name(observer): + """Generates an unique name for an observer. + + Args: + observer: an observer class to observer the bluetooth callbacks. + + Returns: + str: an unique name. + """ + return observer.__class__.__name__ + str(id(observer)) + + +# anext build-in is new in python3.10. Deprecate this function +# when we are able to use it. +async def anext(ait): + return await ait.__anext__() diff --git a/floss/pandora/server/bluetooth.py b/floss/pandora/server/bluetooth.py new file mode 100644 index 0000000000..1e1301a816 --- /dev/null +++ b/floss/pandora/server/bluetooth.py @@ -0,0 +1,249 @@ +# Copyright 2023 Google LLC +# +# 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 +# +# https://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. +"""All functions relative to the bluetooth procedure.""" + +import asyncio +import logging +import threading +import traceback + +from floss.pandora.floss import adapter_client +from floss.pandora.floss import advertising_client +from floss.pandora.floss import manager_client +from floss.pandora.floss import scanner_client +from floss.pandora.floss import utils +from gi.repository import GLib +import pydbus + + +class Bluetooth(object): + """A bluetooth facade exposes all bluetooth functions.""" + + # Default to this adapter during init. We will initialize to the correct + # default adapter after the manager client is initialized. + DEFAULT_ADAPTER = 0 + + # Time to sleep between polls + ADAPTER_CLIENT_POLL_INTERVAL = 0.1 + + # How long we wait for the adapter to come up after we start it. + ADAPTER_DAEMON_TIMEOUT_SEC = 20 + + # How long we wait for the manager daemon to come up after we start it. + DAEMON_TIMEOUT_SEC = 5 + + # Default scanner settings + SCANNER_INTERVAL = 0 + SCANNER_WINDOW = 0 + SCANNER_SCAN_TYPE = 0 + + def __init__(self): + self.setup_mainloop() + + # self state + self.is_clean = False + + # GRPC server state + self.pairing_events: asyncio.Queue = None + self.pairing_answers = None + + # DBUS clients + self.manager_client = manager_client.FlossManagerClient(self.bus) + self.adapter_client = adapter_client.FlossAdapterClient(self.bus, self.DEFAULT_ADAPTER) + self.advertising_client = advertising_client.FlossAdvertisingClient(self.bus, self.DEFAULT_ADAPTER) + self.scanner_client = scanner_client.FlossScannerClient(self.bus, self.DEFAULT_ADAPTER) + + def __del__(self): + if not self.is_clean: + self.cleanup() + + def cleanup(self): + self.mainloop_quit.set() + self.mainloop.quit() + self.is_clean = True + + def setup_mainloop(self): + """Start mainloop thread in background. + + This will also initialize a few + other variables (self.bus, self.mainloop, self.event_context) that may + be necessary for proper operation. + + Raises: + RuntimeError: if we timeout to wait for the mainloop ready. + """ + + self.mainloop_quit = threading.Event() + self.mainloop_ready = threading.Event() + self.thread = threading.Thread(name=utils.GLIB_THREAD_NAME, target=Bluetooth.mainloop_thread, args=(self,)) + self.thread.start() + + # Wait for mainloop to be ready + if not self.mainloop_ready.wait(timeout=5): + raise RuntimeError('Unable to initialize GLib mainloop') + + def mainloop_thread(self): + # Set up mainloop. All subsequent buses and connections will use this + # mainloop. We also use a separate main context to avoid multithreading + # issues. + GLib.threads_init() + self.mainloop = GLib.MainLoop() + + # Set up bus connection + self.bus = pydbus.SystemBus() + + # Set thread ready + self.mainloop_ready.set() + + while not self.mainloop_quit.is_set(): + self.mainloop.run() + + def set_powered(self, powered: bool): + """Set the power of bluetooth adapter and bluetooth clients. + + Args: + powered: Power on or power off. + + Returns: + bool: True if success, False otherwise. + """ + default_adapter = self.manager_client.get_default_adapter() + + def _is_adapter_down(client): + return lambda: not client.has_proxy() + + def _is_adapter_ready(client): + return lambda: client.has_proxy() and client.get_address() + + if powered: + # FIXME: Close rootcanal will cause manager_client failed call has_default_adapter. + # if not self.manager_client.has_default_adapter(): + # logging.warning('set_powered: Default adapter not available.') + # return False + self.manager_client.start(default_adapter) + + self.adapter_client = adapter_client.FlossAdapterClient(self.bus, default_adapter) + self.advertising_client = advertising_client.FlossAdvertisingClient(self.bus, default_adapter) + self.scanner_client = scanner_client.FlossScannerClient(self.bus, default_adapter) + + try: + utils.poll_for_condition(condition=_is_adapter_ready(self.adapter_client), + desc='Wait for adapter start', + sleep_interval=self.ADAPTER_CLIENT_POLL_INTERVAL, + timeout=self.ADAPTER_DAEMON_TIMEOUT_SEC) + except TimeoutError as e: + logging.error('timeout: error starting adapter daemon: %s', e) + logging.error(traceback.format_exc()) + return False + + # We need to observe callbacks for proper operation. + if not self.adapter_client.register_callbacks(): + logging.error('adapter_client: Failed to register callbacks') + return False + if not self.advertising_client.register_advertiser_callback(): + logging.error('advertising_client: Failed to register advertiser callbacks') + return False + if not self.scanner_client.register_scanner_callback(): + logging.error('scanner_client: Failed to register callbacks') + return False + else: + self.manager_client.stop(default_adapter) + try: + utils.poll_for_condition(condition=_is_adapter_down(self.adapter_client), + desc='Wait for adapter stop', + sleep_interval=self.ADAPTER_CLIENT_POLL_INTERVAL, + timeout=self.ADAPTER_DAEMON_TIMEOUT_SEC) + except TimeoutError as e: + logging.error('timeout: error stopping adapter daemon: %s', e) + logging.error(traceback.format_exc()) + return False + return True + + def reset(self): + if not self.set_powered(False): + return False + + if not self.set_powered(True): + return False + return True + + def get_address(self): + return self.adapter_client.get_address() + + def is_connected(self, address): + return self.adapter_client.is_connected(address) + + def is_bonded(self, address): + return self.adapter_client.is_bonded(address) + + def is_discovering(self): + return self.adapter_client.is_discovering() + + def set_discoverable(self, mode, duration=60): + return self.adapter_client.set_property('Discoverable', mode, duration) + + def create_bond(self, address, transport): + return self.adapter_client.create_bond(address, transport) + + def set_pairing_confirmation(self, address, accept): + return self.adapter_client.set_pairing_confirmation(address, accept) + + def connect_device(self, address): + return self.adapter_client.connect_all_enabled_profiles(address) + + def disconnect_device(self, address): + return self.adapter_client.disconnect_all_enabled_profiles(address) + + def start_discovery(self): + if self.adapter_client.is_discovering(): + logging.warning('Adapter is already discovering.') + return True + return self.adapter_client.start_discovery() + + def stop_discovery(self): + if not self.adapter_client.is_discovering(): + logging.warning('Discovery is already stopped.') + return True + return self.adapter_client.stop_discovery() + + def start_advertising_set(self, parameters, advertise_data, scan_response, periodic_parameters, periodic_data, + duration, max_ext_adv_events): + parameters = self.advertising_client.make_dbus_advertising_set_parameters(parameters) + advertise_data = self.advertising_client.make_dbus_advertise_data(advertise_data) + scan_response = utils.make_kv_optional_value(self.advertising_client.make_dbus_advertise_data(scan_response)) + periodic_parameters = utils.make_kv_optional_value( + self.advertising_client.make_dbus_periodic_advertising_parameters(periodic_parameters)) + periodic_data = utils.make_kv_optional_value(self.advertising_client.make_dbus_advertise_data(periodic_data)) + + return self.advertising_client.start_advertising_set(parameters, advertise_data, scan_response, + periodic_parameters, periodic_data, duration, + max_ext_adv_events) + + def stop_advertising_set(self, advertiser_id): + return self.advertising_client.stop_advertising_set(advertiser_id) + + def register_scanner(self): + return self.scanner_client.register_scanner() + + def start_scan(self, scanner_id, settings=None, scan_filter=None): + if settings is None: + settings = self.scanner_client.make_dbus_scan_settings(self.SCANNER_INTERVAL, self.SCANNER_WINDOW, + self.SCANNER_SCAN_TYPE) + return self.scanner_client.start_scan(scanner_id, settings, scan_filter) + + def stop_scan(self, scanner_id): + if not self.scanner_client.remove_monitor(scanner_id): + logging.error('Failed to stop scanning.') + return False + return True diff --git a/floss/pandora/server/host.py b/floss/pandora/server/host.py index 624500ed0a..15955fdca6 100644 --- a/floss/pandora/server/host.py +++ b/floss/pandora/server/host.py @@ -1,20 +1,30 @@ # Copyright 2023 Google LLC # -# Licensed under the Apache License, Version 2.0 (the "License"); +# 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 # # https://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, +# 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. """Host grpc interface.""" +import asyncio +import logging from typing import AsyncGenerator +import uuid as uuid_module +from floss.pandora.floss import adapter_client +from floss.pandora.floss import advertising_client +from floss.pandora.floss import floss_enums +from floss.pandora.floss import scanner_client +from floss.pandora.floss import utils +from floss.pandora.server import bluetooth as bluetooth_module +from google.protobuf import any_pb2 from google.protobuf import empty_pb2 import grpc from pandora import host_grpc_aio @@ -31,86 +41,457 @@ class HostService(host_grpc_aio.HostServicer): /pandora/bt-test-interfaces/pandora/host.proto """ + def __init__(self, server: grpc.aio.Server, bluetooth: bluetooth_module.Bluetooth): + self.server = server + self.bluetooth = bluetooth + self.waited_connections = set() + async def FactoryReset(self, request: empty_pb2.Empty, context: grpc.ServicerContext) -> empty_pb2.Empty: - context.set_code(grpc.StatusCode.UNIMPLEMENTED) # type: ignore - context.set_details("Method not implemented!") # type: ignore - raise NotImplementedError("Method not implemented!") + self.waited_connections = set() + asyncio.create_task(self.server.stop(None)) + return empty_pb2.Empty() async def Reset(self, request: empty_pb2.Empty, context: grpc.ServicerContext) -> empty_pb2.Empty: - context.set_code(grpc.StatusCode.UNIMPLEMENTED) # type: ignore - context.set_details("Method not implemented!") # type: ignore - raise NotImplementedError("Method not implemented!") + self.waited_connections = set() + self.bluetooth.reset() + return empty_pb2.Empty() async def ReadLocalAddress(self, request: empty_pb2.Empty, context: grpc.ServicerContext) -> host_pb2.ReadLocalAddressResponse: - context.set_code(grpc.StatusCode.UNIMPLEMENTED) # type: ignore - context.set_details("Method not implemented!") # type: ignore - raise NotImplementedError("Method not implemented!") + address = self.bluetooth.get_address() + return host_pb2.ReadLocalAddressResponse(address=utils.address_to(address)) async def Connect(self, request: host_pb2.ConnectRequest, context: grpc.ServicerContext) -> host_pb2.ConnectResponse: - context.set_code(grpc.StatusCode.UNIMPLEMENTED) # type: ignore - context.set_details("Method not implemented!") # type: ignore - raise NotImplementedError("Method not implemented!") + + class PairingObserver(adapter_client.BluetoothCallbacks, adapter_client.BluetoothConnectionCallbacks): + """Observer to observe the bond state and the connection state.""" + + def __init__(self, client: adapter_client, task): + self.client = client + self.task = task + + @utils.glib_callback() + def on_bond_state_changed(self, status, address, state): + if address != self.task['address']: + return + + if status != 0: + self.task['connect_device'].set_result( + (False, f'{address} failed to bond. Status: {status}, State: {state}')) + return + + if state == floss_enums.BondState.BONDED: + if not self.client.is_connected(self.task['address']): + logging.info('{address} calling connect_all_enabled_profiles') + if not self.client.connect_all_enabled_profiles(self.task['address']): + self.task['connect_device'].set_result( + (False, f'{self.task["address"]} failed on connect_all_enabled_profiles')) + else: + self.task['connect_device'].set_result((True, None)) + + @utils.glib_callback() + def on_ssp_request(self, remote_device, class_of_device, variant, passkey): + address, _ = remote_device + if address != self.task['address']: + return + + if variant == floss_enums.SspVariant.CONSENT: + self.client.set_pairing_confirmation(address, + True, + method_callback=self.on_set_pairing_confirmation) + + @utils.glib_callback() + def on_set_pairing_confirmation(self, err, result): + if err or not result: + self.task['connect_device'].set_result( + (False, f'Pairing confirmation failed: err: {err}, result: {result}')) + + @utils.glib_callback() + def on_device_connected(self, remote_device): + address, _ = remote_device + if address != self.task['address']: + return + + if self.client.is_bonded(address): + self.task['connect_device'].set_result((True, None)) + + address = utils.address_from(request.address) + + if not self.bluetooth.is_connected(address): + try: + connect_device = asyncio.Future() + observer = PairingObserver(self.bluetooth.adapter_client, { + 'connect_device': connect_device, + 'address': address + }) + name = utils.create_observer_name(observer) + self.bluetooth.adapter_client.register_callback_observer(name, observer) + + if self.bluetooth.is_bonded(address): + self.bluetooth.connect_device(address) + else: + if not self.bluetooth.create_bond(address, floss_enums.BtTransport.BR_EDR): + raise RuntimeError('Failed to call create_bond.') + + success, reason = await connect_device + if not success: + raise RuntimeError(f'Failed to connect to the {address}. Reason: {reason}') + finally: + self.bluetooth.adapter_client.unregister_callback_observer(name, observer) + + cookie = any_pb2.Any(value=utils.address_to(request.address)) + return host_pb2.ConnectResponse(connection=host_pb2.Connection(cookie=cookie)) async def WaitConnection(self, request: host_pb2.WaitConnectionRequest, context: grpc.ServicerContext) -> host_pb2.WaitConnectionResponse: - context.set_code(grpc.StatusCode.UNIMPLEMENTED) # type: ignore - context.set_details("Method not implemented!") # type: ignore - raise NotImplementedError("Method not implemented!") + + class ConnectionObserver(adapter_client.BluetoothConnectionCallbacks): + """Observer to observe the connection state.""" + + def __init__(self, task): + self.task = task + + @utils.glib_callback() + def on_device_connected(self, remote_device): + address, _ = remote_device + if address != self.task['address']: + return + + self.task['wait_connection'].set_result(address) + + if request.address is None: + raise ValueError('Request address field must be set.') + address = utils.address_from(request.address) + + if not self.bluetooth.is_connected(address) or address not in self.waited_connections: + try: + wait_connection = asyncio.Future() + observer = ConnectionObserver({'wait_connection': wait_connection, 'address': address}) + name = utils.create_observer_name(observer) + self.bluetooth.adapter_client.register_callback_observer(name, observer) + + await wait_connection + finally: + self.bluetooth.adapter_client.unregister_callback_observer(name, observer) + self.waited_connection.add(address) + + cookie = any_pb2.Any(value=utils.address_to(address)) + return host_pb2.WaitConnectionResponse(connection=host_pb2.Connection(cookie=cookie)) async def ConnectLE(self, request: host_pb2.ConnectLERequest, context: grpc.ServicerContext) -> host_pb2.ConnectLEResponse: context.set_code(grpc.StatusCode.UNIMPLEMENTED) # type: ignore - context.set_details("Method not implemented!") # type: ignore - raise NotImplementedError("Method not implemented!") + context.set_details('Method not implemented!') # type: ignore + raise NotImplementedError('Method not implemented!') async def Disconnect(self, request: host_pb2.DisconnectRequest, context: grpc.ServicerContext) -> empty_pb2.Empty: - context.set_code(grpc.StatusCode.UNIMPLEMENTED) # type: ignore - context.set_details("Method not implemented!") # type: ignore - raise NotImplementedError("Method not implemented!") + address = utils.address_from(request.connection.cookie.value) + if self.bluetooth.is_connected(address): + self.bluetooth.disconnect_device(address) + return empty_pb2.Empty() async def WaitDisconnection(self, request: host_pb2.WaitDisconnectionRequest, context: grpc.ServicerContext) -> empty_pb2.Empty: - context.set_code(grpc.StatusCode.UNIMPLEMENTED) # type: ignore - context.set_details("Method not implemented!") # type: ignore - raise NotImplementedError("Method not implemented!") + + class ConnectionObserver(adapter_client.BluetoothConnectionCallbacks): + """Observer to observe the connection state.""" + + def __init__(self, task): + self.task = task + + @utils.glib_callback() + def on_device_disconnected(self, remote_device): + address, _ = remote_device + if address != self.task['address']: + return + self.task['wait_disconnection'].set_result(address) + + if request.address is None: + raise ValueError('Request address field must be set') + address = utils.address_from(request.address) + + if self.bluetooth.is_connected(address): + try: + wait_disconnection = asyncio.Future() + observer = ConnectionObserver({'wait_disconnection': wait_disconnection, 'address': address}) + name = utils.create_observer_name(observer) + self.bluetooth.adapter_client.register_callback_observer(name, observer) + await wait_disconnection + finally: + self.bluetooth.adapter_client.unregister_callback_observer(name, observer) + + return empty_pb2.Empty() async def Advertise(self, request: host_pb2.AdvertiseRequest, context: grpc.ServicerContext) -> AsyncGenerator[host_pb2.AdvertiseResponse, None]: - context.set_code(grpc.StatusCode.UNIMPLEMENTED) # type: ignore - context.set_details("Method not implemented!") # type: ignore - raise NotImplementedError("Method not implemented!") - yield host_pb2.AdvertiseResponse() # no-op: to make the linter happy + parameters = { + 'connectable': request.connectable, + 'scannable': True, + 'is_legacy': True, # ROOTCANAL: Extended advertising ignored because the scanner is legacy. + 'is_anonymous': False, + 'include_tx_power': True, + 'primary_phy': 1, + 'secondary_phy': 1, + 'interval': request.interval, + 'tx_power_level': 127, # 0x7f + 'own_address_type': -1, # default + } + + primary_phy = request.primary_phy + if primary_phy == host_pb2.PRIMARY_1M: + parameters['primary_phy'] = floss_enums.LePhy.PHY1M + elif primary_phy == host_pb2.PRIMARY_CODED: + parameters['primary_phy'] = floss_enums.LePhy.PHY_CODED + + secondary_phy = request.secondary_phy + if secondary_phy == host_pb2.SECONDARY_NONE: + parameters['secondary_phy'] = floss_enums.LePhy.INVALID + elif secondary_phy == host_pb2.SECONDARY_1M: + parameters['secondary_phy'] = floss_enums.LePhy.PHY1M + elif secondary_phy == host_pb2.SECONDARY_2M: + parameters['secondary_phy'] = floss_enums.LePhy.PHY2M + elif secondary_phy == host_pb2.SECONDARY_CODED: + parameters['secondary_phy'] = floss_enums.LePhy.PHY_CODED + + own_address_type = request.own_address_type + if own_address_type in (host_pb2.PUBLIC, host_pb2.RESOLVABLE_OR_PUBLIC): + parameters['own_address_type'] = floss_enums.OwnAddressType.PUBLIC + elif own_address_type in (host_pb2.RANDOM, host_pb2.RESOLVABLE_OR_RANDOM): + parameters['own_address_type'] = floss_enums.OwnAddressType.RANDOM + + # TODO: b/289480188 - Support more data and scan response data if needed. + advertise_data = utils.advertise_data_from(request.data) + + class AdvertisingObserver(advertising_client.BluetoothAdvertisingCallbacks): + """Observer to observe the advertising state.""" + + def __init__(self, task): + self.task = task + + @utils.glib_callback() + def on_advertising_set_started(self, reg_id, advertiser_id, tx_power, status): + if reg_id != self.task['reg_id']: + return + + if status is None or floss_enums.GattStatus(status) != floss_enums.GattStatus.SUCCESS: + logging.error('Failed to start advertising.') + advertiser_id = None + self.task['start_advertising'].set_result(advertiser_id) + + class ConnectionObserver(adapter_client.BluetoothConnectionCallbacks): + """Observer to observe all connections.""" + + def __init__(self, loop: asyncio.AbstractEventLoop, task): + self.loop = loop + self.task = task + + @utils.glib_callback() + def on_device_connected(self, remote_device): + address, _ = remote_device + asyncio.run_coroutine_threadsafe(self.task['connections'].put(address), self.loop) + + started_ids = [] + observers = [] + try: + if request.connectable: + connections = asyncio.Queue() + observer = ConnectionObserver(asyncio.get_running_loop(), {'connections': connections}) + name = utils.create_observer_name(observer) + self.bluetooth.adapter_client.register_callback_observer(name, observer) + observers.append((name, observer)) + + while True: + if not self.bluetooth.advertising_client.active_advs: + reg_id = self.bluetooth.start_advertising_set(parameters, advertise_data, None, None, None, 0, 0) + + advertising_request = { + 'start_advertising': asyncio.get_running_loop().create_future(), + 'reg_id': reg_id + } + observer = AdvertisingObserver(advertising_request) + name = utils.create_observer_name(observer) + self.bluetooth.advertising_client.register_callback_observer(name, observer) + observers.append((name, observer)) + + advertiser_id = await asyncio.wait_for(advertising_request['start_advertising'], timeout=5) + started_ids.append(advertiser_id) + + if not request.connectable: + await asyncio.sleep(1) + continue + + logging.info('Advertise: Wait for LE connection...') + address = await connections.get() + logging.info(f'Advertise: Connected to {address}') + + cookie = any_pb2.Any(value=utils.address_to(address)) + yield host_pb2.AdvertiseResponse(connection=host_pb2.Connection(cookie=cookie)) + + # Wait a small delay before restarting the advertisement. + await asyncio.sleep(1) + finally: + for name, observer in observers: + self.bluetooth.adapter_client.unregister_callback_observer(name, observer) + self.bluetooth.advertising_client.unregister_callback_observer(name, observer) + + for started in started_ids: + self.bluetooth.stop_advertising_set(started) async def Scan(self, request: host_pb2.ScanRequest, context: grpc.ServicerContext) -> AsyncGenerator[host_pb2.ScanningResponse, None]: - context.set_code(grpc.StatusCode.UNIMPLEMENTED) # type: ignore - context.set_details("Method not implemented!") # type: ignore - raise NotImplementedError("Method not implemented!") - yield host_pb2.ScanningResponse() # no-op: to make the linter happy + + class ScanObserver(scanner_client.BluetoothScannerCallbacks): + """Observer to observer the scan state and scan results.""" + + def __init__(self, loop: asyncio.AbstractEventLoop, task): + self.loop = loop + self.task = task + + @utils.glib_callback() + def on_scanner_registered(self, uuid, scanner_id, status): + uuid = uuid_module.UUID(bytes=bytes(uuid)) + if uuid != self.task['uuid']: + return + + if floss_enums.GattStatus(status) != floss_enums.GattStatus.SUCCESS: + logging.error('Failed to register scanner! uuid: {uuid}') + scanner_id = None + self.task['register_scanner'].set_result(scanner_id) + + @utils.glib_callback() + def on_scan_result(self, scan_result): + asyncio.run_coroutine_threadsafe(self.task['scan_results'].put(scan_result), self.loop) + + scanner_id = None + name = None + observer = None + try: + uuid = self.bluetooth.register_scanner() + scan = { + 'register_scanner': asyncio.get_running_loop().create_future(), + 'uuid': uuid, + 'scan_results': asyncio.Queue() + } + observer = ScanObserver(asyncio.get_running_loop(), scan) + name = utils.create_observer_name(observer) + self.bluetooth.scanner_client.register_callback_observer(name, observer) + + scanner_id = await asyncio.wait_for(scan['register_scanner'], timeout=10) + + self.bluetooth.start_scan(scanner_id) + while True: + scan_result = await scan['scan_results'].get() + + response = host_pb2.ScanningResponse() + response.tx_power = scan_result['tx_power'] + response.rssi = scan_result['rssi'] + response.sid = scan_result['advertising_sid'] + response.periodic_advertising_interval = scan_result['periodic_adv_int'] + + if scan_result['primary_phy'] == floss_enums.LePhy.PHY1M: + response.primary_phy = host_pb2.PRIMARY_1M + elif scan_result['primary_phy'] == floss_enums.LePhy.PHY_CODED: + response.primary_phy = host_pb2.PRIMARY_CODED + else: + pass + + if scan_result['secondary_phy'] == floss_enums.LePhy.INVALID: + response.secondary_phy = host_pb2.SECONDARY_NONE + elif scan_result['secondary_phy'] == floss_enums.LePhy.PHY1M: + response.secondary_phy = host_pb2.SECONDARY_1M + elif scan_result['secondary_phy'] == floss_enums.LePhy.PHY2M: + response.secondary_phy = host_pb2.SECONDARY_2M + elif scan_result['secondary_phy'] == floss_enums.LePhy.PHY_CODED: + response.secondary_phy = host_pb2.SECONDARY_CODED + + address = bytes.fromhex(scan_result['address'].replace(':', '')) + if scan_result['addr_type'] == floss_enums.BleAddressType.BLE_ADDR_PUBLIC: + response.public = address + elif scan_result['addr_type'] == floss_enums.BleAddressType.BLE_ADDR_RANDOM: + response.random = address + elif scan_result['addr_type'] == floss_enums.BleAddressType.BLE_ADDR_PUBLIC_ID: + response.public_identity = address + elif scan_result['addr_type'] == floss_enums.BleAddressType.BLE_ADDR_RANDOM_ID: + response.random_static_identity = address + + # TODO: b/289480188 - Support more data if needed. + mode = host_pb2.NOT_DISCOVERABLE + if scan_result['flags'] & (1 << 0): + mode = host_pb2.DISCOVERABLE_LIMITED + elif scan_result['flags'] & (1 << 1): + mode = host_pb2.DISCOVERABLE_GENERAL + else: + mode = host_pb2.NOT_DISCOVERABLE + response.data.le_discoverability_mode = mode + yield response + finally: + if scanner_id is not None: + self.bluetooth.stop_scan(scanner_id) + if name is not None and observer is not None: + self.bluetooth.scanner_client.unregister_callback_observer(name, observer) async def Inquiry(self, request: empty_pb2.Empty, context: grpc.ServicerContext) -> AsyncGenerator[host_pb2.InquiryResponse, None]: + + class InquiryResultObserver(adapter_client.BluetoothCallbacks): + """Observer to observe all inquiry results.""" + + def __init__(self, loop: asyncio.AbstractEventLoop, task): + self.loop = loop + self.task = task + + @utils.glib_callback() + def on_device_found(self, remote_device): + address, _ = remote_device + asyncio.run_coroutine_threadsafe(self.task['inquiry_results'].put(address), self.loop) + + class DiscoveryObserver(adapter_client.BluetoothCallbacks): + """Observer to observe discovery state.""" + + def __init__(self, task): + self.task = task + + @utils.glib_callback() + def on_discovering_changed(self, discovering): + if discovering == self.task['discovering']: + self.task['start_inquiry'].set_result(discovering) + + observers = [] + try: + if not self.bluetooth.is_discovering(): + inquriy = {'start_inquiry': asyncio.Future(), 'discovering': True} + observer = DiscoveryObserver(inquriy) + name = utils.create_observer_name(observer) + self.bluetooth.adapter_client.register_callback_observer(name, observer) + observers.append((name, observer)) + + self.bluetooth.start_discovery() + await asyncio.wait_for(inquriy['start_inquiry'], timeout=10) + + inquiry_results = asyncio.Queue() + observer = InquiryResultObserver(asyncio.get_running_loop(), {'inquiry_results': inquiry_results}) + name = utils.create_observer_name(observer) + self.bluetooth.adapter_client.register_callback_observer(name, observer) + observers.append((name, observer)) + + while True: + address = await inquiry_results.get() + yield host_pb2.InquiryResponse(address=utils.address_to(address)) + finally: + self.bluetooth.stop_discovery() + for name, observer in observers: + self.bluetooth.adapter_client.unregister_callback_observer(name, observer) + + async def SetDiscoverabilityMode(self, request: host_pb2.SetDiscoverabilityModeRequest, + context: grpc.ServicerContext) -> empty_pb2.Empty: + mode = request.mode + duration = 600 # Sets general, limited default to 60s. This is unused by the non-discoverable mode. + self.bluetooth.set_discoverable(mode, duration) + return empty_pb2.Empty() + + async def SetConnectabilityMode(self, request: host_pb2.SetConnectabilityModeRequest, + context: grpc.ServicerContext) -> empty_pb2.Empty: context.set_code(grpc.StatusCode.UNIMPLEMENTED) # type: ignore - context.set_details("Method not implemented!") # type: ignore - raise NotImplementedError("Method not implemented!") - yield host_pb2.InquiryResponse() # no-op: to make the linter happy - - async def SetDiscoverabilityMode( - self, - request: host_pb2.SetDiscoverabilityModeRequest, - context: grpc.ServicerContext, - ) -> empty_pb2.Empty: - context.set_code(grpc.StatusCode.UNIMPLEMENTED) # type: ignore - context.set_details("Method not implemented!") # type: ignore - raise NotImplementedError("Method not implemented!") - - async def SetConnectabilityMode( - self, - request: host_pb2.SetConnectabilityModeRequest, - context: grpc.ServicerContext, - ) -> empty_pb2.Empty: - context.set_code(grpc.StatusCode.UNIMPLEMENTED) # type: ignore - context.set_details("Method not implemented!") # type: ignore - raise NotImplementedError("Method not implemented!") + context.set_details('Method not implemented!') # type: ignore + raise NotImplementedError('Method not implemented!') diff --git a/floss/pandora/server/security.py b/floss/pandora/server/security.py index 123080ae02..6311415a47 100644 --- a/floss/pandora/server/security.py +++ b/floss/pandora/server/security.py @@ -13,14 +13,22 @@ # limitations under the License. """Security grpc interface.""" +import asyncio +import logging from typing import AsyncGenerator from typing import AsyncIterator +from floss.pandora.floss import adapter_client +from floss.pandora.floss import floss_enums +from floss.pandora.floss import utils +from floss.pandora.server import bluetooth as bluetooth_module +from google.protobuf import any_pb2 from google.protobuf import empty_pb2 from google.protobuf import wrappers_pb2 import grpc -from pandora import security_pb2 +from pandora import host_pb2 from pandora import security_grpc_aio +from pandora import security_pb2 class SecurityService(security_grpc_aio.SecurityServicer): @@ -33,24 +41,88 @@ class SecurityService(security_grpc_aio.SecurityServicer): /pandora/bt-test-interfaces/pandora/security.proto """ + def __init__(self, server: grpc.aio.Server, bluetooth: bluetooth_module.Bluetooth): + self.server = server + self.bluetooth = bluetooth + async def OnPairing(self, request: AsyncIterator[security_pb2.PairingEventAnswer], context: grpc.ServicerContext) -> AsyncGenerator[security_pb2.PairingEvent, None]: - context.set_code(grpc.StatusCode.UNIMPLEMENTED) # type: ignore - context.set_details("Method not implemented!") # type: ignore - raise NotImplementedError("Method not implemented!") - yield security_pb2.PairingEvent() # no-op: to make the linter happy + + class PairingObserver(adapter_client.BluetoothCallbacks): + """Observer to observe all pairing events.""" + + def __init__(self, loop: asyncio.AbstractEventLoop, task): + self.loop = loop + self.task = task + + @utils.glib_callback() + def on_ssp_request(self, remote_device, class_of_device, variant, passkey): + address, name = remote_device + + result = (address, name, class_of_device, variant, passkey) + asyncio.run_coroutine_threadsafe(self.task['pairing_events'].put(result), self.loop) + + async def streaming_answers(self): + while True: + pairing_answer = await utils.anext(self.bluetooth.pairing_answers) + answer = pairing_answer.WhichOneof('answer') + address = utils.address_from(pairing_answer.event.connection.cookie.value) + + logging.info(f'pairing_answer: {pairing_answer} address: {address}') + + if answer == 'confirm': + self.bluetooth.set_pairing_confirmation(address, True) + elif answer == 'passkey': + pass # TODO: b/289480188 - Supports this method. + elif answer == 'pin': + pass # TODO: b/289480188 - Supports this method. + + observers = [] + try: + self.bluetooth.pairing_events = asyncio.Queue() + observer = PairingObserver(asyncio.get_running_loop(), {'pairing_events': self.bluetooth.pairing_events}) + name = utils.create_observer_name(observer) + self.bluetooth.adapter_client.register_callback_observer(name, observer) + observers.append((name, observer)) + + self.bluetooth.pairing_answers = request + streaming_answers_task = asyncio.create_task(streaming_answers(self)) + await streaming_answers_task + + while True: + address, name, _, variant, passkey = await self.bluetooth.pairing_events.get() + + event = security_pb2.PairingEvent() + event.connection.CopyFrom(host_pb2.Connection(cookie=any_pb2.Any(value=utils.address_to(address)))) + + if variant == floss_enums.SspVariant.PASSKEY_CONFIRMATION: + event.numeric_comparison = passkey + elif variant == floss_enums.SspVariant.PASSKEY_ENTRY: + event.passkey_entry_request.CopyFrom(empty_pb2.Empty()) + elif variant == floss_enums.SspVariant.CONSENT: + event.just_works.CopyFrom(empty_pb2.Empty()) + elif variant == floss_enums.SspVariant.PASSKEY_NOTIFICATION: + event.passkey_entry_notification.CopyFrom(passkey) + yield event + finally: + streaming_answers_task.cancel() + for name, observer in observers: + self.bluetooth.adapter_client.unregister_callback_observer(name, observer) + + self.bluetooth.pairing_events = None + self.bluetooth.pairing_answers = None async def Secure(self, request: security_pb2.SecureRequest, context: grpc.ServicerContext) -> security_pb2.SecureResponse: context.set_code(grpc.StatusCode.UNIMPLEMENTED) # type: ignore - context.set_details("Method not implemented!") # type: ignore - raise NotImplementedError("Method not implemented!") + context.set_details('Method not implemented!') # type: ignore + raise NotImplementedError('Method not implemented!') async def WaitSecurity(self, request: security_pb2.WaitSecurityRequest, context: grpc.ServicerContext) -> security_pb2.WaitSecurityResponse: context.set_code(grpc.StatusCode.UNIMPLEMENTED) # type: ignore - context.set_details("Method not implemented!") # type: ignore - raise NotImplementedError("Method not implemented!") + context.set_details('Method not implemented!') # type: ignore + raise NotImplementedError('Method not implemented!') class SecurityStorageService(security_grpc_aio.SecurityStorageServicer): @@ -63,14 +135,18 @@ class SecurityStorageService(security_grpc_aio.SecurityStorageServicer): /pandora/bt-test-interfaces/pandora/security.proto """ + def __init__(self, server: grpc.aio.Server, bluetooth: bluetooth_module.Bluetooth): + self.server = server + self.bluetooth = bluetooth + async def IsBonded(self, request: security_pb2.IsBondedRequest, context: grpc.ServicerContext) -> wrappers_pb2.BoolValue: context.set_code(grpc.StatusCode.UNIMPLEMENTED) # type: ignore - context.set_details("Method not implemented!") # type: ignore - raise NotImplementedError("Method not implemented!") + context.set_details('Method not implemented!') # type: ignore + raise NotImplementedError('Method not implemented!') async def DeleteBond(self, request: security_pb2.DeleteBondRequest, context: grpc.ServicerContext) -> empty_pb2.Empty: context.set_code(grpc.StatusCode.UNIMPLEMENTED) # type: ignore - context.set_details("Method not implemented!") # type: ignore - raise NotImplementedError("Method not implemented!") + context.set_details('Method not implemented!') # type: ignore + raise NotImplementedError('Method not implemented!') diff --git a/floss/pandora/server/server.py b/floss/pandora/server/server.py new file mode 100644 index 0000000000..0e0d8ae7b8 --- /dev/null +++ b/floss/pandora/server/server.py @@ -0,0 +1,60 @@ +# Copyright 2023 Google LLC +# +# 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 +# +# https://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. +"""Floss bluetooth test server.""" + +import asyncio +import logging + +from floss.pandora.server import bluetooth as bluetoot_module +from floss.pandora.server import host +from floss.pandora.server import security +import grpc +from pandora import host_grpc_aio +from pandora import security_grpc_aio + + +async def serve(port): + """Start serving the Floss bluetooth test server.""" + + logging.basicConfig(filename='/var/log/grpc_server_log', filemode='a', level=logging.DEBUG) + + try: + while True: + server = grpc.aio.server() + bluetooth = bluetoot_module.Bluetooth() + bluetooth.reset() + + host_service = host.HostService(server, bluetooth) + host_grpc_aio.add_HostServicer_to_server(host_service, server) + + security_service = security.SecurityService(server, bluetooth) + security_grpc_aio.add_SecurityServicer_to_server(security_service, server) + + security_storage_service = security.SecurityStorageService(server, bluetooth) + security_grpc_aio.add_SecurityStorageServicer_to_server(security_storage_service, server) + + server.add_insecure_port(f'localhost:{port}') + await server.start() + await server.wait_for_termination() + + bluetooth.cleanup() + del bluetooth + finally: + await server.stop(None) + bluetooth.cleanup() + del bluetooth + + +if __name__ == '__main__': + asyncio.run(serve(8999)) diff --git a/framework/java/android/bluetooth/BluetoothGattServer.java b/framework/java/android/bluetooth/BluetoothGattServer.java index 05eaf1ac6a..03910657c1 100644 --- a/framework/java/android/bluetooth/BluetoothGattServer.java +++ b/framework/java/android/bluetooth/BluetoothGattServer.java @@ -668,7 +668,7 @@ public final class BluetoothGattServer implements BluetoothProfile { /** * Set the preferred connection PHY for this app. Please note that this is just a - * recommendation, whether the PHY change will happen depends on other applications peferences, + * recommendation, whether the PHY change will happen depends on other applications preferences, * local and remote controller capabilities. Controller can override these settings. <p> {@link * BluetoothGattServerCallback#onPhyUpdate} will be triggered as a result of this call, even if * no PHY change happens. It is also triggered when remote device updates the PHY. @@ -856,7 +856,7 @@ public final class BluetoothGattServer implements BluetoothProfile { /** * Add a service to the list of services to be hosted. * - * <p>Once a service has been addded to the list, the service and its + * <p>Once a service has been added to the list, the service and its * included characteristics will be provided by the local device. * * <p>If the local device has already exposed services when this function @@ -943,7 +943,7 @@ public final class BluetoothGattServer implements BluetoothProfile { /** * Returns a list of GATT services offered by this device. * - * <p>An application must call {@link #addService} to add a serice to the + * <p>An application must call {@link #addService} to add a service to the * list of services offered by this device. * * @return List of services. Returns an empty list if no services have been added yet. diff --git a/pandora/server/bumble_experimental/asha.py b/pandora/server/bumble_experimental/asha.py index ce09a6b686..f2c85a7db9 100644 --- a/pandora/server/bumble_experimental/asha.py +++ b/pandora/server/bumble_experimental/asha.py @@ -229,7 +229,7 @@ class AshaService(AshaServicer): if not (connection := self.device.lookup_connection(connection_handle)): raise RuntimeError(f"Unknown connection for connection_handle:{connection_handle}") - decoder = G722Decoder() + decoder = G722Decoder() # type: ignore queue: asyncio.Queue[bytes] = asyncio.Queue() def on_data(asha_connection: BumbleConnection, data: bytes) -> None: diff --git a/service/Android.bp b/service/Android.bp index 97715d825d..7211e68801 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -45,11 +45,6 @@ java_defaults { "-Xep:ReferenceEquality:ERROR", ], }, - product_variables: { - pdk: { - enabled: false, - }, - }, sdk_version: "system_server_current", diff --git a/service/src/com/android/server/bluetooth/BluetoothManagerService.java b/service/src/com/android/server/bluetooth/BluetoothManagerService.java index a9c16deeb2..f95ff2ac3f 100644 --- a/service/src/com/android/server/bluetooth/BluetoothManagerService.java +++ b/service/src/com/android/server/bluetooth/BluetoothManagerService.java @@ -67,6 +67,7 @@ import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemClock; +import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.provider.DeviceConfig; @@ -113,10 +114,13 @@ class BluetoothManagerService { private static final int CRASH_LOG_MAX_SIZE = 100; private static final int DEFAULT_REBIND_COUNT = 3; - private static final int TIMEOUT_BIND_MS = 3000; // Maximum msec to wait for a bind + // Maximum msec to wait for a bind + private static final int TIMEOUT_BIND_MS = + 3000 * SystemProperties.getInt("ro.hw_timeout_multiplier", 1); // Timeout value for synchronous binder call - private static final Duration SYNC_CALLS_TIMEOUT = Duration.ofSeconds(3); + private static final Duration SYNC_CALLS_TIMEOUT = + Duration.ofSeconds(3 * SystemProperties.getInt("ro.hw_timeout_multiplier", 1)); /** * @return timeout value for synchronous binder call @@ -126,15 +130,20 @@ class BluetoothManagerService { } // Maximum msec to wait for service restart - private static final int SERVICE_RESTART_TIME_MS = 400; + private static final int SERVICE_RESTART_TIME_MS + = 400 * SystemProperties.getInt("ro.hw_timeout_multiplier", 1); // Maximum msec to wait for restart due to error - private static final int ERROR_RESTART_TIME_MS = 3000; + private static final int ERROR_RESTART_TIME_MS + = 3000 * SystemProperties.getInt("ro.hw_timeout_multiplier", 1); // Maximum msec to delay MESSAGE_USER_SWITCHED - private static final int USER_SWITCHED_TIME_MS = 200; + private static final int USER_SWITCHED_TIME_MS + = 200 * SystemProperties.getInt("ro.hw_timeout_multiplier", 1); // Delay for the addProxy function in msec - private static final int ADD_PROXY_DELAY_MS = 100; + private static final int ADD_PROXY_DELAY_MS + = 100 * SystemProperties.getInt("ro.hw_timeout_multiplier", 1); // Delay for retrying enable and disable in msec - private static final int ENABLE_DISABLE_DELAY_MS = 300; + private static final int ENABLE_DISABLE_DELAY_MS + = 300 * SystemProperties.getInt("ro.hw_timeout_multiplier", 1); @VisibleForTesting static final int MESSAGE_ENABLE = 1; @VisibleForTesting static final int MESSAGE_DISABLE = 2; diff --git a/system/audio_hal_interface/fuzzer/Android.bp b/system/audio_hal_interface/fuzzer/Android.bp index eed40552e9..0fc5e26792 100644 --- a/system/audio_hal_interface/fuzzer/Android.bp +++ b/system/audio_hal_interface/fuzzer/Android.bp @@ -77,7 +77,6 @@ cc_defaults { "libchrome", "libcutils", "libevent", - "libflatbuffers-cpp", "libg722codec", "libhidlbase", "libjsoncpp", diff --git a/system/binder/android/bluetooth/IBluetoothGatt.aidl b/system/binder/android/bluetooth/IBluetoothGatt.aidl index 221deaa7b1..8c5305e02a 100644 --- a/system/binder/android/bluetooth/IBluetoothGatt.aidl +++ b/system/binder/android/bluetooth/IBluetoothGatt.aidl @@ -47,11 +47,6 @@ import com.android.modules.utils.SynchronousResultReceiver; */ oneway interface IBluetoothGatt { @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)") - void startService(); - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)") - void stopService(); - - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)") void getDevicesMatchingConnectionStates(in int[] states, in AttributionSource attributionSource, in SynchronousResultReceiver receiver); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)") @@ -181,8 +176,6 @@ oneway interface IBluetoothGatt { @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)") void disconnectAll(in AttributionSource attributionSource, in SynchronousResultReceiver receiver); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)") - void unregAll(in AttributionSource attributionSource); - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)") void numHwTrackFiltersAvailable(in AttributionSource attributionSource, in SynchronousResultReceiver receiver); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)") void leSubrateRequest(in int clientIf, in String address, in int subrateMin, in int subrateMax, in int maxLatency, diff --git a/system/bta/Android.bp b/system/bta/Android.bp index 8a3cd65a37..518161945b 100644 --- a/system/bta/Android.bp +++ b/system/bta/Android.bp @@ -9,7 +9,7 @@ package { cc_defaults { name: "fluoride_bta_defaults", - defaults: ["fluoride_basic_defaults"], + defaults: ["fluoride_defaults"], local_include_dirs: [ "dm", "hd", @@ -147,6 +147,9 @@ cc_library_static { ], }, }, + apex_available: [ + "com.android.btservices", + ], host_supported: true, min_sdk_version: "Tiramisu", } @@ -189,6 +192,9 @@ cc_library_static { "lib-bt-packets", "libcom.android.sysprop.bluetooth", ], + apex_available: [ + "com.android.btservices", + ], host_supported: true, min_sdk_version: "Tiramisu", } @@ -237,7 +243,6 @@ cc_test { "libbt-bta", "libbt-bta-core", "libbt-common", - "libbt-protos-lite", "libbtcore", "libchrome", "libcom.android.sysprop.bluetooth", @@ -283,7 +288,6 @@ cc_test { "libbt-bta", "libbt-bta-core", "libbt-common", - "libbt-protos-lite", "libbtcore", "libchrome", "libosi", @@ -398,13 +402,11 @@ cc_test { static_libs: [ "libbluetooth-types", "libbt-common", - "libbt-protos-lite", "libbtcore", "libbtdevice", "libchrome", "libcom.android.sysprop.bluetooth", "libevent", - "libflatbuffers-cpp", "libgmock", ], sanitize: { @@ -460,7 +462,6 @@ cc_test { static_libs: [ "crypto_toolbox_for_tests", "libbt-common", - "libbt-protos-lite", "libchrome", "libgmock", "libosi", @@ -504,7 +505,6 @@ cc_test { static_libs: [ "crypto_toolbox_for_tests", "libbt-common", - "libbt-protos-lite", "libchrome", "libgmock", "libosi", @@ -559,7 +559,6 @@ cc_test { static_libs: [ "crypto_toolbox_for_tests", "libbt-common", - "libbt-protos-lite", "libchrome", "libgmock", "libosi", @@ -723,7 +722,6 @@ cc_test { ], static_libs: [ "libbt-common", - "libbt-protos-lite", "libchrome", "libevent", "libflatbuffers-cpp", @@ -795,7 +793,6 @@ cc_test { "crypto_toolbox_for_tests", "libbt-audio-hal-interface", "libbt-common", - "libbt-protos-lite", "libchrome", "libevent", "libflatbuffers-cpp", @@ -858,7 +855,6 @@ cc_test { static_libs: [ "crypto_toolbox_for_tests", "libbt-common", - "libbt-protos-lite", "libchrome", "libgmock", "libosi", @@ -911,7 +907,6 @@ cc_test { ], static_libs: [ "libbt-common", - "libbt-protos-lite", "libchrome", "libgmock", "liblc3", @@ -975,7 +970,6 @@ cc_test { static_libs: [ "libbt-audio-hal-interface", "libbt-common", - "libbt-protos-lite", "libchrome", "libevent", "libgmock", @@ -1036,7 +1030,6 @@ cc_test { static_libs: [ "crypto_toolbox_for_tests", "libbt-common", - "libbt-protos-lite", "libchrome", "libgmock", ], @@ -1098,7 +1091,6 @@ cc_test { static_libs: [ "crypto_toolbox_for_tests", "libbt-common", - "libbt-protos-lite", "libchrome", "libcom.android.sysprop.bluetooth", "libevent", diff --git a/system/bta/csis/csis_client.cc b/system/bta/csis/csis_client.cc index 1a2533ef97..8a0feab816 100644 --- a/system/bta/csis/csis_client.cc +++ b/system/bta/csis/csis_client.cc @@ -1309,16 +1309,23 @@ class CsisClientImpl : public CsisClient { } } - void CsisActiveObserverSet(bool enable) { + static void csis_ad_type_filter_set(bool enable) { bool is_ad_type_filter_supported = bluetooth::shim::is_ad_type_filter_supported(); - LOG_INFO("Group_id %d: enable: %d, is_ad_type_filter_supported: %d", - discovering_group_, enable, is_ad_type_filter_supported); + + LOG_INFO("enable: %d, is_ad_type_filter_supported: %d", enable, + is_ad_type_filter_supported); + if (is_ad_type_filter_supported) { bluetooth::shim::set_ad_type_rsi_filter(enable); } else { bluetooth::shim::set_empty_filter(enable); } + } + + void CsisActiveObserverSet(bool enable) { + LOG_INFO("Group_id %d: enable: %d", discovering_group_, enable); + csis_ad_type_filter_set(enable); BTA_DmBleCsisObserve( enable, [](tBTA_DM_SEARCH_EVT event, tBTA_DM_SEARCH* p_data) { @@ -1330,6 +1337,7 @@ class CsisClientImpl : public CsisClient { if (event == BTA_DM_INQ_CMPL_EVT) { LOG(INFO) << "BLE observe complete. Num Resp: " << static_cast<int>(p_data->inq_cmpl.num_resps); + csis_ad_type_filter_set(false); instance->OnCsisObserveCompleted(); instance->CsisObserverSetBackground(true); return; diff --git a/system/bta/csis/csis_client_test.cc b/system/bta/csis/csis_client_test.cc index 544fb41fa2..64fcf5ee0e 100644 --- a/system/bta/csis/csis_client_test.cc +++ b/system/bta/csis/csis_client_test.cc @@ -885,6 +885,40 @@ TEST_F(CsisClientTest, test_get_set_sirk) { ASSERT_EQ(g_1->GetSirk(), sirk); } +TEST_F(CsisClientTest, test_csis_member_not_found) { + EXPECT_CALL(dm_interface, BTA_DmBleCsisObserve(true, _)).Times(1); + SetSampleDatabaseDoubleCsis(0x001, 1, 2); + TestAppRegister(); + + /* Here we handle Background Scan request */ + Mock::VerifyAndClearExpectations(&dm_interface); + + tBTA_DM_SEARCH_CBACK* p_results_cb = nullptr; + /* Here is actual Active Scan request */ + EXPECT_CALL(dm_interface, BTA_DmBleCsisObserve(true, _)) + .WillOnce(DoAll(SaveArg<1>(&p_results_cb))); + + TestConnect(test_address); + InjectConnectedEvent(test_address, 1); + GetSearchCompleteEvent(1); + + Mock::VerifyAndClearExpectations(&dm_interface); + /* Verify that scanner has been called to start filtering */ + ASSERT_EQ(1, get_func_call_count("set_empty_filter")); + + /* Check callback is not null and simulate no member found and scan + * completed*/ + ASSERT_NE(p_results_cb, nullptr); + + tBTA_DM_SEARCH result; + result.inq_cmpl.num_resps = 80; + + p_results_cb(BTA_DM_INQ_CMPL_EVT, &result); + + /* Verify that scanner has been called to stop filtering */ + ASSERT_EQ(2, get_func_call_count("set_empty_filter")); +} + class CsisMultiClientTest : public CsisClientTest { protected: const RawAddress test_address_1 = GetTestAddress(1); diff --git a/system/bta/dm/bta_dm_act.cc b/system/bta/dm/bta_dm_act.cc index 48a2bd6a46..0782e049c6 100644 --- a/system/bta/dm/bta_dm_act.cc +++ b/system/bta/dm/bta_dm_act.cc @@ -846,8 +846,11 @@ void bta_dm_close_acl(const RawAddress& bd_addr, bool remove_dev, break; } if (index != bta_dm_cb.device_list.count) { - if (remove_dev) + if (remove_dev) { + LOG_INFO("Setting remove_dev_pending for %s", + ADDRESS_TO_LOGGABLE_CSTR(bd_addr)); bta_dm_cb.device_list.peer_device[index].remove_dev_pending = true; + } } else { APPL_TRACE_ERROR("unknown device, remove ACL failed"); } @@ -1080,8 +1083,8 @@ static bool bta_dm_read_remote_device_name(const RawAddress& bd_addr, bta_dm_search_cb.peer_bdaddr = bd_addr; bta_dm_search_cb.peer_name[0] = 0; - btm_status = BTM_ReadRemoteDeviceName(bta_dm_search_cb.peer_bdaddr, - bta_dm_remname_cback, transport); + btm_status = get_btm_client_interface().peer.BTM_ReadRemoteDeviceName( + bta_dm_search_cb.peer_bdaddr, bta_dm_remname_cback, transport); if (btm_status == BTM_CMD_STARTED) { APPL_TRACE_DEBUG("%s: BTM_ReadRemoteDeviceName is started", __func__); @@ -1094,7 +1097,8 @@ static bool bta_dm_read_remote_device_name(const RawAddress& bd_addr, * "bta_dm_remname_cback" */ /* adding callback to get notified that current reading remote name done */ - BTM_SecAddRmtNameNotifyCallback(&bta_dm_service_search_remname_cback); + get_btm_client_interface().security.BTM_SecAddRmtNameNotifyCallback( + &bta_dm_service_search_remname_cback); return (true); } else { @@ -1420,7 +1424,8 @@ void bta_dm_sdp_result(tBTA_DM_MSG* p_data) { /* callbacks */ /* start next bd_addr if necessary */ - BTM_SecDeleteRmtNameNotifyCallback(&bta_dm_service_search_remname_cback); + get_btm_client_interface().security.BTM_SecDeleteRmtNameNotifyCallback( + &bta_dm_service_search_remname_cback); BTM_LogHistory( kBtmLogTag, bta_dm_search_cb.peer_bdaddr, "Discovery completed", @@ -1490,7 +1495,8 @@ void bta_dm_sdp_result(tBTA_DM_MSG* p_data) { if (bta_dm_search_cb.p_sdp_db) osi_free_and_reset((void**)&bta_dm_search_cb.p_sdp_db); - BTM_SecDeleteRmtNameNotifyCallback(&bta_dm_service_search_remname_cback); + get_btm_client_interface().security.BTM_SecDeleteRmtNameNotifyCallback( + &bta_dm_service_search_remname_cback); p_msg = (tBTA_DM_MSG*)osi_calloc(sizeof(tBTA_DM_MSG)); p_msg->hdr.event = BTA_DM_DISCOVERY_RESULT_EVT; @@ -2053,8 +2059,8 @@ static void bta_dm_discover_device(const RawAddress& remote_bd_addr) { if connection exists, we don't have to wait for ACL link to go down to start search on next device */ if (transport == BT_TRANSPORT_BR_EDR) { - if (BTM_IsAclConnectionUp(bta_dm_search_cb.peer_bdaddr, - BT_TRANSPORT_BR_EDR)) + if (get_btm_client_interface().peer.BTM_IsAclConnectionUp( + bta_dm_search_cb.peer_bdaddr, BT_TRANSPORT_BR_EDR)) bta_dm_search_cb.wait_disc = false; else bta_dm_search_cb.wait_disc = true; @@ -2269,13 +2275,15 @@ static void bta_dm_remname_cback(const tBTM_REMOTE_DEV_NAME* p_remote_name) { p_remote_name->remote_bd_name[0], p_remote_name->length); if (bta_dm_search_cb.peer_bdaddr == p_remote_name->bd_addr) { - BTM_SecDeleteRmtNameNotifyCallback(&bta_dm_service_search_remname_cback); + get_btm_client_interface().security.BTM_SecDeleteRmtNameNotifyCallback( + &bta_dm_service_search_remname_cback); } else { // if we got a different response, maybe ignore it // we will have made a request directly from BTM_ReadRemoteDeviceName so we // expect a dedicated response for us if (p_remote_name->hci_status == HCI_ERR_CONNECTION_EXISTS) { - BTM_SecDeleteRmtNameNotifyCallback(&bta_dm_service_search_remname_cback); + get_btm_client_interface().security.BTM_SecDeleteRmtNameNotifyCallback( + &bta_dm_service_search_remname_cback); LOG_INFO( "Assume command failed due to disconnection hci_status:%s peer:%s", hci_error_code_text(p_remote_name->hci_status).c_str(), @@ -2903,6 +2911,8 @@ static void bta_dm_acl_down(const RawAddress& bd_addr, } } if (remove_device) { + LOG_INFO("remove_dev_pending actually removing %s", + ADDRESS_TO_LOGGABLE_CSTR(bd_addr)); bta_dm_process_remove_device_no_callback(bd_addr); } @@ -3068,8 +3078,13 @@ static void bta_dm_delay_role_switch_cback(UNUSED_ATTR void* data) { ******************************************************************************/ static void bta_dm_reset_sec_dev_pending(const RawAddress& remote_bd_addr) { for (size_t i = 0; i < bta_dm_cb.device_list.count; i++) { - if (bta_dm_cb.device_list.peer_device[i].peer_bdaddr == remote_bd_addr) { - bta_dm_cb.device_list.peer_device[i].remove_dev_pending = false; + auto& dev = bta_dm_cb.device_list.peer_device[i]; + if (dev.peer_bdaddr == remote_bd_addr) { + if (dev.remove_dev_pending) { + LOG_INFO("Clearing remove_dev_pending for %s", + ADDRESS_TO_LOGGABLE_CSTR(dev.peer_bdaddr)); + dev.remove_dev_pending = false; + } return; } } @@ -3096,8 +3111,11 @@ static void bta_dm_remove_sec_dev_entry(const RawAddress& remote_bd_addr) { __func__); BTM_SecClearSecurityFlags(remote_bd_addr); for (int i = 0; i < bta_dm_cb.device_list.count; i++) { - if (bta_dm_cb.device_list.peer_device[i].peer_bdaddr == remote_bd_addr) { - bta_dm_cb.device_list.peer_device[i].remove_dev_pending = TRUE; + auto& dev = bta_dm_cb.device_list.peer_device[i]; + if (dev.peer_bdaddr == remote_bd_addr) { + LOG_INFO("Setting remove_dev_pending for %s", + ADDRESS_TO_LOGGABLE_CSTR(dev.peer_bdaddr)); + dev.remove_dev_pending = TRUE; break; } } @@ -3169,7 +3187,8 @@ static const char* bta_dm_get_remname(void) { /* If the name isn't already stored, try retrieving from BTM */ if (*p_name == '\0') { - const char* p_temp = BTM_SecReadDevName(bta_dm_search_cb.peer_bdaddr); + const char* p_temp = get_btm_client_interface().security.BTM_SecReadDevName( + bta_dm_search_cb.peer_bdaddr); if (p_temp != NULL) p_name = (const char*)p_temp; } @@ -3970,6 +3989,12 @@ static uint8_t bta_dm_ble_smp_cback(tBTM_LE_EVT event, const RawAddress& bda, sec_event.ble_key.key_type = p_data->key.key_type; sec_event.ble_key.p_key_value = p_data->key.p_key_value; bta_dm_cb.p_sec_cback(BTA_DM_BLE_KEY_EVT, &sec_event); + + // Setting remove_dev_pending flag to false, where it will avoid deleting + // the security device record when the ACL connection link goes down in + // case of reconnection. + if (bta_dm_cb.device_list.count) bta_dm_reset_sec_dev_pending(bda); + break; case BTM_LE_COMPLT_EVT: @@ -4450,7 +4475,7 @@ void btm_dm_start_gatt_discovery(const RawAddress& bd_addr) { * Parameters: * ******************************************************************************/ -void bta_dm_proc_open_evt(tBTA_GATTC_OPEN* p_data) { +static void bta_dm_proc_open_evt(tBTA_GATTC_OPEN* p_data) { VLOG(1) << "DM Search state= " << bta_dm_search_get_state() << " search_cb.peer_dbaddr:" << bta_dm_search_cb.peer_bdaddr << " connected_bda=" << p_data->remote_bda.address; diff --git a/system/bta/dm/bta_dm_main.cc b/system/bta/dm/bta_dm_main.cc index 827439f007..af60d17186 100644 --- a/system/bta/dm/bta_dm_main.cc +++ b/system/bta/dm/bta_dm_main.cc @@ -211,7 +211,11 @@ bool bta_dm_search_sm_execute(const BT_HDR_RIGID* p_msg) { break; case BTA_DM_API_SEARCH_CANCEL_EVT: bta_dm_search_clear_queue(); - bta_dm_search_set_state(BTA_DM_SEARCH_CANCELLING); + + if (bluetooth::common::init_flags:: + bta_dm_stop_discovery_on_search_cancel_is_enabled()) { + bta_dm_search_set_state(BTA_DM_SEARCH_CANCELLING); + } bta_dm_search_cancel_notify(); break; case BTA_DM_DISC_CLOSE_TOUT_EVT: diff --git a/system/bta/include/bta_le_audio_api.h b/system/bta/include/bta_le_audio_api.h index 38b6c2e883..1d61bc3906 100644 --- a/system/bta/include/bta_le_audio_api.h +++ b/system/bta/include/bta_le_audio_api.h @@ -63,6 +63,9 @@ class LeAudioClient { bluetooth::le_audio::btle_audio_codec_config_t output_codec_config) = 0; virtual void SetCcidInformation(int ccid, int context_type) = 0; virtual void SetInCall(bool in_call) = 0; + virtual bool IsInCall() = 0; + virtual void SetInVoipCall(bool in_call) = 0; + virtual bool IsInVoipCall() = 0; virtual void SendAudioProfilePreferences( const int group_id, bool is_output_preference_le_audio, bool is_duplex_preference_le_audio) = 0; diff --git a/system/bta/le_audio/audio_hal_client/audio_hal_client.h b/system/bta/le_audio/audio_hal_client/audio_hal_client.h index d24ab7e67d..e470b3f614 100644 --- a/system/bta/le_audio/audio_hal_client/audio_hal_client.h +++ b/system/bta/le_audio/audio_hal_client/audio_hal_client.h @@ -102,7 +102,7 @@ class LeAudioSinkAudioHalClient { class Callbacks { public: virtual ~Callbacks() = default; - virtual void OnAudioSuspend(std::promise<void> do_suspend_promise) = 0; + virtual void OnAudioSuspend(void) = 0; virtual void OnAudioResume(void) = 0; virtual void OnAudioMetadataUpdate( std::vector<struct record_track_metadata> sink_metadata) = 0; @@ -139,7 +139,7 @@ class LeAudioSourceAudioHalClient { public: virtual ~Callbacks() = default; virtual void OnAudioDataReady(const std::vector<uint8_t>& data) = 0; - virtual void OnAudioSuspend(std::promise<void> do_suspend_promise) = 0; + virtual void OnAudioSuspend(void) = 0; virtual void OnAudioResume(void) = 0; virtual void OnAudioMetadataUpdate( std::vector<struct playback_track_metadata> source_metadata) = 0; diff --git a/system/bta/le_audio/audio_hal_client/audio_hal_client_test.cc b/system/bta/le_audio/audio_hal_client/audio_hal_client_test.cc index 5a6729299c..75fbcec646 100644 --- a/system/bta/le_audio/audio_hal_client/audio_hal_client_test.cc +++ b/system/bta/le_audio/audio_hal_client/audio_hal_client_test.cc @@ -208,8 +208,7 @@ class MockLeAudioClientAudioSinkEventReceiver public: MOCK_METHOD((void), OnAudioDataReady, (const std::vector<uint8_t>& data), (override)); - MOCK_METHOD((void), OnAudioSuspend, (std::promise<void> do_suspend_promise), - (override)); + MOCK_METHOD((void), OnAudioSuspend, (), (override)); MOCK_METHOD((void), OnAudioResume, (), (override)); MOCK_METHOD((void), OnAudioMetadataUpdate, (std::vector<struct playback_track_metadata> source_metadata), @@ -219,8 +218,7 @@ class MockLeAudioClientAudioSinkEventReceiver class MockAudioHalClientEventReceiver : public LeAudioSinkAudioHalClient::Callbacks { public: - MOCK_METHOD((void), OnAudioSuspend, (std::promise<void> do_suspend_promise), - (override)); + MOCK_METHOD((void), OnAudioSuspend, (), (override)); MOCK_METHOD((void), OnAudioResume, (), (override)); MOCK_METHOD((void), OnAudioMetadataUpdate, (std::vector<struct record_track_metadata> sink_metadata), @@ -413,7 +411,7 @@ TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSinkSuspend) { /* Expect LeAudio registered event listener to get called when HAL calls the * audio_hal_client's internal suspend callback. */ - EXPECT_CALL(mock_hal_source_event_receiver_, OnAudioSuspend(_)).Times(1); + EXPECT_CALL(mock_hal_source_event_receiver_, OnAudioSuspend()).Times(1); ASSERT_TRUE(source_audio_hal_stream_cb.on_suspend_()); } @@ -427,7 +425,7 @@ TEST_F(LeAudioClientAudioTest, testAudioHalClientSuspend) { /* Expect LeAudio registered event listener to get called when HAL calls the * audio_hal_client's internal suspend callback. */ - EXPECT_CALL(mock_hal_sink_event_receiver_, OnAudioSuspend(_)).Times(1); + EXPECT_CALL(mock_hal_sink_event_receiver_, OnAudioSuspend()).Times(1); ASSERT_TRUE(sink_audio_hal_stream_cb.on_suspend_()); } diff --git a/system/bta/le_audio/audio_hal_client/audio_sink_hal_client.cc b/system/bta/le_audio/audio_hal_client/audio_sink_hal_client.cc index 8d435901dc..feb570fbe8 100644 --- a/system/bta/le_audio/audio_hal_client/audio_sink_hal_client.cc +++ b/system/bta/le_audio/audio_hal_client/audio_sink_hal_client.cc @@ -143,16 +143,11 @@ bool SinkImpl::OnSuspendReq() { return false; } - std::promise<void> do_suspend_promise; - std::future<void> do_suspend_future = do_suspend_promise.get_future(); - bt_status_t status = do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioSinkAudioHalClient::Callbacks::OnAudioSuspend, - base::Unretained(audioSinkCallbacks_), - std::move(do_suspend_promise))); + base::Unretained(audioSinkCallbacks_))); if (status == BT_STATUS_SUCCESS) { - do_suspend_future.wait(); return true; } diff --git a/system/bta/le_audio/audio_hal_client/audio_source_hal_client.cc b/system/bta/le_audio/audio_hal_client/audio_source_hal_client.cc index 38156929f2..130b652807 100644 --- a/system/bta/le_audio/audio_hal_client/audio_source_hal_client.cc +++ b/system/bta/le_audio/audio_hal_client/audio_source_hal_client.cc @@ -252,16 +252,11 @@ bool SourceImpl::OnSuspendReq() { return false; } - // Call OnAudioSuspend and block till it returns. - std::promise<void> do_suspend_promise; - std::future<void> do_suspend_future = do_suspend_promise.get_future(); bt_status_t status = do_in_main_thread( FROM_HERE, base::BindOnce(&LeAudioSourceAudioHalClient::Callbacks::OnAudioSuspend, - base::Unretained(audioSourceCallbacks_), - std::move(do_suspend_promise))); + base::Unretained(audioSourceCallbacks_))); if (status == BT_STATUS_SUCCESS) { - do_suspend_future.wait(); return true; } diff --git a/system/bta/le_audio/broadcaster/broadcaster.cc b/system/bta/le_audio/broadcaster/broadcaster.cc index a066d80b49..50269141ee 100644 --- a/system/bta/le_audio/broadcaster/broadcaster.cc +++ b/system/bta/le_audio/broadcaster/broadcaster.cc @@ -1037,11 +1037,9 @@ class LeAudioBroadcasterImpl : public LeAudioBroadcaster, public BigCallbacks { LOG_VERBOSE("All data sent."); } - virtual void OnAudioSuspend( - std::promise<void> do_suspend_promise) override { + virtual void OnAudioSuspend(void) override { LOG_INFO(); /* TODO: Should we suspend all broadcasts - remove BIGs? */ - do_suspend_promise.set_value(); if (instance) instance->audio_data_path_state_ = AudioDataPathState::SUSPENDED; } diff --git a/system/bta/le_audio/client.cc b/system/bta/le_audio/client.cc index 63b304dcf2..e565eb54ce 100644 --- a/system/bta/le_audio/client.cc +++ b/system/bta/le_audio/client.cc @@ -251,6 +251,7 @@ class LeAudioClientImpl : public LeAudioClient { audio_receiver_state_(AudioState::IDLE), audio_sender_state_(AudioState::IDLE), in_call_(false), + in_voip_call_(false), current_source_codec_config({0, 0, 0, 0}), current_sink_codec_config({0, 0, 0, 0}), lc3_encoder_left_mem(nullptr), @@ -304,6 +305,10 @@ class LeAudioClientImpl : public LeAudioClient { void ReconfigureAfterVbcClose() { LOG_DEBUG("VBC close timeout"); + if (IsInVoipCall()) { + SetInVoipCall(false); + } + auto group = aseGroups_.FindById(active_group_id_); if (!group) { LOG_ERROR("Invalid group: %d", active_group_id_); @@ -917,11 +922,18 @@ class LeAudioClientImpl : public LeAudioClient { remote_contexts.source.clear(); } + /* Do not put the TBS CCID when not using Telecom for the VoIP calls. */ + auto ccid_contexts = remote_contexts; + if (IsInVoipCall() && !IsInCall()) { + ccid_contexts.sink.unset(LeAudioContextType::CONVERSATIONAL); + ccid_contexts.source.unset(LeAudioContextType::CONVERSATIONAL); + } + BidirectionalPair<std::vector<uint8_t>> ccids = { .sink = ContentControlIdKeeper::GetInstance()->GetAllCcids( - remote_contexts.sink), + ccid_contexts.sink), .source = ContentControlIdKeeper::GetInstance()->GetAllCcids( - remote_contexts.source)}; + ccid_contexts.source)}; if (group->IsPendingConfiguration()) { return groupStateMachine_->ConfigureStream( group, configuration_context_type_, remote_contexts, ccids); @@ -1037,6 +1049,15 @@ class LeAudioClientImpl : public LeAudioClient { in_call_ = in_call; } + bool IsInCall() override { return in_call_; } + + void SetInVoipCall(bool in_call) override { + LOG_DEBUG("in_voip_call: %d", in_call); + in_voip_call_ = in_call; + } + + bool IsInVoipCall() override { return in_voip_call_; } + void SendAudioProfilePreferences( const int group_id, bool is_output_preference_le_audio, bool is_duplex_preference_le_audio) override { @@ -1544,8 +1565,7 @@ class LeAudioClientImpl : public LeAudioClient { dev->SetConnectionState(DeviceConnectState::DISCONNECTED); } } - if (group->GetState() == - le_audio::types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { + if (group->IsStreaming() || !group->IsReleasingOrIdle()) { leAudioDevice->closing_stream_for_disconnection_ = true; groupStateMachine_->StopStream(group); return; @@ -1783,35 +1803,35 @@ class LeAudioClientImpl : public LeAudioClient { contexts, len, value)) { return; } - leAudioDevice->SetAvailableContexts(contexts); - leAudioDevice->SetAvailableContexts(contexts); - if (!group) { + leAudioDevice->SetAvailableContexts(contexts); + + if (!group) { return; - } + } - /* Check if we should attach to stream this device */ - if (group->IsInTransition() || !group->IsStreaming()) { + /* Check if we should attach to stream this device */ + if (group->IsInTransition() || !group->IsStreaming()) { return; - } + } - if (leAudioDevice->HaveActiveAse()) { + if (leAudioDevice->HaveActiveAse()) { /* Do nothing, device is streaming */ return; - } + } - if (leAudioDevice->GetConnectionState() != - DeviceConnectState::CONNECTED) { + if (leAudioDevice->GetConnectionState() != + DeviceConnectState::CONNECTED) { /* Do nothing, wait until device is connected */ return; - } + } - auto group_metadata_contexts = - get_bidirectional(group->GetMetadataContexts()); - auto device_available_contexts = leAudioDevice->GetAvailableContexts(); - if (group_metadata_contexts.test_any(device_available_contexts)) { + auto group_metadata_contexts = + get_bidirectional(group->GetMetadataContexts()); + auto device_available_contexts = leAudioDevice->GetAvailableContexts(); + if (group_metadata_contexts.test_any(device_available_contexts)) { AttachToStreamingGroupIfNeeded(leAudioDevice); - } + } } else if (hdl == leAudioDevice->audio_supp_cont_hdls_.val_hdl) { BidirectionalPair<AudioContexts> supp_audio_contexts; @@ -2943,7 +2963,20 @@ class LeAudioClientImpl : public LeAudioClient { return; } - if (!groupStateMachine_->AttachToStream(group, leAudioDevice)) { + /* Do not put the TBS CCID when not using Telecom for the VoIP calls. */ + auto ccid_contexts = group->GetMetadataContexts(); + if (IsInVoipCall() && !IsInCall()) { + ccid_contexts.sink.unset(LeAudioContextType::CONVERSATIONAL); + ccid_contexts.source.unset(LeAudioContextType::CONVERSATIONAL); + } + BidirectionalPair<std::vector<uint8_t>> ccids = { + .sink = ContentControlIdKeeper::GetInstance()->GetAllCcids( + ccid_contexts.sink), + .source = ContentControlIdKeeper::GetInstance()->GetAllCcids( + ccid_contexts.source)}; + + if (!groupStateMachine_->AttachToStream(group, leAudioDevice, + std::move(ccids))) { LOG_WARN("Could not add device %s to the group %d streaming. ", ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_), group->group_id_); @@ -3898,7 +3931,7 @@ class LeAudioClientImpl : public LeAudioClient { } /* Check if the device resume is expected */ - if (!group->GetCachedCodecConfigurationByDirection( + if (!group->GetCodecConfigurationByDirection( configuration_context_type_, le_audio::types::kLeAudioDirectionSink)) { LOG(ERROR) << __func__ << ", invalid resume request for context type: " @@ -4324,7 +4357,7 @@ class LeAudioClientImpl : public LeAudioClient { bluetooth::common::ToString(available_remote_contexts).c_str(), bluetooth::common::ToString(configuration_context_type_).c_str()); - if (in_call_) { + if (IsInCall()) { LOG_DEBUG(" In Call preference used."); return LeAudioContextType::CONVERSATIONAL; } @@ -4336,10 +4369,10 @@ class LeAudioClientImpl : public LeAudioClient { LeAudioContextType context_priority_list[] = { /* Highest priority first */ LeAudioContextType::CONVERSATIONAL, - /* Skip the RINGTONE to avoid reconfigurations when adjusting - * call volume slider while not in a call. - * LeAudioContextType::RINGTONE, + /* Handling RINGTONE will cause the ringtone volume slider to trigger + * reconfiguration. This will be fixed in b/283349711. */ + LeAudioContextType::RINGTONE, LeAudioContextType::LIVE, LeAudioContextType::VOICEASSISTANTS, LeAudioContextType::GAME, @@ -4600,11 +4633,29 @@ class LeAudioClientImpl : public LeAudioClient { BidirectionalPair<AudioContexts> DirectionalRealignMetadataAudioContexts( LeAudioDeviceGroup* group, int remote_direction) { + // Inject conversational when ringtone is played - this is required for all + // the VoIP applications which are not using the telecom API. + if ((remote_direction == le_audio::types::kLeAudioDirectionSink) && + local_metadata_context_types_.source.test( + LeAudioContextType::RINGTONE)) { + /* Simulate, we are already in the call. Sending RINGTONE when there is + * no incoming call to accept or reject on TBS could confuse the remote + * device and interrupt the stream establish procedure. + */ + if (!IsInCall()) { + SetInVoipCall(true); + } + } else if (IsInVoipCall()) { + SetInVoipCall(false); + } + /* Make sure we have CONVERSATIONAL when in a call and it is not mixed * with any other bidirectional context */ - if (in_call_) { - LOG_DEBUG(" In Call preference used."); + if (IsInCall() || IsInVoipCall()) { + LOG_DEBUG(" In Call preference used: %s, voip call: %s", + (IsInCall() ? "true" : "false"), + (IsInVoipCall() ? "true" : "false")); local_metadata_context_types_.sink.unset_all(kLeAudioContextAllBidir); local_metadata_context_types_.source.unset_all(kLeAudioContextAllBidir); local_metadata_context_types_.sink.set( @@ -4634,6 +4685,12 @@ class LeAudioClientImpl : public LeAudioClient { BidirectionalPair<AudioContexts> remote_metadata = { .sink = local_metadata_context_types_.source, .source = local_metadata_context_types_.sink}; + + if (IsInVoipCall()) { + LOG_DEBUG("Unsetting RINGTONE from remote sink "); + remote_metadata.sink.unset(LeAudioContextType::RINGTONE); + } + LOG_DEBUG("local_metadata_context_types_.source= %s", ToString(local_metadata_context_types_.source).c_str()); LOG_DEBUG("local_metadata_context_types_.sink= %s", @@ -5135,11 +5192,13 @@ class LeAudioClientImpl : public LeAudioClient { false)) { return; } + /* If group is inactive, phone is in call and Group is not having CIS * connected, notify upper layer about it, so it can decide to create SCO if * it is in the handover case */ - if (in_call_ && active_group_id_ == bluetooth::groups::kGroupUnknown) { + if ((IsInCall() || IsInVoipCall()) && + active_group_id_ == bluetooth::groups::kGroupUnknown) { callbacks_->OnGroupStatus(group_id, GroupStatus::TURNED_IDLE_DURING_CALL); } } @@ -5313,6 +5372,7 @@ class LeAudioClientImpl : public LeAudioClient { AudioState audio_sender_state_; /* Keep in call state. */ bool in_call_; + bool in_voip_call_; /* Reconnection mode */ tBTM_BLE_CONN_TYPE reconnection_mode_; @@ -5524,9 +5584,8 @@ class SourceCallbacksImpl : public LeAudioSourceAudioHalClient::Callbacks { void OnAudioDataReady(const std::vector<uint8_t>& data) override { if (instance) instance->OnAudioDataReady(data); } - void OnAudioSuspend(std::promise<void> do_suspend_promise) override { + void OnAudioSuspend(void) override { if (instance) instance->OnLocalAudioSourceSuspend(); - do_suspend_promise.set_value(); } void OnAudioResume(void) override { @@ -5542,9 +5601,8 @@ class SourceCallbacksImpl : public LeAudioSourceAudioHalClient::Callbacks { class SinkCallbacksImpl : public LeAudioSinkAudioHalClient::Callbacks { public: - void OnAudioSuspend(std::promise<void> do_suspend_promise) override { + void OnAudioSuspend(void) override { if (instance) instance->OnLocalAudioSinkSuspend(); - do_suspend_promise.set_value(); } void OnAudioResume(void) override { if (instance) instance->OnLocalAudioSinkResume(); diff --git a/system/bta/le_audio/le_audio_client_test.cc b/system/bta/le_audio/le_audio_client_test.cc index 0b4a43fc3d..cf11893d70 100644 --- a/system/bta/le_audio/le_audio_client_test.cc +++ b/system/bta/le_audio/le_audio_client_test.cc @@ -739,16 +739,18 @@ class UnicastTestNoInit : public Test { return true; }); - ON_CALL(mock_state_machine_, AttachToStream(_, _)) + ON_CALL(mock_state_machine_, AttachToStream(_, _, _)) .WillByDefault([](LeAudioDeviceGroup* group, - LeAudioDevice* leAudioDevice) { + LeAudioDevice* leAudioDevice, + types::BidirectionalPair<std::vector<uint8_t>> + ccids) { if (group->GetState() != types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { return false; } group->Configure(group->GetConfigurationContextType(), - group->GetMetadataContexts()); + group->GetMetadataContexts(), ccids); if (!group->CigAssignCisIds(leAudioDevice)) return false; group->CigAssignCisConnHandlesToAses(leAudioDevice); @@ -1694,10 +1696,7 @@ class UnicastTestNoInit : public Test { void LocalAudioSourceSuspend(void) { ASSERT_NE(unicast_source_hal_cb_, nullptr); - std::promise<void> do_suspend_sink_promise; - auto do_suspend_sink_future = do_suspend_sink_promise.get_future(); - unicast_source_hal_cb_->OnAudioSuspend(std::move(do_suspend_sink_promise)); - do_suspend_sink_future.wait(); + unicast_source_hal_cb_->OnAudioSuspend(); } void LocalAudioSourceResume(bool expected_confirmation = true) { @@ -1719,10 +1718,7 @@ class UnicastTestNoInit : public Test { void LocalAudioSinkSuspend(void) { ASSERT_NE(unicast_sink_hal_cb_, nullptr); - std::promise<void> do_suspend_source_promise; - auto do_suspend_source_future = do_suspend_source_promise.get_future(); - unicast_sink_hal_cb_->OnAudioSuspend(std::move(do_suspend_source_promise)); - do_suspend_source_future.wait(); + unicast_sink_hal_cb_->OnAudioSuspend(); } void LocalAudioSinkResume(void) { @@ -1775,8 +1771,6 @@ class UnicastTestNoInit : public Test { * might have different state that it is in the le_audio code - as tearing * down CISes might take some time */ - std::promise<void> do_suspend_sink_promise; - auto do_suspend_sink_future = do_suspend_sink_promise.get_future(); /* It's enough to call only one resume even if it'll be bi-directional * streaming. First suspend will trigger GroupStop. * @@ -1784,16 +1778,11 @@ class UnicastTestNoInit : public Test { * If there will be such test oriented scenario, such resume choose logic * should be applied. */ - unicast_source_hal_cb_->OnAudioSuspend(std::move(do_suspend_sink_promise)); - do_suspend_sink_future.wait(); + unicast_source_hal_cb_->OnAudioSuspend(); if (suspend_source) { ASSERT_NE(unicast_sink_hal_cb_, nullptr); - std::promise<void> do_suspend_source_promise; - auto do_suspend_source_future = do_suspend_source_promise.get_future(); - unicast_sink_hal_cb_->OnAudioSuspend( - std::move(do_suspend_source_promise)); - do_suspend_source_future.wait(); + unicast_sink_hal_cb_->OnAudioSuspend(); } } @@ -4164,6 +4153,88 @@ TEST_F(UnicastTest, RemoveWhileStreaming) { ASSERT_EQ(group, nullptr); } +TEST_F(UnicastTest, DisconnecteWhileAlmostStreaming) { + const RawAddress test_address0 = GetTestAddress(0); + int group_id = bluetooth::groups::kGroupUnknown; + + SetSampleDatabaseEarbudsValid( + 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, + codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, + default_channel_cnt, 0x0004, + /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, + true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, + 0 /*rank*/); + EXPECT_CALL(mock_audio_hal_client_callbacks_, + OnConnectionState(ConnectionState::CONNECTED, test_address0)) + .Times(1); + EXPECT_CALL(mock_audio_hal_client_callbacks_, + OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) + .WillOnce(DoAll(SaveArg<1>(&group_id))); + + ConnectLeAudio(test_address0); + ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); + + constexpr int gmcs_ccid = 1; + constexpr int gtbs_ccid = 2; + + // Audio sessions are started only when device gets active + EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _)).Times(1); + EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _)).Times(1); + LeAudioClient::Get()->SetCcidInformation(gmcs_ccid, 4 /* Media */); + LeAudioClient::Get()->SetCcidInformation(gtbs_ccid, 2 /* Phone */); + LeAudioClient::Get()->GroupSetActive(group_id); + + types::BidirectionalPair<std::vector<uint8_t>> ccids = {.sink = {gmcs_ccid}, + .source = {}}; + EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, ccids)).Times(1); + + /* We want here to CIS be established but device not being yet in streaming + * state + */ + StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); + + SyncOnMainLoop(); + Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); + Mock::VerifyAndClearExpectations(&mock_le_audio_source_hal_client_); + Mock::VerifyAndClearExpectations(&mock_state_machine_); + SyncOnMainLoop(); + + /* This is test code, which will change the group state to the one which + * is required by test + */ + auto group_inject = streaming_groups.at(group_id); + group_inject->SetState(types::AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING); + + EXPECT_CALL(mock_state_machine_, StopStream(_)).Times(1); + + LeAudioDeviceGroup* group = nullptr; + EXPECT_CALL(mock_state_machine_, ProcessHciNotifAclDisconnected(_, _)) + .WillOnce(DoAll(SaveArg<0>(&group))); + + EXPECT_CALL(mock_audio_hal_client_callbacks_, + OnConnectionState(ConnectionState::DISCONNECTED, test_address0)) + .Times(1); + + /* + * StopStream will put calls on main_loop so to keep the correct order + * of operations and to avoid races we put the test command on main_loop as + * well. + */ + do_in_main_thread(FROM_HERE, base::BindOnce( + [](LeAudioClient* client, + const RawAddress& test_address0) { + client->Disconnect(test_address0); + }, + LeAudioClient::Get(), test_address0)); + + SyncOnMainLoop(); + Mock::VerifyAndClearExpectations(&mock_groups_module_); + Mock::VerifyAndClearExpectations(&mock_state_machine_); + Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); + + ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); +} + TEST_F(UnicastTest, EarbudsTwsStyleStreaming) { const RawAddress test_address0 = GetTestAddress(0); int group_id = bluetooth::groups::kGroupUnknown; diff --git a/system/bta/le_audio/mock_state_machine.h b/system/bta/le_audio/mock_state_machine.h index 02bad5f30a..5a2be11b9e 100644 --- a/system/bta/le_audio/mock_state_machine.h +++ b/system/bta/le_audio/mock_state_machine.h @@ -33,7 +33,8 @@ class MockLeAudioGroupStateMachine : public le_audio::LeAudioGroupStateMachine { (override)); MOCK_METHOD((bool), AttachToStream, (le_audio::LeAudioDeviceGroup * group, - le_audio::LeAudioDevice* leAudioDevice), + le_audio::LeAudioDevice* leAudioDevice, + le_audio::types::BidirectionalPair<std::vector<uint8_t>> ccids), (override)); MOCK_METHOD((void), SuspendStream, (le_audio::LeAudioDeviceGroup * group), (override)); diff --git a/system/bta/le_audio/state_machine.cc b/system/bta/le_audio/state_machine.cc index 3ead35ff49..8a20bc427d 100644 --- a/system/bta/le_audio/state_machine.cc +++ b/system/bta/le_audio/state_machine.cc @@ -137,8 +137,8 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { log_history_ = nullptr; } - bool AttachToStream(LeAudioDeviceGroup* group, - LeAudioDevice* leAudioDevice) override { + bool AttachToStream(LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice, + BidirectionalPair<std::vector<uint8_t>> ccids) override { LOG(INFO) << __func__ << " group id: " << group->group_id_ << " device: " << ADDRESS_TO_LOGGABLE_STR(leAudioDevice->address_); @@ -155,11 +155,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { return false; } - BidirectionalPair<std::vector<uint8_t>> ccids = { - .sink = le_audio::ContentControlIdKeeper::GetInstance()->GetAllCcids( - group->GetMetadataContexts().sink), - .source = le_audio::ContentControlIdKeeper::GetInstance()->GetAllCcids( - group->GetMetadataContexts().source)}; if (!group->Configure(group->GetConfigurationContextType(), group->GetMetadataContexts(), ccids)) { LOG_ERROR(" failed to set ASE configuration"); diff --git a/system/bta/le_audio/state_machine.h b/system/bta/le_audio/state_machine.h index e4d92e8af0..44a4d54877 100644 --- a/system/bta/le_audio/state_machine.h +++ b/system/bta/le_audio/state_machine.h @@ -45,8 +45,9 @@ class LeAudioGroupStateMachine { static void Cleanup(void); static LeAudioGroupStateMachine* Get(void); - virtual bool AttachToStream(LeAudioDeviceGroup* group, - LeAudioDevice* leAudioDevice) = 0; + virtual bool AttachToStream( + LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice, + types::BidirectionalPair<std::vector<uint8_t>> ccids) = 0; virtual bool StartStream( LeAudioDeviceGroup* group, types::LeAudioContextType context_type, const types::BidirectionalPair<types::AudioContexts>& diff --git a/system/bta/le_audio/state_machine_test.cc b/system/bta/le_audio/state_machine_test.cc index 89cf100377..d83a9077d2 100644 --- a/system/bta/le_audio/state_machine_test.cc +++ b/system/bta/le_audio/state_machine_test.cc @@ -3323,7 +3323,8 @@ TEST_F(StateMachineTest, testAttachDeviceToTheStream) { EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(1); - LeAudioGroupStateMachine::Get()->AttachToStream(group, lastDevice); + LeAudioGroupStateMachine::Get()->AttachToStream( + group, lastDevice, {.sink = {media_ccid}, .source = {}}); // Check if group keeps streaming ASSERT_EQ(group->GetState(), @@ -3404,8 +3405,8 @@ TEST_F(StateMachineTest, testAttachDeviceToTheStreamDoNotAttach) { LeAudioGroupStateMachine::Get()->StopStream(group); testing::Mock::VerifyAndClearExpectations(&mock_callbacks_); - ASSERT_FALSE( - LeAudioGroupStateMachine::Get()->AttachToStream(group, lastDevice)); + ASSERT_FALSE(LeAudioGroupStateMachine::Get()->AttachToStream( + group, lastDevice, {.sink = {}, .source = {}})); } TEST_F(StateMachineTest, testReconfigureAfterLateDeviceAttached) { @@ -3664,7 +3665,8 @@ TEST_F(StateMachineTest, testAttachDeviceToTheConversationalStream) { EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2); - LeAudioGroupStateMachine::Get()->AttachToStream(group, lastDevice); + LeAudioGroupStateMachine::Get()->AttachToStream( + group, lastDevice, {.sink = {call_ccid}, .source = {call_ccid}}); // Check if group keeps streaming ASSERT_EQ(group->GetState(), @@ -4754,7 +4756,8 @@ TEST_F(StateMachineTest, testAttachDeviceToTheStreamCisFailure) { EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0); - LeAudioGroupStateMachine::Get()->AttachToStream(group, lastDevice); + LeAudioGroupStateMachine::Get()->AttachToStream( + group, lastDevice, {.sink = {media_ccid}, .source = {}}); // Check if group keeps streaming ASSERT_EQ(group->GetState(), diff --git a/system/bta/test/bta_dm_test.cc b/system/bta/test/bta_dm_test.cc index 43c3c2a794..f40bb815d7 100644 --- a/system/bta/test/bta_dm_test.cc +++ b/system/bta/test/bta_dm_test.cc @@ -42,6 +42,7 @@ #include "test/mock/mock_osi_alarm.h" #include "test/mock/mock_osi_allocator.h" #include "test/mock/mock_stack_acl.h" +#include "test/mock/mock_stack_btm.h" #include "test/mock/mock_stack_btm_ble.h" #include "test/mock/mock_stack_btm_inq.h" #include "test/mock/mock_stack_btm_sec.h" @@ -50,8 +51,6 @@ using namespace std::chrono_literals; using ::testing::ElementsAre; -extern struct btm_client_interface_t btm_client_interface; - namespace base { class MessageLoop; } // namespace base @@ -106,6 +105,7 @@ class BtaDmTest : public testing::Test { bluetooth::legacy::testing::bta_dm_deinit_cb(); post_on_bt_main([]() { LOG_INFO("Main thread shutting down"); }); main_thread_shut_down(); + btm_client_interface = {}; } std::unique_ptr<test::fake::FakeOsi> fake_osi_; @@ -392,6 +392,11 @@ TEST_F(BtaDmTest, bta_dm_remname_cback__typical) { strlcpy(reinterpret_cast<char*>(&name.remote_bd_name), kRemoteName, strlen(kRemoteName)); + btm_client_interface.security.BTM_SecDeleteRmtNameNotifyCallback = + [](tBTM_RMT_NAME_CALLBACK*) -> bool { + inc_func_call_count("BTM_SecDeleteRmtNameNotifyCallback"); + return true; + }; bluetooth::legacy::testing::bta_dm_remname_cback(&name); sync_main_handler(); @@ -416,6 +421,11 @@ TEST_F(BtaDmTest, bta_dm_remname_cback__wrong_address) { strlcpy(reinterpret_cast<char*>(&name.remote_bd_name), kRemoteName, strlen(kRemoteName)); + btm_client_interface.security.BTM_SecDeleteRmtNameNotifyCallback = + [](tBTM_RMT_NAME_CALLBACK*) -> bool { + inc_func_call_count("BTM_SecDeleteRmtNameNotifyCallback"); + return true; + }; bluetooth::legacy::testing::bta_dm_remname_cback(&name); sync_main_handler(); @@ -440,6 +450,11 @@ TEST_F(BtaDmTest, bta_dm_remname_cback__HCI_ERR_CONNECTION_EXISTS) { strlcpy(reinterpret_cast<char*>(&name.remote_bd_name), kRemoteName, strlen(kRemoteName)); + btm_client_interface.security.BTM_SecDeleteRmtNameNotifyCallback = + [](tBTM_RMT_NAME_CALLBACK*) -> bool { + inc_func_call_count("BTM_SecDeleteRmtNameNotifyCallback"); + return true; + }; bluetooth::legacy::testing::bta_dm_remname_cback(&name); sync_main_handler(); diff --git a/system/bta/test/bta_sdp_test.cc b/system/bta/test/bta_sdp_test.cc index b91c44d51a..52775f0cc2 100644 --- a/system/bta/test/bta_sdp_test.cc +++ b/system/bta/test/bta_sdp_test.cc @@ -30,6 +30,10 @@ void BTA_dm_on_hw_on(); void BTA_dm_on_hw_off(); +namespace { +const char kName[] = "Hello"; +} + class BtaSdpTest : public testing::Test { protected: void SetUp() override { @@ -100,5 +104,9 @@ TEST_F(BtaSdpRegisteredTest, bta_dm_sdp_result_SDP_SUCCESS) { .sdp_result = SDP_SUCCESS, }, }; + btm_client_interface.security.BTM_SecReadDevName = + [](const RawAddress& bd_addr) -> const char* { return kName; }; + btm_client_interface.security.BTM_SecDeleteRmtNameNotifyCallback = + [](tBTM_RMT_NAME_CALLBACK*) -> bool { return true; }; bta_dm_sdp_result(&msg); } diff --git a/system/btcore/Android.bp b/system/btcore/Android.bp index 0361a809c0..5e4b9de0dd 100644 --- a/system/btcore/Android.bp +++ b/system/btcore/Android.bp @@ -10,7 +10,7 @@ package { cc_defaults { name: "libbtcore_defaults", - defaults: ["fluoride_basic_defaults"], + defaults: ["fluoride_defaults"], local_include_dirs: ["include"], include_dirs: [ "packages/modules/Bluetooth/system", @@ -36,7 +36,7 @@ cc_defaults { cc_library_static { name: "libbthalutils", - defaults: ["fluoride_basic_defaults"], + defaults: ["fluoride_defaults"], srcs: [ "src/hal_util.cc", ], diff --git a/system/btif/Android.bp b/system/btif/Android.bp index 08df424495..d7e341ed6d 100644 --- a/system/btif/Android.bp +++ b/system/btif/Android.bp @@ -39,9 +39,6 @@ cc_library { generated_sources: ["statslog_bt.cpp"], generated_headers: ["statslog_bt.h"], export_generated_headers: ["statslog_bt.h"], - shared_libs: [ - "libcutils", - ], apex_available: [ "com.android.btservices", ], @@ -57,6 +54,7 @@ cc_library { }, host: { static_libs: [ + "libbase", "libstatssocket", ], export_static_lib_headers: [ @@ -89,7 +87,7 @@ genrule { cc_library_static { name: "libbtif", - defaults: ["fluoride_basic_defaults"], + defaults: ["fluoride_defaults"], include_dirs: btifCommonIncludes, srcs: [ // AVRCP Target Service @@ -162,13 +160,16 @@ cc_library_static { cflags: [ "-DBUILDCFG", ], + apex_available: [ + "com.android.btservices", + ], host_supported: true, min_sdk_version: "Tiramisu", } cc_library_static { name: "libbtif-core", - defaults: ["fluoride_basic_defaults"], + defaults: ["fluoride_defaults"], include_dirs: btifCommonIncludes, srcs: [ // Callouts @@ -237,6 +238,9 @@ cc_library_static { "-DBUILDCFG", "-fvisibility=default", ], + apex_available: [ + "com.android.btservices", + ], host_supported: true, min_sdk_version: "Tiramisu", } @@ -269,7 +273,7 @@ cc_test { "libutils", ], static_libs: [ - "android.hardware.audio.common-V1-ndk", + "android.hardware.audio.common-V2-ndk", "android.hardware.bluetooth.a2dp@1.0", "android.hardware.bluetooth.audio-V3-ndk", "android.hardware.bluetooth@1.0", @@ -286,7 +290,6 @@ cc_test { "libbt-bta-core", "libbt-common", "libbt-hci", - "libbt-protos-lite", "libbt-sbc-decoder", "libbt-sbc-encoder", "libbt-stack", @@ -343,7 +346,6 @@ cc_test { static_libs: [ "libbluetooth-types", "libchrome", - "libflatbuffers-cpp", "libosi", ], cflags: ["-DBUILDCFG"], @@ -376,7 +378,6 @@ cc_test { static_libs: [ "libbluetooth-types", "libchrome", - "libflatbuffers-cpp", "libosi", ], cflags: ["-DBUILDCFG"], @@ -413,7 +414,6 @@ cc_test { static_libs: [ "libbluetooth-types", "libbt-common", - "libbt-protos-lite", "libchrome", "libevent", "libosi-AllocationTestHarness", @@ -607,14 +607,13 @@ cc_test { "libchrome", "libcom.android.sysprop.bluetooth", "libevent", - "libflatbuffers-cpp", "libgmock", ], cflags: ["-DBUILDCFG"], target: { android: { static_libs: [ - "android.hardware.audio.common-V1-ndk", + "android.hardware.audio.common-V2-ndk", "android.hardware.bluetooth.audio-V3-ndk", "android.hardware.common-V2-ndk", "android.hardware.common.fmq-V1-ndk", @@ -739,14 +738,13 @@ cc_test { "libchrome", "libcom.android.sysprop.bluetooth", "libevent", - "libflatbuffers-cpp", "libgmock", ], cflags: ["-DBUILDCFG"], target: { android: { static_libs: [ - "android.hardware.audio.common-V1-ndk", + "android.hardware.audio.common-V2-ndk", "android.hardware.bluetooth.audio-V3-ndk", "android.hardware.common-V2-ndk", "android.hardware.common.fmq-V1-ndk", diff --git a/system/btif/co/bta_av_co.cc b/system/btif/co/bta_av_co.cc index 42711010dc..e1724b21ea 100644 --- a/system/btif/co/bta_av_co.cc +++ b/system/btif/co/bta_av_co.cc @@ -406,13 +406,6 @@ class BtaAvCo { const tA2DP_ENCODER_INTERFACE* GetSourceEncoderInterface(); /** - * Get the Sink decoder interface for the current codec. - * - * @return the Sink decoder interface for the current codec - */ - const tA2DP_DECODER_INTERFACE* GetSinkDecoderInterface(); - - /** * Set the codec user configuration. * * @param peer_address the peer address @@ -1446,8 +1439,8 @@ void BtaAvCo::UpdateMtu(tBTA_AV_HNDL bta_av_handle, } bool BtaAvCo::SetActivePeer(const RawAddress& peer_address) { - VLOG(1) << __func__ << ": peer_address=" - << ADDRESS_TO_LOGGABLE_STR(peer_address); + LOG(INFO) << __func__ + << ": peer_address=" << ADDRESS_TO_LOGGABLE_STR(peer_address); std::lock_guard<std::recursive_mutex> lock(codec_lock_); @@ -1505,12 +1498,6 @@ const tA2DP_ENCODER_INTERFACE* BtaAvCo::GetSourceEncoderInterface() { return A2DP_GetEncoderInterface(codec_config_); } -const tA2DP_DECODER_INTERFACE* BtaAvCo::GetSinkDecoderInterface() { - std::lock_guard<std::recursive_mutex> lock(codec_lock_); - - return A2DP_GetDecoderInterface(codec_config_); -} - bool BtaAvCo::SetCodecUserConfig( const RawAddress& peer_address, const btav_a2dp_codec_config_t& codec_user_config, bool* p_restart_output) { @@ -2263,10 +2250,6 @@ const tA2DP_ENCODER_INTERFACE* bta_av_co_get_encoder_interface(void) { return bta_av_co_cb.GetSourceEncoderInterface(); } -const tA2DP_DECODER_INTERFACE* bta_av_co_get_decoder_interface(void) { - return bta_av_co_cb.GetSinkDecoderInterface(); -} - bool bta_av_co_set_codec_user_config( const RawAddress& peer_address, const btav_a2dp_codec_config_t& codec_user_config, bool* p_restart_output) { diff --git a/system/btif/include/btif_av_co.h b/system/btif/include/btif_av_co.h index 1ce810f5e3..d7f80ab559 100644 --- a/system/btif/include/btif_av_co.h +++ b/system/btif/include/btif_av_co.h @@ -40,12 +40,6 @@ void bta_av_co_get_peer_params(const RawAddress& peer_addr, // otherwise NULL. const tA2DP_ENCODER_INTERFACE* bta_av_co_get_encoder_interface(void); -// Gets the current A2DP decoder interface that can be used to decode received -// A2DP packets - see |tA2DP_DECODER_INTERFACE|. -// Returns the A2DP decoder interface if the current codec is setup, otherwise -// NULL. -const tA2DP_DECODER_INTERFACE* bta_av_co_get_decoder_interface(void); - // Sets the user preferred codec configuration. // The peer address is |peer_addr|. // |codec_user_config| contains the preferred codec configuration. diff --git a/system/btif/src/btif_a2dp_sink.cc b/system/btif/src/btif_a2dp_sink.cc index 9a03a3cd44..4340e48375 100644 --- a/system/btif/src/btif_a2dp_sink.cc +++ b/system/btif/src/btif_a2dp_sink.cc @@ -631,7 +631,8 @@ static void btif_a2dp_sink_decoder_update_event( btif_a2dp_sink_cb.rx_flush = false; APPL_TRACE_DEBUG("%s: reset to Sink role", __func__); - btif_a2dp_sink_cb.decoder_interface = bta_av_co_get_decoder_interface(); + btif_a2dp_sink_cb.decoder_interface = + A2DP_GetDecoderInterface(p_buf->codec_info); if (btif_a2dp_sink_cb.decoder_interface == nullptr) { LOG_ERROR("%s: cannot stream audio: no source decoder interface", __func__); return; diff --git a/system/btif/src/btif_av.cc b/system/btif/src/btif_av.cc index 767279e724..23f78fd1e8 100644 --- a/system/btif/src/btif_av.cc +++ b/system/btif/src/btif_av.cc @@ -4035,9 +4035,9 @@ uint8_t btif_av_get_peer_sep(void) { } uint8_t peer_sep = peer->PeerSep(); - LOG_INFO("Peer %s SEP is %s (%d)", - ADDRESS_TO_LOGGABLE_CSTR(peer->PeerAddress()), - (peer_sep == AVDT_TSEP_SRC) ? "Source" : "Sink", peer_sep); + BTIF_TRACE_DEBUG("Peer %s SEP is %s (%d)", + ADDRESS_TO_LOGGABLE_CSTR(peer->PeerAddress()), + (peer_sep == AVDT_TSEP_SRC) ? "Source" : "Sink", peer_sep); return peer_sep; } diff --git a/system/btif/src/btif_avrcp_audio_track.cc b/system/btif/src/btif_avrcp_audio_track.cc index 296c603c7f..2a732f372e 100644 --- a/system/btif/src/btif_avrcp_audio_track.cc +++ b/system/btif/src/btif_avrcp_audio_track.cc @@ -51,8 +51,8 @@ constexpr float kMinTrackGain = 0.0f; void* BtifAvrcpAudioTrackCreate(int trackFreq, int bitsPerSample, int channelCount) { - LOG_VERBOSE("%s Track.cpp: btCreateTrack freq %d bps %d channel %d ", - __func__, trackFreq, bitsPerSample, channelCount); + LOG_INFO("%s Track.cpp: btCreateTrack freq %d bps %d channel %d ", __func__, + trackFreq, bitsPerSample, channelCount); AAudioStreamBuilder* builder; AAudioStream* stream; diff --git a/system/btif/src/btif_dm.cc b/system/btif/src/btif_dm.cc index 1521897450..bfe4c6f41a 100644 --- a/system/btif/src/btif_dm.cc +++ b/system/btif/src/btif_dm.cc @@ -602,11 +602,6 @@ static void bond_state_changed(bt_status_t status, const RawAddress& bd_addr, GetInterfaceToProfiles()->events->invoke_bond_state_changed_cb( status, bd_addr, state, pairing_cb.fail_reason); - int dev_type; - if (!btif_get_device_type(bd_addr, &dev_type)) { - dev_type = BT_DEVICE_TYPE_BREDR; - } - if ((state == BT_BOND_STATE_NONE) && (pairing_cb.bd_addr != bd_addr) && is_bonding_or_sdp()) { LOG_WARN("Ignoring bond state changed for unexpected device: %s pairing: %s", diff --git a/system/btif/src/btif_rc.cc b/system/btif/src/btif_rc.cc index c5c00eb1e1..2daaacb1e3 100644 --- a/system/btif/src/btif_rc.cc +++ b/system/btif/src/btif_rc.cc @@ -3957,6 +3957,9 @@ static void handle_get_metadata_attr_response(tBTA_AV_META_MSG* pmeta_msg, osi_free_and_reset((void**)&p_rsp->p_attrs[i].name.p_str); } } + + osi_free_and_reset((void**)&p_rsp->p_attrs); + do_in_jni_thread(FROM_HERE, base::Bind(bt_rc_ctrl_callbacks->track_changed_cb, p_dev->rc_addr, p_rsp->num_attrs, p_attr)); diff --git a/system/btif/test/btif_rc_test.cc b/system/btif/test/btif_rc_test.cc index c10cee2634..bd213221a4 100644 --- a/system/btif/test/btif_rc_test.cc +++ b/system/btif/test/btif_rc_test.cc @@ -426,3 +426,42 @@ TEST_F(BtifRcConnectionTest, BTA_AV_RC_OPEN_EVT) { auto res = future.get(); CHECK(res.rc_state == true); } + +class BtifTrackChangeCBTest : public BtifRcTest { + protected: + void SetUp() override { + BtifRcTest::SetUp(); + init_ctrl(&btrc_ctrl_callbacks); + jni_thread.StartUp(); + btrc_ctrl_callbacks.track_changed_cb = [](const RawAddress& bd_addr, + uint8_t num_attr, btrc_element_attr_val_t* p_attrs) { + btif_rc_cb.rc_multi_cb[0].rc_addr = bd_addr; + }; + } + + void TearDown() override { + btrc_ctrl_callbacks.track_changed_cb = [](const RawAddress& bd_addr, + uint8_t num_attr, btrc_element_attr_val_t* p_attrs) {}; + BtifRcTest::TearDown(); + } +}; + +TEST_F(BtifTrackChangeCBTest, handle_get_metadata_attr_response) { + tBTA_AV_META_MSG meta_msg = { + .rc_handle = 0, + }; + + tAVRC_GET_ATTRS_RSP rsp = { + .status = AVRC_STS_NO_ERROR, + .num_attrs = 0, + }; + + btif_rc_cb.rc_multi_cb[0].rc_handle = 0; + btif_rc_cb.rc_multi_cb[0].rc_addr = RawAddress::kEmpty; + btif_rc_cb.rc_multi_cb[0].rc_state = BTRC_CONNECTION_STATE_CONNECTED; + btif_rc_cb.rc_multi_cb[0].rc_connected = true; + + handle_get_metadata_attr_response(&meta_msg, &rsp); + + ASSERT_EQ(1, get_func_call_count("osi_free_and_reset")); +} diff --git a/system/build/Android.bp b/system/build/Android.bp index 95b20a2ec7..76b9b476f3 100644 --- a/system/build/Android.bp +++ b/system/build/Android.bp @@ -19,7 +19,6 @@ cc_defaults { // struct BT_HDR is defined as a variable-size header in a struct. "-Wno-gnu-variable-sized-type-not-at-end", ], - c_std: "c99", product_variables: { debuggable: { cflags: [ @@ -57,7 +56,6 @@ cc_defaults { "libbt_shim_bridge", "libbt_shim_ffi", ], - cpp_std: "c++17", min_sdk_version: "current", } @@ -71,7 +69,6 @@ cc_defaults { ], static_libs: [ "libbt-common", - "libbt-protos-lite", "libcutils", "libgmock", "libosi", @@ -86,22 +83,6 @@ cc_defaults { } cc_defaults { - name: "fluoride_basic_defaults", - defaults: [ - "fluoride_defaults", - ], - apex_available: [ - "//apex_available:platform", - "com.android.btservices", - ], - static_libs: [ - "libbt-protos-lite", - "libflatbuffers-cpp", - "libprotobuf-cpp-lite", - ], -} - -cc_defaults { name: "fluoride_defaults", defaults: [ "fluoride_defaults_fuzzable", diff --git a/system/common/Android.bp b/system/common/Android.bp index 3c6a803dca..c40182d5b7 100644 --- a/system/common/Android.bp +++ b/system/common/Android.bp @@ -10,7 +10,7 @@ package { cc_library_static { name: "libbt-common", defaults: [ - "fluoride_basic_defaults", + "fluoride_defaults", ], host_supported: true, include_dirs: [ @@ -28,9 +28,17 @@ cc_library_static { "stop_watch_legacy.cc", "time_util.cc", ], + proto: { + type: "lite", + canonical_path_from_root: false, + export_proto_headers: true, + }, target: { android: { - srcs: ["metrics.cc"], + srcs: [ + ":bluetooth-metrics-proto", + "metrics.cc", + ], }, host: { srcs: ["metrics_linux.cc"], @@ -40,6 +48,9 @@ cc_library_static { "libcrypto", "libcutils", ], + apex_available: [ + "com.android.btservices", + ], min_sdk_version: "Tiramisu", } @@ -85,7 +96,6 @@ cc_test { ], static_libs: [ "libbt-common", - "libbt-protos-lite", "libchrome", "libevent", "libgmock", diff --git a/system/device/Android.bp b/system/device/Android.bp index 59b7a3d9b4..33a04833b0 100644 --- a/system/device/Android.bp +++ b/system/device/Android.bp @@ -10,7 +10,7 @@ package { cc_library_static { name: "libbtdevice", - defaults: ["fluoride_basic_defaults"], + defaults: ["fluoride_defaults"], host_supported: true, local_include_dirs: [ "include", @@ -28,6 +28,9 @@ cc_library_static { "src/esco_parameters.cc", "src/interop.cc", ], + apex_available: [ + "com.android.btservices", + ], min_sdk_version: "Tiramisu", } diff --git a/system/gd/Android.bp b/system/gd/Android.bp index cdf6ae2c7d..70e52fe37e 100644 --- a/system/gd/Android.bp +++ b/system/gd/Android.bp @@ -30,7 +30,6 @@ cc_defaults { enabled: false, }, }, - cpp_std: "c++17", cflags: [ "-DEXPORT_SYMBOL=__attribute__((visibility(\"default\")))", "-DGOOGLE_PROTOBUF_NO_RTTI", @@ -38,7 +37,6 @@ cc_defaults { "-Wno-unused-result", "-fvisibility=hidden", ], - c_std: "c99", header_libs: ["jni_headers"], } @@ -164,6 +162,9 @@ cc_defaults { "libcrypto", "libflatbuffers-cpp", ], + export_shared_lib_headers: [ + "libflatbuffers-cpp", + ], whole_static_libs: [ "libc++fs", ], @@ -470,7 +471,6 @@ cc_defaults { ], shared_libs: [ "libcrypto", - "libflatbuffers-cpp", "libgrpc++", "libgrpc_wrap", ], diff --git a/system/gd/btaa/activity_attribution.h b/system/gd/btaa/activity_attribution.h index 93f5549dd3..7ad4ad42b3 100644 --- a/system/gd/btaa/activity_attribution.h +++ b/system/gd/btaa/activity_attribution.h @@ -36,17 +36,6 @@ struct BtaaAggregationEntry { CreationTime creation_time; }; -class ActivityAttributionCallback { - public: - virtual ~ActivityAttributionCallback() = default; - - // Callback when Bluetooth woke up the system - virtual void OnWakeup(const Activity activity, const hci::Address& address) = 0; - - // Callback when Bluetooth activity logs are ready to be moved - virtual void OnActivityLogsReady(const std::vector<BtaaAggregationEntry> logs) = 0; -}; - class ActivityAttribution : public bluetooth::Module { public: ActivityAttribution() = default; @@ -59,7 +48,6 @@ class ActivityAttribution : public bluetooth::Module { void OnWakelockAcquired(); void OnWakelockReleased(); void OnWakeup(); - void RegisterActivityAttributionCallback(ActivityAttributionCallback* callback); void NotifyActivityAttributionInfo(int uid, const std::string& package_name, const std::string& device_address); static const ModuleFactory Factory; diff --git a/system/gd/btaa/android/activity_attribution.cc b/system/gd/btaa/android/activity_attribution.cc index 79e0cea62d..e7a15bd9d6 100644 --- a/system/gd/btaa/android/activity_attribution.cc +++ b/system/gd/btaa/android/activity_attribution.cc @@ -158,10 +158,6 @@ struct ActivityAttribution::impl { attribution_processor_.OnWakeup(); } - void register_callback(ActivityAttributionCallback* callback) { - callback_ = callback; - } - void notify_activity_attribution_info(int uid, const std::string& package_name, const std::string& device_address) { attribution_processor_.NotifyActivityAttributionInfo(uid, package_name, device_address); } @@ -171,7 +167,6 @@ struct ActivityAttribution::impl { attribution_processor_.Dump(std::move(promise), fb_builder); } - ActivityAttributionCallback* callback_; AttributionProcessor attribution_processor_; HciProcessor hci_processor_; WakelockProcessor wakelock_processor_; @@ -213,10 +208,6 @@ void ActivityAttribution::OnWakeup() { CallOn(pimpl_.get(), &impl::on_wakeup); } -void ActivityAttribution::RegisterActivityAttributionCallback(ActivityAttributionCallback* callback) { - CallOn(pimpl_.get(), &impl::register_callback, callback); -} - void ActivityAttribution::NotifyActivityAttributionInfo( int uid, const std::string& package_name, const std::string& device_address) { CallOn(pimpl_.get(), &impl::notify_activity_attribution_info, uid, package_name, device_address); diff --git a/system/gd/btaa/host/activity_attribution.cc b/system/gd/btaa/host/activity_attribution.cc index ec02daf108..20c7ac76cc 100644 --- a/system/gd/btaa/host/activity_attribution.cc +++ b/system/gd/btaa/host/activity_attribution.cc @@ -29,8 +29,6 @@ void ActivityAttribution::OnWakelockReleased() {} void ActivityAttribution::OnWakeup() {} -void ActivityAttribution::RegisterActivityAttributionCallback(ActivityAttributionCallback* callback) {} - void ActivityAttribution::NotifyActivityAttributionInfo( int uid, const std::string& package_name, const std::string& device_address) {} diff --git a/system/gd/btaa/linux/activity_attribution.cc b/system/gd/btaa/linux/activity_attribution.cc index c4a4eb0aab..c39186750a 100644 --- a/system/gd/btaa/linux/activity_attribution.cc +++ b/system/gd/btaa/linux/activity_attribution.cc @@ -26,14 +26,10 @@ struct ActivityAttribution::impl { impl(ActivityAttribution* module) {} void on_hci_packet(hal::HciPacket packet, hal::SnoopLogger::PacketType type, uint16_t length) {} - - void register_callback(ActivityAttributionCallback* callback) {} }; void ActivityAttribution::Capture(const hal::HciPacket& packet, hal::SnoopLogger::PacketType type) {} -void ActivityAttribution::RegisterActivityAttributionCallback(ActivityAttributionCallback* callback) {} - void ActivityAttribution::NotifyActivityAttributionInfo( int uid, const std::string& package_name, const std::string& device_address) {} diff --git a/system/gd/dumpsys/bundler/Android.bp b/system/gd/dumpsys/bundler/Android.bp index f2f346a9ec..64059976bb 100644 --- a/system/gd/dumpsys/bundler/Android.bp +++ b/system/gd/dumpsys/bundler/Android.bp @@ -49,7 +49,6 @@ genrule { cc_defaults { name: "bluetooth_flatbuffer_bundler_defaults", defaults: ["bluetooth_cflags"], - cpp_std: "c++17", generated_headers: [ "BluetoothGeneratedBundlerSchema_h_bfbs", ], diff --git a/system/gd/hci/le_periodic_sync_manager.h b/system/gd/hci/le_periodic_sync_manager.h index 9cd92cda7e..cb3b10fbb4 100644 --- a/system/gd/hci/le_periodic_sync_manager.h +++ b/system/gd/hci/le_periodic_sync_manager.h @@ -347,6 +347,10 @@ class PeriodicSyncManager { LOG_DEBUG("[PSync]: sync_handle = %d", sync_handle); callbacks_->OnPeriodicSyncLost(sync_handle); auto periodic_sync = GetEstablishedSyncFromHandle(sync_handle); + if (periodic_sync == periodic_syncs_.end()) { + LOG_ERROR("[PSync]: index not found for handle %u", sync_handle); + return; + } periodic_syncs_.erase(periodic_sync); } diff --git a/system/gd/proto/Android.bp b/system/gd/proto/Android.bp index 538d8df347..6136cf84f6 100644 --- a/system/gd/proto/Android.bp +++ b/system/gd/proto/Android.bp @@ -14,7 +14,6 @@ java_library_static { type: "lite", }, srcs: [ - "bluetooth/bluetoothKeystore/keystore.proto", "bluetooth/metrics/bluetooth.proto", ], apex_available: [ @@ -24,21 +23,11 @@ java_library_static { sdk_version: "current", } -cc_library_static { - name: "libbt-protos-lite", - host_supported: true, - proto: { - export_proto_headers: true, - type: "lite", - }, +filegroup { + name: "bluetooth-metrics-proto", srcs: [ - "bluetooth/bluetoothKeystore/keystore.proto", "bluetooth/metrics/bluetooth.proto", ], - apex_available: [ - "com.android.btservices", - ], - min_sdk_version: "30", } cc_library_static { @@ -50,7 +39,6 @@ cc_library_static { include_dirs: ["external/protobuf/src"], }, srcs: [ - "bluetooth/bluetoothKeystore/keystore.proto", "bluetooth/metrics/bluetooth.proto", ], apex_available: [ diff --git a/system/gd/rust/common/src/bridge.rs b/system/gd/rust/common/src/bridge.rs index 6c3c281ee6..974ee8363c 100644 --- a/system/gd/rust/common/src/bridge.rs +++ b/system/gd/rust/common/src/bridge.rs @@ -2,6 +2,7 @@ pub use crate::parameter_provider::*; +#[allow(unsafe_op_in_unsafe_fn)] #[cxx::bridge(namespace = "bluetooth::fake_bluetooth_keystore")] /// ffi extern module pub mod ffi { diff --git a/system/gd/rust/common/src/init_flags.rs b/system/gd/rust/common/src/init_flags.rs index bb6789d872..5d6ea21b69 100644 --- a/system/gd/rust/common/src/init_flags.rs +++ b/system/gd/rust/common/src/init_flags.rs @@ -366,6 +366,7 @@ init_flags!( btaa_hci = true, bta_dm_clear_conn_id_on_client_close = true, btm_dm_flush_discovery_queue_on_search_cancel, + bta_dm_stop_discovery_on_search_cancel, classic_discovery_only, clear_hidd_interrupt_cid_on_disconnect = true, delay_hidh_cleanup_until_hidh_ready_start = true, diff --git a/system/gd/rust/common/src/parameter_provider.rs b/system/gd/rust/common/src/parameter_provider.rs index d4d32a0295..50ac00fb5d 100644 --- a/system/gd/rust/common/src/parameter_provider.rs +++ b/system/gd/rust/common/src/parameter_provider.rs @@ -18,7 +18,11 @@ pub struct ParameterProvider { lock: Mutex<i32>, } +// SAFETY: Nothing about `BluetoothKeystoreInterface` is bound to a specific thread, and all other +// fields are `Send`. unsafe impl Send for ParameterProvider {} + +// SAFETY: Nothing about `BluetoothKeystoreInterface` is bound to a specific thread. unsafe impl Send for BluetoothKeystoreInterface {} impl ParameterProvider { @@ -243,6 +247,8 @@ mod tests { assert_eq!(now, "/data/misc/bluetooth/logs/btsnooz_hci.log"); } + // TODO(b/290018030): Remove this and add proper safety comments. + #[allow(clippy::undocumented_unsafe_blocks)] #[ignore] #[tokio::test] async fn test_bt_keystore_interface() { @@ -292,11 +298,20 @@ mod tests { } let current = (*param_provider.lock().await).get_bt_keystore_interface().await; - let address_current = format!("{:p}", ¤t); - let reality = *hex_to_dec(address_current); - let mut answer = *hex_to_dec(Arc::clone(&address1).lock().await.to_string()); + let reality = current as i64; + // TODO: Fix unsoundness. `address1` contains a string representation of a pointer to a + // `cxx::UniquePtr` on the stack; either `choice_ptr1` (which is still in scope, so it is + // valid) or to `ptr1` (which is in another task which may have finished, so may not be + // valid). If it were valid, then transmuting the `cxx::UniquePtr` to an `i64` is probably + // fine, though a dubious thing to do. + let mut answer = unsafe { *(hex_to_dec(&address1.lock().await) as *const i64) }; if *choice.lock().await == "2" { - answer = *hex_to_dec(Arc::clone(&address2).lock().await.to_string()); + // TODO: Fix unsoundness. `address2` contains a string representation of a pointer to a + // `cxx::UniquePtr` on the stack; either `choice_ptr2` (which is still in scope, so it + // is valid) or to `ptr2` (which is in another task which may have finished, so may not + // be valid). If it were valid, then transmuting the `cxx::UniquePtr` to an `i64` is + // probably fine, though a dubious thing to do. + answer = unsafe { *(hex_to_dec(&address2.lock().await) as *const i64) }; } assert_eq!(reality, answer); } @@ -382,11 +397,9 @@ mod tests { assert_eq!(now, 0b11); } - fn hex_to_dec(origin: String) -> &'static i64 { - let address = unsafe { - let origin = origin.trim_start_matches("0x"); - &*(usize::from_str_radix(origin, 16).unwrap() as *const i64) - }; - address + /// Converts a hex string to an integer + fn hex_to_dec(origin: &str) -> usize { + let origin = origin.trim_start_matches("0x"); + usize::from_str_radix(origin, 16).unwrap() } } diff --git a/system/gd/rust/facade/src/main.rs b/system/gd/rust/facade/src/main.rs index 3bd53b05d4..67e7277e5e 100644 --- a/system/gd/rust/facade/src/main.rs +++ b/system/gd/rust/facade/src/main.rs @@ -13,7 +13,8 @@ use std::sync::{Arc, Mutex}; use tokio::runtime::Runtime; fn main() { - let sigint = install_sigint(); + // SAFETY: There is no signal handler installed before this. + let sigint = unsafe { install_sigint() }; bt_common::init_logging(); let rt = Arc::new(Runtime::new().unwrap()); rt.block_on(async_main(Arc::clone(&rt), sigint)); @@ -70,7 +71,10 @@ async fn async_main(rt: Arc<Runtime>, mut sigint: mpsc::UnboundedReceiver<()>) { } // TODO: remove as this is a temporary nix-based hack to catch SIGINT -fn install_sigint() -> mpsc::UnboundedReceiver<()> { +/// # Safety +/// +/// The old signal handler, if any, must be installed correctly. +unsafe fn install_sigint() -> mpsc::UnboundedReceiver<()> { let (tx, rx) = mpsc::unbounded(); *SIGINT_TX.lock().unwrap() = Some(tx); @@ -79,6 +83,10 @@ fn install_sigint() -> mpsc::UnboundedReceiver<()> { signal::SaFlags::empty(), signal::SigSet::empty(), ); + // SAFETY: The caller guarantees that the old signal handler was installed correctly. + // TODO(b/292218119): Make sure `handle_sigint` only makes system calls that are safe for signal + // handlers, and only accesses global state through atomics. In particular, it must not take any + // shared locks. unsafe { signal::sigaction(signal::SIGINT, &sig_action).unwrap(); } diff --git a/system/gd/rust/linux/client/src/dbus_iface.rs b/system/gd/rust/linux/client/src/dbus_iface.rs index 5b87b9e01c..98e2e0cc5d 100644 --- a/system/gd/rust/linux/client/src/dbus_iface.rs +++ b/system/gd/rust/linux/client/src/dbus_iface.rs @@ -731,7 +731,7 @@ impl IBluetooth for BluetoothDBus { dbus_generated!() } - fn init(&mut self, init_flags: Vec<String>) -> bool { + fn init(&mut self, _init_flags: Vec<String>) -> bool { // Not implemented by server true } diff --git a/system/gd/rust/linux/service/src/iface_bluetooth.rs b/system/gd/rust/linux/service/src/iface_bluetooth.rs index e006675743..ea651d5966 100644 --- a/system/gd/rust/linux/service/src/iface_bluetooth.rs +++ b/system/gd/rust/linux/service/src/iface_bluetooth.rs @@ -441,7 +441,7 @@ impl IBluetooth for IBluetoothDBus { } // Not exposed over D-Bus. The stack is automatically initialized when the daemon starts. - fn init(&mut self, init_flags: Vec<String>) -> bool { + fn init(&mut self, _init_flags: Vec<String>) -> bool { dbus_generated!() } diff --git a/system/gd/rust/linux/service/src/main.rs b/system/gd/rust/linux/service/src/main.rs index 13fa4db682..1f9a5864c1 100644 --- a/system/gd/rust/linux/service/src/main.rs +++ b/system/gd/rust/linux/service/src/main.rs @@ -20,7 +20,7 @@ use btstack::{ battery_manager::BatteryManager, battery_provider_manager::BatteryProviderManager, battery_service::BatteryService, - bluetooth::{Bluetooth, IBluetooth}, + bluetooth::{Bluetooth, IBluetooth, SigData}, bluetooth_admin::BluetoothAdmin, bluetooth_gatt::BluetoothGatt, bluetooth_logging::BluetoothLogging, @@ -49,6 +49,8 @@ const ADMIN_SETTINGS_FILE_PATH: &str = "/var/lib/bluetooth/admin_policy.json"; // The maximum ACL disconnect timeout is 3.5s defined by BTA_DM_DISABLE_TIMER_MS // and BTA_DM_DISABLE_TIMER_RETRIAL_MS const STACK_TURN_OFF_TIMEOUT_MS: Duration = Duration::from_millis(4000); +// Time bt_stack_manager waits for cleanup +const STACK_CLEANUP_TIMEOUT_MS: Duration = Duration::from_millis(1000); const VERBOSE_ONLY_LOG_TAGS: &[&str] = &[ "bt_bta_av", // AV apis @@ -135,7 +137,12 @@ fn main() -> Result<(), Box<dyn Error>> { init_flags.push(String::from("INIT_classic_discovery_only=true")); let (tx, rx) = Stack::create_channel(); - let sig_notifier = Arc::new((Mutex::new(false), Condvar::new())); + let sig_notifier = Arc::new(SigData { + enabled: Mutex::new(false), + enabled_notify: Condvar::new(), + thread_attached: Mutex::new(false), + thread_notify: Condvar::new(), + }); let intf = Arc::new(Mutex::new(get_btinterface().unwrap())); let bluetooth_gatt = @@ -424,7 +431,7 @@ fn main() -> Result<(), Box<dyn Error>> { lazy_static! { /// Data needed for signal handling. - static ref SIG_DATA: Mutex<Option<(Sender<Message>, Arc<(Mutex<bool>, Condvar)>)>> = Mutex::new(None); + static ref SIG_DATA: Mutex<Option<(Sender<Message>, Arc<SigData>)>> = Mutex::new(None); } extern "C" fn handle_sigterm(_signum: i32) { @@ -437,10 +444,23 @@ extern "C" fn handle_sigterm(_signum: i32) { let _ = txl.send(Message::Shutdown).await; }); - let guard = notifier.0.lock().unwrap(); + let guard = notifier.enabled.lock().unwrap(); if *guard { log::debug!("Waiting for stack to turn off for {:?}", STACK_TURN_OFF_TIMEOUT_MS); - let _ = notifier.1.wait_timeout(guard, STACK_TURN_OFF_TIMEOUT_MS); + let _ = notifier.enabled_notify.wait_timeout(guard, STACK_TURN_OFF_TIMEOUT_MS); + } + + log::debug!("SIGTERM cleaning up the stack."); + let txl = tx.clone(); + tokio::spawn(async move { + // Send the cleanup message here. + let _ = txl.send(Message::Cleanup).await; + }); + + let guard = notifier.thread_attached.lock().unwrap(); + if *guard { + log::debug!("Waiting for stack to clean up for {:?}", STACK_CLEANUP_TIMEOUT_MS); + let _ = notifier.thread_notify.wait_timeout(guard, STACK_CLEANUP_TIMEOUT_MS); } } diff --git a/system/gd/rust/linux/stack/src/bluetooth.rs b/system/gd/rust/linux/stack/src/bluetooth.rs index 9210aa978d..a62ec4ca54 100644 --- a/system/gd/rust/linux/stack/src/bluetooth.rs +++ b/system/gd/rust/linux/stack/src/bluetooth.rs @@ -4,8 +4,8 @@ use bt_topshim::btif::{ BaseCallbacks, BaseCallbacksDispatcher, BluetoothInterface, BluetoothProperty, BtAclState, BtBondState, BtConnectionDirection, BtConnectionState, BtDeviceType, BtDiscMode, BtDiscoveryState, BtHciErrorCode, BtPinCode, BtPropertyType, BtScanMode, BtSspVariant, BtState, - BtStatus, BtTransport, BtVendorProductInfo, DisplayAddress, RawAddress, ToggleableProfile, - Uuid, Uuid128Bit, + BtStatus, BtThreadEvent, BtTransport, BtVendorProductInfo, DisplayAddress, RawAddress, + ToggleableProfile, Uuid, Uuid128Bit, }; use bt_topshim::{ metrics, @@ -382,6 +382,15 @@ impl BluetoothDeviceContext { } } +/// Structure to track all the signals for SIGTERM. +pub struct SigData { + pub enabled: Mutex<bool>, + pub enabled_notify: Condvar, + + pub thread_attached: Mutex<bool>, + pub thread_notify: Condvar, +} + /// The interface for adapter callbacks registered through `IBluetooth::register_callback`. pub trait IBluetoothCallback: RPCProxy { /// When any adapter property changes. @@ -500,7 +509,7 @@ pub struct Bluetooth { discoverable_timeout: Option<JoinHandle<()>>, /// Used to notify signal handler that we have turned off the stack. - sig_notifier: Arc<(Mutex<bool>, Condvar)>, + sig_notifier: Arc<SigData>, } impl Bluetooth { @@ -509,7 +518,7 @@ impl Bluetooth { adapter_index: i32, hci_index: i32, tx: Sender<Message>, - sig_notifier: Arc<(Mutex<bool>, Condvar)>, + sig_notifier: Arc<SigData>, intf: Arc<Mutex<BluetoothInterface>>, bluetooth_admin: Arc<Mutex<Box<BluetoothAdmin>>>, bluetooth_gatt: Arc<Mutex<Box<BluetoothGatt>>>, @@ -1173,6 +1182,9 @@ pub(crate) trait BtifBluetoothCallbacks { min_16_digit: bool, ) { } + + #[btif_callback(ThreadEvent)] + fn thread_event(&mut self, event: BtThreadEvent) {} } #[btif_callbacks_dispatcher(dispatch_hid_host_callbacks, HHCallbacks)] @@ -1240,8 +1252,8 @@ impl BtifBluetoothCallbacks for Bluetooth { } // Let the signal notifier know we are turned off. - *self.sig_notifier.0.lock().unwrap() = false; - self.sig_notifier.1.notify_all(); + *self.sig_notifier.enabled.lock().unwrap() = false; + self.sig_notifier.enabled_notify.notify_all(); } BtState::On => { @@ -1265,8 +1277,8 @@ impl BtifBluetoothCallbacks for Bluetooth { self.set_connectable(true); // Notify the signal notifier that we are turned on. - *self.sig_notifier.0.lock().unwrap() = true; - self.sig_notifier.1.notify_all(); + *self.sig_notifier.enabled.lock().unwrap() = true; + self.sig_notifier.enabled_notify.notify_all(); // Signal that the stack is up and running. match self.create_pid_file() { @@ -1726,6 +1738,21 @@ impl BtifBluetoothCallbacks for Bluetooth { None => (), }; } + + fn thread_event(&mut self, event: BtThreadEvent) { + match event { + BtThreadEvent::Associate => { + // Let the signal notifier know stack is initialized. + *self.sig_notifier.thread_attached.lock().unwrap() = true; + self.sig_notifier.thread_notify.notify_all(); + } + BtThreadEvent::Disassociate => { + // Let the signal notifier know stack is done. + *self.sig_notifier.thread_attached.lock().unwrap() = false; + self.sig_notifier.thread_notify.notify_all(); + } + } + } } struct BleDiscoveryCallbacks { diff --git a/system/gd/rust/linux/stack/src/bluetooth_media.rs b/system/gd/rust/linux/stack/src/bluetooth_media.rs index f26cc91006..d05f02738b 100644 --- a/system/gd/rust/linux/stack/src/bluetooth_media.rs +++ b/system/gd/rust/linux/stack/src/bluetooth_media.rs @@ -1095,6 +1095,15 @@ impl BluetoothMedia { let sleep_duration = (first_conn_ts + total_duration).saturating_duration_since(now_ts); sleep(sleep_duration).await; + Self::async_disconnect(fallback_tasks, device_states, txl, addr).await; + } + + async fn async_disconnect( + fallback_tasks: &Arc<Mutex<HashMap<RawAddress, Option<(JoinHandle<()>, Instant)>>>>, + device_states: &Arc<Mutex<HashMap<RawAddress, DeviceConnectionStates>>>, + txl: &Sender<Message>, + addr: &RawAddress, + ) { device_states.lock().unwrap().insert(*addr, DeviceConnectionStates::Disconnecting); fallback_tasks.lock().unwrap().insert(*addr, None); @@ -1119,6 +1128,16 @@ impl BluetoothMedia { let _ = txl.send(Message::Media(MediaActions::ForceEnterConnected(addr.to_string()))).await; } + fn is_bonded(&self, addr: &RawAddress) -> bool { + match &self.adapter { + Some(adapter) => { + BtBondState::Bonded + == adapter.lock().unwrap().get_bond_state_by_addr(&addr.to_string()) + } + _ => false, + } + } + fn notify_media_capability_updated(&mut self, addr: RawAddress) { let mut guard = self.fallback_tasks.lock().unwrap(); let mut states = self.device_states.lock().unwrap(); @@ -1135,8 +1154,22 @@ impl BluetoothMedia { guard.insert(addr, None); } else { // The device is already added or is disconnecting. - // Ignore unless all profiles are cleared. + // Ignore unless all profiles are cleared, where we need to do some clean up. if !is_profile_cleared { + // Unbonded device is special, we need to reject the connection from them. + if !self.is_bonded(&addr) { + let tasks = self.fallback_tasks.clone(); + let states = self.device_states.clone(); + let txl = self.tx.clone(); + let task = topstack::get_runtime().spawn(async move { + warn!( + "[{}]: Rejecting an unbonded device's attempt to connect media", + DisplayAddress(&addr) + ); + BluetoothMedia::async_disconnect(&tasks, &states, &txl, &addr).await; + }); + guard.insert(addr, Some((task, first_conn_ts))); + } return; } } @@ -1222,39 +1255,18 @@ impl BluetoothMedia { } DeviceConnectionStates::FullyConnected => { // Rejecting the unbonded connection after we finished our profile - // reconnectinglogic to avoid a collision. - if let Some(adapter) = &self.adapter { - if BtBondState::Bonded - != adapter.lock().unwrap().get_bond_state_by_addr(&addr.to_string()) - { - warn!( - "[{}]: Rejecting a unbonded device's attempt to connect to media profiles", - DisplayAddress(&addr)); - let fallback_tasks = self.fallback_tasks.clone(); - let device_states = self.device_states.clone(); - let txl = self.tx.clone(); - let task = topstack::get_runtime().spawn(async move { - { - device_states - .lock() - .unwrap() - .insert(addr, DeviceConnectionStates::Disconnecting); - fallback_tasks.lock().unwrap().insert(addr, None); - } - - debug!( - "[{}]: Device connection state: {:?}.", - DisplayAddress(&addr), - DeviceConnectionStates::Disconnecting - ); + // reconnecting logic to avoid a collision. + if !self.is_bonded(&addr) { + warn!( + "[{}]: Rejecting a unbonded device's attempt to connect to media profiles", + DisplayAddress(&addr) + ); - let _ = txl - .send(Message::Media(MediaActions::Disconnect(addr.to_string()))) - .await; - }); - guard.insert(addr, Some((task, first_conn_ts))); - return; - } + let task = topstack::get_runtime().spawn(async move { + BluetoothMedia::async_disconnect(&tasks, &device_states, &txl, &addr).await; + }); + guard.insert(addr, Some((task, ts))); + return; } let cur_a2dp_caps = self.a2dp_caps.get(&addr); diff --git a/system/gd/rust/linux/stack/src/lib.rs b/system/gd/rust/linux/stack/src/lib.rs index 9247167b66..a128782f85 100644 --- a/system/gd/rust/linux/stack/src/lib.rs +++ b/system/gd/rust/linux/stack/src/lib.rs @@ -64,6 +64,7 @@ use bt_topshim::{ pub enum Message { // Shuts down the stack. Shutdown, + Cleanup, // Adapter is enabled and ready. AdapterReady, @@ -196,6 +197,9 @@ impl Stack { match m.unwrap() { Message::Shutdown => { bluetooth.lock().unwrap().disable(); + } + + Message::Cleanup => { bluetooth.lock().unwrap().cleanup(); } diff --git a/system/gd/rust/linux/stack/src/socket_manager.rs b/system/gd/rust/linux/stack/src/socket_manager.rs index 9fb8bae030..2893b76a30 100644 --- a/system/gd/rust/linux/stack/src/socket_manager.rs +++ b/system/gd/rust/linux/stack/src/socket_manager.rs @@ -842,7 +842,7 @@ impl BluetoothSocketManager { SocketActions::OnIncomingSocketReady( cbid, cloned_socket_info, - BtStatus::Fail, + BtStatus::Timeout, ), )) .await; diff --git a/system/gd/rust/shim/src/init_flags.rs b/system/gd/rust/shim/src/init_flags.rs index 39d4a38f7c..e40fdd59f2 100644 --- a/system/gd/rust/shim/src/init_flags.rs +++ b/system/gd/rust/shim/src/init_flags.rs @@ -1,4 +1,5 @@ #[cxx::bridge(namespace = bluetooth::common::init_flags)] +#[allow(unsafe_op_in_unsafe_fn)] mod ffi { struct InitFlagWithValue { flag: &'static str, @@ -18,6 +19,7 @@ mod ffi { fn bta_dm_clear_conn_id_on_client_close_is_enabled() -> bool; fn delay_hidh_cleanup_until_hidh_ready_start_is_enabled() -> bool; fn btm_dm_flush_discovery_queue_on_search_cancel_is_enabled() -> bool; + fn bta_dm_stop_discovery_on_search_cancel_is_enabled() -> bool; fn classic_discovery_only_is_enabled() -> bool; fn clear_hidd_interrupt_cid_on_disconnect_is_enabled() -> bool; fn device_iot_config_logging_is_enabled() -> bool; diff --git a/system/gd/rust/stack/src/hal/hidl_hal.rs b/system/gd/rust/stack/src/hal/hidl_hal.rs index 542a4a38eb..5145f323dc 100644 --- a/system/gd/rust/stack/src/hal/hidl_hal.rs +++ b/system/gd/rust/stack/src/hal/hidl_hal.rs @@ -43,6 +43,7 @@ async fn provide_hidl_hal(rt: Arc<Runtime>) -> RawHal { #[cxx::bridge(namespace = bluetooth::hal)] // TODO Either use or remove these functions, this shouldn't be the long term state #[allow(dead_code)] +#[allow(unsafe_op_in_unsafe_fn)] mod ffi { unsafe extern "C++" { include!("src/hal/ffi/hidl.h"); diff --git a/system/gd/rust/topshim/Android.bp b/system/gd/rust/topshim/Android.bp index 786299c1c1..5f3e6e8f79 100644 --- a/system/gd/rust/topshim/Android.bp +++ b/system/gd/rust/topshim/Android.bp @@ -110,7 +110,8 @@ rust_bindgen { wrapper_src: "bindings/wrapper.hpp", crate_name: "bt_topshim_wrapper_bindgen", source_stem: "bindings", - cpp_std: "c++17", + defaults: ["bluetooth_cflags"], + c_std: "", host_supported: true, bindgen_flags: [ "--allowlist-function=bt_.*", diff --git a/system/gd/rust/topshim/bindings/wrapper.hpp b/system/gd/rust/topshim/bindings/wrapper.hpp index 7139de421e..4d6cde161e 100644 --- a/system/gd/rust/topshim/bindings/wrapper.hpp +++ b/system/gd/rust/topshim/bindings/wrapper.hpp @@ -1,5 +1,3 @@ -#pragma once - // Base #include "hardware/bluetooth.h" #include "include/hal_util.h" diff --git a/system/gd/rust/topshim/facade/Android.bp b/system/gd/rust/topshim/facade/Android.bp index b18b4eb9c7..37d71e7cc2 100644 --- a/system/gd/rust/topshim/facade/Android.bp +++ b/system/gd/rust/topshim/facade/Android.bp @@ -52,7 +52,6 @@ rust_defaults { "libbt-bta-core", "libbt-common", "libbt-hci", - "libbt-protos-lite", "libbt-sbc-decoder", "libbt-sbc-encoder", "libbt-stack", diff --git a/system/gd/rust/topshim/facade/src/main.rs b/system/gd/rust/topshim/facade/src/main.rs index e5b4ce30c7..a2c5d212fc 100644 --- a/system/gd/rust/topshim/facade/src/main.rs +++ b/system/gd/rust/topshim/facade/src/main.rs @@ -36,7 +36,8 @@ use bluetooth_core_rs_for_facade::*; use bt_shim::*; fn main() { - let sigint = install_sigint(); + // SAFETY: There is no signal handler installed before this. + let sigint = unsafe { install_sigint() }; bt_common::init_logging(); let rt = Arc::new(Runtime::new().unwrap()); rt.block_on(async_main(Arc::clone(&rt), sigint)); @@ -123,7 +124,10 @@ async fn async_main(rt: Arc<Runtime>, mut sigint: mpsc::UnboundedReceiver<()>) { } // TODO: remove as this is a temporary nix-based hack to catch SIGINT -fn install_sigint() -> mpsc::UnboundedReceiver<()> { +/// # Safety +/// +/// The old signal handler, if any, must be installed correctly. +unsafe fn install_sigint() -> mpsc::UnboundedReceiver<()> { let (tx, rx) = mpsc::unbounded(); *SIGINT_TX.lock().unwrap() = Some(tx); @@ -132,6 +136,10 @@ fn install_sigint() -> mpsc::UnboundedReceiver<()> { signal::SaFlags::empty(), signal::SigSet::empty(), ); + // SAFETY: The caller guarantees that the old signal handler was installed correctly. + // TODO(b/292218119): Make sure `handle_sigint` only makes system calls that are safe for signal + // handlers, and only accesses global state through atomics. In particular, it must not take any + // shared locks. unsafe { signal::sigaction(signal::SIGINT, &sig_action).unwrap(); } diff --git a/system/gd/rust/topshim/src/btif.rs b/system/gd/rust/topshim/src/btif.rs index ece8ebe5ac..d67be44737 100644 --- a/system/gd/rust/topshim/src/btif.rs +++ b/system/gd/rust/topshim/src/btif.rs @@ -201,6 +201,7 @@ pub enum BtStatus { JniEnvironmentError, JniThreadAttachError, WakeLockError, + Timeout, // Any statuses that couldn't be cleanly converted Unknown = 0xff, @@ -331,6 +332,19 @@ impl Into<u32> for BtDiscMode { #[derive(Clone, Debug, FromPrimitive, ToPrimitive, PartialEq, PartialOrd)] #[repr(u32)] +pub enum BtThreadEvent { + Associate = 0, + Disassociate, +} + +impl From<bindings::bt_cb_thread_evt> for BtThreadEvent { + fn from(item: bindings::bt_cb_thread_evt) -> Self { + BtThreadEvent::from_u32(item).unwrap_or(BtThreadEvent::Associate) + } +} + +#[derive(Clone, Debug, FromPrimitive, ToPrimitive, PartialEq, PartialOrd)] +#[repr(u32)] pub enum BtIoCap { Out, InOut, @@ -882,8 +896,8 @@ pub enum BaseCallbacks { BtConnectionDirection, u16, ), + ThreadEvent(BtThreadEvent), // Unimplemented so far: - // thread_evt_cb // dut_mode_recv_cb // le_test_mode_cb // energy_info_cb @@ -943,6 +957,8 @@ cb_variant!(BaseCb, le_address_associate_cb -> BaseCallbacks::LeAddressAssociate let _1 = unsafe { *(_1 as *const RawAddress) }; }); +cb_variant!(BaseCb, thread_evt_cb -> BaseCallbacks::ThreadEvent, u32 -> BtThreadEvent); + cb_variant!(BaseCb, acl_state_cb -> BaseCallbacks::AclState, u32 -> BtStatus, *mut RawAddress, bindings::bt_acl_state_t -> BtAclState, i32 -> BtTransport, bindings::bt_hci_error_code_t -> BtHciErrorCode, bindings::bt_conn_direction_t -> BtConnectionDirection, u16 -> u16, { let _1 = unsafe { *(_1 as *const RawAddress) }; @@ -1071,7 +1087,7 @@ impl BluetoothInterface { address_consolidate_cb: Some(address_consolidate_cb), le_address_associate_cb: Some(le_address_associate_cb), acl_state_changed_cb: Some(acl_state_cb), - thread_evt_cb: None, + thread_evt_cb: Some(thread_evt_cb), dut_mode_recv_cb: None, le_test_mode_cb: None, energy_info_cb: None, diff --git a/system/hci/Android.bp b/system/hci/Android.bp index cf60675ee3..3212482c83 100644 --- a/system/hci/Android.bp +++ b/system/hci/Android.bp @@ -10,7 +10,7 @@ package { // HCI static library for target cc_library_static { name: "libbt-hci", - defaults: ["fluoride_basic_defaults"], + defaults: ["fluoride_defaults"], srcs: [ "src/buffer_allocator.cc", "src/packet_fragmenter.cc", @@ -32,6 +32,9 @@ cc_library_static { "packages/modules/Bluetooth/system/stack/include", "system/libhwbinder/include", ], + apex_available: [ + "com.android.btservices", + ], host_supported: true, min_sdk_version: "Tiramisu", } @@ -42,7 +45,7 @@ cc_test { test_suites: ["device-tests"], defaults: [ "bluetooth_gtest_x86_asan_workaround", - "fluoride_basic_defaults", + "fluoride_defaults", "fluoride_test_defaults", "mts_defaults", ], diff --git a/system/include/hardware/bluetooth.h b/system/include/hardware/bluetooth.h index 00396f9b11..429d1d7747 100644 --- a/system/include/hardware/bluetooth.h +++ b/system/include/hardware/bluetooth.h @@ -105,7 +105,8 @@ typedef enum { BT_STATUS_AUTH_REJECTED, BT_STATUS_JNI_ENVIRONMENT_ERROR, BT_STATUS_JNI_THREAD_ATTACH_ERROR, - BT_STATUS_WAKELOCK_ERROR + BT_STATUS_WAKELOCK_ERROR, + BT_STATUS_TIMEOUT } bt_status_t; inline std::string bt_status_text(const bt_status_t& status) { @@ -140,6 +141,8 @@ inline std::string bt_status_text(const bt_status_t& status) { return std::string("jni_thread_error"); case BT_STATUS_WAKELOCK_ERROR: return std::string("wakelock_error"); + case BT_STATUS_TIMEOUT: + return std::string("timeout_error"); default: return std::string("UNKNOWN"); } diff --git a/system/include/hardware/bt_activity_attribution.h b/system/include/hardware/bt_activity_attribution.h index a9c37c4751..c8f56bc61a 100644 --- a/system/include/hardware/bt_activity_attribution.h +++ b/system/include/hardware/bt_activity_attribution.h @@ -24,51 +24,10 @@ namespace bluetooth { namespace activity_attribution { -class ActivityAttributionCallbacks { - public: - enum class Activity : uint8_t { - UNKNOWN = 0, - ACL, - ADVERTISE, - CONNECT, - CONTROL, - HFP, - ISO, - SCAN, - VENDOR, - }; - - struct BtaaAggregationEntry { - RawAddress address; - Activity activity; - uint16_t wakeup_count; - uint32_t byte_count; - uint32_t wakelock_duration; - }; - - virtual ~ActivityAttributionCallbacks() = default; - - /** Callback when Bluetooth woke up the system */ - virtual void OnWakeup(const Activity activity, const RawAddress& address) = 0; - - /** Callback when Bluetooth activity logs are ready to be moved */ - virtual void OnActivityLogsReady( - const std::vector<BtaaAggregationEntry> logs) = 0; -}; - class ActivityAttributionInterface { public: virtual ~ActivityAttributionInterface() = default; - /** Init the interface. */ - virtual void Init(void) = 0; - - /** Register JNI callbacks with the interface. */ - virtual void RegisterCallbacks(ActivityAttributionCallbacks* callbacks) = 0; - - /** Closes the interface. */ - virtual void Cleanup(void) = 0; - /** Notify the UID and package name of the app, and the address of associated * active device */ virtual void NotifyActivityAttributionInfo( diff --git a/system/main/Android.bp b/system/main/Android.bp index d84e98b03e..123d749bc0 100644 --- a/system/main/Android.bp +++ b/system/main/Android.bp @@ -20,7 +20,7 @@ filegroup { cc_library_static { name: "libbte", - defaults: ["fluoride_basic_defaults"], + defaults: ["fluoride_defaults"], srcs: [ ":LibBluetoothShimSources", ":LibBluetoothSources", @@ -52,6 +52,9 @@ cc_library_static { "BluetoothGeneratedDumpsysDataSchema_h", "BluetoothGeneratedPackets_h", ], + apex_available: [ + "com.android.btservices", + ], host_supported: true, min_sdk_version: "Tiramisu", } @@ -63,7 +66,7 @@ cc_library { "//packages/modules/Bluetooth:__subpackages__", "//vendor:__subpackages__", ], - defaults: ["fluoride_basic_defaults"], + defaults: ["fluoride_defaults"], header_libs: ["libbluetooth_headers"], export_header_lib_headers: ["libbluetooth_headers"], include_dirs: [ @@ -143,7 +146,6 @@ cc_library_static { "-DBUILDCFG", ], shared_libs: [ - "libflatbuffers-cpp", ], whole_static_libs: [ "libbluetooth_gd", // Gabeldorsche @@ -203,7 +205,6 @@ cc_test { static_libs: [ "libbluetooth-dumpsys", "libbt-common", - "libbt-protos-lite", "libbtdevice", "libchrome", "libevent", diff --git a/system/main/shim/activity_attribution.cc b/system/main/shim/activity_attribution.cc index 4695309b74..12eed9de56 100644 --- a/system/main/shim/activity_attribution.cc +++ b/system/main/shim/activity_attribution.cc @@ -23,9 +23,7 @@ #include "main/shim/entry.h" #include "types/raw_address.h" -class ActivityAttributionInterfaceImpl - : public ActivityAttributionInterface, - public bluetooth::activity_attribution::ActivityAttributionCallback { +class ActivityAttributionInterfaceImpl : public ActivityAttributionInterface { public: ~ActivityAttributionInterfaceImpl() override = default; @@ -35,17 +33,6 @@ class ActivityAttributionInterfaceImpl return instance; } - void Init() override { - bluetooth::shim::GetActivityAttribution() - ->RegisterActivityAttributionCallback(this); - } - - void RegisterCallbacks(ActivityAttributionCallbacks* callbacks) override { - this->callbacks = callbacks; - } - - void Cleanup(void) override{}; - void NotifyActivityAttributionInfo( int uid, const std::string& package_name, const std::string& device_address) override { @@ -53,44 +40,12 @@ class ActivityAttributionInterfaceImpl uid, package_name, device_address); } - void OnWakeup(const Activity activity, - const bluetooth::hci::Address& address) override { - do_in_jni_thread( - FROM_HERE, base::Bind(&ActivityAttributionCallbacks::OnWakeup, - base::Unretained(callbacks), - (ActivityAttributionCallbacks::Activity)activity, - bluetooth::ToRawAddress(address))); - } - - void OnActivityLogsReady( - const std::vector<BtaaAggregationEntry> logs) override { - std::vector<ActivityAttributionCallbacks::BtaaAggregationEntry> - callback_logs; - for (auto& it : logs) { - ActivityAttributionCallbacks::BtaaAggregationEntry entry{ - bluetooth::ToRawAddress(it.address), - (ActivityAttributionCallbacks::Activity)it.activity, it.wakeup_count, - it.byte_count, it.wakelock_duration_ms}; - callback_logs.push_back(entry); - } - do_in_jni_thread( - FROM_HERE, - base::Bind(&ActivityAttributionCallbacks::OnActivityLogsReady, - base::Unretained(callbacks), callback_logs)); - } - private: // Private constructor to prevent construction. ActivityAttributionInterfaceImpl() {} - - ActivityAttributionCallbacks* callbacks; }; ActivityAttributionInterface* bluetooth::shim::get_activity_attribution_instance() { return ActivityAttributionInterfaceImpl::GetInstance(); } - -void bluetooth::shim::init_activity_attribution() { - bluetooth::shim::get_activity_attribution_instance()->Init(); -} diff --git a/system/main/shim/activity_attribution.h b/system/main/shim/activity_attribution.h index 00d8099971..0f1253415e 100644 --- a/system/main/shim/activity_attribution.h +++ b/system/main/shim/activity_attribution.h @@ -28,7 +28,5 @@ namespace shim { ActivityAttributionInterface* get_activity_attribution_instance(); -void init_activity_attribution(); - } // namespace shim } // namespace bluetooth diff --git a/system/main/shim/stack.cc b/system/main/shim/stack.cc index 40dc243024..017b954168 100644 --- a/system/main/shim/stack.cc +++ b/system/main/shim/stack.cc @@ -148,9 +148,6 @@ void Stack::StartEverything() { !common::init_flags::gd_core_is_enabled()) { L2CA_UseLegacySecurityModule(); } - if (common::init_flags::btaa_hci_is_enabled()) { - bluetooth::shim::init_activity_attribution(); - } } void Stack::Start(ModuleList* modules) { diff --git a/system/main/test/main_shim_test.cc b/system/main/test/main_shim_test.cc index fcaf32b4c5..83c6f4350f 100644 --- a/system/main/test/main_shim_test.cc +++ b/system/main/test/main_shim_test.cc @@ -345,8 +345,6 @@ class MockLeAclConnection namespace bluetooth { namespace shim { -void init_activity_attribution() {} - namespace testing { extern os::Handler* mock_handler_; diff --git a/system/osi/Android.bp b/system/osi/Android.bp index da4f9cf2e1..f5c6fc8e68 100644 --- a/system/osi/Android.bp +++ b/system/osi/Android.bp @@ -65,7 +65,7 @@ cc_library_static { "include_internal", ], defaults: [ - "fluoride_basic_defaults", + "fluoride_defaults", "fluoride_osi_defaults", ], // TODO(mcchou): Remove socket_utils sources after platform specific @@ -160,7 +160,6 @@ cc_test { ], static_libs: [ "libbt-common", - "libbt-protos-lite", "libc++fs", "libchrome", "libevent", diff --git a/system/osi/test/fuzzers/alarm/Android.bp b/system/osi/test/fuzzers/alarm/Android.bp index 4bcef92af9..4e09043cce 100644 --- a/system/osi/test/fuzzers/alarm/Android.bp +++ b/system/osi/test/fuzzers/alarm/Android.bp @@ -23,7 +23,6 @@ cc_fuzz { ], static_libs: [ "libbt-common", - "libbt-protos-lite", "libchrome", "libgmock", "libosi", diff --git a/system/rust/src/connection/ffi.rs b/system/rust/src/connection/ffi.rs index 651af1ff36..08506d546b 100644 --- a/system/rust/src/connection/ffi.rs +++ b/system/rust/src/connection/ffi.rs @@ -19,12 +19,14 @@ use super::{ ConnectionManagerClient, LeConnection, }; +// SAFETY: `LeAclManagerShim` can be passed between threads. unsafe impl Send for LeAclManagerShim {} #[cxx::bridge] #[allow(clippy::needless_lifetimes)] #[allow(clippy::too_many_arguments)] #[allow(missing_docs)] +#[allow(unsafe_op_in_unsafe_fn)] mod inner { impl UniquePtr<LeAclManagerShim> {} diff --git a/system/rust/src/core/ffi.rs b/system/rust/src/core/ffi.rs index 83d10d0673..e69002def9 100644 --- a/system/rust/src/core/ffi.rs +++ b/system/rust/src/core/ffi.rs @@ -12,12 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. +// TODO(b/290018030): Remove this and add proper safety comments. +#![allow(clippy::undocumented_unsafe_blocks)] + use crate::core::{start, stop}; use cxx::{type_id, ExternType}; pub use inner::*; +// SAFETY: `GattServerCallbacks` can be passed between threads. unsafe impl Send for GattServerCallbacks {} + +// SAFETY: `future_t` can be passed between threads. unsafe impl Send for Future {} unsafe impl ExternType for Uuid { @@ -30,7 +36,7 @@ unsafe impl ExternType for AddressWithType { type Kind = cxx::kind::Trivial; } -#[allow(dead_code, missing_docs)] +#[allow(dead_code, missing_docs, unsafe_op_in_unsafe_fn)] #[cxx::bridge] mod inner { #[derive(Debug)] diff --git a/system/rust/src/gatt/arbiter.rs b/system/rust/src/gatt/arbiter.rs index 52a0aa7207..66841bd977 100644 --- a/system/rust/src/gatt/arbiter.rs +++ b/system/rust/src/gatt/arbiter.rs @@ -52,6 +52,11 @@ pub fn with_arbiter<T>(f: impl FnOnce(&mut IsolationManager) -> T) -> T { f(ARBITER.read().unwrap().as_ref().expect("Rust stack is not started").lock().as_mut().unwrap()) } +/// Check if the Arbiter is initialized. +pub fn has_arbiter() -> bool { + ARBITER.read().unwrap().is_some() +} + /// Test to see if a buffer contains a valid ATT packet with an opcode we /// are interested in intercepting (those intended for servers that are isolated) fn try_parse_att_server_packet( @@ -89,6 +94,13 @@ fn on_le_connect(tcb_idx: u8, advertiser: u8) { } fn on_le_disconnect(tcb_idx: u8) { + // Disconnection events may be received after a FactoryReset + // is initiated for Bluetooth and the rust arbiter is taken + // down. + if !has_arbiter() { + return; + } + let tcb_idx = TransportIndex(tcb_idx); let was_isolated = with_arbiter(|arbiter| arbiter.is_connection_isolated(tcb_idx)); if was_isolated { diff --git a/system/rust/src/gatt/ffi.rs b/system/rust/src/gatt/ffi.rs index 4f06f0b60b..5a0f3b548e 100644 --- a/system/rust/src/gatt/ffi.rs +++ b/system/rust/src/gatt/ffi.rs @@ -39,6 +39,7 @@ use super::{ #[allow(clippy::needless_lifetimes)] #[allow(clippy::too_many_arguments)] #[allow(missing_docs)] +#[allow(unsafe_op_in_unsafe_fn)] mod inner { impl UniquePtr<GattServerCallbacks> {} diff --git a/system/stack/Android.bp b/system/stack/Android.bp index 97619c7269..894ce03266 100644 --- a/system/stack/Android.bp +++ b/system/stack/Android.bp @@ -32,7 +32,7 @@ cc_test_library { // Bluetooth stack static library for target cc_library_static { name: "libbt-stack", - defaults: ["fluoride_basic_defaults"], + defaults: ["fluoride_defaults"], local_include_dirs: [ "avct", "avdt", @@ -125,10 +125,10 @@ cc_library_static { "bnep/bnep_api.cc", "bnep/bnep_main.cc", "bnep/bnep_utils.cc", - "btm/hfp_msbc_decoder.cc", - "btm/hfp_msbc_encoder.cc", "btm/hfp_lc3_decoder.cc", "btm/hfp_lc3_encoder.cc", + "btm/hfp_msbc_decoder.cc", + "btm/hfp_msbc_encoder.cc", "hid/hidd_api.cc", "hid/hidd_conn.cc", "hid/hidh_api.cc", @@ -169,7 +169,7 @@ filegroup { cc_library_static { name: "libbt-stack-core", - defaults: ["fluoride_basic_defaults"], + defaults: ["fluoride_defaults"], local_include_dirs: [ "avct", "avdt", @@ -643,7 +643,7 @@ cc_test { "libz", ], static_libs: [ - "android.hardware.audio.common-V1-ndk", + "android.hardware.audio.common-V2-ndk", "android.hardware.bluetooth.a2dp@1.0", "android.hardware.bluetooth.audio-V3-ndk", "android.hardware.bluetooth@1.0", @@ -660,7 +660,6 @@ cc_test { "libbt-bta-core", "libbt-common", "libbt-hci", - "libbt-protos-lite", "libbt-sbc-decoder", "libbt-sbc-encoder", "libbt-stack", @@ -739,10 +738,8 @@ cc_test { ], static_libs: [ "libbt-common", - "libbt-protos-lite", "libchrome", "libevent", - "libflatbuffers-cpp", "libgmock", "liblog", "libosi", @@ -818,7 +815,6 @@ cc_test { static_libs: [ "libchrome", "libevent", - "libflatbuffers-cpp", "libgmock", "liblog", "libosi", @@ -932,7 +928,6 @@ cc_test { ], static_libs: [ "libbt-common", - "libbt-protos-lite", "libchrome", "libevent", "libgmock", @@ -997,7 +992,6 @@ cc_test { ], static_libs: [ "libbt-common", - "libbt-protos-lite", "libchrome", "libevent", "liblog", @@ -1104,7 +1098,6 @@ cc_test { static_libs: [ "libFraunhoferAAC", "libbt-common", - "libbt-protos-lite", "libbt-sbc-decoder", "libbt-sbc-encoder", "libchrome", @@ -1156,7 +1149,6 @@ cc_test { ], static_libs: [ "libbt-common", - "libbt-protos-lite", "libchrome", "liblog", "libosi", @@ -1212,7 +1204,6 @@ cc_test { ], static_libs: [ "libbt-common", - "libbt-protos-lite", "libchrome", "libevent", "libgmock", @@ -1319,7 +1310,6 @@ cc_test { ], static_libs: [ "libbt-common", - "libbt-protos-lite", "libchrome", "libevent", "libgmock", @@ -1417,28 +1407,27 @@ cc_test { "btm/btm_sco_hci.cc", "btm/btm_sco_hfp_hal.cc", "btm/btm_sec.cc", - "btm/hfp_msbc_decoder.cc", - "btm/hfp_msbc_encoder.cc", "btm/hfp_lc3_decoder.cc", "btm/hfp_lc3_encoder.cc", + "btm/hfp_msbc_decoder.cc", + "btm/hfp_msbc_encoder.cc", "metrics/stack_metrics_logging.cc", + "test/btm/btm_scn_test.cc", "test/btm/peer_packet_types_test.cc", "test/btm/sco_hci_test.cc", + "test/btm/sco_pkt_status_test.cc", "test/btm/stack_btm_regression_tests.cc", "test/btm/stack_btm_test.cc", - "test/btm/sco_pkt_status_test.cc", "test/common/mock_eatt.cc", "test/stack_include_test.cc", ], static_libs: [ "libbt-common", - "libbt-protos-lite", "libbt-sbc-decoder", "libbt-sbc-encoder", "libbtdevice", "libchrome", "libevent", - "libflatbuffers-cpp", "libgmock", "liblc3", "liblog", @@ -1486,7 +1475,6 @@ cc_test { ], static_libs: [ "libbt-common", - "libbt-protos-lite", "libbtdevice", "libbte", "libchrome", @@ -1542,7 +1530,6 @@ cc_test { ], static_libs: [ "libbt-common", - "libbt-protos-lite", "libbtdevice", "libbte", "libchrome", @@ -1614,11 +1601,9 @@ cc_test { ], static_libs: [ "libbt-common", - "libbt-protos-lite", "libbtdevice", "libchrome", "libevent", - "libflatbuffers-cpp", "libgmock", "liblog", "libosi", @@ -1696,11 +1681,9 @@ cc_test { ], static_libs: [ "libbt-common", - "libbt-protos-lite", "libbtdevice", "libchrome", "libevent", - "libflatbuffers-cpp", "libgmock", "liblog", "libosi", @@ -1778,11 +1761,9 @@ cc_test { ], static_libs: [ "libbt-common", - "libbt-protos-lite", "libbtdevice", "libchrome", "libevent", - "libflatbuffers-cpp", "libgmock", "liblog", "libosi", @@ -1864,11 +1845,9 @@ cc_test { ], static_libs: [ "libbt-common", - "libbt-protos-lite", "libbtdevice", "libchrome", "libevent", - "libflatbuffers-cpp", "libgmock", "liblog", "libosi", diff --git a/system/stack/btm/btm_int_types.h b/system/stack/btm/btm_int_types.h index 9ad5388ac6..ced48d04e3 100644 --- a/system/stack/btm/btm_int_types.h +++ b/system/stack/btm/btm_int_types.h @@ -18,6 +18,8 @@ #ifndef BTM_INT_TYPES_H #define BTM_INT_TYPES_H +#include <gtest/gtest_prod.h> + #include <cstdint> #include <memory> #include <string> @@ -396,6 +398,14 @@ typedef struct tBTM_CB { friend bool BTM_FreeSCN(uint8_t scn); uint8_t btm_scn[BTM_MAX_SCN_]; uint8_t btm_available_index; + + // give access to private method for test: + friend class BtmAllocateSCNTest; + FRIEND_TEST(BtmAllocateSCNTest, can_allocate_all_scns); + FRIEND_TEST(BtmAllocateSCNTest, only_last_scn_available); + FRIEND_TEST(BtmAllocateSCNTest, scn_available_after_available_index); + FRIEND_TEST(BtmAllocateSCNTest, scn_available_before_available_index); + FRIEND_TEST(BtmAllocateSCNTest, no_scn_available); } tBTM_CB; /* security action for L2CAP COC channels */ diff --git a/system/stack/btm/btm_scn.cc b/system/stack/btm/btm_scn.cc index feec0f5fe7..8e87ccc730 100644 --- a/system/stack/btm/btm_scn.cc +++ b/system/stack/btm/btm_scn.cc @@ -34,8 +34,11 @@ extern tBTM_CB btm_cb; uint8_t BTM_AllocateSCN(void) { BTM_TRACE_DEBUG("BTM_AllocateSCN"); - // stack reserves scn 1 for HFP, HSP we still do the correct way - for (uint8_t x = btm_cb.btm_available_index; x < PORT_MAX_RFC_PORTS; x++) { + // stack reserves scn 1 for HFP, HSP we still do the correct way. + // SCN can be allocated in the range of [1, PORT_MAX_RFC_PORTS). Since (x + 1) + // is returned, we iterate to less than PORT_MAX_RFC_PORTS - 1. + for (uint8_t x = btm_cb.btm_available_index; x < PORT_MAX_RFC_PORTS - 1; + x++) { if (!btm_cb.btm_scn[x]) { btm_cb.btm_scn[x] = true; btm_cb.btm_available_index = (x + 1); @@ -43,10 +46,10 @@ uint8_t BTM_AllocateSCN(void) { } } - // In order to avoid OOB, btm_available_index must be less than or equal to - // PORT_MAX_RFC_PORTS + // In order to avoid OOB, btm_available_index must be less than + // PORT_MAX_RFC_PORTS. btm_cb.btm_available_index = - std::min(btm_cb.btm_available_index, (uint8_t)PORT_MAX_RFC_PORTS); + std::min(btm_cb.btm_available_index, (uint8_t)(PORT_MAX_RFC_PORTS - 1)); // If there's no empty SCN from _last_index to BTM_MAX_SCN. for (uint8_t y = 1; y < btm_cb.btm_available_index; y++) { diff --git a/system/stack/btm/btm_scn.h b/system/stack/btm/btm_scn.h index fe9dcb7ad6..733173297b 100644 --- a/system/stack/btm/btm_scn.h +++ b/system/stack/btm/btm_scn.h @@ -18,6 +18,6 @@ #include <cstdint> +uint8_t BTM_AllocateSCN(void); bool BTM_FreeSCN(uint8_t scn); bool BTM_TryAllocateSCN(uint8_t scn); -bool BTM_TryAllocateSCN(uint8_t scn); diff --git a/system/stack/l2cap/l2c_api.cc b/system/stack/l2cap/l2c_api.cc index 6a64d88b61..809f9e6261 100644 --- a/system/stack/l2cap/l2c_api.cc +++ b/system/stack/l2cap/l2c_api.cc @@ -729,6 +729,11 @@ bool L2CA_ConnectCreditBasedRsp(const RawAddress& p_bd_addr, uint8_t id, */ tL2C_CCB* p_ccb = l2cu_find_ccb_by_cid(p_lcb, p_lcb->pending_lead_cid); + if (!p_ccb) { + L2CAP_TRACE_ERROR("%s No CCB for CID:0x%04x", __func__, p_lcb->pending_lead_cid); + return false; + } + for (uint16_t cid : accepted_lcids) { tL2C_CCB* temp_p_ccb = l2cu_find_ccb_by_cid(p_lcb, cid); if (temp_p_ccb == NULL) { @@ -838,7 +843,8 @@ std::vector<uint16_t> L2CA_ConnectCreditBasedReq(uint16_t psm, for (int i = 0; i < p_cfg->number_of_channels; i++) { /* Allocate a channel control block */ - tL2C_CCB* p_ccb = l2cu_allocate_ccb(p_lcb, 0); + tL2C_CCB* p_ccb = + l2cu_allocate_ccb(p_lcb, 0, psm == BT_PSM_EATT /* is_eatt */); if (p_ccb == NULL) { if (i == 0) { L2CAP_TRACE_WARNING("%s no CCB, PSM: 0x%04x", __func__, psm); diff --git a/system/stack/l2cap/l2c_ble.cc b/system/stack/l2cap/l2c_ble.cc index b18acb39fa..3a53d47abd 100644 --- a/system/stack/l2cap/l2c_ble.cc +++ b/system/stack/l2cap/l2c_ble.cc @@ -657,7 +657,8 @@ void l2cble_process_sig_cmd(tL2C_LCB* p_lcb, uint8_t* p, uint16_t pkt_len) { L2CAP_LE_RESULT_SOURCE_CID_ALREADY_ALLOCATED; } else { /* Allocate a ccb for this.*/ - temp_p_ccb = l2cu_allocate_ccb(p_lcb, 0); + temp_p_ccb = l2cu_allocate_ccb( + p_lcb, 0, con_info.psm == BT_PSM_EATT /* is_eatt */); if (temp_p_ccb == NULL) { LOG_ERROR("L2CAP - unable to allocate CCB"); p_lcb->pending_ecoc_connection_cids[i] = 0; @@ -1004,7 +1005,8 @@ void l2cble_process_sig_cmd(tL2C_LCB* p_lcb, uint8_t* p, uint16_t pkt_len) { } /* Allocate a ccb for this.*/ - p_ccb = l2cu_allocate_ccb(p_lcb, 0); + p_ccb = l2cu_allocate_ccb(p_lcb, 0, + con_info.psm == BT_PSM_EATT /* is_eatt */); if (p_ccb == NULL) { L2CAP_TRACE_ERROR("L2CAP - unable to allocate CCB"); l2cu_reject_ble_connection(p_ccb, id, L2CAP_CONN_NO_RESOURCES); diff --git a/system/stack/l2cap/l2c_csm.cc b/system/stack/l2cap/l2c_csm.cc index 32c8ab9a55..44c4c99076 100644 --- a/system/stack/l2cap/l2c_csm.cc +++ b/system/stack/l2cap/l2c_csm.cc @@ -100,9 +100,13 @@ static void l2c_csm_indicate_connection_open(tL2C_CCB* p_ccb) { if (p_ccb->connection_initiator == L2CAP_INITIATOR_LOCAL) { (*p_ccb->p_rcb->api.pL2CA_ConnectCfm_Cb)(p_ccb->local_cid, L2CAP_CONN_OK); } else { - (*p_ccb->p_rcb->api.pL2CA_ConnectInd_Cb)( - p_ccb->p_lcb->remote_bd_addr, p_ccb->local_cid, p_ccb->p_rcb->psm, - p_ccb->remote_id); + if (*p_ccb->p_rcb->api.pL2CA_ConnectInd_Cb) { + (*p_ccb->p_rcb->api.pL2CA_ConnectInd_Cb)( + p_ccb->p_lcb->remote_bd_addr, p_ccb->local_cid, p_ccb->p_rcb->psm, + p_ccb->remote_id); + } else { + LOG_WARN("pL2CA_ConnectInd_Cb is null"); + } } if (p_ccb->chnl_state == CST_OPEN && !p_ccb->p_lcb->is_transport_ble()) { (*p_ccb->p_rcb->api.pL2CA_ConfigCfm_Cb)( diff --git a/system/stack/l2cap/l2c_int.h b/system/stack/l2cap/l2c_int.h index 14a0a20202..d44e16a9b4 100644 --- a/system/stack/l2cap/l2c_int.h +++ b/system/stack/l2cap/l2c_int.h @@ -719,7 +719,8 @@ void l2cu_enqueue_ccb(tL2C_CCB* p_ccb); void l2cu_dequeue_ccb(tL2C_CCB* p_ccb); void l2cu_change_pri_ccb(tL2C_CCB* p_ccb, tL2CAP_CHNL_PRIORITY priority); -tL2C_CCB* l2cu_allocate_ccb(tL2C_LCB* p_lcb, uint16_t cid); +tL2C_CCB* l2cu_allocate_ccb(tL2C_LCB* p_lcb, uint16_t cid, + bool is_eatt = false); void l2cu_release_ccb(tL2C_CCB* p_ccb); tL2C_CCB* l2cu_find_ccb_by_cid(tL2C_LCB* p_lcb, uint16_t local_cid); tL2C_CCB* l2cu_find_ccb_by_remote_cid(tL2C_LCB* p_lcb, uint16_t remote_cid); diff --git a/system/stack/l2cap/l2c_utils.cc b/system/stack/l2cap/l2c_utils.cc index d2d76bda9c..f64782ca5b 100644 --- a/system/stack/l2cap/l2c_utils.cc +++ b/system/stack/l2cap/l2c_utils.cc @@ -1351,7 +1351,7 @@ void l2cu_change_pri_ccb(tL2C_CCB* p_ccb, tL2CAP_CHNL_PRIORITY priority) { * Returns pointer to CCB, or NULL if none * ******************************************************************************/ -tL2C_CCB* l2cu_allocate_ccb(tL2C_LCB* p_lcb, uint16_t cid) { +tL2C_CCB* l2cu_allocate_ccb(tL2C_LCB* p_lcb, uint16_t cid, bool is_eatt) { LOG_DEBUG("is_dynamic = %d, cid 0x%04x", p_lcb != nullptr, cid); if (!l2cb.p_free_ccb_first) { LOG_ERROR("First free ccb is null for cid 0x%04x", cid); @@ -1471,7 +1471,12 @@ tL2C_CCB* l2cu_allocate_ccb(tL2C_LCB* p_lcb, uint16_t cid) { if (p_lcb != NULL) { // once a dynamic channel is opened, timeouts become active - p_lcb->with_active_local_clients = true; + // the exception for this is EATT, since that is managed by GATT clients, + // not by the L2CAP layer (GATT will keep the idle timeout at infinity while + // clients are active) + if (!is_eatt) { + p_lcb->with_active_local_clients = true; + } } return p_ccb; diff --git a/system/stack/test/btm/btm_scn_test.cc b/system/stack/test/btm/btm_scn_test.cc new file mode 100644 index 0000000000..38294e312d --- /dev/null +++ b/system/stack/test/btm/btm_scn_test.cc @@ -0,0 +1,91 @@ +/* + * + * 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. + * + */ + +#include "stack/btm/btm_scn.h" + +#include <gtest/gtest.h> + +#include "stack/btm/btm_int_types.h" // tBTM_CB +#include "stack/include/rfcdefs.h" // PORT_MAX_RFC_PORTS + +extern tBTM_CB btm_cb; + +using testing::Test; + +class BtmAllocateSCNTest : public Test { + public: + protected: + void SetUp() override { + btm_cb.btm_available_index = 1; + for (int i = 0; i < PORT_MAX_RFC_PORTS; i++) { + btm_cb.btm_scn[i] = false; + } + } + + void TearDown() override {} +}; + +TEST_F(BtmAllocateSCNTest, scn_available_after_available_index) { + btm_cb.btm_available_index = 5; + uint8_t occupied_idx[] = {1, 2, 3, 4, 5, 6, 7}; + for (uint8_t idx : occupied_idx) { + btm_cb.btm_scn[idx] = true; + } + + uint8_t scn = BTM_AllocateSCN(); + ASSERT_EQ(scn, 9); // All indexes up to 7 are occupied; hence index 8 i.e. + // scn 9 should return +} + +TEST_F(BtmAllocateSCNTest, scn_available_before_available_index) { + btm_cb.btm_available_index = 28; + uint8_t occupied_idx[] = {26, 27, 28, 29}; + for (uint8_t idx : occupied_idx) { + btm_cb.btm_scn[idx] = true; + } + + uint8_t scn = BTM_AllocateSCN(); + ASSERT_EQ(scn, 2); // All SCN from available to 30 are occupied; hence cycle + // to beginning. +} + +TEST_F(BtmAllocateSCNTest, can_allocate_all_scns) { + for (uint8_t scn = 2; scn < PORT_MAX_RFC_PORTS; scn++) { + EXPECT_EQ(BTM_AllocateSCN(), scn); + } +} + +TEST_F(BtmAllocateSCNTest, only_last_scn_available) { + // Fill all relevants SCN except the last + for (uint8_t scn = 2; scn < PORT_MAX_RFC_PORTS - 1; scn++) { + btm_cb.btm_scn[scn - 1] = true; + } + + EXPECT_EQ(BTM_AllocateSCN(), PORT_MAX_RFC_PORTS - 1); +} + +TEST_F(BtmAllocateSCNTest, no_scn_available) { + btm_cb.btm_available_index = 2; + for (int i = 1; i < PORT_MAX_RFC_PORTS - 1; + i++) { // Fill all relevants SCN indexes (1 to 29) + btm_cb.btm_scn[i] = true; + } + + uint8_t scn = BTM_AllocateSCN(); + EXPECT_EQ(scn, 0) << "scn = " << scn << "and not 0"; +}
\ No newline at end of file diff --git a/system/stack/test/fuzzers/Android.bp b/system/stack/test/fuzzers/Android.bp index 822bc50312..d187b961ed 100644 --- a/system/stack/test/fuzzers/Android.bp +++ b/system/stack/test/fuzzers/Android.bp @@ -29,7 +29,6 @@ cc_defaults { "libbt-bta-core", "libbt-common", "libbt-hci", - "libbt-protos-lite", "libbt-sbc-decoder", "libbt-sbc-encoder", "libbt-stack", diff --git a/system/test/headless/Android.bp b/system/test/headless/Android.bp index aad71a9995..e1f667cc34 100644 --- a/system/test/headless/Android.bp +++ b/system/test/headless/Android.bp @@ -92,7 +92,6 @@ cc_binary { "libbt-bta-core", "libbt-common", "libbt-hci", - "libbt-protos-lite", "libbt-sbc-decoder", "libbt-sbc-encoder", "libbt-stack", diff --git a/system/test/mock/mock_bta_dm_act.cc b/system/test/mock/mock_bta_dm_act.cc index 0cf293fa1d..42365fbd72 100644 --- a/system/test/mock/mock_bta_dm_act.cc +++ b/system/test/mock/mock_bta_dm_act.cc @@ -80,7 +80,6 @@ struct bta_dm_free_sdp_db bta_dm_free_sdp_db; struct bta_dm_inq_cmpl bta_dm_inq_cmpl; struct bta_dm_is_search_request_queued bta_dm_is_search_request_queued; struct bta_dm_pin_reply bta_dm_pin_reply; -struct bta_dm_proc_open_evt bta_dm_proc_open_evt; struct bta_dm_process_remove_device bta_dm_process_remove_device; struct bta_dm_queue_disc bta_dm_queue_disc; struct bta_dm_queue_search bta_dm_queue_search; @@ -295,10 +294,6 @@ void bta_dm_pin_reply(std::unique_ptr<tBTA_DM_API_PIN_REPLY> msg) { inc_func_call_count(__func__); test::mock::bta_dm_act::bta_dm_pin_reply(std::move(msg)); } -void bta_dm_proc_open_evt(tBTA_GATTC_OPEN* p_data) { - inc_func_call_count(__func__); - test::mock::bta_dm_act::bta_dm_proc_open_evt(p_data); -} void bta_dm_process_remove_device(const RawAddress& bd_addr) { inc_func_call_count(__func__); test::mock::bta_dm_act::bta_dm_process_remove_device(bd_addr); diff --git a/system/test/mock/mock_bta_dm_act.h b/system/test/mock/mock_bta_dm_act.h index 661a7537d5..e58a58d7c2 100644 --- a/system/test/mock/mock_bta_dm_act.h +++ b/system/test/mock/mock_bta_dm_act.h @@ -585,16 +585,6 @@ struct bta_dm_pin_reply { }; extern struct bta_dm_pin_reply bta_dm_pin_reply; -// Name: bta_dm_proc_open_evt -// Params: tBTA_GATTC_OPEN* p_data -// Return: void -struct bta_dm_proc_open_evt { - std::function<void(tBTA_GATTC_OPEN* p_data)> body{ - [](tBTA_GATTC_OPEN* p_data) {}}; - void operator()(tBTA_GATTC_OPEN* p_data) { body(p_data); }; -}; -extern struct bta_dm_proc_open_evt bta_dm_proc_open_evt; - // Name: bta_dm_process_remove_device // Params: const RawAddress& bd_addr // Return: void diff --git a/system/test/mock/mock_btif_co_bta_av_co.cc b/system/test/mock/mock_btif_co_bta_av_co.cc index 15612db986..0241237208 100644 --- a/system/test/mock/mock_btif_co_bta_av_co.cc +++ b/system/test/mock/mock_btif_co_bta_av_co.cc @@ -49,7 +49,6 @@ struct bta_av_co_audio_source_data_path bta_av_co_audio_source_data_path; struct bta_av_co_audio_start bta_av_co_audio_start; struct bta_av_co_audio_stop bta_av_co_audio_stop; struct bta_av_co_audio_update_mtu bta_av_co_audio_update_mtu; -struct bta_av_co_get_decoder_interface bta_av_co_get_decoder_interface; struct bta_av_co_get_encoder_effective_frame_size bta_av_co_get_encoder_effective_frame_size; struct bta_av_co_get_encoder_interface bta_av_co_get_encoder_interface; @@ -76,8 +75,6 @@ namespace btif_co_bta_av_co { tA2DP_STATUS bta_av_co_audio_getconfig::return_value = 0; bool bta_av_co_audio_init::return_value = false; BT_HDR* bta_av_co_audio_source_data_path::return_value = nullptr; -const tA2DP_DECODER_INTERFACE* bta_av_co_get_decoder_interface::return_value = - nullptr; int bta_av_co_get_encoder_effective_frame_size::return_value = 0; const tA2DP_ENCODER_INTERFACE* bta_av_co_get_encoder_interface::return_value = nullptr; @@ -180,10 +177,6 @@ void bta_av_co_audio_update_mtu(tBTA_AV_HNDL bta_av_handle, test::mock::btif_co_bta_av_co::bta_av_co_audio_update_mtu(bta_av_handle, peer_address, mtu); } -const tA2DP_DECODER_INTERFACE* bta_av_co_get_decoder_interface(void) { - inc_func_call_count(__func__); - return test::mock::btif_co_bta_av_co::bta_av_co_get_decoder_interface(); -} int bta_av_co_get_encoder_effective_frame_size() { inc_func_call_count(__func__); return test::mock::btif_co_bta_av_co:: diff --git a/system/test/mock/mock_btif_co_bta_av_co.h b/system/test/mock/mock_btif_co_bta_av_co.h index 9f977ce53f..c817f8359c 100644 --- a/system/test/mock/mock_btif_co_bta_av_co.h +++ b/system/test/mock/mock_btif_co_bta_av_co.h @@ -260,17 +260,6 @@ struct bta_av_co_audio_update_mtu { }; extern struct bta_av_co_audio_update_mtu bta_av_co_audio_update_mtu; -// Name: bta_av_co_get_decoder_interface -// Params: void -// Return: const tA2DP_DECODER_INTERFACE* -struct bta_av_co_get_decoder_interface { - static const tA2DP_DECODER_INTERFACE* return_value; - std::function<const tA2DP_DECODER_INTERFACE*(void)> body{ - [](void) { return return_value; }}; - const tA2DP_DECODER_INTERFACE* operator()(void) { return body(); }; -}; -extern struct bta_av_co_get_decoder_interface bta_av_co_get_decoder_interface; - // Name: bta_av_co_get_encoder_effective_frame_size // Params: // Return: int diff --git a/system/test/mock/mock_main_shim_activity_attribution.cc b/system/test/mock/mock_main_shim_activity_attribution.cc index 3b67f5aac9..83cfaa3872 100644 --- a/system/test/mock/mock_main_shim_activity_attribution.cc +++ b/system/test/mock/mock_main_shim_activity_attribution.cc @@ -36,6 +36,3 @@ bluetooth::shim::get_activity_attribution_instance() { inc_func_call_count(__func__); return nullptr; } -void bluetooth::shim::init_activity_attribution() { - inc_func_call_count(__func__); -} diff --git a/system/test/mock/mock_stack_l2cap_utils.cc b/system/test/mock/mock_stack_l2cap_utils.cc index f1166f2947..035591b80f 100644 --- a/system/test/mock/mock_stack_l2cap_utils.cc +++ b/system/test/mock/mock_stack_l2cap_utils.cc @@ -70,7 +70,7 @@ bool l2cu_start_post_bond_timer(uint16_t handle) { inc_func_call_count(__func__); return false; } -tL2C_CCB* l2cu_allocate_ccb(tL2C_LCB* p_lcb, uint16_t cid) { +tL2C_CCB* l2cu_allocate_ccb(tL2C_LCB* p_lcb, uint16_t cid, bool is_eatt) { inc_func_call_count(__func__); return nullptr; } diff --git a/system/test/suite/Android.bp b/system/test/suite/Android.bp index 29fe4ecc35..0158173745 100644 --- a/system/test/suite/Android.bp +++ b/system/test/suite/Android.bp @@ -117,7 +117,7 @@ cc_test { "gatt/gatt_unittest.cc", ], static_libs: [ - "android.hardware.audio.common-V1-ndk", + "android.hardware.audio.common-V2-ndk", "android.hardware.bluetooth.audio-V3-ndk", "android.hardware.bluetooth@1.0", "android.hardware.bluetooth@1.1", diff --git a/system/vendor_libs/linux/interface/Android.bp b/system/vendor_libs/linux/interface/Android.bp index 26e11fbcb8..a47a57dbd8 100644 --- a/system/vendor_libs/linux/interface/Android.bp +++ b/system/vendor_libs/linux/interface/Android.bp @@ -46,7 +46,6 @@ cc_binary { static_libs: [ "async_fd_watcher", ], - c_std: "c99", init_rc: ["android.hardware.bluetooth@1.1-service.btlinux.rc"], } diff --git a/tools/rootcanal/Android.bp b/tools/rootcanal/Android.bp index a451f6a54c..67dd3afa78 100644 --- a/tools/rootcanal/Android.bp +++ b/tools/rootcanal/Android.bp @@ -47,13 +47,11 @@ cc_defaults { "-fvisibility=hidden", ], include_dirs: [ - "packages/modules/Bluetooth/system/gd", - ], - local_include_dirs: [ - "include", + "packages/modules/Bluetooth/tools/rootcanal/include", ], header_libs: [ "libbase_headers", + "pdl_cxx_packet_runtime", ], generated_headers: [ "rootcanal_bredr_bb_packets_cxx_gen", @@ -67,7 +65,6 @@ filegroup { srcs: [ "lib/crypto/crypto.cc", "lib/hci/address.cc", - "lib/hci/class_of_device.cc", "lib/hci/pcap_filter.cc", "lib/log.cc", ], @@ -82,7 +79,6 @@ cc_library_static { host_supported: true, proprietary: true, srcs: [ - ":BluetoothPacketSources", ":lib_sources", "model/controller/acl_connection.cc", "model/controller/acl_connection_handler.cc", @@ -114,6 +110,9 @@ cc_library_static { "net/posix/posix_async_socket_connector.cc", "net/posix/posix_async_socket_server.cc", ], + export_header_lib_headers: [ + "pdl_cxx_packet_runtime", + ], export_include_dirs: [ ".", "include", @@ -136,7 +135,6 @@ cc_library_static { static_libs: [ "libscriptedbeaconpayload-protos-lite", ], - include_dirs: ["packages/modules/Bluetooth/system/gd"], } // This library implements a foreigh function interface over DualModeController @@ -150,7 +148,6 @@ cc_library_host_shared { address: false, }, srcs: [ - ":BluetoothPacketSources", ":lib_sources", "model/controller/acl_connection.cc", "model/controller/acl_connection_handler.cc", @@ -181,7 +178,6 @@ cc_library_host_shared { cflags: [ "-fexceptions", ], - include_dirs: ["packages/modules/Bluetooth/system/gd"], } // Generate the python parser+serializer backend for @@ -225,7 +221,7 @@ genrule { " $(location :pdl_python_generator)" + " --output $(out) --custom-type-location py.bluetooth", srcs: [ - "packets/hci/hci_packets.pdl", + "packets/hci_packets.pdl", ], out: [ "hci_packets.py", @@ -295,7 +291,6 @@ cc_test_host { static_libs: [ "libbt-rootcanal", ], - include_dirs: ["packages/modules/Bluetooth/system/gd"], } // Implement the Bluetooth official LL test suite for root-canal. @@ -315,8 +310,8 @@ python_test_host { "test/LL/CON_/PER/*.py", "test/LL/DDI/ADV/*.py", "test/LL/DDI/SCN/*.py", - "test/LMP/LIH/*.py", "test/LMP/*.py", + "test/LMP/LIH/*.py", "test/main.py", ], data: [ @@ -369,7 +364,6 @@ cc_test_host { enabled: false, }, }, - include_dirs: ["packages/modules/Bluetooth/system/gd"], } // Linux RootCanal Executable @@ -411,29 +405,41 @@ cc_binary_host { enabled: false, }, }, - include_dirs: ["packages/modules/Bluetooth/system/gd"], } genrule { name: "rootcanal_hci_packets_cxx_gen", tools: [ - "bluetooth_packetgen", - ], - cmd: "$(location bluetooth_packetgen) --include=packages/modules/Bluetooth/tools/rootcanal/packets --out=$(genDir) $(in)", + ":pdl_cxx_generator", + ":pdlc", + ], + cmd: "set -o pipefail;" + + " $(location :pdlc) $(in) |" + + " $(location :pdl_cxx_generator)" + + " --namespace bluetooth::hci" + + " --include-header hci/address.h" + + " --output $(out)", srcs: [ - "packets/hci/hci_packets.pdl", + "packets/hci_packets.pdl", ], out: [ - "hci/hci_packets.h", + "packets/hci_packets.h", ], } genrule { name: "rootcanal_link_layer_packets_cxx_gen", tools: [ - "bluetooth_packetgen", - ], - cmd: "$(location bluetooth_packetgen) --root_namespace=model --include=packages/modules/Bluetooth/tools/rootcanal --out=$(genDir) $(in)", + ":pdl_cxx_generator", + ":pdlc", + ], + cmd: "set -o pipefail;" + + " $(location :pdlc) $(in) |" + + " $(location :pdl_cxx_generator)" + + " --namespace model::packets" + + " --include-header hci/address.h" + + " --using-namespace bluetooth::hci" + + " --output $(out)", srcs: [ "packets/link_layer_packets.pdl", ], @@ -445,9 +451,16 @@ genrule { genrule { name: "rootcanal_bredr_bb_packets_cxx_gen", tools: [ - "bluetooth_packetgen", - ], - cmd: "$(location bluetooth_packetgen) --root_namespace=bredr_bb --include=packages/modules/Bluetooth/tools/rootcanal/packets --out=$(genDir) $(in)", + ":pdl_cxx_generator", + ":pdlc", + ], + cmd: "set -o pipefail;" + + " $(location :pdlc) $(in) |" + + " $(location :pdl_cxx_generator)" + + " --namespace bredr_bb" + + " --include-header hci/address.h" + + " --using-namespace bluetooth::hci" + + " --output $(out)", srcs: [ "packets/bredr_bb.pdl", ], @@ -459,6 +472,6 @@ genrule { genrule { name: "rootcanal_hci_packets_rust_gen", defaults: ["pdl_rust_generator_defaults"], - srcs: ["packets/hci/hci_packets.pdl"], + srcs: ["packets/hci_packets.pdl"], out: ["hci_packets.rs"], } diff --git a/tools/rootcanal/CMakeLists.txt b/tools/rootcanal/CMakeLists.txt index 6324bc19d6..53aec54b73 100644 --- a/tools/rootcanal/CMakeLists.txt +++ b/tools/rootcanal/CMakeLists.txt @@ -1,4 +1,4 @@ -set(BT_ROOT ${AOSP_ROOT}/packages/modules/Bluetooth/system) +set(BT_ROOT ${AOSP_ROOT}/packages/modules/Bluetooth) set(ROOTCANAL_ROOT ${AOSP_ROOT}/packages/modules/Bluetooth/tools/rootcanal) set(PDL_ROOT ${AOSP_ROOT}/external/rust/crates/pdl-compiler) @@ -28,7 +28,8 @@ android_license( function(pdl_gen) # Parse arguments. set(options) - set(oneValueArgs NAME INPUT OUTPUT LANG) + set(oneValueArgs NAME INPUT OUTPUT LANG NAMESPACE) + set(multiValueArgs USING INCLUDE) cmake_parse_arguments(pdl "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) @@ -66,102 +67,62 @@ function(pdl_gen) VERBATIM DEPENDS pdlc ${pdl_INPUT_ABSOLUTE}) endif() -endfunction() - -# ~~~ -# ! foobar : Compile .pdl files to C++/Rust! -# -# This function allows you to compile a set of .pdl files -# to rust or C++. It will generate a set of headers and -# sources. -# -# :GENERATED: The set of sources that are generated. -# Add these sources to the library you are building -# :SRC: The set of .pdl files to be compiled. -# :INCLUDES: Include directory used when generating sources. -# :LANG: Optional parameter indicating which language to use, -# either rust or c++, defaults to c++ -# :NAMESPACE: Root name space to use for the generated c++ classes. -# :OUTPUT_DIR: Optional paramater of the directory where the generated -# sources will be placed, defaults to CMAKE_CURRENT_BINARY_DIR/gens -# :SOURCE_DIR: Root directory where sources can be found, -# defaults to CMAKE_CURRENT_SOURCE_DIR -# ~~~ -function(android_bluetooth_packet_gen) - # Parse arguments - set(options) - set(oneValueArgs OUTPUT_DIR GENERATED SOURCE_DIR INCLUDES NAMESPACE LANG) - set(multiValueArgs SRC) - cmake_parse_arguments(packet_gen "${options}" "${oneValueArgs}" - "${multiValueArgs}" ${ARGN}) - - if(NOT packet_gen_OUTPUT_DIR) - set(packet_gen_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/packet_gen) - endif() - - if(packet_gen_NAMESPACE) - set(packet_gen_NAMESPACE "--root_namespace=${packet_gen_NAMESPACE}") - endif() - if(NOT packet_gen_SOURCE_DIR) - set(packet_gen_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) - endif() - - if(NOT packet_gen_SRC) - message( - FATAL_ERROR - "Error: android_packet_gen_compile() called without any .yy files") - return() - endif() - - set(SUFFIX_GEN "h") + if(pdl_LANG STREQUAL "c++") + if(NOT pdl_NAMESPACE) + message(FATAL_ERROR "Error: namespace not specified") + endif() - if(packet_gen_LANG STREQUAL "rust") - set(SUFFIX_GEN "rs") - set(packet_gen_LANG "--rust") - endif() - - # Configure packet_gen - android_compile_for_host( - bluetooth_packetgen ${ANDROID_QEMU2_TOP_DIR}/android/bluetooth/packet_gen - bluetooth_packetgen_EXECUTABLE) + foreach(namespace ${pdl_USING}) + list(APPEND pdl_FLAGS --using-namespace) + list(APPEND pdl_FLAGS ${namespace}) + endforeach() + foreach(header ${pdl_INCLUDE}) + list(APPEND pdl_FLAGS --include-header) + list(APPEND pdl_FLAGS ${header}) + endforeach() - set(BLUE_GEN "") - file(MAKE_DIRECTORY ${packet_gen_OUTPUT_DIR}) + add_custom_command( + OUTPUT "${pdl_OUTPUT_ABSOLUTE}.json" + COMMAND + ${pdlc_EXECUTABLE} + --output-format json + "${pdl_INPUT_ABSOLUTE}" + > "${pdl_OUTPUT_ABSOLUTE}.json" + COMMENT "Analyzing ${pdl_INPUT}" + VERBATIM + DEPENDS pdlc ${pdl_INPUT_ABSOLUTE}) - foreach(FIL ${packet_gen_SRC}) - get_filename_component( - ABS_FIL ${packet_gen_SOURCE_DIR}/${packet_gen_INCLUDES}/${FIL} ABSOLUTE) - get_filename_component(FIL_WE ${FIL} NAME_WE) - get_filename_component(FIL_DIR ${FIL} DIRECTORY) - set(FIL_GEN "${packet_gen_OUTPUT_DIR}/${FIL_DIR}/${FIL_WE}.${SUFFIX_GEN}") add_custom_command( - OUTPUT "${FIL_GEN}" - COMMAND - ${bluetooth_packetgen_EXECUTABLE} ${packet_gen_NAMESPACE} - "--include=${packet_gen_INCLUDES}" "--out=${packet_gen_OUTPUT_DIR}" - ${packet_gen_INCLUDES}/${FIL} ${packet_gen_LANG} - COMMENT "Creating bluetooth packet headers from ${ABS_FIL}" - WORKING_DIRECTORY ${packet_gen_SOURCE_DIR} - VERBATIM - DEPENDS ${bluetooth_packetgen_EXECUTABLE} ${ABS_FIL}) - list(APPEND BLUE_GEN ${FIL_GEN}) - set_source_files_properties(${FIL_GEN} PROPERTIES GENERATED TRUE) - endforeach() - - # Make the library available - if(packet_gen_GENERATED) - set(${packet_gen_GENERATED} "${BLUE_GEN}" PARENT_SCOPE) + OUTPUT "${pdl_OUTPUT_ABSOLUTE}" + COMMAND + ${PDL_ROOT}/scripts/generate_cxx_backend.py + --input "${pdl_OUTPUT_ABSOLUTE}.json" + --output "${pdl_OUTPUT_ABSOLUTE}" + --namespace ${pdl_NAMESPACE} + ${pdl_FLAGS} + COMMENT "Generating c++ header from ${pdl_INPUT}" + VERBATIM + DEPENDS pdlc ${pdl_OUTPUT_ABSOLUTE}.json) endif() endfunction() -android_bluetooth_packet_gen( - GENERATED BluetoothGeneratedPackets_h INCLUDES tools/rootcanal/packets - SRC hci/hci_packets.pdl SOURCE_DIR ${BT_ROOT}/..) - -android_bluetooth_packet_gen( - GENERATED RootCanalGeneratedPackets_h INCLUDES tools/rootcanal NAMESPACE model - SRC packets/link_layer_packets.pdl SOURCE_DIR ${BT_ROOT}/..) +pdl_gen( + NAME BluetoothGeneratedPackets_h + INPUT ${ROOTCANAL_ROOT}/packets/hci_packets.pdl + OUTPUT packets/hci_packets.h + LANG c++ + NAMESPACE "bluetooth::hci" + INCLUDE "hci/address.h") + +pdl_gen( + NAME RootCanalGeneratedPackets_h + INPUT ${ROOTCANAL_ROOT}/packets/link_layer_packets.pdl + OUTPUT packets/link_layer_packets.h + LANG c++ + NAMESPACE model::packets + INCLUDE "hci/address.h" + USING "bluetooth::hci") android_add_library( TARGET libscriptedbeaconpayload-protos-lite LICENSE Apache-2.0 @@ -205,7 +166,7 @@ target_include_directories(libbt-rootcanal.headers INTERFACE ${ROOTCANAL_ROOT}) target_link_libraries(libbt-rootcanal.headers INTERFACE android-emu-base-headers) android_license(TARGET "libbt-rootcanal.headers" LIBNAME None SPDX Apache-2.0 - LICENSE Apache-2.0 LOCAL "${BT_ROOT}/../NOTICE") + LICENSE Apache-2.0 LOCAL "${BT_ROOT}/NOTICE") android_add_library( TARGET librootcanal_log @@ -220,30 +181,6 @@ target_include_directories( ${AOSP_ROOT}/external/fmtlib/include) android_add_library( - TARGET BluetoothPacketSources - LICENSE Apache-2.0 - SOURCE_DIR ${BT_ROOT}/gd/packet - SRC bit_inserter.cc - byte_inserter.cc - byte_observer.cc - fragmenting_inserter.cc - iterator.cc - packet_view.cc - raw_builder.cc - view.cc - DEPS android-emu-base android-emu-base-headers librootcanal_log) - -target_include_directories( - BluetoothPacketSources PUBLIC - ${ROOTCANAL_ROOT} - ${ROOTCANAL_ROOT}/include - ${BT_ROOT}/gd - ${BT_ROOT}) - -target_compile_options(BluetoothPacketSources - PUBLIC -Wno-inconsistent-missing-override) - -android_add_library( TARGET libbt-rootcanal LICENSE Apache-2.0 SOURCE_DIR ${ROOTCANAL_ROOT} @@ -251,7 +188,6 @@ android_add_library( ${RootCanalGeneratedPackets_h} lib/crypto/crypto.cc lib/hci/address.cc - lib/hci/class_of_device.cc lib/hci/pcap_filter.cc lib/log.cc model/controller/acl_connection.cc @@ -287,7 +223,6 @@ android_add_library( DEPS android-emu-base android-emu-base-headers android-emu-base-logging - BluetoothPacketSources crypto librootcanal_config libscriptedbeaconpayload-protos-lite) @@ -300,9 +235,8 @@ target_include_directories( libbt-rootcanal PUBLIC ${ROOTCANAL_ROOT}/include ${ROOTCANAL_ROOT} - ${BT_ROOT}/gd - ${BT_ROOT} - ${CMAKE_CURRENT_BINARY_DIR}/packet_gen + ${PDL_ROOT}/scripts + ${CMAKE_CURRENT_BINARY_DIR}/pdl_gen ${CMAKE_CURRENT_BINARY_DIR}/config) target_compile_options(libbt-rootcanal diff --git a/system/test/rootcanal/Android.bp b/tools/rootcanal/hal/Android.bp index 7145768ec8..f211b70b9c 100644 --- a/system/test/rootcanal/Android.bp +++ b/tools/rootcanal/hal/Android.bp @@ -24,9 +24,7 @@ package { cc_binary { name: "android.hardware.bluetooth@1.1-service.sim", - defaults: [ - "gd_defaults", - ], + defaults: ["rootcanal_defaults"], proprietary: true, relative_install_path: "hw", srcs: [ @@ -36,7 +34,6 @@ cc_binary { visibility: [ "//platform_testing/libraries/sts-common-util/host-side/rootcanal", ], - header_libs: ["libbluetooth_headers"], shared_libs: [ "android.hardware.bluetooth@1.0", "android.hardware.bluetooth@1.1", @@ -44,7 +41,6 @@ cc_binary { "libcrypto", "libcutils", "libhidlbase", - "libjsoncpp", "liblog", "libprotobuf-cpp-full", "libutils", @@ -52,9 +48,6 @@ cc_binary { cflags: [ "-fvisibility=hidden", ], - generated_headers: [ - "libbt_init_flags_bridge_header", - ], whole_static_libs: [ "libbt-rootcanal", ], @@ -63,20 +56,12 @@ cc_binary { "android.hardware.bluetooth-hci", "libscriptedbeaconpayload-protos-lite", ], - include_dirs: [ - "packages/modules/Bluetooth/system", - "packages/modules/Bluetooth/system/gd", - "packages/modules/Bluetooth/system/internal_include", - "packages/modules/Bluetooth/system/stack/include", - ], init_rc: ["android.hardware.bluetooth@1.1-service.sim.rc"], } cc_library_shared { name: "android.hardware.bluetooth@1.1-impl-sim", - defaults: [ - "gd_defaults", - ], + defaults: ["rootcanal_defaults"], proprietary: true, relative_install_path: "hw", srcs: [ @@ -85,7 +70,6 @@ cc_library_shared { visibility: [ "//platform_testing/libraries/sts-common-util/host-side/rootcanal", ], - header_libs: ["libbluetooth_headers"], shared_libs: [ "android.hardware.bluetooth@1.0", "android.hardware.bluetooth@1.1", @@ -93,14 +77,10 @@ cc_library_shared { "libcrypto", "libcutils", "libhidlbase", - "libjsoncpp", "liblog", "libprotobuf-cpp-full", "libutils", ], - generated_headers: [ - "libbt_init_flags_bridge_header", - ], whole_static_libs: [ "libbt-rootcanal", ], @@ -109,10 +89,4 @@ cc_library_shared { "android.hardware.bluetooth-hci", "libscriptedbeaconpayload-protos-lite", ], - include_dirs: [ - "packages/modules/Bluetooth/system", - "packages/modules/Bluetooth/system/gd", - "packages/modules/Bluetooth/system/internal_include", - "packages/modules/Bluetooth/system/stack/include", - ], } diff --git a/system/test/rootcanal/android.hardware.bluetooth@1.1-service.sim.rc b/tools/rootcanal/hal/android.hardware.bluetooth@1.1-service.sim.rc index 262684136e..262684136e 100644 --- a/system/test/rootcanal/android.hardware.bluetooth@1.1-service.sim.rc +++ b/tools/rootcanal/hal/android.hardware.bluetooth@1.1-service.sim.rc diff --git a/system/test/rootcanal/bluetooth_hci.cc b/tools/rootcanal/hal/bluetooth_hci.cc index bab7be1139..d63b870843 100644 --- a/system/test/rootcanal/bluetooth_hci.cc +++ b/tools/rootcanal/hal/bluetooth_hci.cc @@ -69,7 +69,7 @@ class BluetoothDeathRecipient : public hidl_death_recipient { void setHasDied(bool has_died) { has_died_ = has_died; } private: - bool has_died_; + bool has_died_{false}; }; BluetoothHci::BluetoothHci() diff --git a/system/test/rootcanal/bluetooth_hci.h b/tools/rootcanal/hal/bluetooth_hci.h index 1bceda8812..1bceda8812 100644 --- a/system/test/rootcanal/bluetooth_hci.h +++ b/tools/rootcanal/hal/bluetooth_hci.h diff --git a/system/test/rootcanal/service.cc b/tools/rootcanal/hal/service.cc index 7856dcfa5b..7856dcfa5b 100644 --- a/system/test/rootcanal/service.cc +++ b/tools/rootcanal/hal/service.cc diff --git a/tools/rootcanal/include/hci/address.h b/tools/rootcanal/include/hci/address.h index ff273cfa53..a46653ada0 100644 --- a/tools/rootcanal/include/hci/address.h +++ b/tools/rootcanal/include/hci/address.h @@ -16,19 +16,20 @@ #pragma once +#include <packet_runtime.h> + #include <array> #include <cstring> #include <initializer_list> #include <optional> #include <ostream> #include <string> - -#include "packet/custom_field_fixed_size_interface.h" +#include <vector> namespace bluetooth { namespace hci { -class Address final : public packet::CustomFieldFixedSizeInterface<Address> { +class Address final : public pdl::packet::Builder { public: static constexpr size_t kLength = 6; @@ -43,10 +44,6 @@ class Address final : public packet::CustomFieldFixedSizeInterface<Address> { Address(const uint8_t (&address)[kLength]); Address(std::initializer_list<uint8_t> l); - // CustomFieldFixedSizeInterface methods - inline uint8_t* data() override { return address.data(); } - inline const uint8_t* data() const override { return address.data(); } - // storage::Serializable methods std::string ToString() const; static std::optional<Address> FromString(const std::string& from); @@ -59,6 +56,17 @@ class Address final : public packet::CustomFieldFixedSizeInterface<Address> { bool operator!=(const Address& rhs) const { return !(*this == rhs); } bool IsEmpty() const { return *this == kEmpty; } + uint8_t* data() { return address.data(); } + uint8_t const* data() const { return address.data(); } + + // Packet parser interface. + static bool Parse(pdl::packet::slice& input, Address* output); + + // Packet builder interface. + size_t GetSize() const override { return kLength; } + void Serialize(std::vector<uint8_t>& output) const override { + output.insert(output.end(), address.begin(), address.end()); + } // Converts |string| to Address and places it in |to|. If |from| does // not represent a Bluetooth address, |to| is not modified and this function diff --git a/tools/rootcanal/include/hci/address_with_type.h b/tools/rootcanal/include/hci/address_with_type.h index 1de5203ae2..51ddfd9f1a 100644 --- a/tools/rootcanal/include/hci/address_with_type.h +++ b/tools/rootcanal/include/hci/address_with_type.h @@ -22,7 +22,7 @@ #include "crypto/crypto.h" #include "hci/address.h" -#include "hci/hci_packets.h" +#include "packets/hci_packets.h" namespace bluetooth { namespace hci { diff --git a/tools/rootcanal/include/hci/class_of_device.h b/tools/rootcanal/include/hci/class_of_device.h deleted file mode 100644 index 8a1d1f04df..0000000000 --- a/tools/rootcanal/include/hci/class_of_device.h +++ /dev/null @@ -1,73 +0,0 @@ -/****************************************************************************** - * - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - ******************************************************************************/ - -#pragma once - -#include <array> -#include <optional> -#include <string> - -#include "packet/custom_field_fixed_size_interface.h" - -namespace bluetooth { -namespace hci { - -class ClassOfDevice final - : public packet::CustomFieldFixedSizeInterface<ClassOfDevice> { - public: - static constexpr size_t kLength = 3; - - std::array<uint8_t, kLength> cod = {}; - - ClassOfDevice() = default; - ClassOfDevice(const uint8_t (&class_of_device)[kLength]); - - // packet::CustomFieldFixedSizeInterface methods - inline uint8_t* data() override { return cod.data(); } - inline const uint8_t* data() const override { return cod.data(); } - - uint32_t ToUint32() const; - std::string ToString() const; - static std::optional<ClassOfDevice> FromString(const std::string& str); - - bool operator<(const ClassOfDevice& rhs) const { return cod < rhs.cod; } - bool operator==(const ClassOfDevice& rhs) const { return cod == rhs.cod; } - bool operator>(const ClassOfDevice& rhs) const { return (rhs < *this); } - bool operator<=(const ClassOfDevice& rhs) const { return !(*this > rhs); } - bool operator>=(const ClassOfDevice& rhs) const { return !(*this < rhs); } - bool operator!=(const ClassOfDevice& rhs) const { return !(*this == rhs); } - - // Converts |string| to ClassOfDevice and places it in |to|. If |from| does - // not represent a Class of Device, |to| is not modified and this function - // returns false. Otherwise, it returns true. - static bool FromString(const std::string& from, ClassOfDevice& to); - - // Copies |from| raw Class of Device octets to the local object. - // Returns the number of copied octets (always ClassOfDevice::kLength) - size_t FromOctets(const uint8_t* from); - - static bool IsValid(const std::string& class_of_device); -}; - -inline std::ostream& operator<<(std::ostream& os, const ClassOfDevice& c) { - os << c.ToString(); - return os; -} - -} // namespace hci -} // namespace bluetooth diff --git a/tools/rootcanal/include/hci/pcap_filter.h b/tools/rootcanal/include/hci/pcap_filter.h index a9897a270f..01c60eb0bf 100644 --- a/tools/rootcanal/include/hci/pcap_filter.h +++ b/tools/rootcanal/include/hci/pcap_filter.h @@ -16,7 +16,7 @@ #pragma once -#include <hci/hci_packets.h> +#include <packets/hci_packets.h> #include <unordered_map> diff --git a/tools/rootcanal/lib/hci/address.cc b/tools/rootcanal/lib/hci/address.cc index dbb0288972..690a1b4ff3 100644 --- a/tools/rootcanal/lib/hci/address.cc +++ b/tools/rootcanal/lib/hci/address.cc @@ -45,6 +45,20 @@ Address::Address(std::initializer_list<uint8_t> l) { std::copy(l.begin(), std::min(l.begin() + kLength, l.end()), data()); } +bool Address::Parse(pdl::packet::slice& input, Address* output) { + if (input.size() < kLength) { + return false; + } + + std::array<uint8_t, kLength> address{ + input.read_le<uint8_t>(), input.read_le<uint8_t>(), + input.read_le<uint8_t>(), input.read_le<uint8_t>(), + input.read_le<uint8_t>(), input.read_le<uint8_t>(), + }; + *output = Address(address); + return true; +} + std::string Address::ToString() const { std::stringstream ss; for (auto it = address.rbegin(); it != address.rend(); it++) { diff --git a/tools/rootcanal/lib/hci/class_of_device.cc b/tools/rootcanal/lib/hci/class_of_device.cc deleted file mode 100644 index 97ab807a21..0000000000 --- a/tools/rootcanal/lib/hci/class_of_device.cc +++ /dev/null @@ -1,112 +0,0 @@ -/****************************************************************************** - * - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - ******************************************************************************/ - -#include "hci/class_of_device.h" - -#include <algorithm> -#include <cstdint> -#include <cstdio> -#include <cstdlib> -#include <sstream> -#include <vector> - -#include "log.h" - -namespace bluetooth { -namespace hci { - -// ClassOfDevice cannot initialize member variables as it is a POD type -// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) -ClassOfDevice::ClassOfDevice(const uint8_t (&class_of_device)[kLength]) { - std::copy(class_of_device, class_of_device + kLength, cod.data()); -} - -uint32_t ClassOfDevice::ToUint32() const { - return (cod[2]) | (cod[1] << 8) | (cod[0] << 16); -} - -std::string ClassOfDevice::ToString() const { - char buffer[] = "000-0-00"; - std::snprintf(&buffer[0], sizeof(buffer), "%03x-%01x-%02x", - (static_cast<uint16_t>(cod[2]) << 4) | cod[1] >> 4, - cod[1] & 0x0f, cod[0]); - std::string str(buffer); - return str; -} - -std::optional<ClassOfDevice> ClassOfDevice::FromString(const std::string& str) { - if (str.length() != 8) { - return std::nullopt; - } - - std::istringstream stream(str); - std::string token; - int index = 0; - uint16_t values[3]; - - ClassOfDevice new_cod{}; - while (getline(stream, token, '-')) { - if (index >= 3) { - return std::nullopt; - } - - if (index == 0 && token.length() != 3) { - return std::nullopt; - } - if (index == 1 && token.length() != 1) { - return std::nullopt; - } - if (index == 2 && token.length() != 2) { - return std::nullopt; - } - char* temp = nullptr; - values[index] = std::strtol(token.c_str(), &temp, 16); - if (*temp != '\0') { - return std::nullopt; - } - - index++; - } - - if (index != 3) { - return std::nullopt; - } - - new_cod.cod[0] = values[2]; - new_cod.cod[1] = values[1] | ((values[0] & 0xf) << 4); - new_cod.cod[2] = values[0] >> 4; - - return new_cod; -} - -bool ClassOfDevice::FromString(const std::string& from, ClassOfDevice& to) { - auto new_cod = FromString(from); - if (!new_cod) { - to = {}; - return false; - } - to = std::move(*new_cod); - return true; -} - -bool ClassOfDevice::IsValid(const std::string& class_of_device) { - return ClassOfDevice::FromString(class_of_device).has_value(); -} - -} // namespace hci -} // namespace bluetooth diff --git a/tools/rootcanal/lib/hci/pcap_filter.cc b/tools/rootcanal/lib/hci/pcap_filter.cc index edb81e42f3..7dbdedabdc 100644 --- a/tools/rootcanal/lib/hci/pcap_filter.cc +++ b/tools/rootcanal/lib/hci/pcap_filter.cc @@ -14,21 +14,21 @@ * limitations under the License. */ -#include <hci/hci_packets.h> #include <hci/pcap_filter.h> -#include <packet/raw_builder.h> +#include <packets/hci_packets.h> + +#include "log.h" using namespace bluetooth::hci; -using namespace bluetooth::packet; namespace rootcanal { -static PacketView<kLittleEndian> create_packet_view( +static pdl::packet::slice create_packet_view( std::vector<uint8_t> const& packet) { // Wrap the reference to the packet in a shared_ptr with created // a no-op deleter. The packet view will be short lived so there is no // risk of the reference leaking. - return PacketView<kLittleEndian>(std::shared_ptr<std::vector<uint8_t> const>( + return pdl::packet::slice(std::shared_ptr<std::vector<uint8_t> const>( &packet, [](std::vector<uint8_t> const* /* ptr */) {})); } @@ -140,8 +140,7 @@ static std::vector<uint8_t> FilterHciAcl(std::vector<uint8_t> const& packet) { payload.resize(acl.GetPayload().size()); ASSERT(acl.IsValid()); return AclBuilder::Create(acl.GetHandle(), acl.GetPacketBoundaryFlag(), - acl.GetBroadcastFlag(), - std::make_unique<RawBuilder>(payload)) + acl.GetBroadcastFlag(), std::move(payload)) ->SerializeToBytes(); } @@ -160,8 +159,7 @@ static std::vector<uint8_t> FilterHciIso(std::vector<uint8_t> const& packet) { payload.resize(iso.GetPayload().size()); ASSERT(iso.IsValid()); return IsoBuilder::Create(iso.GetConnectionHandle(), iso.GetPbFlag(), - iso.GetTsFlag(), - std::make_unique<RawBuilder>(payload)) + iso.GetTsFlag(), std::move(payload)) ->SerializeToBytes(); } diff --git a/tools/rootcanal/model/controller/acl_connection_handler.cc b/tools/rootcanal/model/controller/acl_connection_handler.cc index fabac2abe7..5642c327be 100644 --- a/tools/rootcanal/model/controller/acl_connection_handler.cc +++ b/tools/rootcanal/model/controller/acl_connection_handler.cc @@ -16,7 +16,7 @@ #include "acl_connection_handler.h" -#include <hci/hci_packets.h> +#include <packets/hci_packets.h> #include "hci/address.h" #include "log.h" diff --git a/tools/rootcanal/model/controller/controller_properties.h b/tools/rootcanal/model/controller/controller_properties.h index eabc91604a..5eb4d48163 100644 --- a/tools/rootcanal/model/controller/controller_properties.h +++ b/tools/rootcanal/model/controller/controller_properties.h @@ -23,7 +23,7 @@ #include <vector> #include "hci/address.h" -#include "hci/hci_packets.h" +#include "packets/hci_packets.h" #include "rootcanal/configuration.pb.h" namespace rootcanal { diff --git a/tools/rootcanal/model/controller/dual_mode_controller.cc b/tools/rootcanal/model/controller/dual_mode_controller.cc index 8b633efc52..175f08aa03 100644 --- a/tools/rootcanal/model/controller/dual_mode_controller.cc +++ b/tools/rootcanal/model/controller/dual_mode_controller.cc @@ -21,7 +21,6 @@ #include "crypto/crypto.h" #include "log.h" -#include "packet/raw_builder.h" using bluetooth::hci::ErrorCode; using bluetooth::hci::LoopbackMode; @@ -61,8 +60,8 @@ void DualModeController::SendCommandCompleteUnknownOpCodeEvent( bluetooth::hci::OpCode op_code) const { send_event_(bluetooth::hci::CommandCompleteBuilder::Create( kNumCommandPackets, op_code, - std::make_unique<bluetooth::packet::RawBuilder>(std::vector<uint8_t>{ - static_cast<uint8_t>(ErrorCode::UNKNOWN_HCI_COMMAND)}))); + std::vector<uint8_t>{ + static_cast<uint8_t>(ErrorCode::UNKNOWN_HCI_COMMAND)})); } DualModeController::DualModeController(ControllerProperties properties) @@ -89,18 +88,15 @@ void DualModeController::ForwardToLl(CommandView command) { void DualModeController::HandleAcl( std::shared_ptr<std::vector<uint8_t>> packet) { - bluetooth::hci::PacketView<bluetooth::hci::kLittleEndian> raw_packet(packet); - auto acl_packet = bluetooth::hci::AclView::Create(raw_packet); + auto acl_packet = bluetooth::hci::AclView::Create(pdl::packet::slice(packet)); ASSERT(acl_packet.IsValid()); if (loopback_mode_ == LoopbackMode::ENABLE_LOCAL) { uint16_t handle = acl_packet.GetHandle(); - std::vector<uint8_t> payload{acl_packet.GetPayload().begin(), - acl_packet.GetPayload().end()}; + std::vector<uint8_t> payload(acl_packet.GetPayload()); send_acl_(bluetooth::hci::AclBuilder::Create( handle, acl_packet.GetPacketBoundaryFlag(), - acl_packet.GetBroadcastFlag(), - std::make_unique<bluetooth::packet::RawBuilder>(payload))); + acl_packet.GetBroadcastFlag(), std::move(payload))); std::vector<bluetooth::hci::CompletedPackets> completed_packets; bluetooth::hci::CompletedPackets cp; @@ -117,8 +113,7 @@ void DualModeController::HandleAcl( void DualModeController::HandleSco( std::shared_ptr<std::vector<uint8_t>> packet) { - bluetooth::hci::PacketView<bluetooth::hci::kLittleEndian> raw_packet(packet); - auto sco_packet = bluetooth::hci::ScoView::Create(raw_packet); + auto sco_packet = bluetooth::hci::ScoView::Create(pdl::packet::slice(packet)); ASSERT(sco_packet.IsValid()); if (loopback_mode_ == LoopbackMode::ENABLE_LOCAL) { uint16_t handle = sco_packet.GetHandle(); @@ -143,16 +138,15 @@ void DualModeController::HandleSco( void DualModeController::HandleIso( std::shared_ptr<std::vector<uint8_t>> packet) { - bluetooth::hci::PacketView<bluetooth::hci::kLittleEndian> raw_packet(packet); - auto iso = bluetooth::hci::IsoView::Create(raw_packet); + auto iso = bluetooth::hci::IsoView::Create(pdl::packet::slice(packet)); ASSERT(iso.IsValid()); link_layer_controller_.HandleIso(iso); } void DualModeController::HandleCommand( std::shared_ptr<std::vector<uint8_t>> packet) { - auto command_packet = bluetooth::hci::CommandView::Create( - bluetooth::hci::PacketView<bluetooth::hci::kLittleEndian>(packet)); + auto command_packet = + bluetooth::hci::CommandView::Create(pdl::packet::slice(packet)); ASSERT(command_packet.IsValid()); OpCode op_code = command_packet.GetOpCode(); @@ -184,17 +178,13 @@ void DualModeController::HandleCommand( op_code != OpCode::READ_BUFFER_SIZE && op_code != OpCode::READ_LOOPBACK_MODE && op_code != OpCode::WRITE_LOOPBACK_MODE) { - std::unique_ptr<bluetooth::packet::RawBuilder> raw_builder_ptr = - std::make_unique<bluetooth::packet::RawBuilder>(255); - raw_builder_ptr->AddOctets(*packet); send_event_(bluetooth::hci::LoopbackCommandBuilder::Create( - std::move(raw_builder_ptr))); + std::vector<uint8_t>(packet->begin(), packet->end()))); } // Quirk to reset the host stack when a command is received before the Hci // Reset command. else if (properties_.quirks.hardware_error_before_reset && - !controller_reset_ && - op_code != OpCode::RESET) { + !controller_reset_ && op_code != OpCode::RESET) { WARNING(id_, "Received command {} before HCI Reset; sending the Hardware" " Error event", @@ -233,10 +223,8 @@ void DualModeController::RegisterEventChannel( send_event_ = [send_event](std::shared_ptr<bluetooth::hci::EventBuilder> event) { auto bytes = std::make_shared<std::vector<uint8_t>>(); - bluetooth::packet::BitInserter bit_inserter(*bytes); - bytes->reserve(event->size()); - event->Serialize(bit_inserter); - send_event(std::move(bytes)); + event->Serialize(*bytes); + send_event(bytes); }; link_layer_controller_.RegisterEventChannel(send_event_); } @@ -246,10 +234,8 @@ void DualModeController::RegisterAclChannel( send_acl) { send_acl_ = [send_acl](std::shared_ptr<bluetooth::hci::AclBuilder> acl_data) { auto bytes = std::make_shared<std::vector<uint8_t>>(); - bluetooth::packet::BitInserter bit_inserter(*bytes); - bytes->reserve(acl_data->size()); - acl_data->Serialize(bit_inserter); - send_acl(std::move(bytes)); + acl_data->Serialize(*bytes); + send_acl(bytes); }; link_layer_controller_.RegisterAclChannel(send_acl_); } @@ -259,10 +245,8 @@ void DualModeController::RegisterScoChannel( send_sco) { send_sco_ = [send_sco](std::shared_ptr<bluetooth::hci::ScoBuilder> sco_data) { auto bytes = std::make_shared<std::vector<uint8_t>>(); - bluetooth::packet::BitInserter bit_inserter(*bytes); - bytes->reserve(sco_data->size()); - sco_data->Serialize(bit_inserter); - send_sco(std::move(bytes)); + sco_data->Serialize(*bytes); + send_sco(bytes); }; link_layer_controller_.RegisterScoChannel(send_sco_); } @@ -272,10 +256,8 @@ void DualModeController::RegisterIsoChannel( send_iso) { send_iso_ = [send_iso](std::shared_ptr<bluetooth::hci::IsoBuilder> iso_data) { auto bytes = std::make_shared<std::vector<uint8_t>>(); - bluetooth::packet::BitInserter bit_inserter(*bytes); - bytes->reserve(iso_data->size()); - iso_data->Serialize(bit_inserter); - send_iso(std::move(bytes)); + iso_data->Serialize(*bytes); + send_iso(bytes); }; link_layer_controller_.RegisterIsoChannel(send_iso_); } @@ -409,7 +391,7 @@ void DualModeController::ReadRemoteVersionInformation(CommandView command) { DEBUG(id_, " connection_handle=0x{:x}", command_view.GetConnectionHandle()); auto status = link_layer_controller_.SendCommandToRemoteByHandle( - OpCode::READ_REMOTE_VERSION_INFORMATION, command.GetPayload(), + OpCode::READ_REMOTE_VERSION_INFORMATION, command.bytes(), command_view.GetConnectionHandle()); send_event_(bluetooth::hci::ReadRemoteVersionInformationStatusBuilder::Create( @@ -487,7 +469,7 @@ void DualModeController::ReadRemoteExtendedFeatures(CommandView command) { DEBUG(id_, " page_number={}", command_view.GetPageNumber()); auto status = link_layer_controller_.SendCommandToRemoteByHandle( - OpCode::READ_REMOTE_EXTENDED_FEATURES, command_view.GetPayload(), + OpCode::READ_REMOTE_EXTENDED_FEATURES, command_view.bytes(), command_view.GetConnectionHandle()); send_event_(bluetooth::hci::ReadRemoteExtendedFeaturesStatusBuilder::Create( @@ -518,7 +500,7 @@ void DualModeController::ReadRemoteSupportedFeatures(CommandView command) { DEBUG(id_, " connection_handle=0x{:x}", command_view.GetConnectionHandle()); auto status = link_layer_controller_.SendCommandToRemoteByHandle( - OpCode::READ_REMOTE_SUPPORTED_FEATURES, command_view.GetPayload(), + OpCode::READ_REMOTE_SUPPORTED_FEATURES, command_view.bytes(), command_view.GetConnectionHandle()); send_event_(bluetooth::hci::ReadRemoteSupportedFeaturesStatusBuilder::Create( @@ -535,7 +517,7 @@ void DualModeController::ReadClockOffset(CommandView command) { uint16_t handle = command_view.GetConnectionHandle(); auto status = link_layer_controller_.SendCommandToRemoteByHandle( - OpCode::READ_CLOCK_OFFSET, command_view.GetPayload(), handle); + OpCode::READ_CLOCK_OFFSET, command_view.bytes(), handle); send_event_(bluetooth::hci::ReadClockOffsetStatusBuilder::Create( status, kNumCommandPackets)); @@ -1200,8 +1182,7 @@ void DualModeController::WriteClassOfDevice(CommandView command) { ASSERT(command_view.IsValid()); DEBUG(id_, "<< Write Class of Device"); - DEBUG(id_, " class_of_device={}", - command_view.GetClassOfDevice().ToString()); + DEBUG(id_, " class_of_device=0x{:x}", command_view.GetClassOfDevice()); link_layer_controller_.SetClassOfDevice(command_view.GetClassOfDevice()); send_event_(bluetooth::hci::WriteClassOfDeviceCompleteBuilder::Create( @@ -1797,8 +1778,7 @@ void DualModeController::RemoteNameRequest(CommandView command) { DEBUG(id_, " bd_addr={}", bd_addr); auto status = link_layer_controller_.SendCommandToRemoteByAddress( - OpCode::REMOTE_NAME_REQUEST, command_view.GetPayload(), GetAddress(), - bd_addr); + OpCode::REMOTE_NAME_REQUEST, command_view.bytes(), GetAddress(), bd_addr); send_event_(bluetooth::hci::RemoteNameRequestStatusBuilder::Create( status, kNumCommandPackets)); @@ -2081,10 +2061,10 @@ void DualModeController::LeCreateConnection(CommandView command) { command_view.GetPeerAddress(), command_view.GetPeerAddressType(), }, - command_view.GetOwnAddressType(), command_view.GetConnIntervalMin(), - command_view.GetConnIntervalMax(), command_view.GetConnLatency(), - command_view.GetSupervisionTimeout(), command_view.GetMinimumCeLength(), - command_view.GetMaximumCeLength()); + command_view.GetOwnAddressType(), command_view.GetConnectionIntervalMin(), + command_view.GetConnectionIntervalMax(), command_view.GetMaxLatency(), + command_view.GetSupervisionTimeout(), command_view.GetMinCeLength(), + command_view.GetMaxCeLength()); send_event_(bluetooth::hci::LeCreateConnectionStatusBuilder::Create( status, kNumCommandPackets)); } @@ -2109,8 +2089,9 @@ void DualModeController::LeConnectionUpdate(CommandView command) { DEBUG(id_, " connection_handle=0x{:x}", command_view.GetConnectionHandle()); ErrorCode status = link_layer_controller_.LeConnectionUpdate( - command_view.GetConnectionHandle(), command_view.GetConnIntervalMin(), - command_view.GetConnIntervalMax(), command_view.GetConnLatency(), + command_view.GetConnectionHandle(), + command_view.GetConnectionIntervalMin(), + command_view.GetConnectionIntervalMax(), command_view.GetMaxLatency(), command_view.GetSupervisionTimeout()); send_event_(bluetooth::hci::LeConnectionUpdateStatusBuilder::Create( @@ -2628,7 +2609,7 @@ void DualModeController::LeSetExtendedScanParameters(CommandView command) { ErrorCode status = link_layer_controller_.LeSetExtendedScanParameters( command_view.GetOwnAddressType(), command_view.GetScanningFilterPolicy(), - command_view.GetScanningPhys(), command_view.GetParameters()); + command_view.GetScanningPhys(), command_view.GetScanningPhyParameters()); send_event_( bluetooth::hci::LeSetExtendedScanParametersCompleteBuilder::Create( kNumCommandPackets, status)); @@ -2659,18 +2640,30 @@ void DualModeController::LeExtendedCreateConnection(CommandView command) { DEBUG(id_, "<< LE Extended Create Connection"); DEBUG(id_, " peer_address={}", command_view.GetPeerAddress()); DEBUG(id_, " peer_address_type={}", - bluetooth::hci::AddressTypeText(command_view.GetPeerAddressType())); + bluetooth::hci::PeerAddressTypeText(command_view.GetPeerAddressType())); DEBUG(id_, " initiator_filter_policy={}", bluetooth::hci::InitiatorFilterPolicyText( command_view.GetInitiatorFilterPolicy())); + AddressType peer_address_type; + switch (command_view.GetPeerAddressType()) { + case PeerAddressType::PUBLIC_DEVICE_OR_IDENTITY_ADDRESS: + default: + peer_address_type = AddressType::PUBLIC_DEVICE_ADDRESS; + break; + case PeerAddressType::RANDOM_DEVICE_OR_IDENTITY_ADDRESS: + peer_address_type = AddressType::RANDOM_DEVICE_ADDRESS; + break; + } + ErrorCode status = link_layer_controller_.LeExtendedCreateConnection( command_view.GetInitiatorFilterPolicy(), command_view.GetOwnAddressType(), AddressWithType{ command_view.GetPeerAddress(), - command_view.GetPeerAddressType(), + peer_address_type, }, - command_view.GetInitiatingPhys(), command_view.GetPhyScanParameters()); + command_view.GetInitiatingPhys(), + command_view.GetInitiatingPhyParameters()); send_event_(bluetooth::hci::LeExtendedCreateConnectionStatusBuilder::Create( status, kNumCommandPackets)); } @@ -2704,7 +2697,7 @@ void DualModeController::LeReadRemoteFeatures(CommandView command) { DEBUG(id_, " connection_handle=0x{:x}", handle); auto status = link_layer_controller_.SendCommandToRemoteByHandle( - OpCode::LE_READ_REMOTE_FEATURES, command_view.GetPayload(), handle); + OpCode::LE_READ_REMOTE_FEATURES, command_view.bytes(), handle); send_event_(bluetooth::hci::LeReadRemoteFeaturesStatusBuilder::Create( status, kNumCommandPackets)); @@ -2794,20 +2787,19 @@ void DualModeController::LeGetVendorCapabilities(CommandView command) { return; } + std::vector<uint8_t> return_parameters = { + static_cast<uint8_t>(ErrorCode::SUCCESS)}; + return_parameters.insert(return_parameters.end(), + properties_.le_vendor_capabilities.begin(), + properties_.le_vendor_capabilities.end()); // Ensure a minimal size for vendor capabilities. - vector<uint8_t> vendor_capabilities = properties_.le_vendor_capabilities; - if (vendor_capabilities.size() < 8) { - vendor_capabilities.resize(8); + if (return_parameters.size() < 9) { + return_parameters.resize(9); } - std::unique_ptr<bluetooth::packet::RawBuilder> raw_builder_ptr = - std::make_unique<bluetooth::packet::RawBuilder>(); - raw_builder_ptr->AddOctets1(static_cast<uint8_t>(ErrorCode::SUCCESS)); - raw_builder_ptr->AddOctets(vendor_capabilities); - send_event_(bluetooth::hci::CommandCompleteBuilder::Create( kNumCommandPackets, OpCode::LE_GET_VENDOR_CAPABILITIES, - std::move(raw_builder_ptr))); + std::move(return_parameters))); } void DualModeController::LeMultiAdv(CommandView command) { @@ -2848,8 +2840,7 @@ void DualModeController::CsrVendorCommand(CommandView command) { // // BlueZ has a reference implementation of the CSR vendor command. - std::vector<uint8_t> parameters(command.GetPayload().begin(), - command.GetPayload().end()); + std::vector<uint8_t> parameters(command.GetPayload()); uint16_t type = 0; uint16_t length = 0; @@ -2928,8 +2919,7 @@ complete: parameters[1] = 0x1; parameters[2] = 0x0; send_event_(bluetooth::hci::EventBuilder::Create( - bluetooth::hci::EventCode::VENDOR_SPECIFIC, - std::make_unique<bluetooth::packet::RawBuilder>(std::move(parameters)))); + bluetooth::hci::EventCode::VENDOR_SPECIFIC, std::move(parameters))); } void DualModeController::CsrReadVarid(CsrVarid varid, diff --git a/tools/rootcanal/model/controller/dual_mode_controller.h b/tools/rootcanal/model/controller/dual_mode_controller.h index c591ca98ae..d8a71eb484 100644 --- a/tools/rootcanal/model/controller/dual_mode_controller.h +++ b/tools/rootcanal/model/controller/dual_mode_controller.h @@ -27,10 +27,10 @@ #include "controller_properties.h" #include "hci/address.h" -#include "hci/hci_packets.h" #include "link_layer_controller.h" #include "model/controller/vendor_commands/csr.h" #include "model/devices/device.h" +#include "packets/hci_packets.h" namespace rootcanal { diff --git a/tools/rootcanal/model/controller/ffi.cc b/tools/rootcanal/model/controller/ffi.cc index 81162ed9dd..888f198a26 100644 --- a/tools/rootcanal/model/controller/ffi.cc +++ b/tools/rootcanal/model/controller/ffi.cc @@ -117,9 +117,7 @@ __attribute__((visibility("default"))) void ffi_controller_receive_ll( std::shared_ptr<std::vector<uint8_t>> bytes = std::make_shared<std::vector<uint8_t>>(data, data + data_len); model::packets::LinkLayerPacketView packet = - model::packets::LinkLayerPacketView::Create( - bluetooth::packet::PacketView<bluetooth::packet::kLittleEndian>( - bytes)); + model::packets::LinkLayerPacketView::Create(pdl::packet::slice(bytes)); if (!packet.IsValid()) { std::cerr << "Dropping malformed LL packet" << std::endl; return; diff --git a/tools/rootcanal/model/controller/le_advertiser.h b/tools/rootcanal/model/controller/le_advertiser.h index 19af1125d4..80fced0440 100644 --- a/tools/rootcanal/model/controller/le_advertiser.h +++ b/tools/rootcanal/model/controller/le_advertiser.h @@ -23,7 +23,7 @@ #include <ratio> #include "hci/address_with_type.h" -#include "hci/hci_packets.h" +#include "packets/hci_packets.h" #include "packets/link_layer_packets.h" namespace rootcanal { diff --git a/tools/rootcanal/model/controller/link_layer_controller.cc b/tools/rootcanal/model/controller/link_layer_controller.cc index a0a87b0733..adafc72b99 100644 --- a/tools/rootcanal/model/controller/link_layer_controller.cc +++ b/tools/rootcanal/model/controller/link_layer_controller.cc @@ -16,13 +16,11 @@ #include "link_layer_controller.h" -#include <hci/hci_packets.h> - #include <algorithm> #include "crypto/crypto.h" #include "log.h" -#include "packet/raw_builder.h" +#include "packets/hci_packets.h" #include "rootcanal_rs.h" using namespace std::chrono; @@ -684,7 +682,7 @@ ErrorCode LinkLayerController::LeAddDeviceToResolvingList( for (auto const& entry : le_resolving_list_) { if ((entry.peer_identity_address_type == peer_identity_address_type && entry.peer_identity_address == peer_identity_address) || - entry.peer_irk == peer_irk) { + (entry.peer_irk == peer_irk && !irk_is_zero(peer_irk))) { INFO(id_, "device is already present in the resolving list"); return ErrorCode::INVALID_HCI_COMMAND_PARAMETERS; } @@ -1283,7 +1281,8 @@ ErrorCode LinkLayerController::LeSetExtendedScanParameters( bluetooth::hci::OwnAddressType own_address_type, bluetooth::hci::LeScanningFilterPolicy scanning_filter_policy, uint8_t scanning_phys, - std::vector<bluetooth::hci::PhyScanParameters> scanning_phy_parameters) { + std::vector<bluetooth::hci::ScanningPhyParameters> + scanning_phy_parameters) { uint8_t supported_phys = properties_.LeSupportedPhys(); // Extended advertising commands are disallowed when legacy advertising @@ -1678,7 +1677,7 @@ ErrorCode LinkLayerController::LeExtendedCreateConnection( bluetooth::hci::InitiatorFilterPolicy initiator_filter_policy, bluetooth::hci::OwnAddressType own_address_type, AddressWithType peer_address, uint8_t initiating_phys, - std::vector<bluetooth::hci::LeCreateConnPhyScanParameters> + std::vector<bluetooth::hci::InitiatingPhyParameters> initiating_phy_parameters) { // Extended advertising commands are disallowed when legacy advertising // commands were used since the last reset. @@ -1753,36 +1752,39 @@ ErrorCode LinkLayerController::LeExtendedCreateConnection( // Note: no explicit error code stated for invalid connection interval // values but assuming Unsupported Feature or Parameter Value (0x11) // error code based on similar advertising command. - if (parameter.conn_interval_min_ < 0x6 || - parameter.conn_interval_min_ > 0x0c80 || - parameter.conn_interval_max_ < 0x6 || - parameter.conn_interval_max_ > 0x0c80) { + if (parameter.connection_interval_min_ < 0x6 || + parameter.connection_interval_min_ > 0x0c80 || + parameter.connection_interval_max_ < 0x6 || + parameter.connection_interval_max_ > 0x0c80) { INFO(id_, "connection_interval_min (0x{:04x}) and/or " "connection_interval_max (0x{:04x}) are outside the range" " of supported values (0x6 - 0x0c80)", - parameter.conn_interval_min_, parameter.conn_interval_max_); + parameter.connection_interval_min_, + parameter.connection_interval_max_); return ErrorCode::UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE; } // The Connection_Interval_Min parameter shall not be greater than the // Connection_Interval_Max parameter. - if (parameter.conn_interval_max_ < parameter.conn_interval_min_) { + if (parameter.connection_interval_max_ < + parameter.connection_interval_min_) { INFO(id_, "connection_interval_min (0x{:04x}) is larger than" " connection_interval_max (0x{:04x})", - parameter.conn_interval_min_, parameter.conn_interval_max_); + parameter.connection_interval_min_, + parameter.connection_interval_max_); return ErrorCode::INVALID_HCI_COMMAND_PARAMETERS; } // Note: no explicit error code stated for invalid max_latency // values but assuming Unsupported Feature or Parameter Value (0x11) // error code based on similar advertising command. - if (parameter.conn_latency_ > 0x01f3) { + if (parameter.max_latency_ > 0x01f3) { INFO(id_, "max_latency (0x{:04x}) is outside the range" " of supported values (0x0 - 0x01f3)", - parameter.conn_latency_); + parameter.max_latency_); return ErrorCode::UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE; } @@ -1802,8 +1804,8 @@ ErrorCode LinkLayerController::LeExtendedCreateConnection( // (1 + Max_Latency) * Connection_Interval_Max * 2, where // Connection_Interval_Max is given in milliseconds. milliseconds min_supervision_timeout = duration_cast<milliseconds>( - (1 + parameter.conn_latency_) * - slots(2 * parameter.conn_interval_max_) * 2); + (1 + parameter.max_latency_) * + slots(2 * parameter.connection_interval_max_) * 2); if (parameter.supervision_timeout_ * 10ms < min_supervision_timeout) { INFO( id_, @@ -1864,10 +1866,10 @@ ErrorCode LinkLayerController::LeExtendedCreateConnection( .scan_interval = initiating_phy_parameters[offset].scan_interval_, .scan_window = initiating_phy_parameters[offset].scan_window_, .connection_interval_min = - initiating_phy_parameters[offset].conn_interval_min_, + initiating_phy_parameters[offset].connection_interval_min_, .connection_interval_max = - initiating_phy_parameters[offset].conn_interval_max_, - .max_latency = initiating_phy_parameters[offset].conn_latency_, + initiating_phy_parameters[offset].connection_interval_max_, + .max_latency = initiating_phy_parameters[offset].max_latency_, .supervision_timeout = initiating_phy_parameters[offset].supervision_timeout_, .min_ce_length = initiating_phy_parameters[offset].min_ce_length_, @@ -1882,10 +1884,10 @@ ErrorCode LinkLayerController::LeExtendedCreateConnection( .scan_interval = initiating_phy_parameters[offset].scan_interval_, .scan_window = initiating_phy_parameters[offset].scan_window_, .connection_interval_min = - initiating_phy_parameters[offset].conn_interval_min_, + initiating_phy_parameters[offset].connection_interval_min_, .connection_interval_max = - initiating_phy_parameters[offset].conn_interval_max_, - .max_latency = initiating_phy_parameters[offset].conn_latency_, + initiating_phy_parameters[offset].connection_interval_max_, + .max_latency = initiating_phy_parameters[offset].max_latency_, .supervision_timeout = initiating_phy_parameters[offset].supervision_timeout_, .min_ce_length = initiating_phy_parameters[offset].min_ce_length_, @@ -1900,10 +1902,10 @@ ErrorCode LinkLayerController::LeExtendedCreateConnection( .scan_interval = initiating_phy_parameters[offset].scan_interval_, .scan_window = initiating_phy_parameters[offset].scan_window_, .connection_interval_min = - initiating_phy_parameters[offset].conn_interval_min_, + initiating_phy_parameters[offset].connection_interval_min_, .connection_interval_max = - initiating_phy_parameters[offset].conn_interval_max_, - .max_latency = initiating_phy_parameters[offset].conn_latency_, + initiating_phy_parameters[offset].connection_interval_max_, + .max_latency = initiating_phy_parameters[offset].max_latency_, .supervision_timeout = initiating_phy_parameters[offset].supervision_timeout_, .min_ce_length = initiating_phy_parameters[offset].min_ce_length_, @@ -2034,11 +2036,8 @@ LinkLayerController::LinkLayerController(const Address& address, auto controller = static_cast<LinkLayerController*>(user); auto event_code = static_cast<EventCode>(data[0]); - auto payload = std::make_unique<bluetooth::packet::RawBuilder>( - std::vector(data + 2, data + len)); - controller->send_event_(bluetooth::hci::EventBuilder::Create( - event_code, std::move(payload))); + event_code, std::vector(data + 2, data + len))); }, .send_lmp_packet = @@ -2046,22 +2045,17 @@ LinkLayerController::LinkLayerController(const Address& address, uintptr_t len) { auto controller = static_cast<LinkLayerController*>(user); - auto payload = std::make_unique<bluetooth::packet::RawBuilder>( - std::vector(data, data + len)); - Address source = controller->GetAddress(); Address dest(*to); controller->SendLinkLayerPacket(model::packets::LmpBuilder::Create( - source, dest, std::move(payload))); + source, dest, std::vector(data, data + len))); }, .send_llcp_packet = [](void* user, uint16_t acl_connection_handle, const uint8_t* data, uintptr_t len) { auto controller = static_cast<LinkLayerController*>(user); - auto payload = std::make_unique<bluetooth::packet::RawBuilder>( - std::vector(data, data + len)); if (!controller->connections_.HasHandle(acl_connection_handle)) { ERROR( @@ -2078,7 +2072,7 @@ LinkLayerController::LinkLayerController(const Address& address, Address destination = connection.GetAddress().GetAddress(); controller->SendLinkLayerPacket(model::packets::LlcpBuilder::Create( - source, destination, std::move(payload))); + source, destination, std::vector(data, data + len))); }}; lm_.reset(link_manager_create(controller_ops_)); @@ -2124,8 +2118,8 @@ ErrorCode LinkLayerController::SendLeCommandToRemoteByAddress( } ErrorCode LinkLayerController::SendCommandToRemoteByAddress( - OpCode opcode, bluetooth::packet::PacketView<true> args, - const Address& own_address, const Address& peer_address) { + OpCode opcode, pdl::packet::slice args, const Address& own_address, + const Address& peer_address) { switch (opcode) { case (OpCode::REMOTE_NAME_REQUEST): // LMP features get requested with remote name requests. @@ -2140,8 +2134,8 @@ ErrorCode LinkLayerController::SendCommandToRemoteByAddress( own_address, peer_address)); break; case (OpCode::READ_REMOTE_EXTENDED_FEATURES): { - uint8_t page_number = - (args.begin() + 2).extract<uint8_t>(); // skip the handle + pdl::packet::slice page_number_slice = args.subrange(5, 2); + uint8_t page_number = page_number_slice.read_le<uint8_t>(); SendLinkLayerPacket( model::packets::ReadRemoteExtendedFeaturesBuilder::Create( own_address, peer_address, page_number)); @@ -2165,7 +2159,7 @@ ErrorCode LinkLayerController::SendCommandToRemoteByAddress( } ErrorCode LinkLayerController::SendCommandToRemoteByHandle( - OpCode opcode, bluetooth::packet::PacketView<true> args, uint16_t handle) { + OpCode opcode, pdl::packet::slice args, uint16_t handle) { if (!connections_.HasHandle(handle)) { return ErrorCode::UNKNOWN_CONNECTION; } @@ -2231,8 +2225,7 @@ ErrorCode LinkLayerController::SendScoToRemote( std::vector<uint8_t> sco_data_bytes(sco_data.begin(), sco_data.end()); SendLinkLayerPacket(model::packets::ScoBuilder::Create( - source, destination, - std::make_unique<bluetooth::packet::RawBuilder>(sco_data_bytes))); + source, destination, std::move(sco_data_bytes))); return ErrorCode::SUCCESS; } @@ -2479,7 +2472,7 @@ void LinkLayerController::IncomingAclPacket( auto acl_packet = bluetooth::hci::AclBuilder::Create( connection_handle, packet_boundary_flag, broadcast_flag, - std::make_unique<bluetooth::packet::RawBuilder>(std::move(fragment))); + std::move(fragment)); send_acl_(std::move(acl_packet)); @@ -2803,6 +2796,7 @@ void LinkLayerController::IncomingInquiryResponsePacket( inquiry_response.GetClockOffset(), inquiry_response.GetRssi(), inquiry_response.GetExtendedInquiryResponse())); } break; + default: WARNING(id_, "Unhandled Incoming Inquiry Response of type {}", static_cast<int>(basic_inquiry_response.GetInquiryType())); @@ -2843,6 +2837,10 @@ Address LinkLayerController::generate_rpa( return rpa; } +bool LinkLayerController::irk_is_zero(std::array<uint8_t, LinkLayerController::kIrkSize> irk) { + return std::all_of(irk.begin(), irk.end(), [](uint8_t b) { return b == 0; }); +} + // Handle legacy advertising PDUs while in the Scanning state. void LinkLayerController::ScanIncomingLeLegacyAdvertisingPdu( model::packets::LeLegacyAdvertisingPduView& pdu, uint8_t rssi) { @@ -2963,10 +2961,10 @@ void LinkLayerController::ScanIncomingLeLegacyAdvertisingPdu( bool should_send_advertising_report = true; if (scanner_.filter_duplicates != bluetooth::hci::FilterDuplicates::DISABLED) { - if (scanner_.IsPacketInHistory(pdu)) { + if (scanner_.IsPacketInHistory(pdu.bytes())) { should_send_advertising_report = false; } else { - scanner_.AddPacketToHistory(pdu); + scanner_.AddPacketToHistory(pdu.bytes()); } } @@ -3290,6 +3288,7 @@ void LinkLayerController::ConnectIncomingLeLegacyAdvertisingPdu( } initiator_.pending_connect_request = advertising_address; + initiator_.initiating_address = initiating_address.GetAddress(); INFO(id_, "Sending LE Connect request to {} with initiating address {}", resolved_advertising_address, initiating_address); @@ -3425,10 +3424,10 @@ void LinkLayerController::ScanIncomingLeExtendedAdvertisingPdu( bool should_send_advertising_report = true; if (scanner_.filter_duplicates != bluetooth::hci::FilterDuplicates::DISABLED) { - if (scanner_.IsPacketInHistory(pdu)) { + if (scanner_.IsPacketInHistory(pdu.bytes())) { should_send_advertising_report = false; } else { - scanner_.AddPacketToHistory(pdu); + scanner_.AddPacketToHistory(pdu.bytes()); } } @@ -3716,6 +3715,7 @@ void LinkLayerController::ConnectIncomingLeExtendedAdvertisingPdu( } initiator_.pending_connect_request = advertising_address; + initiator_.initiating_address = initiating_address.GetAddress(); INFO(id_, "Sending LE Connect request to {} with initiating address {}", resolved_advertising_address, initiating_address); @@ -4091,7 +4091,7 @@ void LinkLayerController::IncomingLeConnectedIsochronousPdu( send_iso_(bluetooth::hci::IsoWithoutTimestampBuilder::Create( cis_connection_handle, packet_boundary_flag, pdu.GetSequenceNumber(), iso_sdu_length, bluetooth::hci::IsoPacketStatusFlag::VALID, - std::make_unique<bluetooth::packet::RawBuilder>(std::move(fragment)))); + std::move(fragment))); remaining_size -= fragment_size; offset += fragment_size; @@ -4917,10 +4917,10 @@ void LinkLayerController::IncomingLeScanResponsePacket( bool should_send_advertising_report = true; if (scanner_.filter_duplicates != bluetooth::hci::FilterDuplicates::DISABLED) { - if (scanner_.IsPacketInHistory(incoming)) { + if (scanner_.IsPacketInHistory(incoming.bytes())) { should_send_advertising_report = false; } else { - scanner_.AddPacketToHistory(incoming); + scanner_.AddPacketToHistory(incoming.bytes()); } } @@ -5171,12 +5171,12 @@ void LinkLayerController::RegisterRemoteChannel( } void LinkLayerController::ForwardToLm(bluetooth::hci::CommandView command) { - auto packet = std::vector(command.begin(), command.end()); + auto packet = command.bytes().bytes(); ASSERT(link_manager_ingest_hci(lm_.get(), packet.data(), packet.size())); } void LinkLayerController::ForwardToLl(bluetooth::hci::CommandView command) { - auto packet = std::vector(command.begin(), command.end()); + auto packet = command.bytes().bytes(); ASSERT(link_layer_ingest_hci(ll_.get(), packet.data(), packet.size())); } @@ -5993,7 +5993,7 @@ void LinkLayerController::Reset() { sco_flow_control_enable_ = false; local_name_.fill(0); extended_inquiry_response_.fill(0); - class_of_device_ = ClassOfDevice({0, 0, 0}); + class_of_device_ = 0; min_encryption_key_size_ = 16; event_mask_ = 0x00001fffffffffff; event_mask_page_2_ = 0x0; @@ -6322,12 +6322,9 @@ TaskId LinkLayerController::StartScoStream(Address address) { connections_.GetScoHandle(address), PacketStatusFlag::CORRECTLY_RECEIVED, {0, 0, 0, 0, 0}); - auto bytes = std::make_shared<std::vector<uint8_t>>(); - bluetooth::packet::BitInserter bit_inserter(*bytes); - sco_builder->Serialize(bit_inserter); - auto raw_view = - bluetooth::hci::PacketView<bluetooth::hci::kLittleEndian>(bytes); - auto sco_view = bluetooth::hci::ScoView::Create(raw_view); + auto sco_bytes = sco_builder->SerializeToBytes(); + auto sco_view = bluetooth::hci::ScoView::Create(pdl::packet::slice( + std::make_shared<std::vector<uint8_t>>(std::move(sco_bytes)))); ASSERT(sco_view.IsValid()); return SchedulePeriodicTask(0ms, 20ms, [this, address, sco_view]() { diff --git a/tools/rootcanal/model/controller/link_layer_controller.h b/tools/rootcanal/model/controller/link_layer_controller.h index 58abc822d0..3e036faff6 100644 --- a/tools/rootcanal/model/controller/link_layer_controller.h +++ b/tools/rootcanal/model/controller/link_layer_controller.h @@ -23,11 +23,11 @@ #include <vector> #include "hci/address.h" -#include "hci/hci_packets.h" #include "include/phy.h" #include "model/controller/acl_connection_handler.h" #include "model/controller/controller_properties.h" #include "model/controller/le_advertiser.h" +#include "packets/hci_packets.h" #include "packets/link_layer_packets.h" #include "rootcanal_rs.h" @@ -36,7 +36,6 @@ namespace rootcanal { using ::bluetooth::hci::Address; using ::bluetooth::hci::AddressType; using ::bluetooth::hci::AuthenticationEnable; -using ::bluetooth::hci::ClassOfDevice; using ::bluetooth::hci::ErrorCode; using ::bluetooth::hci::FilterAcceptListAddressType; using ::bluetooth::hci::OpCode; @@ -64,18 +63,21 @@ class LinkLayerController { static Address generate_rpa( std::array<uint8_t, LinkLayerController::kIrkSize> irk); + // Return true if the input IRK is all 0s. + static bool irk_is_zero(std::array<uint8_t, LinkLayerController::kIrkSize> irk); + LinkLayerController(const Address& address, const ControllerProperties& properties, int id = 0); ~LinkLayerController(); - ErrorCode SendCommandToRemoteByAddress( - OpCode opcode, bluetooth::packet::PacketView<true> args, - const Address& own_address, const Address& peer_address); + ErrorCode SendCommandToRemoteByAddress(OpCode opcode, pdl::packet::slice args, + const Address& own_address, + const Address& peer_address); ErrorCode SendLeCommandToRemoteByAddress(OpCode opcode, const Address& own_address, const Address& peer_address); - ErrorCode SendCommandToRemoteByHandle( - OpCode opcode, bluetooth::packet::PacketView<true> args, uint16_t handle); + ErrorCode SendCommandToRemoteByHandle(OpCode opcode, pdl::packet::slice args, + uint16_t handle); ErrorCode SendScoToRemote(bluetooth::hci::ScoView sco_packet); ErrorCode SendAclToRemote(bluetooth::hci::AclView acl_packet); @@ -481,7 +483,8 @@ class LinkLayerController { bluetooth::hci::OwnAddressType own_address_type, bluetooth::hci::LeScanningFilterPolicy scanning_filter_policy, uint8_t scanning_phys, - std::vector<bluetooth::hci::PhyScanParameters> scanning_phy_parameters); + std::vector<bluetooth::hci::ScanningPhyParameters> + scanning_phy_parameters); // HCI command LE_Set_Extended_Scan_Enable (Vol 4, Part E § 7.8.65). ErrorCode LeSetExtendedScanEnable( @@ -510,7 +513,7 @@ class LinkLayerController { bluetooth::hci::InitiatorFilterPolicy initiator_filter_policy, bluetooth::hci::OwnAddressType own_address_type, AddressWithType peer_address, uint8_t initiating_phys, - std::vector<bluetooth::hci::LeCreateConnPhyScanParameters> + std::vector<bluetooth::hci::InitiatingPhyParameters> initiating_phy_parameters); // Periodic Advertising @@ -719,7 +722,7 @@ class LinkLayerController { } uint16_t GetVoiceSetting() const { return voice_setting_; } - const ClassOfDevice& GetClassOfDevice() const { return class_of_device_; } + uint32_t GetClassOfDevice() const { return class_of_device_; } uint8_t GetMaxLmpFeaturesPageNumber() { return properties_.lmp_features.size() - 1; @@ -738,14 +741,8 @@ class LinkLayerController { void SetExtendedInquiryResponse( std::vector<uint8_t> const& extended_inquiry_response); - void SetClassOfDevice(ClassOfDevice class_of_device) { - class_of_device_ = class_of_device; - } - void SetClassOfDevice(uint32_t class_of_device) { - class_of_device_.cod[0] = class_of_device & UINT8_MAX; - class_of_device_.cod[1] = (class_of_device >> 8) & UINT8_MAX; - class_of_device_.cod[2] = (class_of_device >> 16) & UINT8_MAX; + class_of_device_ = class_of_device; } void SetAuthenticationEnable(AuthenticationEnable enable) { @@ -874,7 +871,7 @@ class LinkLayerController { extended_inquiry_response_{}; // Class of Device (Vol 4, Part E § 6.26). - ClassOfDevice class_of_device_{{0, 0, 0}}; + uint32_t class_of_device_{0}; // Other configuration parameters. @@ -1005,20 +1002,17 @@ class LinkLayerController { std::optional<std::chrono::steady_clock::time_point> periodical_timeout; // Packet History - std::vector<model::packets::LinkLayerPacketView> history; + std::vector<pdl::packet::slice> history; bool IsEnabled() const { return scan_enable; } - bool IsPacketInHistory(model::packets::LinkLayerPacketView packet) const { + bool IsPacketInHistory(pdl::packet::slice const& packet) const { return std::any_of( history.begin(), history.end(), - [packet](model::packets::LinkLayerPacketView const& a) { - return a.size() == packet.size() && - std::equal(a.begin(), a.end(), packet.begin()); - }); + [packet](pdl::packet::slice const& a) { return a == packet; }); } - void AddPacketToHistory(model::packets::LinkLayerPacketView packet) { + void AddPacketToHistory(pdl::packet::slice packet) { history.push_back(packet); } }; diff --git a/tools/rootcanal/model/controller/sco_connection.cc b/tools/rootcanal/model/controller/sco_connection.cc index 827e60df8b..3286a1cdaa 100644 --- a/tools/rootcanal/model/controller/sco_connection.cc +++ b/tools/rootcanal/model/controller/sco_connection.cc @@ -16,8 +16,8 @@ #include "sco_connection.h" -#include <hci/hci_packets.h> #include <log.h> +#include <packets/hci_packets.h> #include <vector> diff --git a/tools/rootcanal/model/devices/baseband_sniffer.cc b/tools/rootcanal/model/devices/baseband_sniffer.cc index 50b1b345d8..056f7c3884 100644 --- a/tools/rootcanal/model/devices/baseband_sniffer.cc +++ b/tools/rootcanal/model/devices/baseband_sniffer.cc @@ -17,7 +17,6 @@ #include "baseband_sniffer.h" #include "log.h" -#include "packet/raw_builder.h" #include "pcap.h" using std::vector; @@ -38,11 +37,7 @@ BaseBandSniffer::BaseBandSniffer(const std::string& filename) { void BaseBandSniffer::AppendRecord( std::unique_ptr<bredr_bb::BaseBandPacketBuilder> packet) { - auto bytes = std::vector<uint8_t>(); - bytes.reserve(packet->size()); - bluetooth::packet::BitInserter i(bytes); - packet->Serialize(i); - + std::vector<uint8_t> bytes = packet->SerializeToBytes(); pcap::WriteRecordHeader(output_, bytes.size()); output_.write((char*)bytes.data(), bytes.size()); output_.flush(); @@ -143,7 +138,7 @@ void BaseBandSniffer::ReceiveLinkLayerPacket( 0, // eir 0, // sr 0, // sp - uap, nap, page_view.GetClassOfDevice().ToUint32(), + uap, nap, page_view.GetClassOfDevice(), 1, // lt_addr 0, // clk 0, // page_scan_mode @@ -152,9 +147,7 @@ void BaseBandSniffer::ReceiveLinkLayerPacket( } else if (packet_type == model::packets::PacketType::LMP) { auto lmp_view = model::packets::LmpView::Create(packet); ASSERT(lmp_view.IsValid()); - auto lmp_bytes = std::vector<uint8_t>(lmp_view.GetPayload().begin(), - lmp_view.GetPayload().end()); - + auto lmp_bytes = lmp_view.GetPayload(); uint8_t bt_packet_type = 0b0011; // DM1 AppendRecord(bredr_bb::DM1AclPacketBuilder::Create( @@ -165,7 +158,7 @@ void BaseBandSniffer::ReceiveLinkLayerPacket( flags, 0x3, // llid 1, // flow - std::make_unique<bluetooth::packet::RawBuilder>(lmp_bytes), + std::move(lmp_bytes), 0 // crc )); } diff --git a/tools/rootcanal/model/devices/baseband_sniffer.h b/tools/rootcanal/model/devices/baseband_sniffer.h index 496d15312a..d27619946e 100644 --- a/tools/rootcanal/model/devices/baseband_sniffer.h +++ b/tools/rootcanal/model/devices/baseband_sniffer.h @@ -24,9 +24,7 @@ namespace rootcanal { namespace bredr_bb { -namespace { class BaseBandPacketBuilder; -} } // namespace bredr_bb using ::bluetooth::hci::Address; diff --git a/tools/rootcanal/model/devices/beacon.h b/tools/rootcanal/model/devices/beacon.h index e83bdfdcd4..5a387bc299 100644 --- a/tools/rootcanal/model/devices/beacon.h +++ b/tools/rootcanal/model/devices/beacon.h @@ -18,6 +18,7 @@ #include <chrono> #include <cstdint> +#include <memory> #include <vector> #include "device.h" diff --git a/tools/rootcanal/model/devices/device.cc b/tools/rootcanal/model/devices/device.cc index bfe86b056a..e7aeabbd9f 100644 --- a/tools/rootcanal/model/devices/device.cc +++ b/tools/rootcanal/model/devices/device.cc @@ -18,8 +18,12 @@ #include <vector> +#include "log.h" + namespace rootcanal { +Device::Device() { ASSERT(Address::FromString("BB:BB:BB:BB:BB:AD", address_)); } + std::string Device::ToString() const { return GetTypeString() + "@" + address_.ToString(); } diff --git a/tools/rootcanal/model/devices/device.h b/tools/rootcanal/model/devices/device.h index a7fb13887c..c9deec67bc 100644 --- a/tools/rootcanal/model/devices/device.h +++ b/tools/rootcanal/model/devices/device.h @@ -18,6 +18,7 @@ #include <chrono> #include <cstdint> +#include <functional> #include <map> #include <string> #include <vector> @@ -34,7 +35,7 @@ using ::bluetooth::hci::Address; // - Provide Get*() and Set*() functions for device attributes. class Device { public: - Device() { ASSERT(Address::FromString("BB:BB:BB:BB:BB:AD", address_)); } + Device(); virtual ~Device() = default; // Return a string representation of the type of device. diff --git a/tools/rootcanal/model/devices/hci_device.cc b/tools/rootcanal/model/devices/hci_device.cc index ef2abac20b..710bd3a598 100644 --- a/tools/rootcanal/model/devices/hci_device.cc +++ b/tools/rootcanal/model/devices/hci_device.cc @@ -54,33 +54,41 @@ HciDevice::HciDevice(std::shared_ptr<HciTransport> transport, })); RegisterEventChannel([this](std::shared_ptr<std::vector<uint8_t>> packet) { - transport_->SendEvent(*packet); + transport_->Send(PacketType::EVENT, *packet); }); RegisterAclChannel([this](std::shared_ptr<std::vector<uint8_t>> packet) { - transport_->SendAcl(*packet); + transport_->Send(PacketType::ACL, *packet); }); RegisterScoChannel([this](std::shared_ptr<std::vector<uint8_t>> packet) { - transport_->SendSco(*packet); + transport_->Send(PacketType::SCO, *packet); }); RegisterIsoChannel([this](std::shared_ptr<std::vector<uint8_t>> packet) { - transport_->SendIso(*packet); + transport_->Send(PacketType::ISO, *packet); }); transport_->RegisterCallbacks( - [this](const std::shared_ptr<std::vector<uint8_t>> command) { - HandleCommand(command); - }, - [this](const std::shared_ptr<std::vector<uint8_t>> acl) { - HandleAcl(acl); - }, - [this](const std::shared_ptr<std::vector<uint8_t>> sco) { - HandleSco(sco); - }, - [this](const std::shared_ptr<std::vector<uint8_t>> iso) { - HandleIso(iso); + [this](PacketType packet_type, + const std::shared_ptr<std::vector<uint8_t>> packet) { + switch (packet_type) { + case PacketType::COMMAND: + HandleCommand(packet); + break; + case PacketType::ACL: + HandleAcl(packet); + break; + case PacketType::SCO: + HandleSco(packet); + break; + case PacketType::ISO: + HandleIso(packet); + break; + default: + ASSERT(false); + break; + } }, [this]() { - INFO("HCI transport closed"); + INFO(id_, "HCI transport closed"); Close(); }); } diff --git a/tools/rootcanal/model/devices/link_layer_socket_device.cc b/tools/rootcanal/model/devices/link_layer_socket_device.cc index 9936318f20..92de96e8e0 100644 --- a/tools/rootcanal/model/devices/link_layer_socket_device.cc +++ b/tools/rootcanal/model/devices/link_layer_socket_device.cc @@ -16,15 +16,12 @@ #include "link_layer_socket_device.h" +#include <packet_runtime.h> + #include <type_traits> // for remove_extent_t #include "log.h" -#include "packet/bit_inserter.h" // for BitInserter -#include "packet/iterator.h" // for Iterator -#include "packet/packet_view.h" // for PacketView, kLittleEndian -#include "packet/raw_builder.h" // for RawBuilder -#include "packet/view.h" // for View -#include "phy.h" // for Phy, Phy::Type +#include "phy.h" // for Phy, Phy::Type using std::vector; @@ -55,9 +52,8 @@ void LinkLayerSocketDevice::Tick() { offset_ += bytes_received; return; } - bluetooth::packet::PacketView<bluetooth::packet::kLittleEndian> size( - {bluetooth::packet::View(size_bytes_, 0, kSizeBytes)}); - bytes_left_ = size.begin().extract<uint32_t>(); + pdl::packet::slice size(std::move(size_bytes_)); + bytes_left_ = size.read_le<uint32_t>(); received_ = std::make_shared<std::vector<uint8_t>>(bytes_left_); offset_ = 0; receiving_size_ = false; @@ -95,15 +91,12 @@ void LinkLayerSocketDevice::Close() { void LinkLayerSocketDevice::ReceiveLinkLayerPacket( model::packets::LinkLayerPacketView packet, Phy::Type /*type*/, int8_t /*rssi*/) { - auto size_packet = bluetooth::packet::RawBuilder(); - size_packet.AddOctets4(packet.size()); + std::vector<uint8_t> packet_bytes = packet.bytes().bytes(); std::vector<uint8_t> size_bytes; - bluetooth::packet::BitInserter bit_inserter(size_bytes); - size_packet.Serialize(bit_inserter); + pdl::packet::Builder::write_le<uint32_t>(size_bytes, packet_bytes.size()); if (socket_->Send(size_bytes.data(), size_bytes.size()) == kSizeBytes) { - std::vector<uint8_t> payload_bytes{packet.begin(), packet.end()}; - socket_->Send(payload_bytes.data(), payload_bytes.size()); + socket_->Send(packet_bytes.data(), packet_bytes.size()); } } diff --git a/tools/rootcanal/model/hci/hci_sniffer.cc b/tools/rootcanal/model/hci/hci_sniffer.cc index f0dbc139f0..ea3e2cbeac 100644 --- a/tools/rootcanal/model/hci/hci_sniffer.cc +++ b/tools/rootcanal/model/hci/hci_sniffer.cc @@ -73,32 +73,14 @@ void HciSniffer::AppendRecord(PacketDirection packet_direction, output_->flush(); } -void HciSniffer::RegisterCallbacks(PacketCallback command_callback, - PacketCallback acl_callback, - PacketCallback sco_callback, - PacketCallback iso_callback, +void HciSniffer::RegisterCallbacks(PacketCallback packet_callback, CloseCallback close_callback) { transport_->RegisterCallbacks( - [this, - command_callback](const std::shared_ptr<std::vector<uint8_t>> command) { - AppendRecord(PacketDirection::HOST_TO_CONTROLLER, PacketType::COMMAND, - *command); - command_callback(command); - }, - [this, acl_callback](const std::shared_ptr<std::vector<uint8_t>> acl) { - AppendRecord(PacketDirection::HOST_TO_CONTROLLER, PacketType::ACL, - *acl); - acl_callback(acl); - }, - [this, sco_callback](const std::shared_ptr<std::vector<uint8_t>> sco) { - AppendRecord(PacketDirection::HOST_TO_CONTROLLER, PacketType::SCO, - *sco); - sco_callback(sco); - }, - [this, iso_callback](const std::shared_ptr<std::vector<uint8_t>> iso) { - AppendRecord(PacketDirection::HOST_TO_CONTROLLER, PacketType::ISO, - *iso); - iso_callback(iso); + [this, packet_callback]( + PacketType packet_type, + const std::shared_ptr<std::vector<uint8_t>> packet) { + AppendRecord(PacketDirection::HOST_TO_CONTROLLER, packet_type, *packet); + packet_callback(packet_type, packet); }, close_callback); } @@ -112,23 +94,10 @@ void HciSniffer::Close() { } } -void HciSniffer::SendEvent(const std::vector<uint8_t>& packet) { - AppendRecord(PacketDirection::CONTROLLER_TO_HOST, PacketType::EVENT, packet); - transport_->SendEvent(packet); -} - -void HciSniffer::SendAcl(const std::vector<uint8_t>& packet) { - AppendRecord(PacketDirection::CONTROLLER_TO_HOST, PacketType::ACL, packet); - transport_->SendAcl(packet); -} - -void HciSniffer::SendSco(const std::vector<uint8_t>& packet) { - AppendRecord(PacketDirection::CONTROLLER_TO_HOST, PacketType::SCO, packet); - transport_->SendSco(packet); +void HciSniffer::Send(PacketType packet_type, + const std::vector<uint8_t>& packet) { + AppendRecord(PacketDirection::CONTROLLER_TO_HOST, packet_type, packet); + transport_->Send(packet_type, packet); } -void HciSniffer::SendIso(const std::vector<uint8_t>& packet) { - AppendRecord(PacketDirection::CONTROLLER_TO_HOST, PacketType::ISO, packet); - transport_->SendIso(packet); -} } // namespace rootcanal diff --git a/tools/rootcanal/model/hci/hci_sniffer.h b/tools/rootcanal/model/hci/hci_sniffer.h index 206414e2f1..5ad47fbe7b 100644 --- a/tools/rootcanal/model/hci/hci_sniffer.h +++ b/tools/rootcanal/model/hci/hci_sniffer.h @@ -52,22 +52,13 @@ class HciSniffer : public HciTransport { void SetOutputStream(std::shared_ptr<std::ostream> outputStream); void SetPcapFilter(std::shared_ptr<PcapFilter> filter); - void SendEvent(const std::vector<uint8_t>& packet) override; + void Send(PacketType packet_type, + const std::vector<uint8_t>& packet) override; - void SendAcl(const std::vector<uint8_t>& packet) override; - - void SendSco(const std::vector<uint8_t>& packet) override; - - void SendIso(const std::vector<uint8_t>& packet) override; - - void RegisterCallbacks(PacketCallback command_callback, - PacketCallback acl_callback, - PacketCallback sco_callback, - PacketCallback iso_callback, + void RegisterCallbacks(PacketCallback packet_callback, CloseCallback close_callback) override; void Tick() override; - void Close() override; private: diff --git a/tools/rootcanal/model/hci/hci_socket_transport.cc b/tools/rootcanal/model/hci/hci_socket_transport.cc index c018b0ef1a..82632e466d 100644 --- a/tools/rootcanal/model/hci/hci_socket_transport.cc +++ b/tools/rootcanal/model/hci/hci_socket_transport.cc @@ -23,44 +23,41 @@ namespace rootcanal { HciSocketTransport::HciSocketTransport(std::shared_ptr<AsyncDataChannel> socket) : socket_(socket) {} -void HciSocketTransport::RegisterCallbacks(PacketCallback command_callback, - PacketCallback acl_callback, - PacketCallback sco_callback, - PacketCallback iso_callback, +void HciSocketTransport::RegisterCallbacks(PacketCallback packet_callback, CloseCallback close_callback) { // TODO: Avoid the copy here by using new buffer in H4DataChannel h4_ = H4DataChannelPacketizer( socket_, - [command_callback](const std::vector<uint8_t>& raw_command) { + [packet_callback](const std::vector<uint8_t>& raw_command) { std::shared_ptr<std::vector<uint8_t>> packet_copy = std::make_shared<std::vector<uint8_t>>(raw_command); - command_callback(packet_copy); + packet_callback(PacketType::COMMAND, packet_copy); }, [](const std::vector<uint8_t>&) { FATAL("Unexpected Event in HciSocketTransport!"); }, - [acl_callback](const std::vector<uint8_t>& raw_acl) { + [packet_callback](const std::vector<uint8_t>& raw_acl) { std::shared_ptr<std::vector<uint8_t>> packet_copy = std::make_shared<std::vector<uint8_t>>(raw_acl); - acl_callback(packet_copy); + packet_callback(PacketType::ACL, packet_copy); }, - [sco_callback](const std::vector<uint8_t>& raw_sco) { + [packet_callback](const std::vector<uint8_t>& raw_sco) { std::shared_ptr<std::vector<uint8_t>> packet_copy = std::make_shared<std::vector<uint8_t>>(raw_sco); - sco_callback(packet_copy); + packet_callback(PacketType::SCO, packet_copy); }, - [iso_callback](const std::vector<uint8_t>& raw_iso) { + [packet_callback](const std::vector<uint8_t>& raw_iso) { std::shared_ptr<std::vector<uint8_t>> packet_copy = std::make_shared<std::vector<uint8_t>>(raw_iso); - iso_callback(packet_copy); + packet_callback(PacketType::ISO, packet_copy); }, close_callback); } void HciSocketTransport::Tick() { h4_.OnDataReady(socket_); } -void HciSocketTransport::SendHci(PacketType packet_type, - const std::vector<uint8_t>& packet) { +void HciSocketTransport::Send(PacketType packet_type, + const std::vector<uint8_t>& packet) { if (!socket_ || !socket_->Connected()) { INFO("Closed socket. Dropping packet of type {}", packet_type); return; @@ -69,22 +66,6 @@ void HciSocketTransport::SendHci(PacketType packet_type, h4_.Send(type, packet.data(), packet.size()); } -void HciSocketTransport::SendEvent(const std::vector<uint8_t>& packet) { - SendHci(PacketType::EVENT, packet); -} - -void HciSocketTransport::SendAcl(const std::vector<uint8_t>& packet) { - SendHci(PacketType::ACL, packet); -} - -void HciSocketTransport::SendSco(const std::vector<uint8_t>& packet) { - SendHci(PacketType::SCO, packet); -} - -void HciSocketTransport::SendIso(const std::vector<uint8_t>& packet) { - SendHci(PacketType::ISO, packet); -} - void HciSocketTransport::Close() { socket_->Close(); } } // namespace rootcanal diff --git a/tools/rootcanal/model/hci/hci_socket_transport.h b/tools/rootcanal/model/hci/hci_socket_transport.h index 6bd2d8e7e5..e6e4a356ac 100644 --- a/tools/rootcanal/model/hci/hci_socket_transport.h +++ b/tools/rootcanal/model/hci/hci_socket_transport.h @@ -36,27 +36,16 @@ class HciSocketTransport : public HciTransport { return std::make_shared<HciSocketTransport>(socket); } - void SendEvent(const std::vector<uint8_t>& packet) override; + void Send(PacketType packet_type, + const std::vector<uint8_t>& packet) override; - void SendAcl(const std::vector<uint8_t>& packet) override; - - void SendSco(const std::vector<uint8_t>& packet) override; - - void SendIso(const std::vector<uint8_t>& packet) override; - - void RegisterCallbacks(PacketCallback command_callback, - PacketCallback acl_callback, - PacketCallback sco_callback, - PacketCallback iso_callback, + void RegisterCallbacks(PacketCallback packet_callback, CloseCallback close_callback) override; void Tick() override; - void Close() override; private: - void SendHci(PacketType packet_type, const std::vector<uint8_t>& packet); - std::shared_ptr<AsyncDataChannel> socket_; H4DataChannelPacketizer h4_{socket_, [](const std::vector<uint8_t>&) {}, diff --git a/tools/rootcanal/model/hci/hci_transport.h b/tools/rootcanal/model/hci/hci_transport.h index e682801255..5aa10f160c 100644 --- a/tools/rootcanal/model/hci/hci_transport.h +++ b/tools/rootcanal/model/hci/hci_transport.h @@ -20,32 +20,28 @@ #include <memory> #include <vector> +#include "model/hci/h4.h" + namespace rootcanal { -using PacketCallback = - std::function<void(const std::shared_ptr<std::vector<uint8_t>>)>; +using PacketCallback = std::function<void( + PacketType, const std::shared_ptr<std::vector<uint8_t>>)>; using CloseCallback = std::function<void()>; class HciTransport { public: virtual ~HciTransport() = default; - virtual void SendEvent(const std::vector<uint8_t>& packet) = 0; - - virtual void SendAcl(const std::vector<uint8_t>& packet) = 0; - - virtual void SendSco(const std::vector<uint8_t>& packet) = 0; + /// Send the input HCI packet with the selected H4 packet type. + /// The packet data contains the H4 header but not the IDC byte. + virtual void Send(PacketType packet_type, + std::vector<uint8_t> const& packet) = 0; - virtual void SendIso(const std::vector<uint8_t>& packet) = 0; - - virtual void RegisterCallbacks(PacketCallback command_callback, - PacketCallback acl_callback, - PacketCallback sco_callback, - PacketCallback iso_callback, + /// Register the handler for received HCI packets. + virtual void RegisterCallbacks(PacketCallback packet_callback, CloseCallback close_callback) = 0; virtual void Tick() = 0; - virtual void Close() = 0; }; diff --git a/tools/rootcanal/model/setup/phy_device.cc b/tools/rootcanal/model/setup/phy_device.cc index 25718614f5..bd2cd78250 100644 --- a/tools/rootcanal/model/setup/phy_device.cc +++ b/tools/rootcanal/model/setup/phy_device.cc @@ -16,6 +16,8 @@ #include "phy_device.h" +#include <log.h> + #include "phy_layer.h" namespace rootcanal { @@ -45,8 +47,7 @@ void PhyDevice::Receive(std::vector<uint8_t> const& packet, Phy::Type type, std::make_shared<std::vector<uint8_t>>(packet); model::packets::LinkLayerPacketView packet_view = model::packets::LinkLayerPacketView::Create( - bluetooth::packet::PacketView<bluetooth::packet::kLittleEndian>( - packet_copy)); + pdl::packet::slice(packet_copy)); if (packet_view.IsValid()) { device_->ReceiveLinkLayerPacket(std::move(packet_view), type, rssi); } else { diff --git a/tools/rootcanal/model/setup/test_model.cc b/tools/rootcanal/model/setup/test_model.cc index b80fde6cd3..56ac1694fc 100644 --- a/tools/rootcanal/model/setup/test_model.cc +++ b/tools/rootcanal/model/setup/test_model.cc @@ -208,7 +208,7 @@ PhyDevice::Identifier TestModel::AddHciConnection( }}; device->SetAddress(bluetooth_address); - INFO("Initialized device with address {}", bluetooth_address.ToString()); + INFO(device->id_, "Initialized device with address {}", bluetooth_address.ToString()); for (auto& [_, phy_layer] : phy_layers_) { phy_layer->Register(phy_devices_[device_id]); diff --git a/tools/rootcanal/packets/hci/hci_packets.pdl b/tools/rootcanal/packets/hci_packets.pdl index 25ecfe435f..a99774499b 100644 --- a/tools/rootcanal/packets/hci/hci_packets.pdl +++ b/tools/rootcanal/packets/hci_packets.pdl @@ -1,7 +1,6 @@ little_endian_packets custom_field Address : 48 "hci/" -custom_field ClassOfDevice : 24 "hci/" enum Enable : 8 { DISABLED = 0x00, @@ -1745,8 +1744,8 @@ packet SetEventFilterInquiryResultAllDevices : SetEventFilterInquiryResult (filt } packet SetEventFilterInquiryResultClassOfDevice : SetEventFilterInquiryResult (filter_condition_type = CLASS_OF_DEVICE) { - class_of_device : ClassOfDevice, - class_of_device_mask : ClassOfDevice, + class_of_device : 24, + class_of_device_mask : 24, } packet SetEventFilterInquiryResultAddress : SetEventFilterInquiryResult (filter_condition_type = ADDRESS) { @@ -1769,8 +1768,8 @@ packet SetEventFilterConnectionSetupAllDevices : SetEventFilterConnectionSetup ( } packet SetEventFilterConnectionSetupClassOfDevice : SetEventFilterConnectionSetup (filter_condition_type = CLASS_OF_DEVICE) { - class_of_device : ClassOfDevice, - class_of_device_mask : ClassOfDevice, + class_of_device : 24, + class_of_device_mask : 24, auto_accept_flag : AutoAcceptFlag, } @@ -2022,11 +2021,11 @@ packet ReadClassOfDevice : Command (op_code = READ_CLASS_OF_DEVICE) { packet ReadClassOfDeviceComplete : CommandComplete (command_op_code = READ_CLASS_OF_DEVICE) { status : ErrorCode, - class_of_device : ClassOfDevice, + class_of_device : 24, } packet WriteClassOfDevice : Command (op_code = WRITE_CLASS_OF_DEVICE) { - class_of_device : ClassOfDevice, + class_of_device : 24, } packet WriteClassOfDeviceComplete : CommandComplete (command_op_code = WRITE_CLASS_OF_DEVICE) { @@ -3280,12 +3279,12 @@ packet LeCreateConnection : Command (op_code = LE_CREATE_CONNECTION) { peer_address_type : AddressType, peer_address : Address, own_address_type : OwnAddressType, - conn_interval_min : 16, // 0x0006-0x0C80 (7.5ms to 4s) - conn_interval_max : 16, // 0x0006-0x0C80 (7.5ms to 4s) - conn_latency : 16, // 0x0006-0x01F3 + connection_interval_min : 16, // 0x0006-0x0C80 (7.5ms to 4s) + connection_interval_max : 16, // 0x0006-0x0C80 (7.5ms to 4s) + max_latency : 16, // 0x0006-0x01F3 supervision_timeout : 16, // 0x00A to 0x0C80 (100ms to 32s) - minimum_ce_length : 16, // 0.625ms - maximum_ce_length : 16, // 0.625ms + min_ce_length : 16, // 0.625ms + max_ce_length : 16, // 0.625ms } packet LeCreateConnectionStatus : CommandStatus (command_op_code = LE_CREATE_CONNECTION) { @@ -3348,12 +3347,12 @@ packet LeRemoveDeviceFromFilterAcceptListComplete : CommandComplete (command_op_ packet LeConnectionUpdate : Command (op_code = LE_CONNECTION_UPDATE) { connection_handle : 12, _reserved_ : 4, - conn_interval_min : 16, // 0x0006-0x0C80 (7.5ms to 4s) - conn_interval_max : 16, // 0x0006-0x0C80 (7.5ms to 4s) - conn_latency : 16, // 0x0006-0x01F3 + connection_interval_min : 16, // 0x0006-0x0C80 (7.5ms to 4s) + connection_interval_max : 16, // 0x0006-0x0C80 (7.5ms to 4s) + max_latency : 16, // 0x0006-0x01F3 supervision_timeout : 16, // 0x00A to 0x0C80 (100ms to 32s) - minimum_ce_length : 16, // 0.625ms - maximum_ce_length : 16, // 0.625ms + min_ce_length : 16, // 0.625ms + max_ce_length : 16, // 0.625ms } packet LeConnectionUpdateStatus : CommandStatus (command_op_code = LE_CONNECTION_UPDATE) { @@ -3989,7 +3988,7 @@ packet LeSetPeriodicAdvertisingEnableComplete : CommandComplete (command_op_code status : ErrorCode, } -struct PhyScanParameters { +struct ScanningPhyParameters { le_scan_type : LeScanType, le_scan_interval : 16, // 0x0004-0xFFFF Default 0x10 (10ms) le_scan_window : 16, // 0x004-0xFFFF Default 0x10 (10ms) @@ -3999,7 +3998,7 @@ packet LeSetExtendedScanParameters : Command (op_code = LE_SET_EXTENDED_SCAN_PAR own_address_type : OwnAddressType, scanning_filter_policy : LeScanningFilterPolicy, scanning_phys : 8, - parameters : PhyScanParameters[], + scanning_phy_parameters : ScanningPhyParameters[], } test LeSetExtendedScanParameters { @@ -4041,12 +4040,12 @@ test LeSetExtendedScanEnableComplete { "\x0e\x04\x01\x42\x20\x00", } -struct LeCreateConnPhyScanParameters { +struct InitiatingPhyParameters { scan_interval : 16, // 0x0004-0xFFFF scan_window : 16, // < = LeScanInterval - conn_interval_min : 16, // 0x0006-0x0C80 (7.5ms to 4s) - conn_interval_max : 16, // 0x0006-0x0C80 (7.5ms to 4s) - conn_latency : 16, // 0x0006-0x01F3 + connection_interval_min : 16, // 0x0006-0x0C80 (7.5ms to 4s) + connection_interval_max : 16, // 0x0006-0x0C80 (7.5ms to 4s) + max_latency : 16, // 0x0006-0x01F3 supervision_timeout : 16, // 0x00A to 0x0C80 (100ms to 32s) min_ce_length : 16, // 0.625ms max_ce_length : 16, // 0.625ms @@ -4055,10 +4054,10 @@ struct LeCreateConnPhyScanParameters { packet LeExtendedCreateConnection : Command (op_code = LE_EXTENDED_CREATE_CONNECTION) { initiator_filter_policy : InitiatorFilterPolicy, own_address_type : OwnAddressType, - peer_address_type : AddressType, + peer_address_type : PeerAddressType, peer_address : Address, initiating_phys : 8, - phy_scan_parameters : LeCreateConnPhyScanParameters[], + initiating_phy_parameters : InitiatingPhyParameters[], } test LeExtendedCreateConnection { @@ -5190,7 +5189,7 @@ struct InquiryResponse { page_scan_repetition_mode : PageScanRepetitionMode, _reserved_ : 8, _reserved_ : 8, - class_of_device : ClassOfDevice, + class_of_device : 24, clock_offset : 15, _reserved_ : 1, } @@ -5223,7 +5222,7 @@ enum ConnectionRequestLinkType : 8 { packet ConnectionRequest : Event (event_code = CONNECTION_REQUEST) { bd_addr : Address, - class_of_device : ClassOfDevice, + class_of_device : 24, link_type : ConnectionRequestLinkType, } @@ -5427,7 +5426,7 @@ struct InquiryResponseWithRssi { address : Address, page_scan_repetition_mode : PageScanRepetitionMode, _reserved_ : 8, - class_of_device : ClassOfDevice, + class_of_device : 24, clock_offset : 15, _reserved_ : 1, rssi : 8, @@ -5514,7 +5513,7 @@ packet ExtendedInquiryResult : Event (event_code = EXTENDED_INQUIRY_RESULT) { address : Address, page_scan_repetition_mode : PageScanRepetitionMode, _reserved_ : 8, - class_of_device : ClassOfDevice, + class_of_device : 24, clock_offset : 15, _reserved_ : 1, rssi : 8, @@ -5626,8 +5625,8 @@ packet LeConnectionComplete : LeMetaEvent (subevent_code = CONNECTION_COMPLETE) role : Role, peer_address_type : AddressType, peer_address : Address, - conn_interval : 16, // 0x006 - 0x0C80 (7.5ms - 4000ms) - conn_latency : 16, // Number of connection events + connection_interval : 16, // 0x006 - 0x0C80 (7.5ms - 4000ms) + peripheral_latency : 16, supervision_timeout : 16, // 0x000A to 0x0C80 (100ms to 32s) central_clock_accuracy : ClockAccuracy, } @@ -5658,8 +5657,8 @@ packet LeConnectionUpdateComplete : LeMetaEvent (subevent_code = CONNECTION_UPDA status : ErrorCode, connection_handle : 12, _reserved_ : 4, - conn_interval : 16, // 0x006 - 0x0C80 (7.5ms - 4000ms) - conn_latency : 16, // Number of connection events + connection_interval : 16, // 0x006 - 0x0C80 (7.5ms - 4000ms) + peripheral_latency : 16, // Number of connection events supervision_timeout : 16, // 0x000A to 0x0C80 (100ms to 32s) } @@ -5714,8 +5713,8 @@ packet LeEnhancedConnectionComplete : LeMetaEvent (subevent_code = ENHANCED_CONN peer_address : Address, local_resolvable_private_address : Address, peer_resolvable_private_address : Address, - conn_interval : 16, // 0x006 - 0x0C80 (7.5ms - 4000ms) - conn_latency : 16, // Number of connection events + connection_interval : 16, // 0x006 - 0x0C80 (7.5ms - 4000ms) + peripheral_latency : 16, supervision_timeout : 16, // 0x000A to 0x0C80 (100ms to 32s) central_clock_accuracy : ClockAccuracy, } diff --git a/tools/rootcanal/packets/link_layer_packets.pdl b/tools/rootcanal/packets/link_layer_packets.pdl index 39b4ed2f5e..d2fcbfca4f 100644 --- a/tools/rootcanal/packets/link_layer_packets.pdl +++ b/tools/rootcanal/packets/link_layer_packets.pdl @@ -1,7 +1,6 @@ little_endian_packets custom_field Address : 48 "hci/" -custom_field ClassOfDevice : 24 "hci/" enum PacketType : 8 { UNKNOWN = 0x00, @@ -110,7 +109,7 @@ packet Inquiry : LinkLayerPacket (type = INQUIRY) { packet BasicInquiryResponse : LinkLayerPacket(type = INQUIRY_RESPONSE) { inquiry_type : InquiryType, page_scan_repetition_mode : 8, - class_of_device : ClassOfDevice, + class_of_device : 24, clock_offset : 15, _reserved_ : 1, _body_, @@ -213,7 +212,7 @@ packet LeScanResponse : LinkLayerPacket (type = LE_SCAN_RESPONSE) { } packet Page : LinkLayerPacket (type = PAGE) { - class_of_device : ClassOfDevice, + class_of_device : 24, allow_role_switch : 8, } @@ -323,7 +322,7 @@ packet ScoConnectionRequest : LinkLayerPacket (type = SCO_CONNECTION_REQUEST) { _reserved_ : 6, retransmission_effort : 8, packet_type : 16, - class_of_device : ClassOfDevice, + class_of_device : 24, } packet ScoConnectionResponse : LinkLayerPacket (type = SCO_CONNECTION_RESPONSE) { diff --git a/tools/rootcanal/py/controller.py b/tools/rootcanal/py/controller.py index 9c3e2a85fd..b4bbecaac0 100644 --- a/tools/rootcanal/py/controller.py +++ b/tools/rootcanal/py/controller.py @@ -373,13 +373,13 @@ class ControllerTest(unittest.IsolatedAsyncioTestCase): peer_address_type=hci.AddressType.PUBLIC_DEVICE_ADDRESS, peer_address=peer_address, initiating_phys=0x1, - phy_scan_parameters=[ - hci.LeCreateConnPhyScanParameters( + initiating_phy_parameters=[ + hci.InitiatingPhyParameters( scan_interval=0x200, scan_window=0x100, - conn_interval_min=0x200, - conn_interval_max=0x200, - conn_latency=0x6, + connection_interval_min=0x200, + connection_interval_max=0x200, + max_latency=0x6, supervision_timeout=0xc80, min_ce_length=0, max_ce_length=0, @@ -418,8 +418,8 @@ class ControllerTest(unittest.IsolatedAsyncioTestCase): role=hci.Role.CENTRAL, peer_address_type=hci.AddressType.PUBLIC_DEVICE_ADDRESS, peer_address=peer_address, - conn_interval=0x200, - conn_latency=0x6, + connection_interval=0x200, + peripheral_latency=0x6, supervision_timeout=0xc80, central_clock_accuracy=hci.ClockAccuracy.PPM_500)) @@ -470,8 +470,8 @@ class ControllerTest(unittest.IsolatedAsyncioTestCase): role=hci.Role.PERIPHERAL, peer_address_type=hci.AddressType.PUBLIC_DEVICE_ADDRESS, peer_address=peer_address, - conn_interval=0x200, - conn_latency=0x200, + connection_interval=0x200, + peripheral_latency=0x200, supervision_timeout=0x200, central_clock_accuracy=hci.ClockAccuracy.PPM_500)) diff --git a/tools/rootcanal/rust/CMakeLists.txt b/tools/rootcanal/rust/CMakeLists.txt index 48751eacff..2e7c7cbaa9 100644 --- a/tools/rootcanal/rust/CMakeLists.txt +++ b/tools/rootcanal/rust/CMakeLists.txt @@ -2,7 +2,7 @@ message(STATUS "Enabling bluetooth LMP module.") pdl_gen( NAME hci_packets_rs - INPUT ${ROOTCANAL_ROOT}/packets/hci/hci_packets.pdl + INPUT ${ROOTCANAL_ROOT}/packets/hci_packets.pdl OUTPUT hci_packets.rs LANG rust) diff --git a/tools/rootcanal/rust/build.rs b/tools/rootcanal/rust/build.rs index cd927f6ee2..793e1f93e1 100644 --- a/tools/rootcanal/rust/build.rs +++ b/tools/rootcanal/rust/build.rs @@ -31,7 +31,7 @@ fn main() { install_generated_module( "hci_packets.rs", "HCI_PACKETS_PREBUILT", - &PathBuf::from("../packets/hci/hci_packets.pdl").canonicalize().unwrap(), + &PathBuf::from("../packets/hci_packets.pdl").canonicalize().unwrap(), ); } @@ -60,8 +60,8 @@ fn generate_module(in_file: &PathBuf) { // Find the pdl tool. Expecting it at CARGO_HOME/bin let pdl = match env::var("CARGO_HOME") { - Ok(dir) => PathBuf::from(dir).join("bin").join("pdl"), - Err(_) => PathBuf::from("pdl"), + Ok(dir) => PathBuf::from(dir).join("bin").join("pdlc"), + Err(_) => PathBuf::from("pdlc"), }; if !Path::new(pdl.as_os_str()).exists() { diff --git a/tools/rootcanal/rust/src/ffi.rs b/tools/rootcanal/rust/src/ffi.rs index 18a229c8b0..0024bbc707 100644 --- a/tools/rootcanal/rust/src/ffi.rs +++ b/tools/rootcanal/rust/src/ffi.rs @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +// TODO(b/290018030): Remove this and add proper safety comments. +#![allow(clippy::undocumented_unsafe_blocks)] + use std::convert::TryFrom; use std::mem::ManuallyDrop; use std::rc::Rc; @@ -105,8 +108,8 @@ pub unsafe extern "C" fn link_manager_add_link( lm: *const LinkManager, peer: *const [u8; 6], ) -> bool { - let lm = ManuallyDrop::new(Rc::from_raw(lm)); - lm.add_link(hci::Address::from(&*peer)).is_ok() + let lm = ManuallyDrop::new(unsafe { Rc::from_raw(lm) }); + unsafe { lm.add_link(hci::Address::from(&*peer)).is_ok() } } /// Unregister a link with a peer inside the link manager @@ -123,8 +126,8 @@ pub unsafe extern "C" fn link_manager_remove_link( lm: *const LinkManager, peer: *const [u8; 6], ) -> bool { - let lm = ManuallyDrop::new(Rc::from_raw(lm)); - lm.remove_link(hci::Address::from(&*peer)).is_ok() + let lm = ManuallyDrop::new(unsafe { Rc::from_raw(lm) }); + unsafe { lm.remove_link(hci::Address::from(&*peer)).is_ok() } } /// Run the Link Manager procedures @@ -135,7 +138,7 @@ pub unsafe extern "C" fn link_manager_remove_link( /// - `lm` must be a valid pointer #[no_mangle] pub unsafe extern "C" fn link_manager_tick(lm: *const LinkManager) { - let lm = ManuallyDrop::new(Rc::from_raw(lm)); + let lm = ManuallyDrop::new(unsafe { Rc::from_raw(lm) }); lm.as_ref().tick(); } @@ -155,8 +158,8 @@ pub unsafe extern "C" fn link_manager_ingest_hci( data: *const u8, len: usize, ) -> bool { - let lm = ManuallyDrop::new(Rc::from_raw(lm)); - let data = slice::from_raw_parts(data, len); + let lm = ManuallyDrop::new(unsafe { Rc::from_raw(lm) }); + let data = unsafe { slice::from_raw_parts(data, len) }; if let Ok(packet) = hci::Command::parse(data) { lm.ingest_hci(packet).is_ok() @@ -184,11 +187,11 @@ pub unsafe extern "C" fn link_manager_ingest_lmp( data: *const u8, len: usize, ) -> bool { - let lm = ManuallyDrop::new(Rc::from_raw(lm)); - let data = slice::from_raw_parts(data, len); + let lm = ManuallyDrop::new(unsafe { Rc::from_raw(lm) }); + let data = unsafe { slice::from_raw_parts(data, len) }; if let Ok(packet) = lmp::LmpPacket::parse(data) { - lm.ingest_lmp(hci::Address::from(&*from), packet).is_ok() + unsafe { lm.ingest_lmp(hci::Address::from(&*from), packet).is_ok() } } else { false } @@ -202,7 +205,9 @@ pub unsafe extern "C" fn link_manager_ingest_lmp( /// - `lm` must be a valid pointers and must not be reused afterwards #[no_mangle] pub unsafe extern "C" fn link_manager_destroy(lm: *const LinkManager) { - let _ = Rc::from_raw(lm); + unsafe { + let _ = Rc::from_raw(lm); + } } /// Create a new link manager instance @@ -231,10 +236,10 @@ pub unsafe extern "C" fn link_layer_add_link( peer_address: *const [u8; 6], role: u8, ) -> bool { - let mut ll = ManuallyDrop::new(Rc::from_raw(ll)); + let mut ll = ManuallyDrop::new(unsafe { Rc::from_raw(ll) }); let ll = Rc::get_mut(&mut ll).unwrap(); let role = hci::Role::try_from(role).unwrap_or(hci::Role::Peripheral); - ll.add_link(handle, hci::Address::from(&*peer_address), role).is_ok() + unsafe { ll.add_link(handle, hci::Address::from(&*peer_address), role).is_ok() } } /// Unregister a link with a peer inside the link layer @@ -248,7 +253,7 @@ pub unsafe extern "C" fn link_layer_add_link( /// - `peer` must be valid for reads for 6 bytes #[no_mangle] pub unsafe extern "C" fn link_layer_remove_link(ll: *const LinkLayer, handle: u16) -> bool { - let mut ll = ManuallyDrop::new(Rc::from_raw(ll)); + let mut ll = ManuallyDrop::new(unsafe { Rc::from_raw(ll) }); let ll = Rc::get_mut(&mut ll).unwrap(); ll.remove_link(handle).is_ok() } @@ -261,7 +266,7 @@ pub unsafe extern "C" fn link_layer_remove_link(ll: *const LinkLayer, handle: u1 /// - `ll` must be a valid pointer #[no_mangle] pub unsafe extern "C" fn link_layer_tick(ll: *const LinkLayer) { - let mut ll = ManuallyDrop::new(Rc::from_raw(ll)); + let mut ll = ManuallyDrop::new(unsafe { Rc::from_raw(ll) }); let ll = Rc::get_mut(&mut ll).unwrap(); ll.tick(); } @@ -282,9 +287,9 @@ pub unsafe extern "C" fn link_layer_ingest_hci( data: *const u8, len: usize, ) -> bool { - let mut ll = ManuallyDrop::new(Rc::from_raw(ll)); + let mut ll = ManuallyDrop::new(unsafe { Rc::from_raw(ll) }); let ll = Rc::get_mut(&mut ll).unwrap(); - let data = slice::from_raw_parts(data, len); + let data = unsafe { slice::from_raw_parts(data, len) }; if let Ok(packet) = hci::Command::parse(data) { ll.ingest_hci(packet).is_ok() @@ -311,9 +316,9 @@ pub unsafe extern "C" fn link_layer_ingest_llcp( data: *const u8, len: usize, ) -> bool { - let mut ll = ManuallyDrop::new(Rc::from_raw(ll)); + let mut ll = ManuallyDrop::new(unsafe { Rc::from_raw(ll) }); let ll = Rc::get_mut(&mut ll).unwrap(); - let data = slice::from_raw_parts(data, len); + let data = unsafe { slice::from_raw_parts(data, len) }; if let Ok(packet) = llcp::LlcpPacket::parse(data) { ll.ingest_llcp(handle, packet).is_ok() @@ -340,10 +345,10 @@ pub unsafe extern "C" fn link_layer_get_cis_connection_handle( cis_id: u8, cis_connection_handle: *mut u16, ) -> bool { - let mut ll = ManuallyDrop::new(Rc::from_raw(ll)); + let mut ll = ManuallyDrop::new(unsafe { Rc::from_raw(ll) }); let ll = Rc::get_mut(&mut ll).unwrap(); ll.get_cis_connection_handle(cig_id, cis_id) - .map(|handle| { + .map(|handle| unsafe { *cis_connection_handle = handle; }) .is_some() @@ -369,16 +374,20 @@ pub unsafe extern "C" fn link_layer_get_cis_information( cis_id: *mut u8, max_sdu_tx: *mut u16, ) -> bool { - let mut ll = ManuallyDrop::new(Rc::from_raw(ll)); + let mut ll = ManuallyDrop::new(unsafe { Rc::from_raw(ll) }); let ll = Rc::get_mut(&mut ll).unwrap(); ll.get_cis(cis_connection_handle) .map(|cis| { if let Some(handle) = cis.acl_connection_handle { - *acl_connection_handle = handle; + unsafe { + *acl_connection_handle = handle; + } + } + unsafe { + *cig_id = cis.cig_id; + *cis_id = cis.cis_id; + *max_sdu_tx = cis.max_sdu_tx().unwrap_or(0); } - *cig_id = cis.cig_id; - *cis_id = cis.cis_id; - *max_sdu_tx = cis.max_sdu_tx().unwrap_or(0); }) .is_some() } @@ -391,5 +400,7 @@ pub unsafe extern "C" fn link_layer_get_cis_information( /// - `ll` must be a valid pointers and must not be reused afterwards #[no_mangle] pub unsafe extern "C" fn link_layer_destroy(ll: *const LinkLayer) { - let _ = Rc::from_raw(ll); + unsafe { + let _ = Rc::from_raw(ll); + } } diff --git a/tools/rootcanal/rust/src/lmp/procedure/secure_simple_pairing.rs b/tools/rootcanal/rust/src/lmp/procedure/secure_simple_pairing.rs index 0c3c4e31a8..543219691f 100644 --- a/tools/rootcanal/rust/src/lmp/procedure/secure_simple_pairing.rs +++ b/tools/rootcanal/rust/src/lmp/procedure/secure_simple_pairing.rs @@ -143,11 +143,14 @@ async fn receive_public_key(ctx: &impl Context, transaction_id: u8) -> PublicKey const COMMITMENT_VALUE_SIZE: usize = 16; const NONCE_SIZE: usize = 16; -async fn receive_commitment(ctx: &impl Context, skip_first: bool) { - let commitment_value = [0; COMMITMENT_VALUE_SIZE]; +fn build_commitment(_ctx: &impl Context) -> [u8; COMMITMENT_VALUE_SIZE] { + [0; COMMITMENT_VALUE_SIZE] +} + +async fn receive_commitment(ctx: &impl Context, confirm: Option<lmp::SimplePairingConfirm>) { + let commitment_value = build_commitment(ctx); - if !skip_first { - let confirm = ctx.receive_lmp_packet::<lmp::SimplePairingConfirm>().await; + if let Some(confirm) = confirm { if confirm.get_commitment_value() != &commitment_value { todo!(); } @@ -177,16 +180,8 @@ async fn receive_commitment(ctx: &impl Context, skip_first: bool) { .await; } -async fn send_commitment(ctx: &impl Context, skip_first: bool) { - let commitment_value = [0; COMMITMENT_VALUE_SIZE]; - - if !skip_first { - ctx.send_lmp_packet( - lmp::SimplePairingConfirmBuilder { transaction_id: 0, commitment_value }.build(), - ); - } - - let confirm = ctx.receive_lmp_packet::<lmp::SimplePairingConfirm>().await; +async fn send_commitment(ctx: &impl Context, confirm: lmp::SimplePairingConfirm) { + let commitment_value = build_commitment(ctx); if confirm.get_commitment_value() != &commitment_value { todo!(); @@ -437,14 +432,33 @@ pub async fn initiate(ctx: &impl Context) -> Result<(), ()> { match auth_method { AuthenticationMethod::NumericComparisonJustWork | AuthenticationMethod::NumericComparisonUserConfirm => { - send_commitment(ctx, true).await; + let confirm = ctx.receive_lmp_packet::<lmp::SimplePairingConfirm>().await; + send_commitment(ctx, confirm).await; - user_confirmation_request(ctx).await?; + if user_confirmation_request(ctx).await.is_err() { + ctx.send_lmp_packet( + lmp::NumericComparisonFailedBuilder { transaction_id: 0 }.build(), + ); + Err(())?; + } Ok(()) } AuthenticationMethod::PasskeyEntry => { - if initiator.io_capability == hci::IoCapability::KeyboardOnly { - user_passkey_request(ctx).await?; + let confirm = if initiator.io_capability == hci::IoCapability::KeyboardOnly { + if user_passkey_request(ctx).await.is_err() { + ctx.send_lmp_packet( + lmp::PasskeyFailedBuilder { transaction_id: 0 }.build(), + ); + Err(())?; + } + ctx.send_lmp_packet( + lmp::SimplePairingConfirmBuilder { + transaction_id: 0, + commitment_value: build_commitment(ctx), + } + .build(), + ); + ctx.receive_lmp_packet::<lmp::SimplePairingConfirm>().await } else { ctx.send_hci_event( hci::UserPasskeyNotificationBuilder { @@ -453,9 +467,32 @@ pub async fn initiate(ctx: &impl Context) -> Result<(), ()> { } .build(), ); - } - for _ in 0..PASSKEY_ENTRY_REPEAT_NUMBER { - send_commitment(ctx, false).await; + ctx.send_lmp_packet( + lmp::SimplePairingConfirmBuilder { + transaction_id: 0, + commitment_value: build_commitment(ctx), + } + .build(), + ); + match ctx + .receive_lmp_packet::<Either<lmp::SimplePairingConfirm, lmp::NotAccepted>>() + .await + { + Either::Left(confirm) => confirm, + Either::Right(_) => Err(())?, + } + }; + send_commitment(ctx, confirm).await; + for _ in 1..PASSKEY_ENTRY_REPEAT_NUMBER { + ctx.send_lmp_packet( + lmp::SimplePairingConfirmBuilder { + transaction_id: 0, + commitment_value: build_commitment(ctx), + } + .build(), + ); + let confirm = ctx.receive_lmp_packet::<lmp::SimplePairingConfirm>().await; + send_commitment(ctx, confirm).await; } Ok(()) } @@ -464,7 +501,15 @@ pub async fn initiate(ctx: &impl Context) -> Result<(), ()> { remote_oob_data_request(ctx).await?; } - send_commitment(ctx, false).await; + ctx.send_lmp_packet( + lmp::SimplePairingConfirmBuilder { + transaction_id: 0, + commitment_value: build_commitment(ctx), + } + .build(), + ); + let confirm = ctx.receive_lmp_packet::<lmp::SimplePairingConfirm>().await; + send_commitment(ctx, confirm).await; Ok(()) } } @@ -472,7 +517,6 @@ pub async fn initiate(ctx: &impl Context) -> Result<(), ()> { .await; if result.is_err() { - ctx.send_lmp_packet(lmp::NumericComparisonFailedBuilder { transaction_id: 0 }.build()); ctx.send_hci_event( hci::SimplePairingCompleteBuilder { status: hci::ErrorCode::AuthenticationFailure, @@ -644,37 +688,78 @@ pub async fn respond(ctx: &impl Context, request: lmp::IoCapabilityReq) -> Resul // Authentication Stage 1 let auth_method = authentication_method(initiator, responder); - let negative_user_confirmation = match auth_method { - AuthenticationMethod::NumericComparisonJustWork - | AuthenticationMethod::NumericComparisonUserConfirm => { - receive_commitment(ctx, true).await; + let result: Result<bool, ()> = async { + match auth_method { + AuthenticationMethod::NumericComparisonJustWork + | AuthenticationMethod::NumericComparisonUserConfirm => { + receive_commitment(ctx, None).await; - let user_confirmation = user_confirmation_request(ctx).await; - user_confirmation.is_err() - } - AuthenticationMethod::PasskeyEntry => { - if responder.io_capability == hci::IoCapability::KeyboardOnly { - // TODO: handle error - let _user_passkey = user_passkey_request(ctx).await; - } else { - ctx.send_hci_event( - hci::UserPasskeyNotificationBuilder { bd_addr: ctx.peer_address(), passkey: 0 } + let user_confirmation = user_confirmation_request(ctx).await; + Ok(user_confirmation.is_err()) + } + AuthenticationMethod::PasskeyEntry => { + let confirm = if responder.io_capability == hci::IoCapability::KeyboardOnly { + let user_passkey = user_passkey_request(ctx).await; + let confirm = ctx.receive_lmp_packet::<lmp::SimplePairingConfirm>().await; + if user_passkey.is_err() { + ctx.send_lmp_packet( + lmp::NotAcceptedBuilder { + transaction_id: 0, + not_accepted_opcode: lmp::Opcode::SimplePairingConfirm, + error_code: hci::ErrorCode::AuthenticationFailure.into(), + }.build(), + ); + return Err(()); + } + confirm + } else { + ctx.send_hci_event( + hci::UserPasskeyNotificationBuilder { + bd_addr: ctx.peer_address(), + passkey: 0, + } .build(), - ); + ); + match ctx + .receive_lmp_packet::<Either<lmp::SimplePairingConfirm, lmp::PasskeyFailed>>() + .await + { + Either::Left(confirm) => confirm, + Either::Right(_) => Err(())?, + } + }; + receive_commitment(ctx, Some(confirm)).await; + for _ in 1..PASSKEY_ENTRY_REPEAT_NUMBER { + let confirm = ctx.receive_lmp_packet::<lmp::SimplePairingConfirm>().await; + receive_commitment(ctx, Some(confirm)).await; + } + Ok(false) } - for _ in 0..PASSKEY_ENTRY_REPEAT_NUMBER { - receive_commitment(ctx, false).await; + AuthenticationMethod::OutOfBand => { + if responder.oob_data_present != hci::OobDataPresent::NotPresent { + // TODO: handle error + let _remote_oob_data = remote_oob_data_request(ctx).await; + } + + let confirm = ctx.receive_lmp_packet::<lmp::SimplePairingConfirm>().await; + receive_commitment(ctx, Some(confirm)).await; + Ok(false) } - false } - AuthenticationMethod::OutOfBand => { - if responder.oob_data_present != hci::OobDataPresent::NotPresent { - // TODO: handle error - let _remote_oob_data = remote_oob_data_request(ctx).await; - } + } + .await; - receive_commitment(ctx, false).await; - false + let negative_user_confirmation = match result { + Ok(negative_user_confirmation) => negative_user_confirmation, + Err(_) => { + ctx.send_hci_event( + hci::SimplePairingCompleteBuilder { + status: hci::ErrorCode::AuthenticationFailure, + bd_addr: ctx.peer_address(), + } + .build(), + ); + return Err(()); } }; @@ -867,8 +952,7 @@ mod tests { } #[test] - #[should_panic] // TODO: make the test pass - fn passkey_entry_initiator_failure_on_initiating_side() { + fn passkey_entry_initiator_negative_reply_on_initiating_side() { let context = TestContext::new(); let procedure = initiate; @@ -876,8 +960,15 @@ mod tests { } #[test] - #[should_panic] // TODO: make the test pass - fn passkey_entry_responder_failure_on_initiating_side() { + fn passkey_entry_responder_negative_reply_on_responding_side() { + let context = TestContext::new(); + let procedure = respond; + + include!("../../../test/SP/BV-14bis-C.in"); + } + + #[test] + fn passkey_entry_responder_negative_reply_on_initiating_side() { let context = TestContext::new(); let procedure = respond; @@ -885,6 +976,14 @@ mod tests { } #[test] + fn passkey_entry_initiator_negative_reply_on_responding_side() { + let context = TestContext::new(); + let procedure = initiate; + + include!("../../../test/SP/BV-15bis-C.in"); + } + + #[test] #[should_panic] // TODO: make the test pass fn passkey_entry_initiator_failure_on_responding_side() { let context = TestContext::new(); diff --git a/tools/rootcanal/rust/src/packets.rs b/tools/rootcanal/rust/src/packets.rs index dba39c9aa5..249c4c9912 100644 --- a/tools/rootcanal/rust/src/packets.rs +++ b/tools/rootcanal/rust/src/packets.rs @@ -54,18 +54,6 @@ pub mod hci { } } - impl fmt::Display for ClassOfDevice { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{:03X}-{:01X}-{:02X}", - (self.0 >> 12) & 0xfff, - (self.0 >> 8) & 0xf, - self.0 & 0xff, - ) - } - } - pub fn command_remote_device_address(command: &Command) -> Option<Address> { use CommandChild::*; #[allow(unused_imports)] diff --git a/tools/rootcanal/rust/test/SP/BV-14bis-C.in b/tools/rootcanal/rust/test/SP/BV-14bis-C.in new file mode 100644 index 0000000000..b98f45096d --- /dev/null +++ b/tools/rootcanal/rust/test/SP/BV-14bis-C.in @@ -0,0 +1,106 @@ +// Passkey entry responder, negative reply on responding side: +// - Test case not present in LMP.TS, but other permutations are described in SP/BV-14-C, SP/BV-15-C +// - IUT is KeyboardOnly, responder +// - Lower Tester is Display, initiator +// - IUT fails passkey entry with User_Passkey_Request_NegativeReply, responds Not Accepted to the SimplePairingConfirm +sequence! { procedure, context, + // ACL Connection Established + Lower Tester -> IUT: IoCapabilityReq { + transaction_id: 0, + io_capabilities: 0x00, + oob_authentication_data: 0x00, + authentication_requirement: 0x01, + } + IUT -> Upper Tester: IoCapabilityResponse { + bd_addr: context.peer_address(), + io_capability: IoCapability::DisplayOnly, + oob_data_present: OobDataPresent::NotPresent, + authentication_requirements: AuthenticationRequirements::NoBondingMitmProtection, + } + IUT -> Upper Tester: IoCapabilityRequest { + bd_addr: context.peer_address(), + } + Upper Tester -> IUT: IoCapabilityRequestReply { + bd_addr: context.peer_address(), + io_capability: IoCapability::KeyboardOnly, + oob_present: OobDataPresent::NotPresent, + authentication_requirements: AuthenticationRequirements::NoBondingMitmProtection, + } + IUT -> Upper Tester: IoCapabilityRequestReplyComplete { + num_hci_command_packets: 1, + status: ErrorCode::Success, + bd_addr: context.peer_address(), + } + IUT -> Lower Tester: IoCapabilityRes { + transaction_id: 0, + io_capabilities: 0x02, + oob_authentication_data: 0x00, + authentication_requirement: 0x01, + } + // Public Key Exchange + Lower Tester -> IUT: EncapsulatedHeader { + transaction_id: 0, + major_type: 1, + minor_type: 1, + payload_length: 48, + } + IUT -> Lower Tester: Accepted { + transaction_id: 0, + accepted_opcode: Opcode::EncapsulatedHeader, + } + repeat 3 times with (part in peer_p192_public_key()) { + Lower Tester -> IUT: EncapsulatedPayload { + transaction_id: 0, + data: part, + } + IUT -> Lower Tester: Accepted { + transaction_id: 0, + accepted_opcode: Opcode::EncapsulatedPayload, + } + } + IUT -> Lower Tester: EncapsulatedHeader { + transaction_id: 0, + major_type: 1, + minor_type: 1, + payload_length: 48, + } + Lower Tester -> IUT: Accepted { + transaction_id: 0, + accepted_opcode: Opcode::EncapsulatedHeader, + } + repeat 3 times with (part in local_p192_public_key(&context)) { + IUT -> Lower Tester: EncapsulatedPayload { + transaction_id: 0, + data: part, + } + Lower Tester -> IUT: Accepted { + transaction_id: 0, + accepted_opcode: Opcode::EncapsulatedPayload, + } + } + // Authentication Stage 1: Passkey Entry Protocol + IUT -> Upper Tester: UserPasskeyRequest { + bd_addr: context.peer_address(), + } + Upper Tester -> IUT: UserPasskeyRequestNegativeReply { + bd_addr: context.peer_address(), + } + IUT -> Upper Tester: UserPasskeyRequestNegativeReplyComplete { + num_hci_command_packets: 1, + status: ErrorCode::Success, + bd_addr: context.peer_address(), + } + Lower Tester -> IUT: SimplePairingConfirm { + transaction_id: 0, + commitment_value: [0; 16], + } + IUT -> Lower Tester: NotAccepted { + transaction_id: 0, + not_accepted_opcode: Opcode::SimplePairingConfirm, + error_code: ErrorCode::AuthenticationFailure.into(), + } + IUT -> Upper Tester: SimplePairingComplete { + status: ErrorCode::AuthenticationFailure, + bd_addr: context.peer_address(), + } +} diff --git a/tools/rootcanal/rust/test/SP/BV-15bis-C.in b/tools/rootcanal/rust/test/SP/BV-15bis-C.in new file mode 100644 index 0000000000..f9fda299c8 --- /dev/null +++ b/tools/rootcanal/rust/test/SP/BV-15bis-C.in @@ -0,0 +1,118 @@ +// Passkey entry initiator, negative reply on responding side: +// - Test case not present in LMP.TS, but other permutations are described in SP/BV-14-C, SP/BV-15-C +// - IUT is DisplayOnly, initiator +// - Lower Tester is KeyboardOnly, responder +// - Lower Tester fails passkey entry with User_Passkey_Request_NegativeReply, responds Not Accepted to the SimplePairingConfirm +sequence! { procedure, context, + // ACL Connection Established + Upper Tester -> IUT: AuthenticationRequested { + connection_handle: context.peer_handle() + } + IUT -> Upper Tester: AuthenticationRequestedStatus { + num_hci_command_packets: 1, + status: ErrorCode::Success, + } + IUT -> Upper Tester: LinkKeyRequest { + bd_addr: context.peer_address(), + } + Upper Tester -> IUT: LinkKeyRequestNegativeReply { + bd_addr: context.peer_address(), + } + IUT -> Upper Tester: LinkKeyRequestNegativeReplyComplete { + num_hci_command_packets: 1, + status: ErrorCode::Success, + bd_addr: context.peer_address(), + } + IUT -> Upper Tester: IoCapabilityRequest { + bd_addr: context.peer_address(), + } + Upper Tester -> IUT: IoCapabilityRequestReply { + bd_addr: context.peer_address(), + io_capability: IoCapability::DisplayOnly, + oob_present: OobDataPresent::NotPresent, + authentication_requirements: AuthenticationRequirements::NoBondingMitmProtection, + } + IUT -> Upper Tester: IoCapabilityRequestReplyComplete { + num_hci_command_packets: 1, + status: ErrorCode::Success, + bd_addr: context.peer_address(), + } + IUT -> Lower Tester: IoCapabilityReq { + transaction_id: 0, + io_capabilities: 0x00, + oob_authentication_data: 0x00, + authentication_requirement: 0x01, + } + Lower Tester -> IUT: IoCapabilityRes { + transaction_id: 0, + io_capabilities: 0x02, + oob_authentication_data: 0x00, + authentication_requirement: 0x01, + } + IUT -> Upper Tester: IoCapabilityResponse { + bd_addr: context.peer_address(), + io_capability: IoCapability::KeyboardOnly, + oob_data_present: OobDataPresent::NotPresent, + authentication_requirements: AuthenticationRequirements::NoBondingMitmProtection, + } + // Public Key Exchange + IUT -> Lower Tester: EncapsulatedHeader { + transaction_id: 0, + major_type: 1, + minor_type: 1, + payload_length: 48, + } + Lower Tester -> IUT: Accepted { + transaction_id: 0, + accepted_opcode: Opcode::EncapsulatedHeader, + } + repeat 3 times with (part in local_p192_public_key(&context)) { + IUT -> Lower Tester: EncapsulatedPayload { + transaction_id: 0, + data: part, + } + Lower Tester -> IUT: Accepted { + transaction_id: 0, + accepted_opcode: Opcode::EncapsulatedPayload, + } + } + Lower Tester -> IUT: EncapsulatedHeader { + transaction_id: 0, + major_type: 1, + minor_type: 1, + payload_length: 48, + } + IUT -> Lower Tester: Accepted { + transaction_id: 0, + accepted_opcode: Opcode::EncapsulatedHeader, + } + repeat 3 times with (part in peer_p192_public_key()) { + Lower Tester -> IUT: EncapsulatedPayload { + transaction_id: 0, + data: part, + } + IUT -> Lower Tester: Accepted { + transaction_id: 0, + accepted_opcode: Opcode::EncapsulatedPayload, + } + } + // Authentication Stage 1: Passkey Entry Protocol + IUT -> Upper Tester: UserPasskeyNotification { bd_addr: context.peer_address(), passkey: 0 } + IUT -> Lower Tester: SimplePairingConfirm { + transaction_id: 0, + commitment_value: [0; 16], + } + Lower Tester -> IUT: NotAccepted { + transaction_id: 0, + not_accepted_opcode: Opcode::SimplePairingConfirm, + error_code: ErrorCode::AuthenticationFailure.into(), + } + IUT -> Upper Tester: SimplePairingComplete { + status: ErrorCode::AuthenticationFailure, + bd_addr: context.peer_address(), + } + IUT -> Upper Tester: AuthenticationComplete { + status: ErrorCode::AuthenticationFailure, + connection_handle: context.peer_handle(), + } +} diff --git a/tools/rootcanal/test/LL/CON_/CEN/BV_41_C.py b/tools/rootcanal/test/LL/CON_/CEN/BV_41_C.py index c3b719082e..9d377ef54a 100644 --- a/tools/rootcanal/test/LL/CON_/CEN/BV_41_C.py +++ b/tools/rootcanal/test/LL/CON_/CEN/BV_41_C.py @@ -46,13 +46,13 @@ class Test(ControllerTest): peer_address_type=hci.AddressType.PUBLIC_DEVICE_ADDRESS, peer_address=peer_address, initiating_phys=0x1, - phy_scan_parameters=[ - hci.LeCreateConnPhyScanParameters( + initiating_phy_parameters=[ + hci.InitiatingPhyParameters( scan_interval=0x200, scan_window=0x100, - conn_interval_min=0x200, - conn_interval_max=0x200, - conn_latency=0x6, + connection_interval_min=0x200, + connection_interval_max=0x200, + max_latency=0x6, supervision_timeout=0xc80, min_ce_length=0, max_ce_length=0, @@ -91,8 +91,8 @@ class Test(ControllerTest): role=hci.Role.CENTRAL, peer_address_type=hci.AddressType.PUBLIC_DEVICE_ADDRESS, peer_address=peer_address, - conn_interval=0x200, - conn_latency=0x6, + connection_interval=0x200, + peripheral_latency=0x6, supervision_timeout=0xc80, central_clock_accuracy=hci.ClockAccuracy.PPM_500)) diff --git a/tools/rootcanal/test/LL/CON_/CEN/BV_43_C.py b/tools/rootcanal/test/LL/CON_/CEN/BV_43_C.py index ec5b68ba4a..914df4cfe9 100644 --- a/tools/rootcanal/test/LL/CON_/CEN/BV_43_C.py +++ b/tools/rootcanal/test/LL/CON_/CEN/BV_43_C.py @@ -43,13 +43,13 @@ class Test(ControllerTest): peer_address_type=hci.AddressType.PUBLIC_DEVICE_ADDRESS, peer_address=peer_address, initiating_phys=0x1, - phy_scan_parameters=[ - hci.LeCreateConnPhyScanParameters( + initiating_phy_parameters=[ + hci.InitiatingPhyParameters( scan_interval=0x200, scan_window=0x100, - conn_interval_min=0x200, - conn_interval_max=0x200, - conn_latency=0x6, + connection_interval_min=0x200, + connection_interval_max=0x200, + max_latency=0x6, supervision_timeout=0xc80, min_ce_length=0, max_ce_length=0, @@ -88,8 +88,8 @@ class Test(ControllerTest): role=hci.Role.CENTRAL, peer_address_type=hci.AddressType.PUBLIC_DEVICE_ADDRESS, peer_address=peer_address, - conn_interval=0x200, - conn_latency=0x6, + connection_interval=0x200, + peripheral_latency=0x6, supervision_timeout=0xc80, central_clock_accuracy=hci.ClockAccuracy.PPM_500)) diff --git a/tools/rootcanal/test/LL/CON_/PER/BV_40_C.py b/tools/rootcanal/test/LL/CON_/PER/BV_40_C.py index 201beeb3cd..045bca32a3 100644 --- a/tools/rootcanal/test/LL/CON_/PER/BV_40_C.py +++ b/tools/rootcanal/test/LL/CON_/PER/BV_40_C.py @@ -77,8 +77,8 @@ class Test(ControllerTest): role=hci.Role.PERIPHERAL, peer_address_type=hci.AddressType.PUBLIC_DEVICE_ADDRESS, peer_address=peer_address, - conn_interval=0x200, - conn_latency=0x200, + connection_interval=0x200, + peripheral_latency=0x200, supervision_timeout=0x200, central_clock_accuracy=hci.ClockAccuracy.PPM_500)) diff --git a/tools/rootcanal/test/LL/CON_/PER/BV_42_C.py b/tools/rootcanal/test/LL/CON_/PER/BV_42_C.py index 4f19d701c5..ae067c2269 100644 --- a/tools/rootcanal/test/LL/CON_/PER/BV_42_C.py +++ b/tools/rootcanal/test/LL/CON_/PER/BV_42_C.py @@ -77,8 +77,8 @@ class Test(ControllerTest): role=hci.Role.PERIPHERAL, peer_address_type=hci.AddressType.PUBLIC_DEVICE_ADDRESS, peer_address=peer_address, - conn_interval=0x200, - conn_latency=0x200, + connection_interval=0x200, + peripheral_latency=0x200, supervision_timeout=0x200, central_clock_accuracy=hci.ClockAccuracy.PPM_500)) diff --git a/tools/rootcanal/test/LL/DDI/ADV/BV_06_C.py b/tools/rootcanal/test/LL/DDI/ADV/BV_06_C.py index f850c57143..f52edd3deb 100644 --- a/tools/rootcanal/test/LL/DDI/ADV/BV_06_C.py +++ b/tools/rootcanal/test/LL/DDI/ADV/BV_06_C.py @@ -95,8 +95,8 @@ class Test(ControllerTest): role=hci.Role.PERIPHERAL, peer_address_type=hci.AddressType.PUBLIC_DEVICE_ADDRESS, peer_address=peer_address, - conn_interval=LL_initiator_connInterval, - conn_latency=LL_initiator_connPeripheralLatency, + connection_interval=LL_initiator_connInterval, + peripheral_latency=LL_initiator_connPeripheralLatency, supervision_timeout=LL_initiator_connSupervisionTimeout, central_clock_accuracy=hci.ClockAccuracy.PPM_500)) diff --git a/tools/rootcanal/test/LL/DDI/ADV/BV_07_C.py b/tools/rootcanal/test/LL/DDI/ADV/BV_07_C.py index c4b10cdc8e..271a4fc564 100644 --- a/tools/rootcanal/test/LL/DDI/ADV/BV_07_C.py +++ b/tools/rootcanal/test/LL/DDI/ADV/BV_07_C.py @@ -125,8 +125,8 @@ class Test(ControllerTest): role=hci.Role.PERIPHERAL, peer_address_type=hci.AddressType.PUBLIC_DEVICE_ADDRESS, peer_address=peer_address, - conn_interval=LL_initiator_connInterval, - conn_latency=LL_initiator_connPeripheralLatency, + connection_interval=LL_initiator_connInterval, + peripheral_latency=LL_initiator_connPeripheralLatency, supervision_timeout=LL_initiator_connSupervisionTimeout, central_clock_accuracy=hci.ClockAccuracy.PPM_500)) diff --git a/tools/rootcanal/test/LL/DDI/ADV/BV_09_C.py b/tools/rootcanal/test/LL/DDI/ADV/BV_09_C.py index d583ef4cf3..719ddc76e3 100644 --- a/tools/rootcanal/test/LL/DDI/ADV/BV_09_C.py +++ b/tools/rootcanal/test/LL/DDI/ADV/BV_09_C.py @@ -310,8 +310,8 @@ class Test(ControllerTest): peer_address_type=(hci.AddressType.PUBLIC_DEVICE_ADDRESS if peer_address_type == ll.AddressType.PUBLIC else hci.AddressType.RANDOM_DEVICE_ADDRESS), peer_address=peer_address, - conn_interval=self.LL_initiator_connInterval, - conn_latency=self.LL_initiator_connPeripheralLatency, + connection_interval=self.LL_initiator_connInterval, + peripheral_latency=self.LL_initiator_connPeripheralLatency, supervision_timeout=self.LL_initiator_connSupervisionTimeout, central_clock_accuracy=hci.ClockAccuracy.PPM_500)) @@ -380,8 +380,8 @@ class Test(ControllerTest): peer_address_type=(hci.AddressType.PUBLIC_DEVICE_ADDRESS if peer_address_type == ll.AddressType.PUBLIC else hci.AddressType.RANDOM_DEVICE_ADDRESS), peer_address=peer_address, - conn_interval=self.LL_initiator_connInterval, - conn_latency=self.LL_initiator_connPeripheralLatency, + connection_interval=self.LL_initiator_connInterval, + peripheral_latency=self.LL_initiator_connPeripheralLatency, supervision_timeout=self.LL_initiator_connSupervisionTimeout, central_clock_accuracy=hci.ClockAccuracy.PPM_500)) diff --git a/tools/rootcanal/test/LL/DDI/ADV/BV_11_C.py b/tools/rootcanal/test/LL/DDI/ADV/BV_11_C.py index 1b0953ac51..014417f9be 100644 --- a/tools/rootcanal/test/LL/DDI/ADV/BV_11_C.py +++ b/tools/rootcanal/test/LL/DDI/ADV/BV_11_C.py @@ -137,8 +137,8 @@ class Test(ControllerTest): role=hci.Role.PERIPHERAL, peer_address_type=hci.AddressType.PUBLIC_DEVICE_ADDRESS, peer_address=peer_address, - conn_interval=self.LL_initiator_connInterval, - conn_latency=self.LL_initiator_connPeripheralLatency, + connection_interval=self.LL_initiator_connInterval, + peripheral_latency=self.LL_initiator_connPeripheralLatency, supervision_timeout=self.LL_initiator_connSupervisionTimeout, central_clock_accuracy=hci.ClockAccuracy.PPM_500)) diff --git a/tools/rootcanal/test/LL/DDI/ADV/BV_17_C.py b/tools/rootcanal/test/LL/DDI/ADV/BV_17_C.py index 8795116d66..c27619c2da 100644 --- a/tools/rootcanal/test/LL/DDI/ADV/BV_17_C.py +++ b/tools/rootcanal/test/LL/DDI/ADV/BV_17_C.py @@ -69,7 +69,7 @@ class Test(ControllerTest): await self.steps_4_6(peer_address=peer_address, scan_response_data=scan_response_data) # 7. Configure Scan Response Data in the IUT using device name length of 31 as response data. - scan_response_data = [31] + [0] * 31 + scan_response_data = [31] + [0] * 30 controller.send_cmd(hci.LeSetScanResponseData(advertising_data=scan_response_data)) await self.expect_evt(hci.LeSetScanResponseDataComplete(status=ErrorCode.SUCCESS, num_hci_command_packets=1)) @@ -104,7 +104,7 @@ class Test(ControllerTest): await self.steps_4_6(peer_address=peer_address, scan_response_data=scan_response_data) # 12. Configure Scan Response Data in the IUT using device name length of 31 as response data. - scan_response_data = [31] + [0] * 31 + scan_response_data = [31] + [0] * 30 controller.send_cmd(hci.LeSetScanResponseData(advertising_data=scan_response_data)) await self.expect_evt(hci.LeSetScanResponseDataComplete(status=ErrorCode.SUCCESS, num_hci_command_packets=1)) diff --git a/tools/rootcanal/test/LL/DDI/ADV/BV_19_C.py b/tools/rootcanal/test/LL/DDI/ADV/BV_19_C.py index ba996c7ad6..9247324a4f 100644 --- a/tools/rootcanal/test/LL/DDI/ADV/BV_19_C.py +++ b/tools/rootcanal/test/LL/DDI/ADV/BV_19_C.py @@ -112,8 +112,8 @@ class Test(ControllerTest): role=hci.Role.PERIPHERAL, peer_address_type=hci.AddressType.PUBLIC_DEVICE_ADDRESS, peer_address=public_peer_address, - conn_interval=self.LL_initiator_connInterval, - conn_latency=self.LL_initiator_connPeripheralLatency, + connection_interval=self.LL_initiator_connInterval, + peripheral_latency=self.LL_initiator_connPeripheralLatency, supervision_timeout=self.LL_initiator_connSupervisionTimeout, central_clock_accuracy=hci.ClockAccuracy.PPM_500)) diff --git a/tools/rootcanal/test/LL/DDI/SCN/BV_19_C.py b/tools/rootcanal/test/LL/DDI/SCN/BV_19_C.py index 04bda10551..41c3db5b94 100644 --- a/tools/rootcanal/test/LL/DDI/SCN/BV_19_C.py +++ b/tools/rootcanal/test/LL/DDI/SCN/BV_19_C.py @@ -82,10 +82,10 @@ class Test(ControllerTest): hci.LeSetExtendedScanParameters(own_address_type=hci.OwnAddressType.PUBLIC_DEVICE_ADDRESS, scanning_filter_policy=hci.LeScanningFilterPolicy.ACCEPT_ALL, scanning_phys=0x1, - parameters=[ - hci.PhyScanParameters(le_scan_type=hci.LeScanType.PASSIVE, - le_scan_interval=0x0010, - le_scan_window=0x0010) + scanning_phy_parameters=[ + hci.ScanningPhyParameters(le_scan_type=hci.LeScanType.PASSIVE, + le_scan_interval=0x0010, + le_scan_window=0x0010) ])) await self.expect_evt( diff --git a/tools/rootcanal/test/LL/DDI/SCN/BV_79_C.py b/tools/rootcanal/test/LL/DDI/SCN/BV_79_C.py index a27b0a1058..80a6245c68 100644 --- a/tools/rootcanal/test/LL/DDI/SCN/BV_79_C.py +++ b/tools/rootcanal/test/LL/DDI/SCN/BV_79_C.py @@ -52,10 +52,10 @@ class Test(ControllerTest): hci.LeSetExtendedScanParameters(own_address_type=hci.OwnAddressType.PUBLIC_DEVICE_ADDRESS, scanning_filter_policy=hci.LeScanningFilterPolicy.ACCEPT_ALL, scanning_phys=0x1, - parameters=[ - hci.PhyScanParameters(le_scan_type=hci.LeScanType.PASSIVE, - le_scan_interval=0x0010, - le_scan_window=0x0010) + scanning_phy_parameters=[ + hci.ScanningPhyParameters(le_scan_type=hci.LeScanType.PASSIVE, + le_scan_interval=0x0010, + le_scan_window=0x0010) ])) await self.expect_evt( diff --git a/tools/rootcanal/test/controller/le/le_add_device_to_resolving_list_test.cc b/tools/rootcanal/test/controller/le/le_add_device_to_resolving_list_test.cc index 427861e0e9..b2a44d4812 100644 --- a/tools/rootcanal/test/controller/le/le_add_device_to_resolving_list_test.cc +++ b/tools/rootcanal/test/controller/le/le_add_device_to_resolving_list_test.cc @@ -130,4 +130,16 @@ TEST_F(LeAddDeviceToResolvingListTest, PeerIrkDuplicate) { ErrorCode::INVALID_HCI_COMMAND_PARAMETERS); } +TEST_F(LeAddDeviceToResolvingListTest, EmptyPeerIrkDuplicate) { + ASSERT_EQ(controller_.LeAddDeviceToResolvingList( + PeerAddressType::PUBLIC_DEVICE_OR_IDENTITY_ADDRESS, Address{1}, + std::array<uint8_t, 16>{0}, std::array<uint8_t, 16>{1}), + ErrorCode::SUCCESS); + + ASSERT_EQ(controller_.LeAddDeviceToResolvingList( + PeerAddressType::RANDOM_DEVICE_OR_IDENTITY_ADDRESS, Address{1}, + std::array<uint8_t, 16>{0}, std::array<uint8_t, 16>{1}), + ErrorCode::SUCCESS); +} + } // namespace rootcanal diff --git a/tools/rootcanal/test/controller/le/le_scanning_filter_duplicates_test.cc b/tools/rootcanal/test/controller/le/le_scanning_filter_duplicates_test.cc index a37dafc4d8..feed4edcd7 100644 --- a/tools/rootcanal/test/controller/le/le_scanning_filter_duplicates_test.cc +++ b/tools/rootcanal/test/controller/le/le_scanning_filter_duplicates_test.cc @@ -23,10 +23,8 @@ #include <vector> #include "hci/address.h" -#include "hci/hci_packets.h" #include "model/controller/link_layer_controller.h" -#include "packet/bit_inserter.h" -#include "packet/packet_view.h" +#include "packets/hci_packets.h" #include "packets/link_layer_packets.h" namespace rootcanal { @@ -73,7 +71,7 @@ class LeScanningFilterDuplicates : public ::testing::Test { void StartExtendedScan(FilterDuplicates filter_duplicates, uint16_t duration = 0, uint16_t period = 0) { - bluetooth::hci::PhyScanParameters param; + bluetooth::hci::ScanningPhyParameters param; param.le_scan_type_ = LeScanType::ACTIVE; param.le_scan_interval_ = 0x4; param.le_scan_window_ = 0x4; @@ -95,7 +93,7 @@ class LeScanningFilterDuplicates : public ::testing::Test { /// Helper for building ScanResponse packets static model::packets::LinkLayerPacketView LeScanResponse( std::vector<uint8_t> const data = {}) { - return FromBuilder(*model::packets::LeScanResponseBuilder::Create( + return FromBuilder(model::packets::LeScanResponseBuilder::Create( Address::kEmpty, Address::kEmpty, model::packets::AddressType::PUBLIC, data)); } @@ -103,7 +101,7 @@ class LeScanningFilterDuplicates : public ::testing::Test { /// Helper for building LeLegacyAdvertisingPdu packets static model::packets::LinkLayerPacketView LeLegacyAdvertisingPdu( std::vector<uint8_t> const data = {}) { - return FromBuilder(*model::packets::LeLegacyAdvertisingPduBuilder::Create( + return FromBuilder(model::packets::LeLegacyAdvertisingPduBuilder::Create( Address::kEmpty, Address::kEmpty, model::packets::AddressType::PUBLIC, model::packets::AddressType::PUBLIC, model::packets::LegacyAdvertisingType::ADV_IND, data)); @@ -112,13 +110,21 @@ class LeScanningFilterDuplicates : public ::testing::Test { /// Helper for building LeExtendedAdvertisingPdu packets static model::packets::LinkLayerPacketView LeExtendedAdvertisingPdu( std::vector<uint8_t> const data = {}) { - return FromBuilder(*model::packets::LeExtendedAdvertisingPduBuilder::Create( + return FromBuilder(model::packets::LeExtendedAdvertisingPduBuilder::Create( Address::kEmpty, Address::kEmpty, model::packets::AddressType::PUBLIC, model::packets::AddressType::PUBLIC, 0, 1, 0, 0, 0, model::packets::PrimaryPhyType::LE_1M, model::packets::SecondaryPhyType::LE_1M, 0, data)); } + static model::packets::LinkLayerPacketView FromBuilder( + std::unique_ptr<pdl::packet::Builder> builder) { + auto data = + std::make_shared<std::vector<uint8_t>>(builder->SerializeToBytes()); + return model::packets::LinkLayerPacketView::Create( + pdl::packet::slice(data)); + } + enum Filtered { kFiltered, kReported, @@ -154,18 +160,6 @@ class LeScanningFilterDuplicates : public ::testing::Test { static void remote_listener_( std::shared_ptr<model::packets::LinkLayerPacketBuilder> /* packet */, Phy::Type /* phy */, int8_t /* tx_power */) {} - - /// Helper for building packet view from packet builder - static model::packets::LinkLayerPacketView FromBuilder( - model::packets::LinkLayerPacketBuilder& builder) { - std::shared_ptr<std::vector<uint8_t>> buffer(new std::vector<uint8_t>); - auto bit_inserter = bluetooth::packet::BitInserter(*buffer); - - builder.Serialize(bit_inserter); - - return model::packets::LinkLayerPacketView::Create( - PacketView<kLittleEndian>(buffer)); - } }; unsigned LeScanningFilterDuplicates::event_listener_called_ = 0; diff --git a/tools/rootcanal/test/controller/le/le_set_extended_scan_enable_test.cc b/tools/rootcanal/test/controller/le/le_set_extended_scan_enable_test.cc index 79b9663619..278b9d06aa 100644 --- a/tools/rootcanal/test/controller/le/le_set_extended_scan_enable_test.cc +++ b/tools/rootcanal/test/controller/le/le_set_extended_scan_enable_test.cc @@ -33,10 +33,10 @@ class LeSetExtendedScanEnableTest : public ::testing::Test { LinkLayerController controller_{address_, properties_}; }; -static PhyScanParameters MakePhyScanParameters(LeScanType scan_type, - uint16_t scan_interval, - uint16_t scan_window) { - PhyScanParameters parameters; +static ScanningPhyParameters MakeScanningPhyParameters(LeScanType scan_type, + uint16_t scan_interval, + uint16_t scan_window) { + ScanningPhyParameters parameters; parameters.le_scan_type_ = scan_type; parameters.le_scan_interval_ = scan_interval; parameters.le_scan_window_ = scan_window; @@ -44,22 +44,24 @@ static PhyScanParameters MakePhyScanParameters(LeScanType scan_type, } TEST_F(LeSetExtendedScanEnableTest, EnableUsingPublicAddress) { - ASSERT_EQ(controller_.LeSetExtendedScanParameters( - OwnAddressType::PUBLIC_DEVICE_ADDRESS, - LeScanningFilterPolicy::ACCEPT_ALL, 0x1, - {MakePhyScanParameters(LeScanType::PASSIVE, 0x2000, 0x200)}), - ErrorCode::SUCCESS); + ASSERT_EQ( + controller_.LeSetExtendedScanParameters( + OwnAddressType::PUBLIC_DEVICE_ADDRESS, + LeScanningFilterPolicy::ACCEPT_ALL, 0x1, + {MakeScanningPhyParameters(LeScanType::PASSIVE, 0x2000, 0x200)}), + ErrorCode::SUCCESS); ASSERT_EQ(controller_.LeSetExtendedScanEnable( true, FilterDuplicates::DISABLED, 0, 0), ErrorCode::SUCCESS); } TEST_F(LeSetExtendedScanEnableTest, EnableUsingRandomAddress) { - ASSERT_EQ(controller_.LeSetExtendedScanParameters( - OwnAddressType::RANDOM_DEVICE_ADDRESS, - LeScanningFilterPolicy::ACCEPT_ALL, 0x1, - {MakePhyScanParameters(LeScanType::PASSIVE, 0x2000, 0x200)}), - ErrorCode::SUCCESS); + ASSERT_EQ( + controller_.LeSetExtendedScanParameters( + OwnAddressType::RANDOM_DEVICE_ADDRESS, + LeScanningFilterPolicy::ACCEPT_ALL, 0x1, + {MakeScanningPhyParameters(LeScanType::PASSIVE, 0x2000, 0x200)}), + ErrorCode::SUCCESS); ASSERT_EQ(controller_.LeSetRandomAddress(Address{1}), ErrorCode::SUCCESS); ASSERT_EQ(controller_.LeSetExtendedScanEnable( true, FilterDuplicates::DISABLED, 0, 0), @@ -67,11 +69,12 @@ TEST_F(LeSetExtendedScanEnableTest, EnableUsingRandomAddress) { } TEST_F(LeSetExtendedScanEnableTest, EnableUsingResolvableAddress) { - ASSERT_EQ(controller_.LeSetExtendedScanParameters( - OwnAddressType::RESOLVABLE_OR_RANDOM_ADDRESS, - LeScanningFilterPolicy::ACCEPT_ALL, 0x1, - {MakePhyScanParameters(LeScanType::PASSIVE, 0x2000, 0x200)}), - ErrorCode::SUCCESS); + ASSERT_EQ( + controller_.LeSetExtendedScanParameters( + OwnAddressType::RESOLVABLE_OR_RANDOM_ADDRESS, + LeScanningFilterPolicy::ACCEPT_ALL, 0x1, + {MakeScanningPhyParameters(LeScanType::PASSIVE, 0x2000, 0x200)}), + ErrorCode::SUCCESS); ASSERT_EQ(controller_.LeSetRandomAddress(Address{1}), ErrorCode::SUCCESS); ASSERT_EQ(controller_.LeSetExtendedScanEnable( true, FilterDuplicates::DISABLED, 0, 0), @@ -79,11 +82,12 @@ TEST_F(LeSetExtendedScanEnableTest, EnableUsingResolvableAddress) { } TEST_F(LeSetExtendedScanEnableTest, ResetEachPeriod) { - ASSERT_EQ(controller_.LeSetExtendedScanParameters( - OwnAddressType::PUBLIC_DEVICE_ADDRESS, - LeScanningFilterPolicy::ACCEPT_ALL, 0x1, - {MakePhyScanParameters(LeScanType::PASSIVE, 0x2000, 0x200)}), - ErrorCode::SUCCESS); + ASSERT_EQ( + controller_.LeSetExtendedScanParameters( + OwnAddressType::PUBLIC_DEVICE_ADDRESS, + LeScanningFilterPolicy::ACCEPT_ALL, 0x1, + {MakeScanningPhyParameters(LeScanType::PASSIVE, 0x2000, 0x200)}), + ErrorCode::SUCCESS); ASSERT_EQ(controller_.LeSetExtendedScanEnable( true, FilterDuplicates::RESET_EACH_PERIOD, 100, 1000), ErrorCode::SUCCESS); @@ -96,11 +100,12 @@ TEST_F(LeSetExtendedScanEnableTest, Disable) { } TEST_F(LeSetExtendedScanEnableTest, ValidDuration) { - ASSERT_EQ(controller_.LeSetExtendedScanParameters( - OwnAddressType::PUBLIC_DEVICE_ADDRESS, - LeScanningFilterPolicy::ACCEPT_ALL, 0x1, - {MakePhyScanParameters(LeScanType::PASSIVE, 0x2000, 0x200)}), - ErrorCode::SUCCESS); + ASSERT_EQ( + controller_.LeSetExtendedScanParameters( + OwnAddressType::PUBLIC_DEVICE_ADDRESS, + LeScanningFilterPolicy::ACCEPT_ALL, 0x1, + {MakeScanningPhyParameters(LeScanType::PASSIVE, 0x2000, 0x200)}), + ErrorCode::SUCCESS); ASSERT_EQ(controller_.LeSetExtendedScanEnable( true, FilterDuplicates::DISABLED, 127, 1), @@ -108,11 +113,12 @@ TEST_F(LeSetExtendedScanEnableTest, ValidDuration) { } TEST_F(LeSetExtendedScanEnableTest, InvalidDuration) { - ASSERT_EQ(controller_.LeSetExtendedScanParameters( - OwnAddressType::PUBLIC_DEVICE_ADDRESS, - LeScanningFilterPolicy::ACCEPT_ALL, 0x1, - {MakePhyScanParameters(LeScanType::PASSIVE, 0x2000, 0x200)}), - ErrorCode::SUCCESS); + ASSERT_EQ( + controller_.LeSetExtendedScanParameters( + OwnAddressType::PUBLIC_DEVICE_ADDRESS, + LeScanningFilterPolicy::ACCEPT_ALL, 0x1, + {MakeScanningPhyParameters(LeScanType::PASSIVE, 0x2000, 0x200)}), + ErrorCode::SUCCESS); ASSERT_EQ(controller_.LeSetExtendedScanEnable( true, FilterDuplicates::RESET_EACH_PERIOD, 0, 0), @@ -123,20 +129,22 @@ TEST_F(LeSetExtendedScanEnableTest, InvalidDuration) { } TEST_F(LeSetExtendedScanEnableTest, NoRandomAddress) { - ASSERT_EQ(controller_.LeSetExtendedScanParameters( - OwnAddressType::RANDOM_DEVICE_ADDRESS, - LeScanningFilterPolicy::ACCEPT_ALL, 0x1, - {MakePhyScanParameters(LeScanType::PASSIVE, 0x2000, 0x200)}), - ErrorCode::SUCCESS); + ASSERT_EQ( + controller_.LeSetExtendedScanParameters( + OwnAddressType::RANDOM_DEVICE_ADDRESS, + LeScanningFilterPolicy::ACCEPT_ALL, 0x1, + {MakeScanningPhyParameters(LeScanType::PASSIVE, 0x2000, 0x200)}), + ErrorCode::SUCCESS); ASSERT_EQ(controller_.LeSetExtendedScanEnable( true, FilterDuplicates::DISABLED, 0, 0), ErrorCode::INVALID_HCI_COMMAND_PARAMETERS); - ASSERT_EQ(controller_.LeSetExtendedScanParameters( - OwnAddressType::RESOLVABLE_OR_RANDOM_ADDRESS, - LeScanningFilterPolicy::ACCEPT_ALL, 0x1, - {MakePhyScanParameters(LeScanType::PASSIVE, 0x2000, 0x200)}), - ErrorCode::SUCCESS); + ASSERT_EQ( + controller_.LeSetExtendedScanParameters( + OwnAddressType::RESOLVABLE_OR_RANDOM_ADDRESS, + LeScanningFilterPolicy::ACCEPT_ALL, 0x1, + {MakeScanningPhyParameters(LeScanType::PASSIVE, 0x2000, 0x200)}), + ErrorCode::SUCCESS); ASSERT_EQ(controller_.LeSetExtendedScanEnable( true, FilterDuplicates::DISABLED, 0, 0), ErrorCode::INVALID_HCI_COMMAND_PARAMETERS); diff --git a/tools/rootcanal/test/controller/le/le_set_extended_scan_parameters_test.cc b/tools/rootcanal/test/controller/le/le_set_extended_scan_parameters_test.cc index 0c153ea1ac..3e1ab5390a 100644 --- a/tools/rootcanal/test/controller/le/le_set_extended_scan_parameters_test.cc +++ b/tools/rootcanal/test/controller/le/le_set_extended_scan_parameters_test.cc @@ -33,10 +33,10 @@ class LeSetExtendedScanParametersTest : public ::testing::Test { LinkLayerController controller_{address_, properties_}; }; -static PhyScanParameters MakePhyScanParameters(LeScanType scan_type, - uint16_t scan_interval, - uint16_t scan_window) { - PhyScanParameters parameters; +static ScanningPhyParameters MakeScanningPhyParameters(LeScanType scan_type, + uint16_t scan_interval, + uint16_t scan_window) { + ScanningPhyParameters parameters; parameters.le_scan_type_ = scan_type; parameters.le_scan_interval_ = scan_interval; parameters.le_scan_window_ = scan_window; @@ -47,8 +47,8 @@ TEST_F(LeSetExtendedScanParametersTest, Success) { ASSERT_EQ(controller_.LeSetExtendedScanParameters( OwnAddressType::PUBLIC_DEVICE_ADDRESS, LeScanningFilterPolicy::ACCEPT_ALL, 0x5, - {MakePhyScanParameters(LeScanType::PASSIVE, 0x2000, 0x200), - MakePhyScanParameters(LeScanType::ACTIVE, 0x2000, 0x200)}), + {MakeScanningPhyParameters(LeScanType::PASSIVE, 0x2000, 0x200), + MakeScanningPhyParameters(LeScanType::ACTIVE, 0x2000, 0x200)}), ErrorCode::SUCCESS); } @@ -56,8 +56,8 @@ TEST_F(LeSetExtendedScanParametersTest, ScanningActive) { ASSERT_EQ(controller_.LeSetExtendedScanParameters( OwnAddressType::PUBLIC_DEVICE_ADDRESS, LeScanningFilterPolicy::ACCEPT_ALL, 0x5, - {MakePhyScanParameters(LeScanType::PASSIVE, 0x2000, 0x200), - MakePhyScanParameters(LeScanType::ACTIVE, 0x2000, 0x200)}), + {MakeScanningPhyParameters(LeScanType::PASSIVE, 0x2000, 0x200), + MakeScanningPhyParameters(LeScanType::ACTIVE, 0x2000, 0x200)}), ErrorCode::SUCCESS); ASSERT_EQ(controller_.LeSetExtendedScanEnable( true, FilterDuplicates::DISABLED, 0, 0), @@ -66,17 +66,18 @@ TEST_F(LeSetExtendedScanParametersTest, ScanningActive) { ASSERT_EQ(controller_.LeSetExtendedScanParameters( OwnAddressType::PUBLIC_DEVICE_ADDRESS, LeScanningFilterPolicy::ACCEPT_ALL, 0x5, - {MakePhyScanParameters(LeScanType::PASSIVE, 0x2000, 0x200), - MakePhyScanParameters(LeScanType::ACTIVE, 0x2000, 0x200)}), + {MakeScanningPhyParameters(LeScanType::PASSIVE, 0x2000, 0x200), + MakeScanningPhyParameters(LeScanType::ACTIVE, 0x2000, 0x200)}), ErrorCode::COMMAND_DISALLOWED); } TEST_F(LeSetExtendedScanParametersTest, ReservedPhy) { - ASSERT_EQ(controller_.LeSetExtendedScanParameters( - OwnAddressType::PUBLIC_DEVICE_ADDRESS, - LeScanningFilterPolicy::ACCEPT_ALL, 0x80, - {MakePhyScanParameters(LeScanType::PASSIVE, 0x2000, 0x200)}), - ErrorCode::UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE); + ASSERT_EQ( + controller_.LeSetExtendedScanParameters( + OwnAddressType::PUBLIC_DEVICE_ADDRESS, + LeScanningFilterPolicy::ACCEPT_ALL, 0x80, + {MakeScanningPhyParameters(LeScanType::PASSIVE, 0x2000, 0x200)}), + ErrorCode::UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE); } TEST_F(LeSetExtendedScanParametersTest, InvalidPhyParameters) { @@ -85,19 +86,20 @@ TEST_F(LeSetExtendedScanParametersTest, InvalidPhyParameters) { LeScanningFilterPolicy::ACCEPT_ALL, 0x1, {}), ErrorCode::INVALID_HCI_COMMAND_PARAMETERS); - ASSERT_EQ(controller_.LeSetExtendedScanParameters( - OwnAddressType::PUBLIC_DEVICE_ADDRESS, - LeScanningFilterPolicy::ACCEPT_ALL, 0x1, - {MakePhyScanParameters(LeScanType::PASSIVE, 0x2000, 0x200), - MakePhyScanParameters(LeScanType::PASSIVE, 0x2000, 0x200)}), - ErrorCode::INVALID_HCI_COMMAND_PARAMETERS); + ASSERT_EQ( + controller_.LeSetExtendedScanParameters( + OwnAddressType::PUBLIC_DEVICE_ADDRESS, + LeScanningFilterPolicy::ACCEPT_ALL, 0x1, + {MakeScanningPhyParameters(LeScanType::PASSIVE, 0x2000, 0x200), + MakeScanningPhyParameters(LeScanType::PASSIVE, 0x2000, 0x200)}), + ErrorCode::INVALID_HCI_COMMAND_PARAMETERS); } TEST_F(LeSetExtendedScanParametersTest, InvalidScanInterval) { ASSERT_EQ(controller_.LeSetExtendedScanParameters( OwnAddressType::PUBLIC_DEVICE_ADDRESS, LeScanningFilterPolicy::ACCEPT_ALL, 0x1, - {MakePhyScanParameters(LeScanType::PASSIVE, 0x0, 0x200)}), + {MakeScanningPhyParameters(LeScanType::PASSIVE, 0x0, 0x200)}), ErrorCode::INVALID_HCI_COMMAND_PARAMETERS); } @@ -105,14 +107,15 @@ TEST_F(LeSetExtendedScanParametersTest, InvalidScanWindow) { ASSERT_EQ(controller_.LeSetExtendedScanParameters( OwnAddressType::PUBLIC_DEVICE_ADDRESS, LeScanningFilterPolicy::ACCEPT_ALL, 0x1, - {MakePhyScanParameters(LeScanType::PASSIVE, 0x2000, 0x0)}), + {MakeScanningPhyParameters(LeScanType::PASSIVE, 0x2000, 0x0)}), ErrorCode::INVALID_HCI_COMMAND_PARAMETERS); - ASSERT_EQ(controller_.LeSetExtendedScanParameters( - OwnAddressType::PUBLIC_DEVICE_ADDRESS, - LeScanningFilterPolicy::ACCEPT_ALL, 0x1, - {MakePhyScanParameters(LeScanType::PASSIVE, 0x2000, 0x2001)}), - ErrorCode::INVALID_HCI_COMMAND_PARAMETERS); + ASSERT_EQ( + controller_.LeSetExtendedScanParameters( + OwnAddressType::PUBLIC_DEVICE_ADDRESS, + LeScanningFilterPolicy::ACCEPT_ALL, 0x1, + {MakeScanningPhyParameters(LeScanType::PASSIVE, 0x2000, 0x2001)}), + ErrorCode::INVALID_HCI_COMMAND_PARAMETERS); } } // namespace rootcanal diff --git a/tools/rootcanal/test/controller/le/test_helpers.h b/tools/rootcanal/test/controller/le/test_helpers.h index 074e258d5c..4c9016c22c 100644 --- a/tools/rootcanal/test/controller/le/test_helpers.h +++ b/tools/rootcanal/test/controller/le/test_helpers.h @@ -49,18 +49,18 @@ MakeAdvertisingEventProperties(unsigned mask = 0) { return set; } -[[maybe_unused]] static bluetooth::hci::LeCreateConnPhyScanParameters +[[maybe_unused]] static bluetooth::hci::InitiatingPhyParameters MakeInitiatingPhyParameters(uint16_t scan_interval, uint16_t scan_window, uint16_t connection_interval_min, uint16_t connection_interval_max, uint16_t max_latency, uint16_t supervision_timeout, uint16_t min_ce_length, uint16_t max_ce_length) { - bluetooth::hci::LeCreateConnPhyScanParameters parameters; + bluetooth::hci::InitiatingPhyParameters parameters; parameters.scan_interval_ = scan_interval; parameters.scan_window_ = scan_window; - parameters.conn_interval_min_ = connection_interval_min; - parameters.conn_interval_max_ = connection_interval_max; - parameters.conn_latency_ = max_latency; + parameters.connection_interval_min_ = connection_interval_min; + parameters.connection_interval_max_ = connection_interval_max; + parameters.max_latency_ = max_latency; parameters.supervision_timeout_ = supervision_timeout; parameters.min_ce_length_ = min_ce_length; parameters.max_ce_length_ = max_ce_length; diff --git a/tools/rootcanal/test/pcap_filter_unittest.cc b/tools/rootcanal/test/pcap_filter_unittest.cc index 88035402e4..41a80768a7 100644 --- a/tools/rootcanal/test/pcap_filter_unittest.cc +++ b/tools/rootcanal/test/pcap_filter_unittest.cc @@ -18,7 +18,7 @@ #include <gtest/gtest.h> -#include "hci/hci_packets.h" +#include "packets/hci_packets.h" namespace rootcanal { |