diff --git a/system/binder/android/bluetooth/IBluetoothLeAudio.aidl b/system/binder/android/bluetooth/IBluetoothLeAudio.aidl index f8901ed20c60724dd3016a4c442dbac5fa0981ee..2f158e3d3865556c1bf09b797481e2361a1baee9 100644 --- a/system/binder/android/bluetooth/IBluetoothLeAudio.aidl +++ b/system/binder/android/bluetooth/IBluetoothLeAudio.aidl @@ -46,6 +46,7 @@ interface IBluetoothLeAudio { @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)") int getConnectionPolicy(in BluetoothDevice device, in AttributionSource attributionSource); + /* Same value as bluetooth::groups::kGroupUnknown */ const int LE_AUDIO_GROUP_ID_INVALID = -1; const int GROUP_STATUS_INACTIVE = 0; diff --git a/system/bta/Android.bp b/system/bta/Android.bp index 802fec6bcfe0ecd0d3b968e5a2812db908fdfdcf..24cf506503417af710bbcb60f64d3a5ae0bdacea 100644 --- a/system/bta/Android.bp +++ b/system/bta/Android.bp @@ -91,6 +91,12 @@ cc_library_static { "groups/groups.cc", "vc/device.cc", "vc/vc.cc", + "le_audio/client.cc", + "le_audio/devices.cc", + "le_audio/state_machine.cc", + "le_audio/client_parser.cc", + "le_audio/client_audio.cc", + "le_audio/le_audio_types.cc", "hearing_aid/hearing_aid.cc", "hearing_aid/hearing_aid_audio_source.cc", "hf_client/bta_hf_client_act.cc", @@ -394,3 +400,114 @@ cc_test { cfi: false, }, } + +// bta unit tests for LE Audio +// ======================================================== +cc_test { + name: "bluetooth_le_audio_test", + test_suites: ["device-tests"], + defaults: [ + "fluoride_defaults", + "clang_coverage_bin", + ], + host_supported: true, + include_dirs: [ + "packages/modules/Bluetooth/system", + "packages/modules/Bluetooth/system/bta/include", + "packages/modules/Bluetooth/system/bta/test/common", + "packages/modules/Bluetooth/system/btif/include", + "packages/modules/Bluetooth/system/gd", + "packages/modules/Bluetooth/system/stack/include", + ], + srcs : [ + ":TestStubOsi", + "test/common/bta_gatt_api_mock.cc", + "test/common/bta_gatt_queue_mock.cc", + "test/common/btm_api_mock.cc", + "le_audio/client_audio.cc", + "le_audio/client_audio_test.cc", + "le_audio/client_parser.cc", + "le_audio/client_parser_test.cc", + "le_audio/devices.cc", + "le_audio/devices_test.cc", + "le_audio/le_audio_types.cc", + "le_audio/le_audio_types_test.cc", + "le_audio/mock_iso_manager.cc", + "test/common/mock_controller.cc", + "le_audio/state_machine.cc", + "le_audio/state_machine_test.cc" + ], + shared_libs: [ + "libprotobuf-cpp-lite", + "libcrypto", + "liblog", // __android_log_print + ], + static_libs : [ + "libgmock", + "libbt-common", + "libbt-protos-lite", + "libosi", + ], + sanitize: { + cfi: false, + }, +} + +cc_test { + name: "bluetooth_le_audio_client_test", + test_suites: ["device-tests"], + defaults: [ + "fluoride_bta_defaults", + "clang_coverage_bin", + ], + host_supported: true, + include_dirs: [ + "packages/modules/Bluetooth/system", + "packages/modules/Bluetooth/system/bta/include", + "packages/modules/Bluetooth/system/bta/test/common", + "packages/modules/Bluetooth/system/stack/include", + ], + srcs : [ + "gatt/database.cc", + "gatt/database_builder.cc", + "le_audio/client.cc", + "le_audio/client_parser.cc", + "le_audio/devices.cc", + "le_audio/le_audio_client_test.cc", + "le_audio/le_audio_types.cc", + "le_audio/mock_iso_manager.cc", + "le_audio/mock_le_audio_client_audio.cc", + "le_audio/mock_state_machine.cc", + "test/common/btm_api_mock.cc", + "test/common/bta_gatt_api_mock.cc", + "test/common/bta_gatt_queue_mock.cc", + "test/common/btif_storage_mock.cc", + "test/common/mock_csis_client.cc", + "test/common/mock_controller.cc", + "test/common/mock_device_groups.cc", + ], + shared_libs: [ + "libprotobuf-cpp-lite", + "libcrypto", + "liblog", + ], + static_libs : [ + "crypto_toolbox_for_tests", + "libgmock", + "libbt-common", + "libbt-protos-lite", + "libosi", + "liblc3codec", + ], + sanitize: { + cfi: true, + scs: true, + address: true, + all_undefined: true, + integer_overflow: true, + diag: { + undefined : true + }, + }, +} + diff --git a/system/bta/csis/csis_client.cc b/system/bta/csis/csis_client.cc index ec6318a0f3b1ab61fef8cfb072b51ae13410b97f..dffecf81a119c91324c5659525c5e092bf895878 100644 --- a/system/bta/csis/csis_client.cc +++ b/system/bta/csis/csis_client.cc @@ -1144,7 +1144,7 @@ class CsisClientImpl : public CsisClient { if ((csis_group->GetDiscoveryState() != CsisDiscoveryState::CSIS_DISCOVERY_IDLE)) { LOG(ERROR) << __func__ - << " Incorrect ase group: " << loghex(csis_group->GetGroupId()) + << " Incorrect ase group: " << csis_group->GetGroupId() << " state " << loghex(static_cast<int>(csis_group->GetDiscoveryState())); return; diff --git a/system/bta/include/bta_le_audio_api.h b/system/bta/include/bta_le_audio_api.h new file mode 100644 index 0000000000000000000000000000000000000000..e4edcc1374ef54a1a28b97a9f950e47178f83a5e --- /dev/null +++ b/system/bta/include/bta_le_audio_api.h @@ -0,0 +1,48 @@ +/* + * Copyright 2019 HIMSA II K/S - www.himsa.com. Represented by EHIMA - + * www.ehima.com + * + * 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 <base/callback_forward.h> +#include <hardware/bt_le_audio.h> + +/* Interface class */ +class LeAudioClient { + public: + virtual ~LeAudioClient(void) = default; + + static void Initialize(bluetooth::le_audio::LeAudioClientCallbacks* callbacks, + base::Closure initCb, + base::Callback<bool()> hal_2_1_verifier); + static void Cleanup(void); + static LeAudioClient* Get(void); + static void DebugDump(int fd); + + virtual void RemoveDevice(const RawAddress& address) = 0; + virtual void Connect(const RawAddress& address) = 0; + virtual void Disconnect(const RawAddress& address) = 0; + virtual void GroupAddNode(const int group_id, const RawAddress& addr) = 0; + virtual void GroupRemoveNode(const int group_id, const RawAddress& addr) = 0; + virtual void GroupStream(const int group_id, const uint16_t content_type) = 0; + virtual void GroupSuspend(const int group_id) = 0; + virtual void GroupStop(const int group_id) = 0; + virtual void GroupDestroy(const int group_id) = 0; + virtual void GroupSetActive(const int group_id) = 0; + virtual std::vector<RawAddress> GetGroupDevices(const int group_id) = 0; + static void AddFromStorage(const RawAddress& addr, bool autoconnect); + static bool IsLeAudioClientRunning(); +}; diff --git a/system/bta/le_audio/client.cc b/system/bta/le_audio/client.cc new file mode 100644 index 0000000000000000000000000000000000000000..082609825c7c26d8f0656fe005c3ae47055aa911 --- /dev/null +++ b/system/bta/le_audio/client.cc @@ -0,0 +1,2839 @@ +/* + * Copyright 2021 HIMSA II K/S - www.himsa.com. Represented by EHIMA - + * www.ehima.com + * + * 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 <base/bind.h> +#include <base/strings/string_number_conversions.h> + +#include "advertise_data_parser.h" +#include "bta/csis/csis_types.h" +#include "bta_api.h" +#include "bta_gatt_api.h" +#include "bta_gatt_queue.h" +#include "bta_groups.h" +#include "bta_le_audio_api.h" +#include "btif_storage.h" +#include "btm_iso_api.h" +#include "client_audio.h" +#include "client_parser.h" +#include "device/include/controller.h" +#include "devices.h" +#include "embdrv/lc3/Api/Lc3Decoder.hpp" +#include "embdrv/lc3/Api/Lc3Encoder.hpp" +#include "gatt/bta_gattc_int.h" +#include "le_audio_types.h" +#include "osi/include/osi.h" +#include "stack/btm/btm_dev.h" +#include "stack/btm/btm_sec.h" +#include "stack/include/btu.h" // do_in_main_thread +#include "state_machine.h" + +using base::Closure; +using bluetooth::Uuid; +using bluetooth::groups::DeviceGroups; +using bluetooth::groups::DeviceGroupsCallbacks; +using bluetooth::hci::IsoManager; +using bluetooth::hci::iso_manager::cig_create_cmpl_evt; +using bluetooth::hci::iso_manager::cig_remove_cmpl_evt; +using bluetooth::hci::iso_manager::CigCallbacks; +using bluetooth::le_audio::ConnectionState; +using bluetooth::le_audio::GroupNodeStatus; +using bluetooth::le_audio::GroupStatus; +using bluetooth::le_audio::GroupStreamStatus; +using le_audio::LeAudioDevice; +using le_audio::LeAudioDeviceGroup; +using le_audio::LeAudioDeviceGroups; +using le_audio::LeAudioDevices; +using le_audio::LeAudioGroupStateMachine; +using le_audio::types::ase; +using le_audio::types::AseState; +using le_audio::types::AudioContexts; +using le_audio::types::AudioLocations; +using le_audio::types::AudioStreamDataPathState; +using le_audio::types::hdl_pair; +using le_audio::types::kDefaultScanDurationS; +using le_audio::types::LeAudioContextType; + +using le_audio::client_parser::ascs:: + kCtpResponseCodeInvalidConfigurationParameterValue; +using le_audio::client_parser::ascs::kCtpResponseCodeSuccess; +using le_audio::client_parser::ascs::kCtpResponseInvalidAseCisMapping; +using le_audio::client_parser::ascs::kCtpResponseNoReason; + +namespace { +void le_audio_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data); + +class LeAudioClientImpl; +LeAudioClientImpl* instance; +LeAudioClientAudioSinkReceiver* audioSinkReceiver; +LeAudioClientAudioSourceReceiver* audioSourceReceiver; +CigCallbacks* stateMachineHciCallbacks; +LeAudioGroupStateMachine::Callbacks* stateMachineCallbacks; +DeviceGroupsCallbacks* device_group_callbacks; + +/* + * Coordinatet Set Identification Profile (CSIP) based on CSIP 1.0 + * and Coordinatet Set Identification Service (CSIS) 1.0 + * + * CSIP allows to organize audio servers into sets e.g. Stereo Set, 5.1 Set + * and speed up connecting it. + * + * Since leaudio has already grouping API it was decided to integrate here CSIS + * and allow it to group devices semi-automatically. + * + * Flow: + * If connected device contains CSIS services, and it is included into CAP + * service, implementation marks device as a set member and waits for the + * bta/csis to learn about groups and notify implementation about assigned + * group id. + * + */ +/* LeAudioClientImpl class represents main implementation class for le audio + * feature in stack. This class implements GATT, le audio and ISO related parts. + * + * This class is represented in single instance and manages a group of devices, + * and devices. All devices calls back static method from it and are dispatched + * to target receivers (e.g. ASEs, devices). + * + * This instance also implements a LeAudioClient which is a upper layer API. + * Also LeAudioClientCallbacks are callbacks for upper layer. + * + * This class may be bonded with Test socket which allows to drive an instance + * for test purposes. + */ +class LeAudioClientImpl : public LeAudioClient { + public: + virtual ~LeAudioClientImpl() = default; + + LeAudioClientImpl( + bluetooth::le_audio::LeAudioClientCallbacks* callbacks_, + LeAudioGroupStateMachine::Callbacks* state_machine_callbacks_, + base::Closure initCb) + : gatt_if_(0), + callbacks_(callbacks_), + active_group_id_(bluetooth::groups::kGroupUnknown), + current_context_type_(LeAudioContextType::MEDIA), + audio_sink_ready_to_receive(false), + audio_source_ready_to_send(false), + current_source_codec_config({0, 0, 0, 0}), + current_sink_codec_config({0, 0, 0, 0}), + lc3_encoder(nullptr), + lc3_decoder(nullptr), + audio_source_instance_(nullptr), + audio_sink_instance_(nullptr) { + LeAudioGroupStateMachine::Initialize(state_machine_callbacks_); + groupStateMachine_ = LeAudioGroupStateMachine::Get(); + + BTA_GATTC_AppRegister( + le_audio_gattc_callback, + base::Bind( + [](base::Closure initCb, uint8_t client_id, uint8_t status) { + if (status != GATT_SUCCESS) { + LOG(ERROR) << "Can't start LeAudio profile - no gatt " + "clients left!"; + return; + } + instance->gatt_if_ = client_id; + initCb.Run(); + }, + initCb), + true); + + DeviceGroups::Get()->Initialize(device_group_callbacks); + } + + void AseInitialStateReadRequest(LeAudioDevice* leAudioDevice) { + int ases_num = leAudioDevice->ases_.size(); + void* notify_flag_ptr = NULL; + + for (int i = 0; i < ases_num; i++) { + /* Last read ase characteristic should issue connected state callback + * to upper layer + */ + + if (leAudioDevice->notify_connected_after_read_ && + (i == (ases_num - 1))) { + notify_flag_ptr = + INT_TO_PTR(leAudioDevice->notify_connected_after_read_); + } + + BtaGattQueue::ReadCharacteristic(leAudioDevice->conn_id_, + leAudioDevice->ases_[i].hdls.val_hdl, + OnGattReadRspStatic, notify_flag_ptr); + } + } + + void OnGroupAddedCb(const RawAddress& address, const bluetooth::Uuid& uuid, + int group_id) { + LOG(INFO) << __func__ << " address: " << address << " group uuid " << uuid + << " group_id: " << group_id; + + /* We are interested in the groups which are in the context of CAP */ + if (uuid != le_audio::uuid::kCapServiceUuid) return; + + LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); + if (!leAudioDevice) return; + if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) { + LOG(INFO) << __func__ + << " group already set: " << leAudioDevice->group_id_; + return; + } + + group_add_node(group_id, address); + } + + void OnGroupMemberAddedCb(const RawAddress& address, int group_id) { + LOG(INFO) << __func__ << " address: " << address + << " group_id: " << group_id; + + auto group = aseGroups_.FindById(group_id); + if (!group) { + LOG(ERROR) << __func__ << " Not interested in group id: " << group_id; + return; + } + + LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); + if (!leAudioDevice) return; + if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) { + LOG(INFO) << __func__ + << " group already set: " << leAudioDevice->group_id_; + return; + } + + group_add_node(group_id, address); + } + + void OnGroupMemberRemovedCb(const RawAddress& address, int group_id) { + LOG(INFO) << __func__ << " address: " << address + << " group_id: " << group_id; + + LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); + if (!leAudioDevice) return; + if (leAudioDevice->group_id_ == bluetooth::groups::kGroupUnknown) { + LOG(INFO) << __func__ << " device already not assigned to the group."; + return; + } + + LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); + if (group == NULL) { + LOG(INFO) << __func__ + << " device not in the group: " << leAudioDevice->address_ + << ", " << group_id; + return; + } + + group_remove_node(group, address); + } + + /* This callback happens if kLeAudioDeviceSetStateTimeoutMs timeout happens + * during transition from origin to target state + */ + void OnLeAudioDeviceSetStateTimeout(int group_id) { + LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); + + if (!group) { + /* Group removed */ + return; + } + + /* Releasement didn't finished in time */ + if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) { + LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice(); + LOG_ASSERT(leAudioDevice) + << __func__ << " Shouldn't be called without an active device."; + + do { + if (instance) instance->DisconnectDevice(leAudioDevice, true); + leAudioDevice = group->GetNextActiveDevice(leAudioDevice); + } while (leAudioDevice); + + return; + } + + LOG(ERROR) << __func__ << ", State not achieved on time, releasing ases"; + + groupStateMachine_->StopStream(group); + } + + void UpdateContextAndLocations(LeAudioDeviceGroup* group, + LeAudioDevice* leAudioDevice) { + std::optional<AudioContexts> new_group_updated_contexts = + group->UpdateActiveContextsMap(leAudioDevice->GetAvailableContexts()); + + if (new_group_updated_contexts || group->ReloadAudioLocations()) { + callbacks_->OnAudioConf(group->audio_directions_, group->group_id_, + group->snk_audio_locations_.to_ulong(), + group->src_audio_locations_.to_ulong(), + new_group_updated_contexts->to_ulong()); + } + } + + void CancelStreamingRequest() { + if (audio_source_ready_to_send) { + LeAudioClientAudioSource::CancelStreamingRequest(); + audio_source_ready_to_send = false; + } + + if (audio_sink_ready_to_receive) { + LeAudioClientAudioSink::CancelStreamingRequest(); + audio_sink_ready_to_receive = false; + } + } + + void ControlPointNotificationHandler( + struct le_audio::client_parser::ascs::ctp_ntf& ntf) { + for (auto& entry : ntf.entries) { + switch (entry.response_code) { + case kCtpResponseCodeInvalidConfigurationParameterValue: + switch (entry.reason) { + case kCtpResponseInvalidAseCisMapping: + CancelStreamingRequest(); + break; + case kCtpResponseNoReason: + default: + break; + } + break; + case kCtpResponseCodeSuccess: + FALLTHROUGH; + default: + break; + } + } + } + + void group_add_node(const int group_id, const RawAddress& address, + bool update_group_module = false) { + LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); + LeAudioDeviceGroup* new_group; + LeAudioDeviceGroup* old_group = nullptr; + int old_group_id = bluetooth::groups::kGroupUnknown; + + if (!leAudioDevice) { + /* TODO This part possible to remove as this is to handle adding device to + * the group which is unknown and not connected. + */ + LOG(INFO) << __func__ << ", leAudioDevice unknown , address: " << address + << " group: " << loghex(group_id); + + if (group_id == bluetooth::groups::kGroupUnknown) return; + + LOG(INFO) << __func__ << "Set member adding ..."; + leAudioDevices_.Add(address, true); + leAudioDevice = leAudioDevices_.FindByAddress(address); + } else { + if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) { + old_group = aseGroups_.FindById(leAudioDevice->group_id_); + old_group_id = old_group->group_id_; + } + } + + auto id = DeviceGroups::Get()->GetGroupId(address, + le_audio::uuid::kCapServiceUuid); + if (group_id == bluetooth::groups::kGroupUnknown) { + if (id == bluetooth::groups::kGroupUnknown) { + DeviceGroups::Get()->AddDevice(address, + le_audio::uuid::kCapServiceUuid); + /* We will get back here when group will be created */ + return; + } + + new_group = aseGroups_.Add(id); + if (!new_group) { + LOG(ERROR) << __func__ + << ", can't create group - group is already there?"; + return; + } + } else { + LOG_ASSERT(id == group_id) + << " group id missmatch? leaudio id: " << group_id + << " groups module " << id; + new_group = aseGroups_.FindById(group_id); + if (!new_group) { + new_group = aseGroups_.Add(group_id); + } else { + if (new_group->IsDeviceInTheGroup(leAudioDevice)) return; + } + } + + /* If device was in the group and it was not removed by the application, + * lets do it now + */ + if (old_group) group_remove_node(old_group, address, update_group_module); + + new_group->AddNode(leAudioDevices_.GetByAddress(address)); + + callbacks_->OnGroupNodeStatus(address, new_group->group_id_, + GroupNodeStatus::ADDED); + + /* If device is connected and added to the group, lets read ASE states */ + if (leAudioDevice->conn_id_ != GATT_INVALID_CONN_ID) + AseInitialStateReadRequest(leAudioDevice); + + /* Group may be destroyed once moved its last node to new group */ + if (aseGroups_.FindById(old_group_id) != nullptr) { + /* Removing node from group may touch its context integrity */ + std::optional<AudioContexts> old_group_updated_contexts = + old_group->UpdateActiveContextsMap(old_group->GetActiveContexts()); + + if (old_group_updated_contexts || old_group->ReloadAudioLocations()) { + callbacks_->OnAudioConf(old_group->audio_directions_, old_group_id, + old_group->snk_audio_locations_.to_ulong(), + old_group->src_audio_locations_.to_ulong(), + old_group_updated_contexts->to_ulong()); + } + } + + UpdateContextAndLocations(new_group, leAudioDevice); + } + + void GroupAddNode(const int group_id, const RawAddress& address) override { + auto id = DeviceGroups::Get()->GetGroupId(address, + le_audio::uuid::kCapServiceUuid); + if (id == group_id) return; + + if (id != bluetooth::groups::kGroupUnknown) { + DeviceGroups::Get()->RemoveDevice(address, id); + } + + DeviceGroups::Get()->AddDevice(address, le_audio::uuid::kCapServiceUuid, + group_id); + } + + void group_remove_node(LeAudioDeviceGroup* group, const RawAddress& address, + bool update_group_module = false) { + int group_id = group->group_id_; + group->RemoveNode(leAudioDevices_.GetByAddress(address)); + + if (update_group_module) { + int groups_group_id = DeviceGroups::Get()->GetGroupId( + address, le_audio::uuid::kCapServiceUuid); + if (groups_group_id == group_id) { + DeviceGroups::Get()->RemoveDevice(address, group_id); + } + } + + callbacks_->OnGroupNodeStatus(address, group_id, GroupNodeStatus::REMOVED); + + /* Remove group if this was the last leAudioDevice in this group */ + if (group->IsEmpty()) { + aseGroups_.Remove(group_id); + + return; + } + + /* Removing node from group touch its context integrity */ + std::optional<AudioContexts> updated_contexts = + group->UpdateActiveContextsMap(group->GetActiveContexts()); + + if (updated_contexts || group->ReloadAudioLocations()) + callbacks_->OnAudioConf(group->audio_directions_, group->group_id_, + group->snk_audio_locations_.to_ulong(), + group->src_audio_locations_.to_ulong(), + updated_contexts->to_ulong()); + } + + void GroupRemoveNode(const int group_id, const RawAddress& address) override { + LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); + LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); + + LOG(INFO) << __func__ << " group_id: " << group_id + << " address: " << address; + + if (!leAudioDevice) { + LOG(ERROR) << __func__ + << ", Skipping unknown leAudioDevice, address: " << address; + return; + } + + if (leAudioDevice->group_id_ != group_id) { + LOG(ERROR) << __func__ << "Device is not in group_id: " << group_id + << ", but in group_id: " << leAudioDevice->group_id_; + return; + } + + if (group == NULL) { + LOG(ERROR) << __func__ << " device not in the group ?!"; + return; + } + + group_remove_node(group, address, true); + } + + void GroupStream(const int group_id, const uint16_t context_type) override { + LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); + auto final_context_type = context_type; + + if (context_type >= static_cast<uint16_t>(LeAudioContextType::RFU)) { + LOG(ERROR) << __func__ << ", stream context type is not supported: " + << loghex(context_type); + CancelStreamingRequest(); + return; + } + + if (!group) { + LOG(ERROR) << __func__ << ", unknown group id: " << group_id; + CancelStreamingRequest(); + return; + } + + auto supported_context_type = group->GetActiveContexts(); + if (!(context_type & supported_context_type.to_ulong())) { + LOG(ERROR) << " Unsupported context type by remote device: " + << loghex(context_type) << ". Switching to unspecified"; + final_context_type = + static_cast<uint16_t>(LeAudioContextType::UNSPECIFIED); + } + + if (!group->IsAnyDeviceConnected()) { + LOG(ERROR) << __func__ << ", group " << group_id << " is not connected "; + CancelStreamingRequest(); + return; + } + + /* Check if any group is in the transition state. If so, we don't allow to + * start new group to stream */ + if (aseGroups_.IsAnyInTransition()) { + LOG(INFO) << __func__ << " some group is already in the transition state"; + CancelStreamingRequest(); + return; + } + + if (groupStateMachine_->StartStream( + group, static_cast<LeAudioContextType>(final_context_type))) + stream_request_started_ = true; + else + ClientAudioIntefraceRelease(); + } + + void GroupSuspend(const int group_id) override { + LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); + + if (!group) { + LOG(ERROR) << __func__ << ", unknown group id: " << group_id; + return; + } + + if (!group->IsAnyDeviceConnected()) { + LOG(ERROR) << __func__ << ", group is not connected"; + return; + } + + if (group->IsInTransition()) { + LOG(INFO) << __func__ + << ", group is in transition from: " << group->GetState() + << ", to: " << group->GetTargetState(); + return; + } + + if (group->GetState() != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { + LOG(ERROR) << __func__ + << ", invalid current state of group: " << group->GetState(); + return; + } + + audio_source_ready_to_send = false; + audio_sink_ready_to_receive = false; + + groupStateMachine_->SuspendStream(group); + } + + void GroupStop(const int group_id) override { + LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); + + if (!group) { + LOG(ERROR) << __func__ << ", unknown group id: " << group_id; + return; + } + + if (group->IsEmpty()) { + LOG(ERROR) << __func__ << ", group is empty"; + return; + } + + if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) { + LOG(ERROR) << __func__ + << ", group already stopped: " << group->GetState(); + return; + } + + groupStateMachine_->StopStream(group); + } + + void GroupDestroy(const int group_id) override { + LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); + + if (!group) { + LOG(ERROR) << __func__ << ", unknown group id: " << group_id; + return; + } + + // Disconnect and remove each device within the group + auto* dev = group->GetFirstDevice(); + while (dev) { + auto* next_dev = group->GetNextDevice(dev); + RemoveDevice(dev->address_); + dev = next_dev; + } + } + + void GroupSetActive(const int group_id) override { + DLOG(INFO) << __func__ << " group_id: " << group_id; + + if (group_id == bluetooth::groups::kGroupUnknown) { + if (active_group_id_ == bluetooth::groups::kGroupUnknown) { + /* Nothing to do */ + return; + } + + StopAudio(); + GroupStop(active_group_id_); + callbacks_->OnGroupStatus(active_group_id_, GroupStatus::INACTIVE); + active_group_id_ = group_id; + + return; + } + + LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); + if (!group) { + LOG(ERROR) << __func__ + << ", Invalid group: " << static_cast<int>(group_id); + return; + } + + if (active_group_id_ != bluetooth::groups::kGroupUnknown) { + LOG(WARNING) << __func__ << ", Another group already active: " + << static_cast<int>(active_group_id_); + return; + } + + if (!audio_source_instance_) { + audio_source_instance_ = LeAudioClientAudioSource::Acquire(); + if (!audio_source_instance_) { + LOG(ERROR) << __func__ << ", could not acquire audio source interface"; + return; + } + } + + if (!audio_sink_instance_) { + audio_sink_instance_ = LeAudioClientAudioSink::Acquire(); + if (!audio_sink_instance_) { + LOG(ERROR) << __func__ << ", could not acquire audio sink interface"; + LeAudioClientAudioSource::Release(audio_source_instance_); + return; + } + } + + /* Configure audio HAL sessions with most frequent context */ + UpdateCurrentHalSessions(group_id, LeAudioContextType::MEDIA); + if (current_source_codec_config.IsInvalid() && + current_sink_codec_config.IsInvalid()) { + LOG(WARNING) << __func__ << ", unsupported device configurations"; + callbacks_->OnGroupStatus(active_group_id_, GroupStatus::INACTIVE); + return; + } + + active_group_id_ = group_id; + callbacks_->OnGroupStatus(active_group_id_, GroupStatus::ACTIVE); + } + + void RemoveDevice(const RawAddress& address) override { + LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); + if (!leAudioDevice) { + return; + } + + /* Remove the group assignment if not yet removed. It might happen that the + * group module has already called the appropriate callback and we have + * already removed the group assignment. + */ + if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) { + auto group = aseGroups_.FindById(leAudioDevice->group_id_); + group_remove_node(group, address, true); + } + + if (leAudioDevice->conn_id_ != GATT_INVALID_CONN_ID) { + Disconnect(address); + leAudioDevice->removing_device_ = true; + return; + } + + leAudioDevices_.Remove(address); + } + + void Connect(const RawAddress& address) override { + LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); + if (!leAudioDevice) { + leAudioDevices_.Add(address, true); + } else { + leAudioDevice->connecting_actively_ = true; + } + + BTA_GATTC_Open(gatt_if_, address, true, false); + } + + std::vector<RawAddress> GetGroupDevices(const int group_id) override { + LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); + std::vector<RawAddress> all_group_device_addrs; + + if (group != nullptr) { + LeAudioDevice* leAudioDevice = group->GetFirstDevice(); + while (leAudioDevice) { + all_group_device_addrs.push_back(leAudioDevice->address_); + leAudioDevice = group->GetNextDevice(leAudioDevice); + }; + } + + return all_group_device_addrs; + } + + /* Restore paired device from storage to recreate groups */ + void AddFromStorage(const RawAddress& address, bool autoconnect) { + LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); + + LOG(INFO) << __func__ << ", restoring: " << address; + + if (!leAudioDevice) { + leAudioDevices_.Add(address, false); + leAudioDevice = leAudioDevices_.FindByAddress(address); + } + + int group_id = DeviceGroups::Get()->GetGroupId( + address, le_audio::uuid::kCapServiceUuid); + if (group_id != bluetooth::groups::kGroupUnknown) { + group_add_node(group_id, address); + } + + if (autoconnect) Connect(address); + } + + void Disconnect(const RawAddress& address) override { + LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); + + if (!leAudioDevice) { + LOG(ERROR) << __func__ << ", leAudioDevice not connected (" << address + << ")"; + return; + } + + /* cancel pending direct connect */ + if (leAudioDevice->connecting_actively_) + BTA_GATTC_CancelOpen(gatt_if_, address, true); + + /* Removes all registrations for connection */ + BTA_GATTC_CancelOpen(0, address, false); + + if (leAudioDevice->conn_id_ == GATT_INVALID_CONN_ID) { + LOG(ERROR) << __func__ << ", leAudioDevice not connected (" << address + << ")"; + return; + } + + DisconnectDevice(leAudioDevice); + } + + void DisconnectDevice(LeAudioDevice* leAudioDevice, + bool acl_force_disconnect = false) { + if (leAudioDevice->conn_id_ == GATT_INVALID_CONN_ID) { + return; + } + + if (acl_force_disconnect) { + uint16_t acl_handle = + BTM_GetHCIConnHandle(leAudioDevice->address_, BT_TRANSPORT_LE); + if (acl_handle != HCI_INVALID_HANDLE) { + acl_disconnect_from_handle(acl_handle, HCI_ERR_PEER_USER); + return; + } + } + + BtaGattQueue::Clean(leAudioDevice->conn_id_); + BTA_GATTC_Close(leAudioDevice->conn_id_); + leAudioDevice->conn_id_ = GATT_INVALID_CONN_ID; + } + + void DeregisterNotifications(LeAudioDevice* leAudioDevice) { + /* GATTC will ommit not registered previously handles */ + for (auto pac_tuple : leAudioDevice->snk_pacs_) { + BTA_GATTC_DeregisterForNotifications(gatt_if_, leAudioDevice->address_, + std::get<0>(pac_tuple).val_hdl); + } + for (auto pac_tuple : leAudioDevice->src_pacs_) { + BTA_GATTC_DeregisterForNotifications(gatt_if_, leAudioDevice->address_, + std::get<0>(pac_tuple).val_hdl); + } + + if (leAudioDevice->snk_audio_locations_hdls_.val_hdl != 0) + BTA_GATTC_DeregisterForNotifications( + gatt_if_, leAudioDevice->address_, + leAudioDevice->snk_audio_locations_hdls_.val_hdl); + if (leAudioDevice->src_audio_locations_hdls_.val_hdl != 0) + BTA_GATTC_DeregisterForNotifications( + gatt_if_, leAudioDevice->address_, + leAudioDevice->src_audio_locations_hdls_.val_hdl); + if (leAudioDevice->audio_avail_hdls_.val_hdl != 0) + BTA_GATTC_DeregisterForNotifications( + gatt_if_, leAudioDevice->address_, + leAudioDevice->audio_avail_hdls_.val_hdl); + if (leAudioDevice->audio_supp_cont_hdls_.val_hdl != 0) + BTA_GATTC_DeregisterForNotifications( + gatt_if_, leAudioDevice->address_, + leAudioDevice->audio_supp_cont_hdls_.val_hdl); + if (leAudioDevice->ctp_hdls_.val_hdl != 0) + BTA_GATTC_DeregisterForNotifications(gatt_if_, leAudioDevice->address_, + leAudioDevice->ctp_hdls_.val_hdl); + + for (struct ase& ase : leAudioDevice->ases_) + BTA_GATTC_DeregisterForNotifications(gatt_if_, leAudioDevice->address_, + ase.hdls.val_hdl); + } + + /* This is a generic read/notify/indicate handler for gatt. Here messages + * are dispatched to correct elements e.g. ASEs, PACs, audio locations etc. + */ + void LeAudioCharValueHandle(uint16_t conn_id, uint16_t hdl, uint16_t len, + uint8_t* value) { + LeAudioDevice* leAudioDevice = leAudioDevices_.FindByConnId(conn_id); + struct ase* ase; + + if (!leAudioDevice) { + LOG(ERROR) << __func__ << ", no leAudioDevice assigned to connection id: " + << static_cast<int>(conn_id); + return; + } + + ase = leAudioDevice->GetAseByValHandle(hdl); + + if (ase) { + LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_); + groupStateMachine_->ProcessGattNotifEvent(value, len, ase, leAudioDevice, + group); + + return; + } + + auto snk_pac_ent = std::find_if( + leAudioDevice->snk_pacs_.begin(), leAudioDevice->snk_pacs_.end(), + [&hdl](auto& pac_ent) { return std::get<0>(pac_ent).val_hdl == hdl; }); + if (snk_pac_ent != leAudioDevice->snk_pacs_.end()) { + std::vector<struct le_audio::types::acs_ac_record> pac_recs; + + /* Guard consistency of PAC records structure */ + if (!le_audio::client_parser::pacs::ParsePac(pac_recs, len, value)) + return; + + LOG(INFO) << __func__ << ", Registering sink PACs"; + leAudioDevice->RegisterPACs(&std::get<1>(*snk_pac_ent), &pac_recs); + + /* Update supported context types including internal capabilities */ + LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_); + + /* Active context map should be considered to be updated in response to + * PACs update. + * Read of available context during initial attribute discovery. + * Group would be assigned once service search is completed. + */ + if (group) + group->UpdateActiveContextsMap(leAudioDevice->GetAvailableContexts()); + + return; + } + + auto src_pac_ent = std::find_if( + leAudioDevice->src_pacs_.begin(), leAudioDevice->src_pacs_.end(), + [&hdl](auto& pac_ent) { return std::get<0>(pac_ent).val_hdl == hdl; }); + if (src_pac_ent != leAudioDevice->src_pacs_.end()) { + std::vector<struct le_audio::types::acs_ac_record> pac_recs; + + /* Guard consistency of PAC records structure */ + if (!le_audio::client_parser::pacs::ParsePac(pac_recs, len, value)) + return; + + LOG(INFO) << __func__ << ", Registering source PACs"; + leAudioDevice->RegisterPACs(&std::get<1>(*src_pac_ent), &pac_recs); + + /* Update supported context types including internal capabilities */ + LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_); + + /* Active context map should be considered to be updated in response to + * PACs update. + * Read of available context during initial attribute discovery. + * Group would be assigned once service search is completed. + */ + if (group) + group->UpdateActiveContextsMap(leAudioDevice->GetAvailableContexts()); + + return; + } + + if (hdl == leAudioDevice->snk_audio_locations_hdls_.val_hdl) { + AudioLocations snk_audio_locations; + + le_audio::client_parser::pacs::ParseAudioLocations(snk_audio_locations, + len, value); + + /* Value may not change */ + if ((leAudioDevice->audio_directions_ & + le_audio::types::kLeAudioDirectionSink) && + (leAudioDevice->snk_audio_locations_ ^ snk_audio_locations).none()) + return; + + /* Presence of PAC characteristic for source means support for source + * audio location. Value of 0x00000000 means mono/unspecified + */ + leAudioDevice->audio_directions_ |= + le_audio::types::kLeAudioDirectionSink; + leAudioDevice->snk_audio_locations_ = snk_audio_locations; + + LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_); + /* Read of source audio locations during initial attribute discovery. + * Group would be assigned once service search is completed. + */ + if (group && group->ReloadAudioLocations()) { + callbacks_->OnAudioConf(group->audio_directions_, group->group_id_, + group->snk_audio_locations_.to_ulong(), + group->src_audio_locations_.to_ulong(), + group->GetActiveContexts().to_ulong()); + } + } else if (hdl == leAudioDevice->src_audio_locations_hdls_.val_hdl) { + AudioLocations src_audio_locations; + + le_audio::client_parser::pacs::ParseAudioLocations(src_audio_locations, + len, value); + + /* Value may not change */ + if ((leAudioDevice->audio_directions_ & + le_audio::types::kLeAudioDirectionSource) && + (leAudioDevice->src_audio_locations_ ^ src_audio_locations).none()) + return; + + /* Presence of PAC characteristic for source means support for source + * audio location. Value of 0x00000000 means mono/unspecified + */ + leAudioDevice->audio_directions_ |= + le_audio::types::kLeAudioDirectionSource; + leAudioDevice->src_audio_locations_ = src_audio_locations; + + LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_); + /* Read of source audio locations during initial attribute discovery. + * Group would be assigned once service search is completed. + */ + if (group && group->ReloadAudioLocations()) { + callbacks_->OnAudioConf(group->audio_directions_, group->group_id_, + group->snk_audio_locations_.to_ulong(), + group->src_audio_locations_.to_ulong(), + group->GetActiveContexts().to_ulong()); + } + } else if (hdl == leAudioDevice->audio_avail_hdls_.val_hdl) { + auto avail_audio_contexts = std::make_unique< + struct le_audio::client_parser::pacs::acs_available_audio_contexts>(); + + le_audio::client_parser::pacs::ParseAvailableAudioContexts( + *avail_audio_contexts, len, value); + + auto updated_avail_contexts = leAudioDevice->SetAvailableContexts( + avail_audio_contexts->snk_avail_cont, + avail_audio_contexts->src_avail_cont); + + if (updated_avail_contexts.any()) { + /* Update scenario map considering changed active context types */ + LeAudioDeviceGroup* group = + aseGroups_.FindById(leAudioDevice->group_id_); + /* Read of available context during initial attribute discovery. + * Group would be assigned once service search is completed. + */ + if (group) { + std::optional<AudioContexts> updated_contexts = + group->UpdateActiveContextsMap(updated_avail_contexts); + if (updated_contexts) { + callbacks_->OnAudioConf(group->audio_directions_, group->group_id_, + group->snk_audio_locations_.to_ulong(), + group->src_audio_locations_.to_ulong(), + updated_contexts->to_ulong()); + } + } + } + } else if (hdl == leAudioDevice->audio_supp_cont_hdls_.val_hdl) { + auto supp_audio_contexts = std::make_unique< + struct le_audio::client_parser::pacs::acs_supported_audio_contexts>(); + + le_audio::client_parser::pacs::ParseSupportedAudioContexts( + *supp_audio_contexts, len, value); + /* Just store if for now */ + leAudioDevice->SetSupportedContexts(supp_audio_contexts->snk_supp_cont, + supp_audio_contexts->src_supp_cont); + } else if (hdl == leAudioDevice->ctp_hdls_.val_hdl) { + auto ntf = + std::make_unique<struct le_audio::client_parser::ascs::ctp_ntf>(); + + if (ParseAseCtpNotification(*ntf, len, value)) + ControlPointNotificationHandler(*ntf); + } else { + LOG(ERROR) << __func__ << ", Unknown attribute read: " << loghex(hdl); + } + } + + void OnGattReadRsp(uint16_t conn_id, tGATT_STATUS status, uint16_t hdl, + uint16_t len, uint8_t* value, void* data) { + LeAudioCharValueHandle(conn_id, hdl, len, value); + } + + void OnGattConnected(tGATT_STATUS status, uint16_t conn_id, + tGATT_IF client_if, RawAddress address, + tBT_TRANSPORT transport, uint16_t mtu) { + LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); + + if (!leAudioDevice) return; + + if (status != GATT_SUCCESS) { + /* autoconnect connection failed, that's ok */ + if (!leAudioDevice->connecting_actively_) return; + + LOG(ERROR) << "Failed to connect to LeAudio leAudioDevice, status: " + << +status; + callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, address); + return; + } + + if (controller_get_interface()->supports_ble_2m_phy()) { + LOG(INFO) << address << " set preferred PHY to 2M"; + BTM_BleSetPhy(address, PHY_LE_2M, PHY_LE_2M, 0); + } + + BTM_RequestPeerSCA(leAudioDevice->address_, transport); + + leAudioDevice->connecting_actively_ = false; + leAudioDevice->conn_id_ = conn_id; + + if (mtu == GATT_DEF_BLE_MTU_SIZE) { + LOG(INFO) << __func__ << ", Configure MTU"; + BtaGattQueue::ConfigureMtu(leAudioDevice->conn_id_, 240); + } + + /* If we know services, register for notifications */ + if (leAudioDevice->known_service_handles_) + RegisterKnownNotifications(leAudioDevice); + + if (BTM_SecIsSecurityPending(address)) { + /* if security collision happened, wait for encryption done + * (BTA_GATTC_ENC_CMPL_CB_EVT) */ + return; + } + + /* verify bond */ + if (BTM_IsEncrypted(address, BT_TRANSPORT_LE)) { + /* if link has been encrypted */ + OnEncryptionComplete(address, BTM_SUCCESS); + return; + } + + if (BTM_IsLinkKeyKnown(address, BT_TRANSPORT_LE)) { + int result = BTM_SetEncryption( + address, BT_TRANSPORT_LE, + [](const RawAddress* bd_addr, tBT_TRANSPORT transport, + void* p_ref_data, tBTM_STATUS status) { + if (instance) instance->OnEncryptionComplete(*bd_addr, status); + }, + nullptr, BTM_BLE_SEC_ENCRYPT); + + LOG(INFO) << __func__ + << "Encryption required. Request result: " << result; + return; + } + + LOG(ERROR) << __func__ << " Encryption error"; + } + + void RegisterKnownNotifications(LeAudioDevice* leAudioDevice) { + LOG(INFO) << __func__ << " device: " << leAudioDevice->address_; + + /* GATTC will ommit not registered previously handles */ + for (auto pac_tuple : leAudioDevice->snk_pacs_) { + BTA_GATTC_RegisterForNotifications(gatt_if_, leAudioDevice->address_, + std::get<0>(pac_tuple).val_hdl); + } + for (auto pac_tuple : leAudioDevice->src_pacs_) { + BTA_GATTC_RegisterForNotifications(gatt_if_, leAudioDevice->address_, + std::get<0>(pac_tuple).val_hdl); + } + + if (leAudioDevice->snk_audio_locations_hdls_.val_hdl != 0) + BTA_GATTC_RegisterForNotifications( + gatt_if_, leAudioDevice->address_, + leAudioDevice->snk_audio_locations_hdls_.val_hdl); + if (leAudioDevice->src_audio_locations_hdls_.val_hdl != 0) + BTA_GATTC_RegisterForNotifications( + gatt_if_, leAudioDevice->address_, + leAudioDevice->src_audio_locations_hdls_.val_hdl); + if (leAudioDevice->audio_avail_hdls_.val_hdl != 0) + BTA_GATTC_RegisterForNotifications( + gatt_if_, leAudioDevice->address_, + leAudioDevice->audio_avail_hdls_.val_hdl); + if (leAudioDevice->audio_supp_cont_hdls_.val_hdl != 0) + BTA_GATTC_RegisterForNotifications( + gatt_if_, leAudioDevice->address_, + leAudioDevice->audio_supp_cont_hdls_.val_hdl); + if (leAudioDevice->ctp_hdls_.val_hdl != 0) + BTA_GATTC_RegisterForNotifications(gatt_if_, leAudioDevice->address_, + leAudioDevice->ctp_hdls_.val_hdl); + + for (struct ase& ase : leAudioDevice->ases_) + BTA_GATTC_RegisterForNotifications(gatt_if_, leAudioDevice->address_, + ase.hdls.val_hdl); + } + + void OnEncryptionComplete(const RawAddress& address, uint8_t status) { + LOG(INFO) << __func__ << " " << address << "status: " << int{status}; + + LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); + if (leAudioDevice == NULL) { + LOG(WARNING) << "Skipping unknown device" << address; + return; + } + + if (status != BTM_SUCCESS) { + LOG(ERROR) << "Encryption failed" + << " status: " << int{status}; + BTA_GATTC_Close(leAudioDevice->conn_id_); + if (leAudioDevice->connecting_actively_) { + callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, address); + } + return; + } + + if (leAudioDevice->encrypted_) { + LOG(INFO) << __func__ << " link already encrypted, nothing to do"; + return; + } + + leAudioDevice->encrypted_ = true; + + /* If we know services and read is not ongoing, this is reconnection and + * just notify connected */ + if (leAudioDevice->known_service_handles_ && + !leAudioDevice->notify_connected_after_read_) { + connectionReady(leAudioDevice); + return; + } + + BTA_GATTC_ServiceSearchRequest( + leAudioDevice->conn_id_, + &le_audio::uuid::kPublishedAudioCapabilityServiceUuid); + } + + void OnGattDisconnected(uint16_t conn_id, tGATT_IF client_if, + RawAddress address, tGATT_DISCONN_REASON reason) { + LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); + + if (!leAudioDevice) { + LOG(ERROR) << ", skipping unknown leAudioDevice, address: " << address; + return; + } + + LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_); + + groupStateMachine_->ProcessHciNotifAclDisconnected(group, leAudioDevice); + + DeregisterNotifications(leAudioDevice); + + callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, address); + leAudioDevice->conn_id_ = GATT_INVALID_CONN_ID; + leAudioDevice->encrypted_ = false; + + if (leAudioDevice->removing_device_) leAudioDevices_.Remove(address); + } + + bool subscribe_for_indications(uint16_t conn_id, const RawAddress& address, + uint16_t handle, uint16_t ccc_handle, + bool ntf) { + std::vector<uint8_t> value(2); + uint8_t* ptr = value.data(); + + if (BTA_GATTC_RegisterForNotifications(gatt_if_, address, handle) != + GATT_SUCCESS) { + LOG(ERROR) << __func__ << ", cannot register for notification: " + << static_cast<int>(handle); + return false; + } + + UINT16_TO_STREAM(ptr, ntf ? GATT_CHAR_CLIENT_CONFIG_NOTIFICATION + : GATT_CHAR_CLIENT_CONFIG_INDICTION); + + BtaGattQueue::WriteDescriptor( + conn_id, ccc_handle, std::move(value), GATT_WRITE, + [](uint16_t conn_id, tGATT_STATUS status, uint16_t handle, void* data) { + if (instance) instance->OnGattWriteCcc(conn_id, status, handle, data); + }, + nullptr); + return true; + } + + /* Find the handle for the client characteristics configuration of a given + * characteristics. + */ + uint16_t find_ccc_handle(const gatt::Characteristic& charac) { + auto iter = std::find_if( + charac.descriptors.begin(), charac.descriptors.end(), + [](const auto& desc) { + return desc.uuid == Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG); + }); + + return iter == charac.descriptors.end() ? 0 : (*iter).handle; + } + + void OnServiceChangeEvent(const RawAddress& address) { + LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); + if (!leAudioDevice) { + DLOG(ERROR) << __func__ + << ", skipping unknown leAudioDevice, address: " << address; + return; + } + + LOG(INFO) << __func__ << ": address=" << address; + leAudioDevice->known_service_handles_ = false; + leAudioDevice->csis_member_ = false; + BtaGattQueue::Clean(leAudioDevice->conn_id_); + DeregisterNotifications(leAudioDevice); + } + + void OnGattServiceDiscoveryDone(const RawAddress& address) { + LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); + if (!leAudioDevice) { + DLOG(ERROR) << __func__ + << ", skipping unknown leAudioDevice, address: " << address; + return; + } + + if (!leAudioDevice->known_service_handles_) + BTA_GATTC_ServiceSearchRequest( + leAudioDevice->conn_id_, + &le_audio::uuid::kPublishedAudioCapabilityServiceUuid); + } + /* This method is called after connection beginning to identify and initialize + * a le audio device. Any missing mandatory attribute will result in reverting + * and cleaning up device. + */ + void OnServiceSearchComplete(uint16_t conn_id, tGATT_STATUS status) { + LeAudioDevice* leAudioDevice = leAudioDevices_.FindByConnId(conn_id); + + if (!leAudioDevice) { + DLOG(ERROR) << __func__ << ", skipping unknown leAudioDevice, conn_id: " + << loghex(conn_id); + return; + } + + LOG(INFO) << __func__ << " test csis_member " + << leAudioDevice->csis_member_; + + if (status != GATT_SUCCESS) { + /* close connection and report service discovery complete with error */ + LOG(ERROR) << "Service discovery failed"; + + DisconnectDevice(leAudioDevice); + return; + } + + const std::list<gatt::Service>* services = BTA_GATTC_GetServices(conn_id); + + const gatt::Service* pac_svc = nullptr; + const gatt::Service* ase_svc = nullptr; + + for (const gatt::Service& tmp : *services) { + if (tmp.uuid == le_audio::uuid::kPublishedAudioCapabilityServiceUuid) { + LOG(INFO) << "Found Audio Capability service, handle: " + << loghex(tmp.handle); + pac_svc = &tmp; + } else if (tmp.uuid == le_audio::uuid::kAudioStreamControlServiceUuid) { + LOG(INFO) << "Found Audio Stream Endpoint service, handle: " + << loghex(tmp.handle); + ase_svc = &tmp; + } else if (tmp.uuid == le_audio::uuid::kCapServiceUuid) { + LOG(INFO) << "Found CAP Service, handle: " << loghex(tmp.handle); + + /* Try to find context for CSIS instances */ + for (auto& included_srvc : tmp.included_services) { + if (included_srvc.uuid == bluetooth::csis::kCsisServiceUuid) { + LOG(INFO) << __func__ << " CSIS included into CAS"; + if (bluetooth::csis::CsisClient::IsCsisClientRunning()) + leAudioDevice->csis_member_ = true; + + break; + } + } + } + } + + if (!pac_svc || !ase_svc) { + LOG(ERROR) << "No mandatory le audio services found"; + + DisconnectDevice(leAudioDevice); + return; + } + + /* Refresh PACs handles */ + leAudioDevice->ClearPACs(); + + for (const gatt::Characteristic& charac : pac_svc->characteristics) { + if (charac.uuid == + le_audio::uuid::kSinkPublishedAudioCapabilityCharacteristicUuid) { + struct hdl_pair hdl_pair; + hdl_pair.val_hdl = charac.value_handle; + hdl_pair.ccc_hdl = find_ccc_handle(charac); + + if (hdl_pair.ccc_hdl == 0) { + LOG(ERROR) << __func__ << ", snk pac char doesn't have ccc"; + + DisconnectDevice(leAudioDevice); + return; + } + + if (!subscribe_for_indications(conn_id, leAudioDevice->address_, + hdl_pair.val_hdl, hdl_pair.ccc_hdl, + true)) { + DisconnectDevice(leAudioDevice); + return; + } + + /* Obtain initial state of sink PACs */ + BtaGattQueue::ReadCharacteristic(conn_id, hdl_pair.val_hdl, + OnGattReadRspStatic, NULL); + + leAudioDevice->snk_pacs_.push_back(std::make_tuple( + hdl_pair, std::vector<struct le_audio::types::acs_ac_record>())); + + LOG(INFO) << "Found Sink PAC characteristic, handle: " + << loghex(charac.value_handle) + << ", ccc handle: " << loghex(hdl_pair.ccc_hdl); + } else if (charac.uuid == + le_audio::uuid:: + kSourcePublishedAudioCapabilityCharacteristicUuid) { + struct hdl_pair hdl_pair; + hdl_pair.val_hdl = charac.value_handle; + hdl_pair.ccc_hdl = find_ccc_handle(charac); + + if (hdl_pair.ccc_hdl == 0) { + LOG(ERROR) << __func__ << ", src pac char doesn't have ccc"; + + DisconnectDevice(leAudioDevice); + return; + } + + if (!subscribe_for_indications(conn_id, leAudioDevice->address_, + hdl_pair.val_hdl, hdl_pair.ccc_hdl, + true)) { + DisconnectDevice(leAudioDevice); + return; + } + + /* Obtain initial state of source PACs */ + BtaGattQueue::ReadCharacteristic(conn_id, hdl_pair.val_hdl, + OnGattReadRspStatic, NULL); + + leAudioDevice->src_pacs_.push_back(std::make_tuple( + hdl_pair, std::vector<struct le_audio::types::acs_ac_record>())); + + LOG(INFO) << "Found Source PAC characteristic, handle: " + << loghex(charac.value_handle) + << ", ccc handle: " << loghex(hdl_pair.ccc_hdl); + } else if (charac.uuid == + le_audio::uuid::kSinkAudioLocationCharacteristicUuid) { + leAudioDevice->snk_audio_locations_hdls_.val_hdl = charac.value_handle; + leAudioDevice->snk_audio_locations_hdls_.ccc_hdl = + find_ccc_handle(charac); + + if (leAudioDevice->snk_audio_locations_hdls_.ccc_hdl == 0) + LOG(INFO) << __func__ + << ", snk audio locations char doesn't have" + "ccc"; + + if (leAudioDevice->snk_audio_locations_hdls_.ccc_hdl != 0 && + !subscribe_for_indications( + conn_id, leAudioDevice->address_, + leAudioDevice->snk_audio_locations_hdls_.val_hdl, + leAudioDevice->snk_audio_locations_hdls_.ccc_hdl, true)) { + DisconnectDevice(leAudioDevice); + return; + } + + /* Obtain initial state of sink audio locations */ + BtaGattQueue::ReadCharacteristic( + conn_id, leAudioDevice->snk_audio_locations_hdls_.val_hdl, + OnGattReadRspStatic, NULL); + + LOG(INFO) << "Found Sink audio locations characteristic, handle: " + << loghex(charac.value_handle) << ", ccc handle: " + << loghex(leAudioDevice->snk_audio_locations_hdls_.ccc_hdl); + } else if (charac.uuid == + le_audio::uuid::kSourceAudioLocationCharacteristicUuid) { + leAudioDevice->src_audio_locations_hdls_.val_hdl = charac.value_handle; + leAudioDevice->src_audio_locations_hdls_.ccc_hdl = + find_ccc_handle(charac); + + if (leAudioDevice->src_audio_locations_hdls_.ccc_hdl == 0) + LOG(INFO) << __func__ + << ", snk audio locations char doesn't have" + "ccc"; + + if (leAudioDevice->src_audio_locations_hdls_.ccc_hdl != 0 && + !subscribe_for_indications( + conn_id, leAudioDevice->address_, + leAudioDevice->src_audio_locations_hdls_.val_hdl, + leAudioDevice->src_audio_locations_hdls_.ccc_hdl, true)) { + DisconnectDevice(leAudioDevice); + return; + } + + /* Obtain initial state of source audio locations */ + BtaGattQueue::ReadCharacteristic( + conn_id, leAudioDevice->src_audio_locations_hdls_.val_hdl, + OnGattReadRspStatic, NULL); + + LOG(INFO) << "Found Source audio locations characteristic, handle: " + << loghex(charac.value_handle) << ", ccc handle: " + << loghex(leAudioDevice->src_audio_locations_hdls_.ccc_hdl); + } else if (charac.uuid == + le_audio::uuid::kAudioContextAvailabilityCharacteristicUuid) { + leAudioDevice->audio_avail_hdls_.val_hdl = charac.value_handle; + leAudioDevice->audio_avail_hdls_.ccc_hdl = find_ccc_handle(charac); + + if (leAudioDevice->audio_avail_hdls_.ccc_hdl == 0) { + LOG(ERROR) << __func__ << ", audio avails char doesn't have ccc"; + + DisconnectDevice(leAudioDevice); + return; + } + + if (!subscribe_for_indications(conn_id, leAudioDevice->address_, + leAudioDevice->audio_avail_hdls_.val_hdl, + leAudioDevice->audio_avail_hdls_.ccc_hdl, + true)) { + DisconnectDevice(leAudioDevice); + return; + } + + /* Obtain initial state */ + BtaGattQueue::ReadCharacteristic( + conn_id, leAudioDevice->audio_avail_hdls_.val_hdl, + OnGattReadRspStatic, NULL); + + LOG(INFO) << "Found Audio Availability Context characteristic, handle: " + << loghex(charac.value_handle) << ", ccc handle: " + << loghex(leAudioDevice->audio_avail_hdls_.ccc_hdl); + } else if (charac.uuid == + le_audio::uuid::kAudioSupportedContextCharacteristicUuid) { + leAudioDevice->audio_supp_cont_hdls_.val_hdl = charac.value_handle; + leAudioDevice->audio_supp_cont_hdls_.ccc_hdl = find_ccc_handle(charac); + + if (leAudioDevice->audio_supp_cont_hdls_.ccc_hdl == 0) + LOG(INFO) << __func__ << ", audio avails char doesn't have ccc"; + + if (leAudioDevice->audio_supp_cont_hdls_.ccc_hdl != 0 && + !subscribe_for_indications( + conn_id, leAudioDevice->address_, + leAudioDevice->audio_supp_cont_hdls_.val_hdl, + leAudioDevice->audio_supp_cont_hdls_.ccc_hdl, true)) { + DisconnectDevice(leAudioDevice); + return; + } + + /* Obtain initial state */ + BtaGattQueue::ReadCharacteristic( + conn_id, leAudioDevice->audio_supp_cont_hdls_.val_hdl, + OnGattReadRspStatic, NULL); + + LOG(INFO) << "Found Audio Supported Context characteristic, handle: " + << loghex(charac.value_handle) << ", ccc handle: " + << loghex(leAudioDevice->audio_supp_cont_hdls_.ccc_hdl); + } + } + + /* Refresh ASE handles */ + leAudioDevice->ases_.clear(); + + for (const gatt::Characteristic& charac : ase_svc->characteristics) { + LOG(INFO) << "Found characteristic, uuid: " << charac.uuid.ToString(); + if (charac.uuid == le_audio::uuid::kSinkAudioStreamEndpointUuid || + charac.uuid == le_audio::uuid::kSourceAudioStreamEndpointUuid) { + uint16_t ccc_handle = find_ccc_handle(charac); + if (ccc_handle == 0) { + LOG(ERROR) << __func__ << ", audio avails char doesn't have ccc"; + + DisconnectDevice(leAudioDevice); + return; + } + + if (!subscribe_for_indications(conn_id, leAudioDevice->address_, + charac.value_handle, ccc_handle, true)) { + DisconnectDevice(leAudioDevice); + return; + } + + int direction = + charac.uuid == le_audio::uuid::kSinkAudioStreamEndpointUuid + ? le_audio::types::kLeAudioDirectionSink + : le_audio::types::kLeAudioDirectionSource; + + leAudioDevice->ases_.emplace_back(charac.value_handle, ccc_handle, + direction); + + LOG(INFO) << "Found ASE characteristic, handle: " + << loghex(charac.value_handle) + << ", ccc handle: " << loghex(ccc_handle) + << ", direction: " << direction; + } else if (charac.uuid == + le_audio::uuid:: + kAudioStreamEndpointControlPointCharacteristicUuid) { + leAudioDevice->ctp_hdls_.val_hdl = charac.value_handle; + leAudioDevice->ctp_hdls_.ccc_hdl = find_ccc_handle(charac); + + if (leAudioDevice->ctp_hdls_.ccc_hdl == 0) { + LOG(ERROR) << __func__ << ", ase ctp doesn't have ccc"; + + DisconnectDevice(leAudioDevice); + return; + } + + if (!subscribe_for_indications(conn_id, leAudioDevice->address_, + leAudioDevice->ctp_hdls_.val_hdl, + leAudioDevice->ctp_hdls_.ccc_hdl, + true)) { + DisconnectDevice(leAudioDevice); + return; + } + + LOG(INFO) << "Found ASE Control Point characteristic, handle: " + << loghex(charac.value_handle) << ", ccc handle: " + << loghex(leAudioDevice->ctp_hdls_.ccc_hdl); + } + } + + leAudioDevice->known_service_handles_ = true; + leAudioDevice->notify_connected_after_read_ = true; + + /* If already known group id */ + if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) { + AseInitialStateReadRequest(leAudioDevice); + return; + } + + /* If device does not belong to any group yet we either add it to the + * group by our selfs now or wait for Csis to do it. In both cases, let's + * check if group is already assigned. + */ + int group_id = DeviceGroups::Get()->GetGroupId( + leAudioDevice->address_, le_audio::uuid::kCapServiceUuid); + if (group_id != bluetooth::groups::kGroupUnknown) { + instance->group_add_node(group_id, leAudioDevice->address_); + return; + } + + /* CSIS will trigger adding to group */ + if (leAudioDevice->csis_member_) { + LOG(INFO) << __func__ << " waiting for CSIS to create group for device " + << leAudioDevice->address_; + return; + } + + /* If there is no Csis just add device by our own */ + DeviceGroups::Get()->AddDevice(leAudioDevice->address_, + le_audio::uuid::kCapServiceUuid); + } + + void OnGattWriteCcc(uint16_t conn_id, tGATT_STATUS status, uint16_t hdl, + void* data) { + LeAudioDevice* leAudioDevice = leAudioDevices_.FindByConnId(conn_id); + std::vector<struct ase>::iterator ase_it; + + if (!leAudioDevice) { + LOG(ERROR) << __func__ << ", unknown conn_id=" << loghex(conn_id); + return; + } + + if (status == GATT_SUCCESS) { + LOG(INFO) << __func__ + << ", successfully registered on ccc: " << loghex(hdl); + return; + } + + LOG(ERROR) << __func__ + << ", Failed to register for indications: " << loghex(hdl) + << ", status: " << loghex((int)(status)); + + ase_it = + std::find_if(leAudioDevice->ases_.begin(), leAudioDevice->ases_.end(), + [&hdl](const struct ase& ase) -> bool { + return ase.hdls.ccc_hdl == hdl; + }); + + if (ase_it == leAudioDevice->ases_.end()) { + LOG(ERROR) << __func__ + << ", unknown ccc handle: " << static_cast<int>(hdl); + return; + } + + BTA_GATTC_DeregisterForNotifications(gatt_if_, leAudioDevice->address_, + ase_it->hdls.val_hdl); + } + + void AttachToStreamingGroupIfNeeded(LeAudioDevice* leAudioDevice) { + if (leAudioDevice->group_id_ != active_group_id_) { + LOG(INFO) << __func__ << " group " << leAudioDevice->group_id_ + << " is not streaming. Nothing to do"; + return; + } + + LOG(INFO) << __func__ << " attaching to group " + << leAudioDevice->group_id_; + + /* Restore configuration */ + LeAudioDeviceGroup* group = aseGroups_.FindById(active_group_id_); + auto* stream_conf = &group->stream_conf; + + if (!stream_conf->valid) { + LOG(ERROR) << __func__ + << " Configuration not valid. (btw not sure we need this " + "flag)"; + return; + } + + le_audio::types::AudioLocations sink_group_audio_locations = 0; + uint8_t sink_num_of_active_ases = 0; + + for (auto [cis_handle, audio_location] : stream_conf->sink_streams) { + sink_group_audio_locations |= audio_location; + sink_num_of_active_ases++; + } + + le_audio::types::AudioLocations source_group_audio_locations = 0; + uint8_t source_num_of_active_ases = 0; + + for (auto [cis_handle, audio_location] : stream_conf->source_streams) { + source_group_audio_locations |= audio_location; + source_num_of_active_ases++; + } + + for (auto& ent : stream_conf->conf->confs) { + if (ent.direction == le_audio::types::kLeAudioDirectionSink) { + /* Sink*/ + if (!leAudioDevice->ConfigureAses(ent, group->GetCurrentContextType(), + &sink_num_of_active_ases, + sink_group_audio_locations, + source_group_audio_locations, true)) { + LOG(INFO) << __func__ << " Could not set sink configuration of " + << stream_conf->conf->name; + return; + } + } else { + /* Source*/ + if (!leAudioDevice->ConfigureAses(ent, group->GetCurrentContextType(), + &source_num_of_active_ases, + sink_group_audio_locations, + source_group_audio_locations, true)) { + LOG(INFO) << __func__ << " Could not set source configuration of " + << stream_conf->conf->name; + return; + } + } + } + + groupStateMachine_->AttachToStream(group, leAudioDevice); + } + + void connectionReady(LeAudioDevice* leAudioDevice) { + callbacks_->OnConnectionState(ConnectionState::CONNECTED, + leAudioDevice->address_); + + if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) { + LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_); + UpdateContextAndLocations(group, leAudioDevice); + AttachToStreamingGroupIfNeeded(leAudioDevice); + } + + if (leAudioDevice->first_connection_) { + btif_storage_set_leaudio_autoconnect(leAudioDevice->address_, true); + leAudioDevice->first_connection_ = false; + } + } + + bool IsAseAcceptingAudioData(struct ase* ase) { + if (ase == nullptr) return false; + if (ase->state != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) return false; + if (ase->data_path_state != AudioStreamDataPathState::DATA_PATH_ESTABLISHED) + return false; + + return true; + } + + void get_left_and_right_stream(const std::vector<uint8_t>& data, + std::vector<int16_t>& chan_left, + std::vector<int16_t>& chan_right, + bool prepare_mono = false) { + uint16_t num_of_frames_per_ch = lc3_encoder->lc3Config.NF; + + chan_left.reserve(num_of_frames_per_ch); + chan_right.reserve(num_of_frames_per_ch); + for (int i = 0; i < num_of_frames_per_ch; i++) { + const uint8_t* sample = data.data() + i * 4; + + int16_t left = (int16_t)((*(sample + 1) << 8) + *sample) >> 1; + + sample += 2; + int16_t right = (int16_t)((*(sample + 1) << 8) + *sample) >> 1; + + if (prepare_mono) { + uint16_t mono_data = (int16_t)(((uint32_t)left + (uint32_t)right) >> 1); + left = mono_data; + right = mono_data; + } + + chan_left.push_back(left); + chan_right.push_back(right); + } + } + + void PrepareAndSendToTwoDevices( + const std::vector<uint8_t>& data, + struct le_audio::stream_configuration* stream_conf) { + uint16_t byte_count = stream_conf->sink_octets_per_codec_frame; + uint16_t left_cis_handle = 0; + uint16_t right_cis_handle = 0; + uint16_t number_of_required_samples_per_channel = lc3_encoder->lc3Config.NF; + + for (auto [cis_handle, audio_location] : stream_conf->sink_streams) { + if (audio_location & le_audio::codec_spec_conf::kLeAudioLocationAnyLeft) + left_cis_handle = cis_handle; + if (audio_location & le_audio::codec_spec_conf::kLeAudioLocationAnyRight) + right_cis_handle = cis_handle; + } + + if (data.size() < 2 /* bytes per sample */ * 2 /* channels */ * + number_of_required_samples_per_channel) { + LOG(ERROR) << __func__ << "Missing samples"; + return; + } + + bool mono = (left_cis_handle == 0) || (right_cis_handle == 0); + std::vector<int16_t> chan_left; + std::vector<int16_t> chan_right; + get_left_and_right_stream(data, chan_left, chan_right, mono); + + std::vector<uint8_t> chan_left_enc(byte_count, 0); + std::vector<uint8_t> chan_right_enc(byte_count, 0); + + uint8_t err = 0; + if (left_cis_handle) + err |= lc3_encoder->run((const int16_t*)chan_left.data(), + chan_left_enc.size(), chan_left_enc.data(), 0); + if (right_cis_handle) + err |= lc3_encoder->run((const int16_t*)chan_right.data(), + chan_right_enc.size(), chan_right_enc.data(), 1); + + if (err != Lc3Encoder::ERROR_FREE) { + LOG(ERROR) << " error while encoding; error code: " + << "\t encoded samples: " << chan_left_enc.size() + << "\t err: " << static_cast<uint8_t>(err); + return; + } + + /* Send data to the controller */ + if (left_cis_handle) + IsoManager::GetInstance()->SendIsoData( + left_cis_handle, chan_left_enc.data(), chan_left_enc.size()); + + if (right_cis_handle) + IsoManager::GetInstance()->SendIsoData( + right_cis_handle, chan_right_enc.data(), chan_right_enc.size()); + } + + void PrepareAndSendToSingleDevice( + const std::vector<uint8_t>& data, + struct le_audio::stream_configuration* stream_conf) { + int num_channels = lc3_encoder->lc3Config.Nc; + uint16_t byte_count = stream_conf->sink_octets_per_codec_frame; + auto cis_handle = stream_conf->sink_streams.front().first; + uint16_t number_of_required_samples_per_channel = lc3_encoder->lc3Config.NF; + + if ((int)data.size() < (2 /* bytes per sample */ * num_channels * + number_of_required_samples_per_channel)) { + LOG(ERROR) << __func__ << "Missing samples"; + return; + } + + std::vector<uint8_t> chan_encoded(num_channels * byte_count, 0); + uint8_t err = 0; + if (num_channels == 1) { + err = lc3_encoder->run((const int16_t*)data.data(), byte_count, + chan_encoded.data(), 0); + + } else { + std::vector<int16_t> chan_left; + std::vector<int16_t> chan_right; + get_left_and_right_stream(data, chan_left, chan_right, false); + + err |= lc3_encoder->run((const int16_t*)chan_left.data(), byte_count, + chan_encoded.data(), 0); + + err |= lc3_encoder->run((const int16_t*)chan_right.data(), byte_count, + chan_encoded.data() + byte_count, 1); + } + + if (err != Lc3Encoder::ERROR_FREE) { + LOG(ERROR) << " error while encoding; error code: " + << "\t err: " << static_cast<uint8_t>(err); + return; + } + + /* Send data to the controller */ + IsoManager::GetInstance()->SendIsoData(cis_handle, chan_encoded.data(), + chan_encoded.size()); + } + + struct le_audio::stream_configuration* GetStreamSinkConfiguration( + LeAudioDeviceGroup* group) { + struct le_audio::stream_configuration* stream_conf = &group->stream_conf; + int num_of_devices = 0; + int num_of_channels = 0; + uint32_t sample_freq_hz = 0; + uint32_t frame_duration_us = 0; + uint16_t octets_per_frame = 0; + + LOG(INFO) << __func__ << " group_id: " << group->group_id_; + + /* This contains pair of cis handle and audio location */ + std::vector<std::pair<uint16_t, uint32_t>> streams; + + for (auto* device = group->GetFirstActiveDevice(); device != nullptr; + device = group->GetNextActiveDevice(device)) { + auto* ase = device->GetFirstActiveAseByDirection( + le_audio::types::kLeAudioDirectionSink); + + if (ase) { + LOG(INFO) << __func__ << "device: " << device->address_; + num_of_devices++; + } + + for (; ase != nullptr; + ase = device->GetNextActiveAseWithSameDirection(ase)) { + streams.emplace_back(std::make_pair( + ase->cis_conn_hdl, ase->codec_config.audio_channel_allocation)); + num_of_channels += ase->codec_config.channel_count; + if (sample_freq_hz == 0) { + sample_freq_hz = ase->codec_config.GetSamplingFrequencyHz(); + } else { + LOG_ASSERT(sample_freq_hz == + ase->codec_config.GetSamplingFrequencyHz()) + << __func__ << " sample freq mismatch: " << +sample_freq_hz + << " != " << ase->codec_config.GetSamplingFrequencyHz(); + } + + if (frame_duration_us == 0) { + frame_duration_us = ase->codec_config.GetFrameDurationUs(); + } else { + LOG_ASSERT(frame_duration_us == + ase->codec_config.GetFrameDurationUs()) + << __func__ << " frame duration mismatch: " << +frame_duration_us + << " != " << ase->codec_config.GetFrameDurationUs(); + } + + if (octets_per_frame == 0) { + octets_per_frame = ase->codec_config.octets_per_codec_frame; + } else { + LOG_ASSERT(octets_per_frame == + ase->codec_config.octets_per_codec_frame) + << __func__ << " octets per frame mismatch: " << +octets_per_frame + << " != " << ase->codec_config.octets_per_codec_frame; + } + + LOG(INFO) << __func__ << " Added CIS: " << +ase->cis_conn_hdl + << " to stream. Allocation: " + << +ase->codec_config.audio_channel_allocation + << " sample_freq: " << +sample_freq_hz + << " frame_duration: " << +frame_duration_us + << " octects per frame: " << +octets_per_frame; + } + } + + if (streams.empty()) return nullptr; + + stream_conf->sink_streams = std::move(streams); + stream_conf->sink_num_of_devices = num_of_devices; + stream_conf->sink_num_of_channels = num_of_channels; + stream_conf->sink_sample_frequency_hz = sample_freq_hz; + stream_conf->sink_frame_duration_us = frame_duration_us; + stream_conf->sink_octets_per_codec_frame = octets_per_frame; + stream_conf->valid = true; + stream_conf->conf = group->GetActiveConfiguration(); + + LOG(INFO) << __func__ << " configuration: " << stream_conf->conf->name; + + return stream_conf; + } + + void OnAudioDataReady(const std::vector<uint8_t>& data) { + if (active_group_id_ == bluetooth::groups::kGroupUnknown || + !audio_source_ready_to_send) + return; + + LeAudioDeviceGroup* group = aseGroups_.FindById(active_group_id_); + if (!group) { + LOG(ERROR) << __func__ << "There is no streaming group available"; + return; + } + + auto stream_conf = group->stream_conf; + if (!stream_conf.valid || (stream_conf.sink_num_of_devices > 2)) { + LOG(ERROR) << __func__ << " Stream configufation is not valid."; + return; + } + + if (stream_conf.sink_num_of_devices == 2) { + PrepareAndSendToTwoDevices(data, &stream_conf); + } else { + PrepareAndSendToSingleDevice(data, &stream_conf); + } + } + + void SendAudioData(uint8_t* data, uint16_t size) { + /* Get only one channel for MONO microphone */ + /* Gather data for channel */ + uint16_t required_for_channel_byte_count = + lc3_decoder->lc3Config.getByteCountFromBitrate(32000); + size_t required_byte_count = current_sink_codec_config.num_channels * + required_for_channel_byte_count; + + if (required_byte_count != size) { + LOG(ERROR) << "Insufficient data for decoding and send, required: " + << int(required_byte_count) << ", received: " << int(size); + return; + } + + uint8_t BEC_detect = 0; + std::vector<int16_t> pcm_data_decoded(lc3_decoder->lc3Config.NF, 0); + auto err = lc3_decoder->run(data, required_for_channel_byte_count, 0, + pcm_data_decoded.data(), + pcm_data_decoded.size(), BEC_detect); + + /* TODO: How handle failing decoding ? */ + if (err != Lc3Decoder::ERROR_FREE) { + LOG(ERROR) << " error while decoding error code: " + << static_cast<int>(err); + return; + } + + uint16_t to_write = sizeof(int16_t) * pcm_data_decoded.size(); + uint16_t written = LeAudioClientAudioSink::SendData( + (uint8_t*)pcm_data_decoded.data(), to_write); + + /* TODO: What to do if not all data sinked ? */ + if (written != to_write) LOG(ERROR) << __func__ << ", not all data sinked"; + + LOG(INFO) << __func__; + } + + static inline Lc3Config::FrameDuration Lc3ConfigFrameDuration( + uint32_t frame_duration_us) { + if (frame_duration_us == LeAudioCodecConfiguration::kInterval7500Us) + return Lc3Config::FrameDuration::d7p5ms; + else + return Lc3Config::FrameDuration::d10ms; + } + + bool StartSendingAudio(int group_id) { + LOG(INFO) << __func__; + + LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); + LeAudioDevice* device = group->GetFirstActiveDevice(); + LOG_ASSERT(device) << __func__ + << " Shouldn't be called without an active device."; + + /* Assume 2 ases max just for now. */ + auto* stream_conf = GetStreamSinkConfiguration(group); + if (stream_conf == nullptr) { + LOG(ERROR) << __func__ << " could not get sink configuration"; + return false; + } + + if (lc3_encoder) { + LOG(WARNING) + << " The encoder instance should have been already released."; + delete lc3_encoder; + lc3_encoder = nullptr; + } + + /* One or multiple audio channels encoder */ + lc3_encoder = new Lc3Encoder(Lc3Config( + current_source_codec_config.sample_rate, + Lc3ConfigFrameDuration(current_source_codec_config.data_interval_us), + current_source_codec_config.num_channels)); + + uint16_t remote_delay_ms = + group->GetRemoteDelay(le_audio::types::kLeAudioDirectionSink); + + LeAudioClientAudioSource::UpdateRemoteDelay(remote_delay_ms); + LeAudioClientAudioSource::ConfirmStreamingRequest(); + + return true; + } + + struct le_audio::stream_configuration* GetStreamSourceConfiguration( + LeAudioDeviceGroup* group) { + LeAudioDevice* device = group->GetFirstActiveDevice(); + LOG_ASSERT(device) << __func__ + << " Shouldn't be called without an active device."; + + ase* ase = device->GetFirstActiveAseByDirection( + le_audio::types::kLeAudioDirectionSource); + + if (!ase) return nullptr; + + /* For now we support one microphone only*/ + + auto* stream_conf = &group->stream_conf; + std::vector<std::pair<uint16_t, uint32_t>> streams; + + stream_conf->source_streams.emplace_back(std::make_pair( + ase->cis_conn_hdl, ase->codec_config.audio_channel_allocation)); + + stream_conf->source_num_of_devices = 1; + stream_conf->source_num_of_channels = 1; + stream_conf->source_sample_frequency_hz = + ase->codec_config.GetSamplingFrequencyHz(); + stream_conf->source_frame_duration_us = + ase->codec_config.GetFrameDurationUs(); + stream_conf->source_octets_per_codec_frame = + ase->codec_config.octets_per_codec_frame; + stream_conf->valid = true; + + stream_conf->conf = group->GetActiveConfiguration(); + + LOG(INFO) << __func__ << " Added CIS: " << +ase->cis_conn_hdl + << " to stream. Allocation: " + << +ase->codec_config.audio_channel_allocation + << " sample_freq: " << +stream_conf->source_sample_frequency_hz + << " frame_duration: " << +stream_conf->source_frame_duration_us + << " octects per frame: " + << +stream_conf->source_octets_per_codec_frame; + + return stream_conf; + } + + void StartReceivingAudio(int group_id) { + LOG(INFO) << __func__; + + LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); + + auto* stream_conf = GetStreamSourceConfiguration(group); + if (!stream_conf) { + LOG(WARNING) << " Could not get source configuration for group " + << active_group_id_ << " probably microphone not configured"; + return; + } + + Lc3Config lc3Config( + current_sink_codec_config.sample_rate, + Lc3ConfigFrameDuration(current_sink_codec_config.data_interval_us), 1); + + lc3_decoder = new Lc3Decoder(lc3Config); + + uint16_t remote_delay_ms = + group->GetRemoteDelay(le_audio::types::kLeAudioDirectionSource); + + LeAudioClientAudioSink::UpdateRemoteDelay(remote_delay_ms); + LeAudioClientAudioSink::ConfirmStreamingRequest(); + } + + void SuspendAudio(void) { + audio_sink_ready_to_receive = false; + audio_source_ready_to_send = false; + + if (lc3_encoder) { + delete lc3_encoder; + lc3_encoder = nullptr; + } + + if (lc3_decoder) { + LOG(INFO) << __func__ << " stopping sink"; + + delete lc3_decoder; + lc3_decoder = nullptr; + } + } + + void StopAudio(void) { + SuspendAudio(); + ClientAudioIntefraceRelease(); + } + + void Dump(int fd) { + dprintf(fd, " Active group: %d\n", active_group_id_); + dprintf(fd, " LE Audio Groups:\n"); + aseGroups_.Dump(fd); + dprintf(fd, " Not grouped devices:\n"); + leAudioDevices_.Dump(fd, bluetooth::groups::kGroupUnknown); + } + + void Cleanup(void) { + leAudioDevices_.Cleanup(); + aseGroups_.Cleanup(); + StopAudio(); + if (gatt_if_) BTA_GATTC_AppDeregister(gatt_if_); + } + + void UpdateCurrentHalSessions(int group_id, LeAudioContextType context_type) { + if (group_id == bluetooth::groups::kGroupUnknown) { + LOG(WARNING) << ", cannot start straming if no active group set"; + return; + } + + auto group = aseGroups_.FindById(group_id); + if (!group) { + LOG(ERROR) << __func__ + << ", Invalid group: " << static_cast<int>(group_id); + return; + } + + std::optional<LeAudioCodecConfiguration> source_configuration = + group->GetCodecConfigurationByDirection( + context_type, le_audio::types::kLeAudioDirectionSink); + std::optional<LeAudioCodecConfiguration> sink_configuration = + group->GetCodecConfigurationByDirection( + context_type, le_audio::types::kLeAudioDirectionSource); + + if (source_configuration) { + /* Stream configuration differs from previous one */ + if (!current_source_codec_config.IsInvalid() && + (*source_configuration != current_source_codec_config)) + LeAudioClientAudioSource::Stop(); + + current_source_codec_config = *source_configuration; + + LeAudioClientAudioSource::Start(current_source_codec_config, + audioSinkReceiver); + } else { + if (!current_source_codec_config.IsInvalid()) { + LeAudioClientAudioSource::Stop(); + current_source_codec_config = {0, 0, 0, 0}; + } + + LOG(INFO) << __func__ + << ", group does not supports source direction for" + " context: " + << static_cast<int>(context_type); + } + + if (sink_configuration) { + /* Stream configuration differs from previous one */ + if (!current_sink_codec_config.IsInvalid() && + (*sink_configuration != current_sink_codec_config)) + LeAudioClientAudioSink::Stop(); + + current_sink_codec_config = *sink_configuration; + + LeAudioClientAudioSink::Start(current_sink_codec_config, + audioSourceReceiver); + } else { + if (!current_sink_codec_config.IsInvalid()) { + LeAudioClientAudioSink::Stop(); + current_sink_codec_config = {0, 0, 0, 0}; + } + + LOG(INFO) << __func__ + << ", group does not supports sink direction for" + " context: " + << static_cast<int>(context_type); + } + } + + void OnAudioResume() { + if (active_group_id_ == bluetooth::groups::kGroupUnknown) { + LOG(WARNING) << ", cannot start straming if no active group set"; + return; + } + + auto group = aseGroups_.FindById(active_group_id_); + if (!group) { + LOG(ERROR) << __func__ + << ", Invalid group: " << static_cast<int>(active_group_id_); + return; + } + + std::optional<LeAudioCodecConfiguration> source_configuration = + group->GetCodecConfigurationByDirection( + current_context_type_, le_audio::types::kLeAudioDirectionSink); + std::optional<LeAudioCodecConfiguration> sink_configuration = + group->GetCodecConfigurationByDirection( + current_context_type_, le_audio::types::kLeAudioDirectionSource); + + /* Check if Bluetooth audio HAL session requires reconfiguration */ + bool sessions_requires_update = + (((source_configuration && + (*source_configuration != current_source_codec_config)) || + (!source_configuration && + !current_source_codec_config.IsInvalid()))) || + ((sink_configuration && + (*sink_configuration != current_sink_codec_config)) || + (!sink_configuration && !current_sink_codec_config.IsInvalid())); + if (sessions_requires_update) { + CancelStreamingRequest(); + do_in_main_thread(FROM_HERE, + base::Bind(&LeAudioClientImpl::UpdateCurrentHalSessions, + base::Unretained(instance), active_group_id_, + current_context_type_)); + return; + } + + /* TODO check if group already started streaming */ + + GroupStream(active_group_id_, static_cast<uint16_t>(current_context_type_)); + } + + void OnAudioSuspend() { + if (active_group_id_ == bluetooth::groups::kGroupUnknown) { + LOG(WARNING) << ", there is no longer active group"; + return; + } + + GroupStop(active_group_id_); + } + + void OnAudioSinkSuspend() { + LOG(INFO) << __func__; + + if (!audio_source_ready_to_send) return; + + audio_source_ready_to_send = false; + + /* Last suspends group - triggers group stop */ + if (!audio_sink_ready_to_receive && !audio_source_ready_to_send) + OnAudioSuspend(); + } + + void OnAudioSinkResume() { + LOG(INFO) << __func__; + + auto group = aseGroups_.FindById(active_group_id_); + if (!group) { + LOG(ERROR) << __func__ + << ", Invalid group: " << static_cast<int>(active_group_id_); + return; + } + + /* Check if the device resume is expected */ + if (!group->GetCodecConfigurationByDirection( + current_context_type_, le_audio::types::kLeAudioDirectionSink)) { + LOG(ERROR) << __func__ << ", invalid resume request for context type: " + << loghex(static_cast<int>(current_context_type_)); + LeAudioClientAudioSource::CancelStreamingRequest(); + return; + } + + /* First resume request from sink/source triggers group start */ + if (!audio_sink_ready_to_receive && !audio_source_ready_to_send) { + audio_source_ready_to_send = true; + OnAudioResume(); + + return; + } + + if (audio_sink_ready_to_receive) { + audio_source_ready_to_send = true; + /* If signalling part is completed trigger start reveivin audio here, + * otherwise it'll be called on group streaming state callback + */ + if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) + StartSendingAudio(active_group_id_); + } + } + + void OnAudioSourceSuspend() { + LOG(INFO) << __func__; + + if (!audio_sink_ready_to_receive) return; + + audio_sink_ready_to_receive = false; + + /* Last suspends group - triggers group stop */ + if (!audio_sink_ready_to_receive && !audio_source_ready_to_send) + OnAudioSuspend(); + } + + void OnAudioSourceResume() { + LOG(INFO) << __func__; + + auto group = aseGroups_.FindById(active_group_id_); + if (!group) { + LOG(ERROR) << __func__ + << ", Invalid group: " << static_cast<int>(active_group_id_); + return; + } + + /* Check if the device resume is expected */ + if (!group->GetCodecConfigurationByDirection( + current_context_type_, le_audio::types::kLeAudioDirectionSource)) { + LOG(ERROR) << __func__ << ", invalid resume request for context type: " + << loghex(static_cast<int>(current_context_type_)); + LeAudioClientAudioSink::CancelStreamingRequest(); + return; + } + + /* First resume request from sink/source triggers group start */ + if (!audio_sink_ready_to_receive && !audio_source_ready_to_send) { + OnAudioResume(); + audio_sink_ready_to_receive = true; + + return; + } + + if (audio_source_ready_to_send) { + audio_sink_ready_to_receive = true; + /* If signalling part is completed trigger start reveivin audio here, + * otherwise it'll be called on group streaming state callback + */ + if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) + StartReceivingAudio(active_group_id_); + } + } + + void OnAudioMetadataUpdate(audio_usage_t usage, + audio_content_type_t content_type) { + LOG(INFO) << __func__ << ", content_type = " + << audio_content_type_to_string(content_type) + << ", usage = " << audio_usage_to_string(usage); + + LeAudioContextType new_context = LeAudioContextType::RFU; + + switch (content_type) { + case AUDIO_CONTENT_TYPE_SPEECH: + new_context = LeAudioContextType::CONVERSATIONAL; + break; + case AUDIO_CONTENT_TYPE_MUSIC: + case AUDIO_CONTENT_TYPE_MOVIE: + case AUDIO_CONTENT_TYPE_SONIFICATION: + new_context = LeAudioContextType::MEDIA; + break; + default: + break; + } + + /* Context is not clear, consider also usage of stream */ + if (new_context == LeAudioContextType::RFU) { + switch (usage) { + case AUDIO_USAGE_VOICE_COMMUNICATION: + new_context = LeAudioContextType::CONVERSATIONAL; + break; + case AUDIO_USAGE_GAME: + new_context = LeAudioContextType::GAME; + break; + case AUDIO_USAGE_NOTIFICATION: + new_context = LeAudioContextType::NOTIFICATIONS; + break; + case AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE: + new_context = LeAudioContextType::RINGTONE; + break; + case AUDIO_USAGE_ALARM: + new_context = LeAudioContextType::ALERTS; + break; + case AUDIO_USAGE_EMERGENCY: + new_context = LeAudioContextType::EMERGENCYALARM; + break; + default: + new_context = LeAudioContextType::MEDIA; + break; + } + } + + auto group = aseGroups_.FindById(active_group_id_); + if (!group) { + LOG(ERROR) << __func__ + << ", Invalid group: " << static_cast<int>(active_group_id_); + return; + } + + if ((new_context != current_context_type_) && + (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)) { + if (active_group_id_ == bluetooth::groups::kGroupUnknown) { + LOG(WARNING) << ", cannot start straming if no active group set"; + return; + } + + std::optional<LeAudioCodecConfiguration> source_configuration = + group->GetCodecConfigurationByDirection( + current_context_type_, le_audio::types::kLeAudioDirectionSink); + std::optional<LeAudioCodecConfiguration> sink_configuration = + group->GetCodecConfigurationByDirection( + current_context_type_, le_audio::types::kLeAudioDirectionSource); + + if (source_configuration && + (*source_configuration != current_source_codec_config)) + return; + if (sink_configuration && + (*sink_configuration != current_sink_codec_config)) + return; + + /* Configuration is the same for new context, just will do update + * metadata of stream + */ + GroupStream(active_group_id_, static_cast<uint16_t>(new_context)); + } + + current_context_type_ = new_context; + } + + static void OnGattReadRspStatic(uint16_t conn_id, tGATT_STATUS status, + uint16_t hdl, uint16_t len, uint8_t* value, + void* data) { + if (!instance) return; + + if (status == GATT_SUCCESS) { + instance->LeAudioCharValueHandle(conn_id, hdl, len, + static_cast<uint8_t*>(value)); + } + + /* We use data to keep notify connected flag. */ + if (data && !!PTR_TO_INT(data)) { + LeAudioDevice* leAudioDevice = + instance->leAudioDevices_.FindByConnId(conn_id); + leAudioDevice->notify_connected_after_read_ = false; + instance->connectionReady(leAudioDevice); + } + } + + void IsoCigEventsCb(uint16_t event_type, void* data) { + switch (event_type) { + case bluetooth::hci::iso_manager::kIsoEventCigOnCreateCmpl: { + auto* evt = static_cast<cig_create_cmpl_evt*>(data); + LeAudioDeviceGroup* group = aseGroups_.FindById(evt->cig_id); + groupStateMachine_->ProcessHciNotifOnCigCreate( + group, evt->status, evt->cig_id, evt->conn_handles); + } break; + case bluetooth::hci::iso_manager::kIsoEventCigOnRemoveCmpl: { + auto* evt = static_cast<cig_remove_cmpl_evt*>(data); + LeAudioDeviceGroup* group = aseGroups_.FindById(evt->cig_id); + groupStateMachine_->ProcessHciNotifOnCigRemove(evt->status, group); + } break; + default: + LOG(ERROR) << __func__ << " Invalid event " << int{event_type}; + } + } + + void IsoCisEventsCb(uint16_t event_type, void* data) { + switch (event_type) { + case bluetooth::hci::iso_manager::kIsoEventCisDataAvailable: { + auto* event = + static_cast<bluetooth::hci::iso_manager::cis_data_evt*>(data); + + if (!audio_sink_ready_to_receive) break; + + SendAudioData(event->p_msg->data + event->p_msg->offset, + event->p_msg->len - event->p_msg->offset); + } break; + case bluetooth::hci::iso_manager::kIsoEventCisEstablishCmpl: { + auto* event = + static_cast<bluetooth::hci::iso_manager::cis_establish_cmpl_evt*>( + data); + + LeAudioDevice* leAudioDevice = + leAudioDevices_.FindByCisConnHdl(event->cis_conn_hdl); + if (!leAudioDevice) { + LOG(ERROR) << __func__ << ", no bonded Le Audio Device with CIS: " + << +event->cis_conn_hdl; + break; + } + LeAudioDeviceGroup* group = + aseGroups_.FindById(leAudioDevice->group_id_); + + if (event->max_pdu_mtos > 0) + group->SetTransportLatency(le_audio::types::kLeAudioDirectionSink, + event->trans_lat_mtos); + if (event->max_pdu_stom > 0) + group->SetTransportLatency(le_audio::types::kLeAudioDirectionSource, + event->trans_lat_stom); + + groupStateMachine_->ProcessHciNotifCisEstablished(group, leAudioDevice, + event); + } break; + case bluetooth::hci::iso_manager::kIsoEventCisDisconnected: { + auto* event = + static_cast<bluetooth::hci::iso_manager::cis_disconnected_evt*>( + data); + + LeAudioDevice* leAudioDevice = + leAudioDevices_.FindByCisConnHdl(event->cis_conn_hdl); + if (!leAudioDevice) { + LOG(ERROR) << __func__ << ", no bonded Le Audio Device with CIS: " + << +event->cis_conn_hdl; + break; + } + LeAudioDeviceGroup* group = + aseGroups_.FindById(leAudioDevice->group_id_); + + groupStateMachine_->ProcessHciNotifCisDisconnected(group, leAudioDevice, + event); + } break; + default: + LOG(INFO) << ", Not handeled ISO event"; + break; + } + } + + void IsoSetupIsoDataPathCb(uint8_t status, uint16_t conn_handle, + uint8_t /* cig_id */) { + LeAudioDevice* leAudioDevice = + leAudioDevices_.FindByCisConnHdl(conn_handle); + LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_); + + instance->groupStateMachine_->ProcessHciNotifSetupIsoDataPath( + group, leAudioDevice, status, conn_handle); + } + + void IsoRemoveIsoDataPathCb(uint8_t status, uint16_t conn_handle, + uint8_t /* cig_id */) { + LeAudioDevice* leAudioDevice = + leAudioDevices_.FindByCisConnHdl(conn_handle); + LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_); + + instance->groupStateMachine_->ProcessHciNotifRemoveIsoDataPath( + group, leAudioDevice, status, conn_handle); + } + + void IsoLinkQualityReadCb( + uint8_t conn_handle, uint8_t cig_id, uint32_t txUnackedPackets, + uint32_t txFlushedPackets, uint32_t txLastSubeventPackets, + uint32_t retransmittedPackets, uint32_t crcErrorPackets, + uint32_t rxUnreceivedPackets, uint32_t duplicatePackets) { + LeAudioDevice* leAudioDevice = + leAudioDevices_.FindByCisConnHdl(conn_handle); + if (!leAudioDevice) { + LOG(WARNING) << __func__ << ", device under connection handle: " + << loghex(conn_handle) + << ", has been disconnecected in meantime"; + return; + } + LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_); + + instance->groupStateMachine_->ProcessHciNotifIsoLinkQualityRead( + group, leAudioDevice, conn_handle, txUnackedPackets, txFlushedPackets, + txLastSubeventPackets, retransmittedPackets, crcErrorPackets, + rxUnreceivedPackets, duplicatePackets); + } + + void StatusReportCb(int group_id, GroupStreamStatus status) { + switch (status) { + case GroupStreamStatus::STREAMING: + stream_request_started_ = false; + if (audio_source_ready_to_send) StartSendingAudio(active_group_id_); + if (audio_sink_ready_to_receive) StartReceivingAudio(active_group_id_); + break; + case GroupStreamStatus::SUSPENDED: + /** Stop Audio but don't release all the Audio resources */ + SuspendAudio(); + break; + case GroupStreamStatus::IDLE: + if (stream_request_started_) { + stream_request_started_ = false; + CancelStreamingRequest(); + } + break; + default: + break; + } + } + + private: + tGATT_IF gatt_if_; + bluetooth::le_audio::LeAudioClientCallbacks* callbacks_; + LeAudioDevices leAudioDevices_; + LeAudioDeviceGroups aseGroups_; + LeAudioGroupStateMachine* groupStateMachine_; + int active_group_id_; + bool stream_request_started_; + LeAudioContextType current_context_type_; + + bool audio_sink_ready_to_receive; + bool audio_source_ready_to_send; + + LeAudioCodecConfiguration current_source_codec_config; + LeAudioCodecConfiguration current_sink_codec_config; + Lc3Encoder* lc3_encoder; + Lc3Decoder* lc3_decoder; + std::vector<uint8_t> encoded_data; + const void* audio_source_instance_; + const void* audio_sink_instance_; + + void ClientAudioIntefraceRelease() { + if (audio_source_instance_) { + LeAudioClientAudioSource::Stop(); + LeAudioClientAudioSource::Release(audio_source_instance_); + audio_source_instance_ = nullptr; + } + + if (audio_sink_instance_) { + LeAudioClientAudioSink::Stop(); + LeAudioClientAudioSink::Release(audio_sink_instance_); + audio_sink_instance_ = nullptr; + } + } +}; + +/* This is a generic callback method for gatt client which handles every client + * application events. + */ +void le_audio_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) { + if (!p_data || !instance) return; + + DLOG(INFO) << __func__ << " event = " << +event; + + switch (event) { + case BTA_GATTC_DEREG_EVT: + break; + + case BTA_GATTC_NOTIF_EVT: + instance->LeAudioCharValueHandle( + p_data->notify.conn_id, p_data->notify.handle, p_data->notify.len, + static_cast<uint8_t*>(p_data->notify.value)); + + if (!p_data->notify.is_notify) + BTA_GATTC_SendIndConfirm(p_data->notify.conn_id, p_data->notify.handle); + + break; + + case BTA_GATTC_OPEN_EVT: + instance->OnGattConnected(p_data->open.status, p_data->open.conn_id, + p_data->open.client_if, p_data->open.remote_bda, + p_data->open.transport, p_data->open.mtu); + break; + + case BTA_GATTC_ENC_CMPL_CB_EVT: + instance->OnEncryptionComplete(p_data->enc_cmpl.remote_bda, BTM_SUCCESS); + break; + + case BTA_GATTC_CLOSE_EVT: + instance->OnGattDisconnected( + p_data->close.conn_id, p_data->close.client_if, + p_data->close.remote_bda, p_data->close.reason); + break; + + case BTA_GATTC_SEARCH_CMPL_EVT: + instance->OnServiceSearchComplete(p_data->search_cmpl.conn_id, + p_data->search_cmpl.status); + break; + + case BTA_GATTC_SRVC_DISC_DONE_EVT: + instance->OnGattServiceDiscoveryDone(p_data->service_changed.remote_bda); + break; + + case BTA_GATTC_SRVC_CHG_EVT: + instance->OnServiceChangeEvent(p_data->remote_bda); + break; + case BTA_GATTC_CFG_MTU_EVT: + break; + + default: + break; + } +} + +class LeAudioStateMachineHciCallbacksImpl : public CigCallbacks { + public: + void OnCigEvent(uint8_t event, void* data) override { + if (instance) instance->IsoCigEventsCb(event, data); + } + + void OnCisEvent(uint8_t event, void* data) override { + if (instance) instance->IsoCisEventsCb(event, data); + } + + void OnSetupIsoDataPath(uint8_t status, uint16_t conn_handle, + uint8_t cig_id) override { + if (instance) instance->IsoSetupIsoDataPathCb(status, conn_handle, cig_id); + } + + void OnRemoveIsoDataPath(uint8_t status, uint16_t conn_handle, + uint8_t cig_id) override { + if (instance) instance->IsoRemoveIsoDataPathCb(status, conn_handle, cig_id); + } + + void OnIsoLinkQualityRead( + uint8_t conn_handle, uint8_t cig_id, uint32_t txUnackedPackets, + uint32_t txFlushedPackets, uint32_t txLastSubeventPackets, + uint32_t retransmittedPackets, uint32_t crcErrorPackets, + uint32_t rxUnreceivedPackets, uint32_t duplicatePackets) { + if (instance) + instance->IsoLinkQualityReadCb(conn_handle, cig_id, txUnackedPackets, + txFlushedPackets, txLastSubeventPackets, + retransmittedPackets, crcErrorPackets, + rxUnreceivedPackets, duplicatePackets); + } +}; + +LeAudioStateMachineHciCallbacksImpl stateMachineHciCallbacksImpl; + +class CallbacksImpl : public LeAudioGroupStateMachine::Callbacks { + public: + void StatusReportCb(int group_id, GroupStreamStatus status) override { + if (instance) instance->StatusReportCb(group_id, status); + } + + void OnStateTransitionTimeout(int group_id) override { + if (instance) instance->OnLeAudioDeviceSetStateTimeout(group_id); + } +}; + +CallbacksImpl stateMachineCallbacksImpl; + +class LeAudioClientAudioSinkReceiverImpl + : public LeAudioClientAudioSinkReceiver { + public: + void OnAudioDataReady(const std::vector<uint8_t>& data) override { + if (instance) instance->OnAudioDataReady(data); + } + void OnAudioSuspend(std::promise<void> do_suspend_promise) override { + if (instance) instance->OnAudioSinkSuspend(); + do_suspend_promise.set_value(); + } + + void OnAudioResume(std::promise<void> do_resume_promise) override { + if (instance) instance->OnAudioSinkResume(); + do_resume_promise.set_value(); + } + + void OnAudioMetadataUpdate(std::promise<void> do_metadata_update_promise, + audio_usage_t usage, + audio_content_type_t content_type) override { + if (instance) instance->OnAudioMetadataUpdate(usage, content_type); + do_metadata_update_promise.set_value(); + } +}; + +class LeAudioClientAudioSourceReceiverImpl + : public LeAudioClientAudioSourceReceiver { + public: + void OnAudioSuspend(std::promise<void> do_suspend_promise) override { + if (instance) instance->OnAudioSourceSuspend(); + do_suspend_promise.set_value(); + } + void OnAudioResume(std::promise<void> do_resume_promise) override { + if (instance) instance->OnAudioSourceResume(); + do_resume_promise.set_value(); + } +}; + +LeAudioClientAudioSinkReceiverImpl audioSinkReceiverImpl; +LeAudioClientAudioSourceReceiverImpl audioSourceReceiverImpl; + +class DeviceGroupsCallbacksImpl : public DeviceGroupsCallbacks { + public: + void OnGroupAdded(const RawAddress& address, const bluetooth::Uuid& uuid, + int group_id) override { + if (instance) instance->OnGroupAddedCb(address, uuid, group_id); + } + void OnGroupMemberAdded(const RawAddress& address, int group_id) override { + if (instance) instance->OnGroupMemberAddedCb(address, group_id); + } + void OnGroupMemberRemoved(const RawAddress& address, int group_id) override { + if (instance) instance->OnGroupMemberRemovedCb(address, group_id); + } + void OnGroupRemoved(const bluetooth::Uuid& uuid, int group_id) { + /* to implement if needed */ + } + void OnGroupAddFromStorage(const RawAddress& address, + const bluetooth::Uuid& uuid, int group_id) { + /* to implement if needed */ + } +}; + +class DeviceGroupsCallbacksImpl; +DeviceGroupsCallbacksImpl deviceGroupsCallbacksImpl; + +} // namespace + +void LeAudioClient::AddFromStorage(const RawAddress& addr, bool autoconnect) { + if (!instance) { + LOG(ERROR) << "Not initialized yet"; + return; + } + + instance->AddFromStorage(addr, autoconnect); +} + +bool LeAudioClient::IsLeAudioClientRunning(void) { return instance != nullptr; } + +LeAudioClient* LeAudioClient::Get() { + CHECK(instance); + return instance; +} + +/* Initializer of main le audio implementation class and its instance */ +void LeAudioClient::Initialize( + bluetooth::le_audio::LeAudioClientCallbacks* callbacks_, + base::Closure initCb, base::Callback<bool()> hal_2_1_verifier) { + if (instance) { + LOG(ERROR) << "Already initialized"; + return; + } + + if (!controller_get_interface() + ->supports_ble_connected_isochronous_stream_central() && + !controller_get_interface() + ->supports_ble_connected_isochronous_stream_peripheral()) { + LOG(ERROR) << "Controller reports no ISO support." + " LeAudioClient Init aborted."; + return; + } + + LOG_ASSERT(std::move(hal_2_1_verifier).Run()) + << __func__ + << ", LE Audio Client requires Bluetooth Audio HAL V2.1 at least. Either " + "disable LE Audio Profile, or update your HAL"; + + IsoManager::GetInstance()->Start(); + + audioSinkReceiver = &audioSinkReceiverImpl; + audioSourceReceiver = &audioSourceReceiverImpl; + stateMachineHciCallbacks = &stateMachineHciCallbacksImpl; + stateMachineCallbacks = &stateMachineCallbacksImpl; + device_group_callbacks = &deviceGroupsCallbacksImpl; + instance = new LeAudioClientImpl(callbacks_, stateMachineCallbacks, initCb); + + IsoManager::GetInstance()->RegisterCigCallbacks(stateMachineHciCallbacks); +} + +void LeAudioClient::DebugDump(int fd) { + dprintf(fd, "LeAudio Manager: \n"); + if (instance) + instance->Dump(fd); + else + dprintf(fd, " Not initialized \n"); + + LeAudioClientAudioSource::DebugDump(fd); + LeAudioClientAudioSink::DebugDump(fd); + dprintf(fd, "\n"); +} + +void LeAudioClient::Cleanup(void) { + if (!instance) { + LOG(ERROR) << "Not initialized"; + return; + } + + LeAudioClientImpl* ptr = instance; + instance = nullptr; + ptr->Cleanup(); + delete ptr; + + LeAudioGroupStateMachine::Cleanup(); + IsoManager::GetInstance()->Stop(); +} diff --git a/system/bta/le_audio/client_audio.cc b/system/bta/le_audio/client_audio.cc new file mode 100644 index 0000000000000000000000000000000000000000..e7583503740a679e0082a865b0507a7851f4b638 --- /dev/null +++ b/system/bta/le_audio/client_audio.cc @@ -0,0 +1,573 @@ +/****************************************************************************** + * + * Copyright 2019 HIMSA II K/S - www.himsa.com. Represented by EHIMA - + * www.ehima.com + * + * 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 "client_audio.h" + +#include "audio_hal_interface/le_audio_software.h" +#include "btu.h" +#include "common/repeating_timer.h" +#include "common/time_util.h" +#include "osi/include/wakelock.h" + +using bluetooth::audio::le_audio::LeAudioClientInterface; + +namespace { +LeAudioCodecConfiguration source_codec_config; +bluetooth::common::RepeatingTimer audio_timer; +LeAudioClientInterface* leAudioClientInterface = nullptr; +LeAudioClientInterface::Sink* sinkClientInterface = nullptr; +LeAudioClientInterface::Source* sourceClientInterface = nullptr; +LeAudioClientAudioSinkReceiver* localAudioSinkReceiver = nullptr; +LeAudioClientAudioSourceReceiver* localAudioSourceReceiver = nullptr; + +enum { + HAL_UNINITIALIZED, + HAL_STOPPED, + HAL_STARTED, +} le_audio_sink_hal_state, + le_audio_source_hal_state; + +struct AudioHalStats { + size_t media_read_total_underflow_bytes; + size_t media_read_total_underflow_count; + uint64_t media_read_last_underflow_us; + + AudioHalStats() { Reset(); } + + void Reset() { + media_read_total_underflow_bytes = 0; + media_read_total_underflow_count = 0; + media_read_last_underflow_us = 0; + } +}; + +AudioHalStats stats; + +bool le_audio_sink_on_resume_req(bool start_media_task); +bool le_audio_sink_on_suspend_req(); + +void send_audio_data() { + uint32_t bytes_per_tick = + (source_codec_config.num_channels * source_codec_config.sample_rate * + source_codec_config.data_interval_us / 1000 * + (source_codec_config.bits_per_sample / 8)) / + 1000; + + std::vector<uint8_t> data(bytes_per_tick); + + uint32_t bytes_read = 0; + if (sinkClientInterface != nullptr) { + bytes_read = sinkClientInterface->Read(data.data(), bytes_per_tick); + } else { + LOG(ERROR) << __func__ << ", no LE Audio sink client interface - aborting."; + return; + } + + // LOG(INFO) << __func__ << ", bytes_read: " << static_cast<int>(bytes_read) + // << ", bytes_per_tick: " << static_cast<int>(bytes_per_tick); + + if (bytes_read < bytes_per_tick) { + stats.media_read_total_underflow_bytes += bytes_per_tick - bytes_read; + stats.media_read_total_underflow_count++; + stats.media_read_last_underflow_us = + bluetooth::common::time_get_os_boottime_us(); + } + + if (localAudioSinkReceiver != nullptr) { + localAudioSinkReceiver->OnAudioDataReady(data); + } +} + +void start_audio_ticks() { + wakelock_acquire(); + audio_timer.SchedulePeriodic( + get_main_thread()->GetWeakPtr(), FROM_HERE, base::Bind(&send_audio_data), + base::TimeDelta::FromMicroseconds(source_codec_config.data_interval_us)); +} + +void stop_audio_ticks() { + audio_timer.CancelAndWait(); + wakelock_release(); +} + +bool le_audio_sink_on_resume_req(bool start_media_task) { + if (localAudioSinkReceiver != nullptr) { + // Call OnAudioResume and block till it returns. + std::promise<void> do_resume_promise; + std::future<void> do_resume_future = do_resume_promise.get_future(); + bt_status_t status = do_in_main_thread( + FROM_HERE, + base::BindOnce(&LeAudioClientAudioSinkReceiver::OnAudioResume, + base::Unretained(localAudioSinkReceiver), + std::move(do_resume_promise))); + if (status == BT_STATUS_SUCCESS) { + do_resume_future.wait(); + } else { + LOG(ERROR) << __func__ + << ": LE_AUDIO_CTRL_CMD_START: do_in_main_thread err=" + << status; + return false; + } + } else { + LOG(ERROR) << __func__ + << ": LE_AUDIO_CTRL_CMD_START: audio sink receiver not started"; + return false; + } + + return true; +} + +bool le_audio_source_on_resume_req(bool start_media_task) { + if (localAudioSourceReceiver != nullptr) { + // Call OnAudioResume and block till it returns. + std::promise<void> do_resume_promise; + std::future<void> do_resume_future = do_resume_promise.get_future(); + bt_status_t status = do_in_main_thread( + FROM_HERE, + base::BindOnce(&LeAudioClientAudioSourceReceiver::OnAudioResume, + base::Unretained(localAudioSourceReceiver), + std::move(do_resume_promise))); + if (status == BT_STATUS_SUCCESS) { + do_resume_future.wait(); + } else { + LOG(ERROR) << __func__ + << ": LE_AUDIO_CTRL_CMD_START: do_in_main_thread err=" + << status; + return false; + } + } else { + LOG(ERROR) + << __func__ + << ": LE_AUDIO_CTRL_CMD_START: audio source receiver not started"; + return false; + } + + return true; +} + +bool le_audio_sink_on_suspend_req() { + stop_audio_ticks(); + if (localAudioSinkReceiver != nullptr) { + // 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(&LeAudioClientAudioSinkReceiver::OnAudioSuspend, + base::Unretained(localAudioSinkReceiver), + std::move(do_suspend_promise))); + if (status == BT_STATUS_SUCCESS) { + do_suspend_future.wait(); + return true; + } else { + LOG(ERROR) << __func__ + << ": LE_AUDIO_CTRL_CMD_SUSPEND: do_in_main_thread err=" + << status; + } + } else { + LOG(ERROR) << __func__ + << ": LE_AUDIO_CTRL_CMD_SUSPEND: audio receiver not started"; + } + return false; +} + +bool le_audio_source_on_suspend_req() { + if (localAudioSourceReceiver != nullptr) { + // 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(&LeAudioClientAudioSourceReceiver::OnAudioSuspend, + base::Unretained(localAudioSourceReceiver), + std::move(do_suspend_promise))); + if (status == BT_STATUS_SUCCESS) { + do_suspend_future.wait(); + return true; + } else { + LOG(ERROR) << __func__ + << ": LE_AUDIO_CTRL_CMD_SUSPEND: do_in_main_thread err=" + << status; + } + } else { + LOG(ERROR) << __func__ + << ": LE_AUDIO_CTRL_CMD_SUSPEND: audio receiver not started"; + } + return false; +} + +bool le_audio_sink_on_metadata_update_req(audio_usage_t usage, + audio_content_type_t content_type) { + if (localAudioSinkReceiver == nullptr) { + LOG(ERROR) << __func__ << ", audio receiver not started"; + return false; + } + + // Call OnAudioSuspend and block till it returns. + std::promise<void> do_update_metadata_promise; + std::future<void> do_update_metadata_future = + do_update_metadata_promise.get_future(); + bt_status_t status = do_in_main_thread( + FROM_HERE, + base::BindOnce(&LeAudioClientAudioSinkReceiver::OnAudioMetadataUpdate, + base::Unretained(localAudioSinkReceiver), + std::move(do_update_metadata_promise), usage, + content_type)); + + if (status == BT_STATUS_SUCCESS) { + do_update_metadata_future.wait(); + return true; + } + + LOG(ERROR) << __func__ << ", do_in_main_thread err=" << status; + + return false; +} + +} // namespace + +bool LeAudioClientAudioSource::Start( + const LeAudioCodecConfiguration& codec_configuration, + LeAudioClientAudioSinkReceiver* audioReceiver) { + LOG(INFO) << __func__; + + if (!sinkClientInterface) { + LOG(ERROR) << "sinkClientInterface is not Acquired!"; + return false; + } + + if (le_audio_sink_hal_state == HAL_STARTED) { + LOG(ERROR) << "LE audio device HAL is already in use!"; + return false; + } + + LOG(INFO) << __func__ << ": Le Audio Source Open, bits per sample: " + << int{codec_configuration.bits_per_sample} + << ", num channels: " << int{codec_configuration.num_channels} + << ", sample rate: " << codec_configuration.sample_rate + << ", data interval: " << codec_configuration.data_interval_us; + + stats.Reset(); + + /* Global config for periodic audio data */ + source_codec_config = codec_configuration; + LeAudioClientInterface::PcmParameters pcmParameters = { + .data_interval_us = codec_configuration.data_interval_us, + .sample_rate = codec_configuration.sample_rate, + .bits_per_sample = codec_configuration.bits_per_sample, + .channels_count = codec_configuration.num_channels}; + + sinkClientInterface->SetPcmParameters(pcmParameters); + sinkClientInterface->StartSession(); + + localAudioSinkReceiver = audioReceiver; + le_audio_sink_hal_state = HAL_STARTED; + + return true; +} + +void LeAudioClientAudioSource::Stop() { + LOG(INFO) << __func__; + if (!sinkClientInterface) { + LOG(ERROR) << __func__ << " sinkClientInterface stopped"; + return; + } + + if (le_audio_sink_hal_state != HAL_STARTED) { + LOG(ERROR) << "LE audio device HAL was not started!"; + return; + } + + LOG(INFO) << __func__ << ": Le Audio Source Close"; + + sinkClientInterface->StopSession(); + le_audio_sink_hal_state = HAL_STOPPED; + localAudioSinkReceiver = nullptr; + + stop_audio_ticks(); +} + +const void* LeAudioClientAudioSource::Acquire() { + LOG(INFO) << __func__; + if (sinkClientInterface != nullptr) { + LOG(WARNING) << __func__ << ", Sink client interface already initialized"; + return nullptr; + } + + /* Get pointer to singleton LE audio client interface */ + if (leAudioClientInterface == nullptr) { + leAudioClientInterface = LeAudioClientInterface::Get(); + + if (leAudioClientInterface == nullptr) { + LOG(ERROR) << __func__ << ", can't get LE audio client interface"; + return nullptr; + } + } + + auto sink_stream_cb = bluetooth::audio::le_audio::StreamCallbacks{ + .on_resume_ = le_audio_sink_on_resume_req, + .on_suspend_ = le_audio_sink_on_suspend_req, + .on_metadata_update_ = le_audio_sink_on_metadata_update_req, + }; + + sinkClientInterface = + leAudioClientInterface->GetSink(sink_stream_cb, get_main_thread()); + + if (sinkClientInterface == nullptr) { + LOG(ERROR) << __func__ << ", can't get LE audio sink client interface"; + return nullptr; + } + + le_audio_sink_hal_state = HAL_STOPPED; + return sinkClientInterface; +} + +void LeAudioClientAudioSource::Release(const void* instance) { + LOG(INFO) << __func__; + if (sinkClientInterface != instance) { + LOG(WARNING) << "Trying to release not own session"; + return; + } + + if (le_audio_sink_hal_state == HAL_UNINITIALIZED) { + LOG(WARNING) << "LE audio device HAL is not running."; + return; + } + + sinkClientInterface->Cleanup(); + leAudioClientInterface->ReleaseSink(sinkClientInterface); + le_audio_sink_hal_state = HAL_UNINITIALIZED; + sinkClientInterface = nullptr; +} + +void LeAudioClientAudioSource::ConfirmStreamingRequest() { + LOG(INFO) << __func__; + if ((sinkClientInterface == nullptr) || + (le_audio_sink_hal_state != HAL_STARTED)) { + LOG(ERROR) << "LE audio device HAL was not started!"; + return; + } + + sinkClientInterface->ConfirmStreamingRequest(); + LOG(INFO) << __func__ << ", start_audio_ticks"; + start_audio_ticks(); +} + +void LeAudioClientAudioSource::CancelStreamingRequest() { + LOG(INFO) << __func__; + if ((sinkClientInterface == nullptr) || + (le_audio_sink_hal_state != HAL_STARTED)) { + LOG(ERROR) << "LE audio device HAL was not started!"; + return; + } + + sinkClientInterface->CancelStreamingRequest(); +} + +void LeAudioClientAudioSource::UpdateRemoteDelay(uint16_t remote_delay_ms) { + LOG(INFO) << __func__; + if ((sinkClientInterface == nullptr) || + (le_audio_sink_hal_state != HAL_STARTED)) { + LOG(ERROR) << "LE audio device HAL was not started!"; + return; + } + + sinkClientInterface->SetRemoteDelay(remote_delay_ms); +} + +void LeAudioClientAudioSource::DebugDump(int fd) { + uint64_t now_us = bluetooth::common::time_get_os_boottime_us(); + std::stringstream stream; + stream << " Le Audio Audio HAL:" + << "\n Counts (underflow) : " + << stats.media_read_total_underflow_count + << "\n Bytes (underflow) : " + << stats.media_read_total_underflow_bytes + << "\n Last update time ago in ms (underflow) : " + << (stats.media_read_last_underflow_us > 0 + ? (unsigned long long)(now_us - + stats.media_read_last_underflow_us) / + 1000 + : 0) + << std::endl; + dprintf(fd, "%s", stream.str().c_str()); +} + +bool LeAudioClientAudioSink::Start( + const LeAudioCodecConfiguration& codec_configuration, + LeAudioClientAudioSourceReceiver* audioReceiver) { + LOG(INFO) << __func__; + if (!sourceClientInterface) { + LOG(ERROR) << "sourceClientInterface is not Acquired!"; + return false; + } + + if (le_audio_source_hal_state == HAL_STARTED) { + LOG(ERROR) << "LE audio device HAL is already in use!"; + return false; + } + + LOG(INFO) << __func__ << ": Le Audio Sink Open, bit rate: " + << codec_configuration.bits_per_sample + << ", num channels: " << int{codec_configuration.num_channels} + << ", sample rate: " << codec_configuration.sample_rate + << ", data interval: " << codec_configuration.data_interval_us; + + LeAudioClientInterface::PcmParameters pcmParameters = { + .data_interval_us = codec_configuration.data_interval_us, + .sample_rate = codec_configuration.sample_rate, + .bits_per_sample = codec_configuration.bits_per_sample, + .channels_count = codec_configuration.num_channels}; + + sourceClientInterface->SetPcmParameters(pcmParameters); + sourceClientInterface->StartSession(); + + localAudioSourceReceiver = audioReceiver; + le_audio_source_hal_state = HAL_STARTED; + return true; +} + +void LeAudioClientAudioSink::Stop() { + LOG(INFO) << __func__; + if (!sourceClientInterface) { + LOG(ERROR) << __func__ << " sourceClientInterface stopped"; + return; + } + + if (le_audio_source_hal_state != HAL_STARTED) { + LOG(ERROR) << "LE audio device HAL was not started!"; + return; + } + + LOG(INFO) << __func__ << ": Le Audio Sink Close"; + + sourceClientInterface->StopSession(); + le_audio_source_hal_state = HAL_STOPPED; + localAudioSourceReceiver = nullptr; +} + +const void* LeAudioClientAudioSink::Acquire() { + LOG(INFO) << __func__; + if (sourceClientInterface != nullptr) { + LOG(WARNING) << __func__ << ", Source client interface already initialized"; + return nullptr; + } + + /* Get pointer to singleton LE audio client interface */ + if (leAudioClientInterface == nullptr) { + leAudioClientInterface = LeAudioClientInterface::Get(); + + if (leAudioClientInterface == nullptr) { + LOG(ERROR) << __func__ << ", can't get LE audio client interface"; + return nullptr; + } + } + + auto source_stream_cb = bluetooth::audio::le_audio::StreamCallbacks{ + .on_resume_ = le_audio_source_on_resume_req, + .on_suspend_ = le_audio_source_on_suspend_req, + }; + + sourceClientInterface = + leAudioClientInterface->GetSource(source_stream_cb, get_main_thread()); + + if (sourceClientInterface == nullptr) { + LOG(ERROR) << __func__ << ", can't get LE audio source client interface"; + return nullptr; + } + + le_audio_source_hal_state = HAL_STOPPED; + return sourceClientInterface; +} + +void LeAudioClientAudioSink::Release(const void* instance) { + LOG(INFO) << __func__; + if (sourceClientInterface != instance) { + LOG(WARNING) << "Trying to release not own session"; + return; + } + + if (le_audio_source_hal_state == HAL_UNINITIALIZED) { + LOG(WARNING) << ", LE audio device source HAL is not running."; + return; + } + + sourceClientInterface->Cleanup(); + leAudioClientInterface->ReleaseSource(sourceClientInterface); + le_audio_source_hal_state = HAL_UNINITIALIZED; + sourceClientInterface = nullptr; +} + +size_t LeAudioClientAudioSink::SendData(uint8_t* data, uint16_t size) { + size_t bytes_written; + if (!sourceClientInterface) { + LOG(ERROR) << "sourceClientInterface not initialized!"; + return 0; + } + + if (le_audio_source_hal_state != HAL_STARTED) { + LOG(ERROR) << "LE audio device HAL was not started!"; + return 0; + } + + /* TODO: What to do if not all data is written ? */ + bytes_written = sourceClientInterface->Write(data, size); + if (bytes_written != size) + LOG(ERROR) << ", Not all data is written to source HAL. bytes written: " + << static_cast<int>(bytes_written) + << ", total: " << static_cast<int>(size); + + return bytes_written; +} + +void LeAudioClientAudioSink::ConfirmStreamingRequest() { + LOG(INFO) << __func__; + if ((sourceClientInterface == nullptr) || + (le_audio_source_hal_state != HAL_STARTED)) { + LOG(ERROR) << "LE audio device HAL was not started!"; + return; + } + + sourceClientInterface->ConfirmStreamingRequest(); +} + +void LeAudioClientAudioSink::CancelStreamingRequest() { + LOG(INFO) << __func__; + if ((sourceClientInterface == nullptr) || + (le_audio_source_hal_state != HAL_STARTED)) { + LOG(ERROR) << "LE audio device HAL was not started!"; + return; + } + + sourceClientInterface->CancelStreamingRequest(); +} + +void LeAudioClientAudioSink::UpdateRemoteDelay(uint16_t remote_delay_ms) { + if ((sourceClientInterface == nullptr) || + (le_audio_source_hal_state != HAL_STARTED)) { + LOG(ERROR) << "LE audio device HAL was not started!"; + return; + } + + sourceClientInterface->SetRemoteDelay(remote_delay_ms); +} + +void LeAudioClientAudioSink::DebugDump(int fd) { + /* TODO: Add some statistic for source client interface */ +} diff --git a/system/bta/le_audio/client_audio.h b/system/bta/le_audio/client_audio.h new file mode 100644 index 0000000000000000000000000000000000000000..1bca6185fe7aec60dcb9bf5933646226e990bee2 --- /dev/null +++ b/system/bta/le_audio/client_audio.h @@ -0,0 +1,131 @@ +/* + * Copyright 2020 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * 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 <future> + +#include "audio_hal_interface/le_audio_software.h" + +/* Implementations of Le Audio will also implement this interface */ +class LeAudioClientAudioSinkReceiver { + public: + virtual ~LeAudioClientAudioSinkReceiver() = default; + virtual void OnAudioDataReady(const std::vector<uint8_t>& data) = 0; + virtual void OnAudioSuspend(std::promise<void> do_suspend_promise) = 0; + virtual void OnAudioResume(std::promise<void> do_resume_promise) = 0; + virtual void OnAudioMetadataUpdate( + std::promise<void> do_update_metadata_promise, audio_usage_t usage, + audio_content_type_t content_type) = 0; +}; +class LeAudioClientAudioSourceReceiver { + public: + virtual ~LeAudioClientAudioSourceReceiver() = default; + virtual void OnAudioSuspend(std::promise<void> do_suspend_promise) = 0; + virtual void OnAudioResume(std::promise<void> do_resume_promise) = 0; +}; + +/* Represents configuration of audio codec, as exchanged between le audio and + * phone. + * It can also be passed to the audio source to configure its parameters. + */ +struct LeAudioCodecConfiguration { + static constexpr uint8_t kChannelNumberMono = + bluetooth::audio::le_audio::kChannelNumberMono; + static constexpr uint8_t kChannelNumberStereo = + bluetooth::audio::le_audio::kChannelNumberStereo; + + static constexpr uint32_t kSampleRate48000 = + bluetooth::audio::le_audio::kSampleRate48000; + static constexpr uint32_t kSampleRate44100 = + bluetooth::audio::le_audio::kSampleRate44100; + static constexpr uint32_t kSampleRate32000 = + bluetooth::audio::le_audio::kSampleRate32000; + static constexpr uint32_t kSampleRate24000 = + bluetooth::audio::le_audio::kSampleRate24000; + static constexpr uint32_t kSampleRate16000 = + bluetooth::audio::le_audio::kSampleRate16000; + static constexpr uint32_t kSampleRate8000 = + bluetooth::audio::le_audio::kSampleRate8000; + + static constexpr uint8_t kBitsPerSample16 = + bluetooth::audio::le_audio::kBitsPerSample16; + static constexpr uint8_t kBitsPerSample24 = + bluetooth::audio::le_audio::kBitsPerSample24; + static constexpr uint8_t kBitsPerSample32 = + bluetooth::audio::le_audio::kBitsPerSample32; + + static constexpr uint32_t kInterval7500Us = 7500; + static constexpr uint32_t kInterval10000Us = 10000; + + /** number of channels */ + uint8_t num_channels; + + /** sampling rate that the codec expects to receive from audio framework */ + uint32_t sample_rate; + + /** bits per sample that codec expects to receive from audio framework */ + uint8_t bits_per_sample; + + /** Data interval determines how often we send samples to the remote. This + * should match how often we grab data from audio source, optionally we can + * grab data every 2 or 3 intervals, but this would increase latency. + * + * Value is provided in us. + */ + uint32_t data_interval_us; + + bool operator!=(const LeAudioCodecConfiguration& other) { + return !((num_channels == other.num_channels) && + (sample_rate == other.sample_rate) && + (bits_per_sample == other.bits_per_sample) && + (data_interval_us == other.data_interval_us)); + } + + bool IsInvalid() { + return (num_channels == 0) || (sample_rate == 0) || + (bits_per_sample == 0) || (data_interval_us == 0); + } +}; + +/* Represents source of audio for le audio client */ +class LeAudioClientAudioSource { + public: + static bool Start(const LeAudioCodecConfiguration& codecConfiguration, + LeAudioClientAudioSinkReceiver* audioReceiver); + static void Stop(); + static const void* Acquire(); + static void Release(const void* instance); + static void ConfirmStreamingRequest(); + static void CancelStreamingRequest(); + static void UpdateRemoteDelay(uint16_t remote_delay_ms); + static void DebugDump(int fd); +}; + +/* Represents audio sink for le audio client */ +class LeAudioClientAudioSink { + public: + static bool Start(const LeAudioCodecConfiguration& codecConfiguration, + LeAudioClientAudioSourceReceiver* audioReceiver); + static void Stop(); + static const void* Acquire(); + static void Release(const void* instance); + static size_t SendData(uint8_t* data, uint16_t size); + static void ConfirmStreamingRequest(); + static void CancelStreamingRequest(); + static void UpdateRemoteDelay(uint16_t remote_delay_ms); + static void DebugDump(int fd); +}; diff --git a/system/bta/le_audio/client_audio_test.cc b/system/bta/le_audio/client_audio_test.cc new file mode 100644 index 0000000000000000000000000000000000000000..919f267aebf3eaf6ec7e8beba7edfa5883a5efef --- /dev/null +++ b/system/bta/le_audio/client_audio_test.cc @@ -0,0 +1,519 @@ +/* + * Copyright 2021 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * 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 "client_audio.h" + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include <chrono> +#include <future> + +#include "audio_hal_interface/le_audio_software.h" +#include "base/bind_helpers.h" +#include "common/message_loop_thread.h" +#include "hardware/bluetooth.h" +#include "osi/include/wakelock.h" + +using ::testing::_; +using ::testing::Assign; +using ::testing::AtLeast; +using ::testing::DoAll; +using ::testing::Invoke; +using ::testing::Mock; +using ::testing::Return; +using ::testing::ReturnPointee; +using ::testing::SaveArg; +using std::chrono_literals::operator""ms; + +bluetooth::common::MessageLoopThread message_loop_thread("test message loop"); +bluetooth::common::MessageLoopThread* get_main_thread() { + return &message_loop_thread; +} +bt_status_t do_in_main_thread(const base::Location& from_here, + base::OnceClosure task) { + if (!message_loop_thread.DoInThread(from_here, std::move(task))) { + LOG(ERROR) << __func__ << ": failed from " << from_here.ToString(); + return BT_STATUS_FAIL; + } + return BT_STATUS_SUCCESS; +} + +static base::MessageLoop* message_loop_; +base::MessageLoop* get_main_message_loop() { return message_loop_; } + +static void init_message_loop_thread() { + message_loop_thread.StartUp(); + if (!message_loop_thread.IsRunning()) { + FAIL() << "unable to create message loop thread."; + } + + if (!message_loop_thread.EnableRealTimeScheduling()) + LOG(ERROR) << "Unable to set real time scheduling"; + + message_loop_ = message_loop_thread.message_loop(); + if (message_loop_ == nullptr) FAIL() << "unable to get message loop."; +} + +static void cleanup_message_loop_thread() { + message_loop_ = nullptr; + message_loop_thread.ShutDown(); +} + +using bluetooth::audio::le_audio::LeAudioClientInterface; + +class MockLeAudioClientInterfaceSink : public LeAudioClientInterface::Sink { + public: + MOCK_METHOD((void), Cleanup, (), (override)); + MOCK_METHOD((void), SetPcmParameters, + (const LeAudioClientInterface::PcmParameters& params), + (override)); + MOCK_METHOD((void), SetRemoteDelay, (uint16_t delay_report_ms), (override)); + MOCK_METHOD((void), StartSession, (), (override)); + MOCK_METHOD((void), StopSession, (), (override)); + MOCK_METHOD((void), ConfirmStreamingRequest, (), (override)); + MOCK_METHOD((void), CancelStreamingRequest, (), (override)); + MOCK_METHOD((size_t), Read, (uint8_t * p_buf, uint32_t len)); +}; + +class MockLeAudioClientInterfaceSource : public LeAudioClientInterface::Source { + public: + MOCK_METHOD((void), Cleanup, (), (override)); + MOCK_METHOD((void), SetPcmParameters, + (const LeAudioClientInterface::PcmParameters& params), + (override)); + MOCK_METHOD((void), SetRemoteDelay, (uint16_t delay_report_ms), (override)); + MOCK_METHOD((void), StartSession, (), (override)); + MOCK_METHOD((void), StopSession, (), (override)); + MOCK_METHOD((void), ConfirmStreamingRequest, (), (override)); + MOCK_METHOD((void), CancelStreamingRequest, (), (override)); + MOCK_METHOD((size_t), Write, (const uint8_t* p_buf, uint32_t len)); +}; + +class MockLeAudioClientInterface : public LeAudioClientInterface { + public: + MockLeAudioClientInterface() = default; + ~MockLeAudioClientInterface() = default; + + MOCK_METHOD((Sink*), GetSink, + (bluetooth::audio::le_audio::StreamCallbacks stream_cb, + bluetooth::common::MessageLoopThread* message_loop)); + MOCK_METHOD((Source*), GetSource, + (bluetooth::audio::le_audio::StreamCallbacks stream_cb, + bluetooth::common::MessageLoopThread* message_loop)); +}; + +LeAudioClientInterface* mockInterface; + +namespace bluetooth { +namespace audio { +namespace le_audio { +MockLeAudioClientInterface* interface_mock; +MockLeAudioClientInterfaceSink* sink_mock; +MockLeAudioClientInterfaceSource* source_mock; + +LeAudioClientInterface* LeAudioClientInterface::Get() { return interface_mock; } + +LeAudioClientInterface::Sink* LeAudioClientInterface::GetSink( + StreamCallbacks stream_cb, + bluetooth::common::MessageLoopThread* message_loop) { + return interface_mock->GetSink(stream_cb, message_loop); +} + +LeAudioClientInterface::Source* LeAudioClientInterface::GetSource( + StreamCallbacks stream_cb, + bluetooth::common::MessageLoopThread* message_loop) { + return interface_mock->GetSource(stream_cb, message_loop); +} + +bool LeAudioClientInterface::ReleaseSink(LeAudioClientInterface::Sink* sink) { + return true; +} +bool LeAudioClientInterface::ReleaseSource( + LeAudioClientInterface::Source* source) { + return true; +} + +void LeAudioClientInterface::Sink::Cleanup() {} +void LeAudioClientInterface::Sink::SetPcmParameters( + const PcmParameters& params) {} +void LeAudioClientInterface::Sink::SetRemoteDelay(uint16_t delay_report_ms) {} +void LeAudioClientInterface::Sink::StartSession() {} +void LeAudioClientInterface::Sink::StopSession() {} +void LeAudioClientInterface::Sink::ConfirmStreamingRequest(){}; +void LeAudioClientInterface::Sink::CancelStreamingRequest(){}; + +void LeAudioClientInterface::Source::Cleanup() {} +void LeAudioClientInterface::Source::SetPcmParameters( + const PcmParameters& params) {} +void LeAudioClientInterface::Source::SetRemoteDelay(uint16_t delay_report_ms) {} +void LeAudioClientInterface::Source::StartSession() {} +void LeAudioClientInterface::Source::StopSession() {} +void LeAudioClientInterface::Source::ConfirmStreamingRequest(){}; +void LeAudioClientInterface::Source::CancelStreamingRequest(){}; + +size_t LeAudioClientInterface::Source::Write(const uint8_t* p_buf, + uint32_t len) { + return source_mock->Write(p_buf, len); +} + +size_t LeAudioClientInterface::Sink::Read(uint8_t* p_buf, uint32_t len) { + return sink_mock->Read(p_buf, len); +} +} // namespace le_audio +} // namespace audio +} // namespace bluetooth + +class MockLeAudioClientAudioSinkEventReceiver + : public LeAudioClientAudioSinkReceiver { + 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), OnAudioResume, (std::promise<void> do_resume_promise), + (override)); + MOCK_METHOD((void), OnAudioMetadataUpdate, + (std::promise<void> do_update_metadata_promise, + audio_usage_t usage, audio_content_type_t content_type), + (override)); +}; + +class MockLeAudioClientAudioSourceEventReceiver + : public LeAudioClientAudioSourceReceiver { + public: + MOCK_METHOD((void), OnAudioSuspend, (std::promise<void> do_suspend_promise), + (override)); + MOCK_METHOD((void), OnAudioResume, (std::promise<void> do_resume_promise), + (override)); +}; + +class LeAudioClientAudioTest : public ::testing::Test { + public: + void SetUp(void) override { + init_message_loop_thread(); + bluetooth::audio::le_audio::interface_mock = &mock_client_interface_; + bluetooth::audio::le_audio::sink_mock = &mock_client_interface_sink_; + bluetooth::audio::le_audio::source_mock = &mock_client_interface_source_; + + // Init sink Audio HAL mock + is_sink_acquired = false; + hal_sink_stream_cb = {.on_suspend_ = nullptr, .on_resume_ = nullptr}; + + ON_CALL(mock_client_interface_, GetSink(_, _)) + .WillByDefault(DoAll(SaveArg<0>(&hal_sink_stream_cb), + Assign(&is_sink_acquired, true), + Return(bluetooth::audio::le_audio::sink_mock))); + ON_CALL(mock_client_interface_sink_, Cleanup()) + .WillByDefault(Assign(&is_sink_acquired, false)); + + // Init source Audio HAL mock + is_source_acquired = false; + hal_source_stream_cb = {.on_suspend_ = nullptr, .on_resume_ = nullptr}; + + ON_CALL(mock_client_interface_, GetSource(_, _)) + .WillByDefault(DoAll(SaveArg<0>(&hal_source_stream_cb), + Assign(&is_source_acquired, true), + Return(bluetooth::audio::le_audio::source_mock))); + ON_CALL(mock_client_interface_source_, Cleanup()) + .WillByDefault(Assign(&is_source_acquired, false)); + } + + void AcquireAudioSink(void) { + audio_sink_instance_ = LeAudioClientAudioSink::Acquire(); + } + + void ReleaseAudioSink(void) { + LeAudioClientAudioSink::Release(audio_sink_instance_); + audio_sink_instance_ = nullptr; + } + + void AcquireAudioSource(void) { + audio_source_instance_ = LeAudioClientAudioSource::Acquire(); + } + + void ReleaseAudioSource(void) { + LeAudioClientAudioSource::Release(audio_source_instance_); + audio_source_instance_ = nullptr; + } + + void TearDown(void) override { + /* We have to call Cleanup to tidy up some static variables. + * If on the HAL end Source is running it means we are running the Sink + * on our end, and vice versa. + */ + if (is_source_acquired == true) ReleaseAudioSink(); + if (is_sink_acquired == true) ReleaseAudioSource(); + + cleanup_message_loop_thread(); + + bluetooth::audio::le_audio::sink_mock = nullptr; + bluetooth::audio::le_audio::source_mock = nullptr; + } + + MockLeAudioClientInterface mock_client_interface_; + MockLeAudioClientInterfaceSink mock_client_interface_sink_; + MockLeAudioClientInterfaceSource mock_client_interface_source_; + + MockLeAudioClientAudioSinkEventReceiver mock_hal_sink_event_receiver_; + MockLeAudioClientAudioSourceEventReceiver mock_hal_source_event_receiver_; + + bool is_source_acquired = false; + bool is_sink_acquired = false; + const void* audio_sink_instance_ = nullptr; + const void* audio_source_instance_ = nullptr; + + bluetooth::audio::le_audio::StreamCallbacks hal_source_stream_cb; + bluetooth::audio::le_audio::StreamCallbacks hal_sink_stream_cb; + + const LeAudioCodecConfiguration default_codec_conf{ + .num_channels = LeAudioCodecConfiguration::kChannelNumberMono, + .sample_rate = LeAudioCodecConfiguration::kSampleRate44100, + .bits_per_sample = LeAudioCodecConfiguration::kBitsPerSample24, + .data_interval_us = LeAudioCodecConfiguration::kInterval10000Us, + }; +}; + +TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSinkInitializeCleanup) { + EXPECT_CALL(mock_client_interface_, GetSource(_, _)) + .WillOnce(DoAll(Assign(&is_source_acquired, true), + Return(bluetooth::audio::le_audio::source_mock))); + AcquireAudioSink(); + + EXPECT_CALL(mock_client_interface_source_, Cleanup()) + .WillOnce(Assign(&is_source_acquired, false)); + ReleaseAudioSink(); +} + +TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSourceInitializeCleanup) { + EXPECT_CALL(mock_client_interface_, GetSink(_, _)) + .WillOnce(DoAll(Assign(&is_sink_acquired, true), + Return(bluetooth::audio::le_audio::sink_mock))); + AcquireAudioSource(); + + EXPECT_CALL(mock_client_interface_sink_, Cleanup()) + .WillOnce(Assign(&is_sink_acquired, false)); + ReleaseAudioSource(); +} + +TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSinkStartStop) { + LeAudioClientInterface::PcmParameters params; + EXPECT_CALL(mock_client_interface_source_, SetPcmParameters(_)) + .Times(1) + .WillOnce(SaveArg<0>(¶ms)); + EXPECT_CALL(mock_client_interface_source_, StartSession()).Times(1); + + AcquireAudioSink(); + ASSERT_TRUE(LeAudioClientAudioSink::Start(default_codec_conf, + &mock_hal_source_event_receiver_)); + + ASSERT_EQ(params.channels_count, + bluetooth::audio::le_audio::kChannelNumberMono); + ASSERT_EQ(params.sample_rate, bluetooth::audio::le_audio::kSampleRate44100); + ASSERT_EQ(params.bits_per_sample, + bluetooth::audio::le_audio::kBitsPerSample24); + ASSERT_EQ(params.data_interval_us, 10000u); + + EXPECT_CALL(mock_client_interface_source_, StopSession()).Times(1); + + LeAudioClientAudioSink::Stop(); +} + +TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSourceStartStop) { + LeAudioClientInterface::PcmParameters params; + EXPECT_CALL(mock_client_interface_sink_, SetPcmParameters(_)) + .Times(1) + .WillOnce(SaveArg<0>(¶ms)); + EXPECT_CALL(mock_client_interface_sink_, StartSession()).Times(1); + + AcquireAudioSource(); + ASSERT_TRUE(LeAudioClientAudioSource::Start(default_codec_conf, + &mock_hal_sink_event_receiver_)); + + ASSERT_EQ(params.channels_count, + bluetooth::audio::le_audio::kChannelNumberMono); + ASSERT_EQ(params.sample_rate, bluetooth::audio::le_audio::kSampleRate44100); + ASSERT_EQ(params.bits_per_sample, + bluetooth::audio::le_audio::kBitsPerSample24); + ASSERT_EQ(params.data_interval_us, 10000u); + + EXPECT_CALL(mock_client_interface_sink_, StopSession()).Times(1); + + LeAudioClientAudioSource::Stop(); +} + +TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSinkSendData) { + AcquireAudioSink(); + ASSERT_TRUE(LeAudioClientAudioSink::Start(default_codec_conf, + &mock_hal_source_event_receiver_)); + + const uint8_t* exp_p = nullptr; + uint32_t exp_len = 0; + uint8_t input_buf[] = { + 0x02, + 0x03, + 0x05, + 0x19, + }; + ON_CALL(mock_client_interface_source_, Write(_, _)) + .WillByDefault(DoAll(SaveArg<0>(&exp_p), SaveArg<1>(&exp_len), + ReturnPointee(&exp_len))); + + ASSERT_EQ(LeAudioClientAudioSink::SendData(input_buf, sizeof(input_buf)), + sizeof(input_buf)); + ASSERT_EQ(exp_len, sizeof(input_buf)); + ASSERT_EQ(exp_p, input_buf); + + LeAudioClientAudioSource::Stop(); +} + +TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSinkSuspend) { + AcquireAudioSink(); + ASSERT_TRUE(LeAudioClientAudioSink::Start(default_codec_conf, + &mock_hal_source_event_receiver_)); + + ASSERT_NE(hal_source_stream_cb.on_suspend_, nullptr); + + /* Expect LeAudio registered event listener to get called when HAL calls the + * client_audio's internal suspend callback. + */ + EXPECT_CALL(mock_hal_source_event_receiver_, OnAudioSuspend(_)).Times(1); + ASSERT_TRUE(hal_source_stream_cb.on_suspend_()); +} + +TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSourceSuspend) { + AcquireAudioSource(); + ASSERT_TRUE(LeAudioClientAudioSource::Start(default_codec_conf, + &mock_hal_sink_event_receiver_)); + + ASSERT_NE(hal_sink_stream_cb.on_suspend_, nullptr); + + /* Expect LeAudio registered event listener to get called when HAL calls the + * client_audio's internal suspend callback. + */ + EXPECT_CALL(mock_hal_sink_event_receiver_, OnAudioSuspend(_)).Times(1); + ASSERT_TRUE(hal_sink_stream_cb.on_suspend_()); +} + +TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSinkResume) { + AcquireAudioSink(); + ASSERT_TRUE(LeAudioClientAudioSink::Start(default_codec_conf, + &mock_hal_source_event_receiver_)); + + ASSERT_NE(hal_source_stream_cb.on_resume_, nullptr); + + /* Expect LeAudio registered event listener to get called when HAL calls the + * client_audio's internal resume callback. + */ + EXPECT_CALL(mock_hal_source_event_receiver_, OnAudioResume(_)).Times(1); + bool start_media_task = false; + ASSERT_TRUE(hal_source_stream_cb.on_resume_(start_media_task)); +} + +TEST_F(LeAudioClientAudioTest, + testLeAudioClientAudioSourceResumeStartSourceTask) { + const LeAudioCodecConfiguration codec_conf{ + .num_channels = LeAudioCodecConfiguration::kChannelNumberStereo, + .sample_rate = LeAudioCodecConfiguration::kSampleRate16000, + .bits_per_sample = LeAudioCodecConfiguration::kBitsPerSample24, + .data_interval_us = LeAudioCodecConfiguration::kInterval10000Us, + }; + AcquireAudioSource(); + ASSERT_TRUE(LeAudioClientAudioSource::Start(codec_conf, + &mock_hal_sink_event_receiver_)); + + std::chrono::time_point<std::chrono::system_clock> resumed_ts; + std::chrono::time_point<std::chrono::system_clock> executed_ts; + std::promise<void> promise; + auto future = promise.get_future(); + + uint32_t calculated_bytes_per_tick = 0; + EXPECT_CALL(mock_client_interface_sink_, Read(_, _)) + .Times(AtLeast(1)) + .WillOnce(Invoke([&](uint8_t* p_buf, uint32_t len) -> uint32_t { + executed_ts = std::chrono::system_clock::now(); + calculated_bytes_per_tick = len; + + // fake some data from audio framework + for (uint32_t i = 0u; i < len; ++i) { + p_buf[i] = i; + } + + // Return exactly as much data as requested + promise.set_value(); + return len; + })); + + std::promise<void> data_promise; + auto data_future = data_promise.get_future(); + + /* Expect this callback to be called to Client by the HAL glue layer */ + std::vector<uint8_t> media_data_to_send; + EXPECT_CALL(mock_hal_sink_event_receiver_, OnAudioDataReady(_)) + .WillOnce(Invoke([&](const std::vector<uint8_t>& data) -> void { + media_data_to_send = std::move(data); + data_promise.set_value(); + })); + + /* Expect LeAudio registered event listener to get called when HAL calls the + * client_audio's internal resume callback. + */ + ASSERT_NE(hal_sink_stream_cb.on_resume_, nullptr); + EXPECT_CALL(mock_hal_sink_event_receiver_, OnAudioResume(_)).Times(1); + resumed_ts = std::chrono::system_clock::now(); + bool start_media_task = true; + ASSERT_TRUE(hal_sink_stream_cb.on_resume_(start_media_task)); + LeAudioClientAudioSource::ConfirmStreamingRequest(); + + ASSERT_EQ(future.wait_for(std::chrono::seconds(1)), + std::future_status::ready); + + ASSERT_EQ(data_future.wait_for(std::chrono::seconds(1)), + std::future_status::ready); + + // Check agains expected payload size + const uint32_t channel_bytes_per_sample = (24 /*bps*/ / 8); + const uint32_t channel_bytes_per_10ms_at_16000Hz = + ((10ms).count() * channel_bytes_per_sample * 16000 /*Hz*/) / + (1000ms).count(); + + // Expect 2 channel (stereo) data + ASSERT_EQ(calculated_bytes_per_tick, 2 * channel_bytes_per_10ms_at_16000Hz); + + // Verify callback call interval for the requested 10ms (+2ms error margin) + auto delta = std::chrono::duration_cast<std::chrono::milliseconds>( + executed_ts - resumed_ts); + EXPECT_TRUE((delta >= 10ms) && (delta <= 12ms)); + + // Verify if we got just right amount of data in the callback call + ASSERT_EQ(media_data_to_send.size(), calculated_bytes_per_tick); +} + +TEST_F(LeAudioClientAudioTest, testLeAudioClientAudioSourceResume) { + AcquireAudioSource(); + ASSERT_TRUE(LeAudioClientAudioSource::Start(default_codec_conf, + &mock_hal_sink_event_receiver_)); + + ASSERT_NE(hal_sink_stream_cb.on_resume_, nullptr); + + /* Expect LeAudio registered event listener to get called when HAL calls the + * client_audio's internal resume callback. + */ + EXPECT_CALL(mock_hal_sink_event_receiver_, OnAudioResume(_)).Times(1); + bool start_media_task = false; + ASSERT_TRUE(hal_sink_stream_cb.on_resume_(start_media_task)); +} diff --git a/system/bta/le_audio/client_parser.cc b/system/bta/le_audio/client_parser.cc new file mode 100644 index 0000000000000000000000000000000000000000..cb6936d0fa3ca3ddc4fc2ee728f6117ab26b5cf9 --- /dev/null +++ b/system/bta/le_audio/client_parser.cc @@ -0,0 +1,628 @@ +/* + * Copyright 2019 HIMSA II K/S - www.himsa.com. Represented by EHIMA - + * www.ehima.com + * + * 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. + */ + +/* + * This module contains API of the audio stream control protocol. + */ + +#include "client_parser.h" + +#include <base/strings/string_number_conversions.h> +#include <endian.h> +#include <hardware/bt_common_types.h> + +#include <map> +#include <memory> +#include <numeric> + +#include "bta_le_audio_api.h" +#include "gap_api.h" +#include "le_audio_types.h" +#include "osi/include/allocator.h" + +using le_audio::types::acs_ac_record; + +namespace le_audio { +namespace client_parser { +namespace ascs { +static std::map<uint8_t, std::string> ase_state_map_string = { + {kAseStateIdle, "Idle"}, + {kAseStateCodecConfigured, "Codec Configured"}, + {kAseStateQosConfigured, "QoS Configured"}, + {kAseStateEnabling, "Enabling"}, + {kAseStateStreaming, "Streaming"}, + {kAseStateDisabling, "Disabling"}, + {kAseStateReleasing, "Releasing"}, +}; + +static std::map<uint8_t, std::string> ctp_opcode_map_string = { + {kCtpOpcodeCodecConfiguration, "Config Codec"}, + {kCtpOpcodeQosConfiguration, "Config QoS"}, + {kCtpOpcodeEnable, "Enable"}, + {kCtpOpcodeReceiverStartReady, "Receiver Start Ready"}, + {kCtpOpcodeDisable, "Disable"}, + {kCtpOpcodeReceiverStopReady, "Receiver Stop Ready"}, + {kCtpOpcodeUpdateMetadata, "Update Metadata"}, + {kCtpOpcodeRelease, "Release"}, +}; + +static std::map<uint8_t, std::string> ctp_reason_map_string = { + {kCtpResponseNoReason, ""}, + {kCtpResponseCodecId, "Codec ID"}, + {kCtpResponseCodecSpecificConfiguration, "Codec specific configuration"}, + {kCtpResponseSduInterval, "SDU interval"}, + {kCtpResponseFraming, "Framing"}, + {kCtpResponsePhy, "PHY"}, + {kCtpResponseMaximumSduSize, "Maximum SDU size"}, + {kCtpResponseRetransmissionNumber, "Retransmission number"}, + {kCtpResponseMaxTransportLatency, "Max Transport latency"}, + {kCtpResponsePresentationDelay, "Presentation delay"}, + {kCtpResponseInvalidAseCisMapping, "Invalid ASE CIS mapping"}, +}; + +static std::map<uint8_t, std::string> ctp_response_code_map_string = { + {kCtpResponseCodeSuccess, "Success"}, + {kCtpResponseCodeUnsupportedOpcode, "Unsupported Opcode"}, + {kCtpResponseCodeInvalidLength, "Invalid Length"}, + {kCtpResponseCodeInvalidAseId, "Invalid ASE ID"}, + {kCtpResponseCodeInvalidAseStateMachineTransition, + "Invalid ASE State Machine Transition"}, + {kCtpResponseCodeInvalidAseDirection, "Invalid ASE Direction"}, + {kCtpResponseCodeUnsupportedAudioCapabilities, + "Unsupported Audio Capabilities"}, + {kCtpResponseCodeUnsupportedConfigurationParameterValue, + "Unsupported Configuration Parameter Value"}, + {kCtpResponseCodeRejectedConfigurationParameterValue, + "Rejected Configuration Parameter Value"}, + {kCtpResponseCodeInvalidConfigurationParameterValue, + "Invalid Configuration Parameter Value"}, + {kCtpResponseCodeUnsupportedMetadata, "Unsupported Metadata"}, + {kCtpResponseCodeRejectedMetadata, "Rejected Metadata"}, + {kCtpResponseCodeInvalidMetadata, "Invalid Metadata"}, + {kCtpResponseCodeInsufficientResources, "Insufficient Resources"}, + {kCtpResponseCodeUnspecifiedError, "Unspecified Error"}, +}; + +bool ParseAseStatusHeader(ase_rsp_hdr& arh, uint16_t len, + const uint8_t* value) { + if (len < kAseRspHdrMinLen) { + LOG(ERROR) << __func__ + << ", wrong len of ASE char (header): " << static_cast<int>(len); + + return false; + } + + STREAM_TO_UINT8(arh.id, value); + STREAM_TO_UINT8(arh.state, value); + + LOG(INFO) << "ASE status: " + << "\tASE id: " << loghex(arh.id) + << "\tASE state: " << ase_state_map_string[arh.state] << " (" + << loghex(arh.state) << ")"; + + return true; +} + +bool ParseAseStatusCodecConfiguredStateParams( + struct ase_codec_configured_state_params& rsp, uint16_t len, + const uint8_t* value) { + uint8_t codec_spec_conf_len; + + if (len < kAseStatusCodecConfMinLen) { + LOG(ERROR) << "Wrong len of codec conf status (Codec conf header)"; + return false; + } + + STREAM_TO_UINT8(rsp.framing, value); + STREAM_TO_UINT8(rsp.preferred_phy, value); + STREAM_TO_UINT8(rsp.preferred_retrans_nb, value); + STREAM_TO_UINT16(rsp.max_transport_latency, value); + STREAM_TO_UINT24(rsp.pres_delay_min, value); + STREAM_TO_UINT24(rsp.pres_delay_max, value); + STREAM_TO_UINT24(rsp.preferred_pres_delay_min, value); + STREAM_TO_UINT24(rsp.preferred_pres_delay_max, value); + STREAM_TO_UINT8(rsp.codec_id.coding_format, value); + STREAM_TO_UINT16(rsp.codec_id.vendor_company_id, value); + STREAM_TO_UINT16(rsp.codec_id.vendor_codec_id, value); + STREAM_TO_UINT8(codec_spec_conf_len, value); + + len -= kAseStatusCodecConfMinLen; + + if (len != codec_spec_conf_len) { + LOG(ERROR) << "Wrong len of codec conf status (Codec spec conf)"; + return false; + } + if (codec_spec_conf_len) + rsp.codec_spec_conf = + std::vector<uint8_t>(value, value + codec_spec_conf_len); + + LOG(INFO) << __func__ << ", Codec configuration" + << "\n\tFraming: " << loghex(rsp.framing) + << "\n\tPreferred PHY: " << loghex(rsp.preferred_phy) + << "\n\tPreferred retransmission number: " + << loghex(rsp.preferred_retrans_nb) << "\n\tMax transport latency: " + << loghex(rsp.max_transport_latency) + << "\n\tPresence delay min: " << loghex(rsp.pres_delay_min) + << "\n\tPresence delay max: " << loghex(rsp.pres_delay_max) + << "\n\tPreferredPresentationDelayMin: " + << loghex(rsp.preferred_pres_delay_min) + << "\n\tPreferredPresentationDelayMax: " + << loghex(rsp.preferred_pres_delay_max) + << "\n\tCoding format: " << loghex(rsp.codec_id.coding_format) + << "\n\tVendor codec company ID: " + << loghex(rsp.codec_id.vendor_company_id) + << "\n\tVendor codec ID: " << loghex(rsp.codec_id.vendor_codec_id) + << "\n\tCodec specific conf len: " << (int)codec_spec_conf_len + << "\n\tCodec specific conf: " + << base::HexEncode(rsp.codec_spec_conf.data(), + rsp.codec_spec_conf.size()); + + return true; +} + +bool ParseAseStatusQosConfiguredStateParams( + struct ase_qos_configured_state_params& rsp, uint16_t len, + const uint8_t* value) { + if (len != kAseStatusCodecQosConfMinLen) { + LOG(ERROR) << "Wrong len of ASE characteristic (QOS conf header)"; + return false; + } + + STREAM_TO_UINT8(rsp.cig_id, value); + STREAM_TO_UINT8(rsp.cis_id, value); + STREAM_TO_UINT24(rsp.sdu_interval, value); + STREAM_TO_UINT8(rsp.framing, value); + STREAM_TO_UINT8(rsp.phy, value); + STREAM_TO_UINT16(rsp.max_sdu, value); + STREAM_TO_UINT8(rsp.retrans_nb, value); + STREAM_TO_UINT16(rsp.max_transport_latency, value); + STREAM_TO_UINT24(rsp.pres_delay, value); + + LOG(INFO) << __func__ << ", Codec QoS Configured" + << "\n\tCIG: " << loghex(rsp.cig_id) + << "\n\tCIS: " << loghex(rsp.cis_id) + << "\n\tSDU interval: " << loghex(rsp.sdu_interval) + << "\n\tFraming: " << loghex(rsp.framing) + << "\n\tPHY: " << loghex(rsp.phy) + << "\n\tMax SDU: " << loghex(rsp.max_sdu) + << "\n\tRetransmission number: " << loghex(rsp.retrans_nb) + << "\n\tMax transport latency: " + << loghex(rsp.max_transport_latency) + << "\n\tPresentation delay: " << loghex(rsp.pres_delay); + + return true; +} + +bool ParseAseStatusTransientStateParams(struct ase_transient_state_params& rsp, + uint16_t len, const uint8_t* value) { + uint8_t metadata_len; + + if (len < kAseStatusTransMinLen) { + LOG(ERROR) << "Wrong len of ASE characteristic (metadata)"; + return false; + } + + STREAM_TO_UINT8(metadata_len, value); + len -= kAseStatusTransMinLen; + + if (len != metadata_len) { + LOG(ERROR) << "Wrong len of ASE characteristic (metadata)"; + return false; + } + + if (metadata_len > 0) + rsp.metadata = std::vector<uint8_t>(value, value + metadata_len); + + LOG(INFO) << __func__ << ", Status enabling/streaming/disabling metadata:" + << base::HexEncode(rsp.metadata.data(), rsp.metadata.size()); + + return true; +} + +bool ParseAseCtpNotification(struct ctp_ntf& ntf, uint16_t len, + const uint8_t* value) { + uint8_t num_entries; + + if (len < kCtpNtfMinLen) { + LOG(ERROR) << "Wrong len of ASE control point notification: " << (int)len; + return false; + } + + STREAM_TO_UINT8(ntf.op, value); + STREAM_TO_UINT8(num_entries, value); + + if (len != kCtpNtfMinLen + (num_entries * kCtpAseEntryMinLen)) { + LOG(ERROR) << "Wrong len of ASE control point notification (ASE IDs)"; + return false; + } + + for (int i = 0; i < num_entries; i++) { + struct ctp_ase_entry entry; + + STREAM_TO_UINT8(entry.ase_id, value); + STREAM_TO_UINT8(entry.response_code, value); + STREAM_TO_UINT8(entry.reason, value); + + ntf.entries.push_back(std::move(entry)); + } + + LOG(INFO) << __func__ << ", Control point notification" + << "\n\tOpcode: " << ctp_opcode_map_string[ntf.op] << " (" + << loghex(ntf.op) << ")" + << "\n\tNum ASE IDs: " << (int)num_entries; + for (size_t i = 0; i < num_entries; i++) + LOG(INFO) << "\n\tASE ID[" << loghex(ntf.entries[i].ase_id) + << "] response: " + << ctp_response_code_map_string[ntf.entries[i].response_code] + << " (" << loghex(ntf.entries[i].response_code) << ")" + << " reason: " << ctp_reason_map_string[ntf.entries[i].reason] + << " (" << loghex(ntf.entries[i].reason) << ")"; + + return true; +} + +bool PrepareAseCtpCodecConfig(const std::vector<struct ctp_codec_conf>& confs, + std::vector<uint8_t>& value) { + if (confs.size() == 0) return false; + + std::string conf_ents_str; + size_t msg_len = std::accumulate( + confs.begin(), confs.end(), + confs.size() * kCtpCodecConfMinLen + kAseNumSize + kCtpOpSize, + [&conf_ents_str](size_t cur_len, auto const& conf) { + auto ltv_map = conf.codec_config.GetAsLtvMap(); + for (const auto& [type, value] : ltv_map.Values()) { + conf_ents_str += + "\ttype: " + std::to_string(type) + + "\tlen: " + std::to_string(value.size()) + + "\tdata: " + base::HexEncode(value.data(), value.size()) + "\n"; + }; + + return cur_len + ltv_map.RawPacketSize(); + }); + value.resize(msg_len); + + uint8_t* msg = value.data(); + UINT8_TO_STREAM(msg, kCtpOpcodeCodecConfiguration); + + UINT8_TO_STREAM(msg, confs.size()); + for (const struct ctp_codec_conf& conf : confs) { + UINT8_TO_STREAM(msg, conf.ase_id); + UINT8_TO_STREAM(msg, conf.target_latency); + UINT8_TO_STREAM(msg, conf.target_phy); + UINT8_TO_STREAM(msg, conf.codec_id.coding_format); + UINT16_TO_STREAM(msg, conf.codec_id.vendor_company_id); + UINT16_TO_STREAM(msg, conf.codec_id.vendor_codec_id); + + auto ltv_map = conf.codec_config.GetAsLtvMap(); + auto codec_spec_conf_len = ltv_map.RawPacketSize(); + + UINT8_TO_STREAM(msg, codec_spec_conf_len); + msg = ltv_map.RawPacket(msg); + + LOG(INFO) << __func__ << ", Codec configuration" + << "\n\tAse id: " << loghex(conf.ase_id) + << "\n\tTarget latency: " << loghex(conf.target_latency) + << "\n\tTarget PHY: " << loghex(conf.target_phy) + << "\n\tCoding format: " << loghex(conf.codec_id.coding_format) + << "\n\tVendor codec company ID: " + << loghex(conf.codec_id.vendor_company_id) + << "\n\tVendor codec ID: " + << loghex(conf.codec_id.vendor_codec_id) + << "\n\tCodec config len: " + << static_cast<int>(codec_spec_conf_len) + << "\n\tCodec spec conf: " + << "\n" + << conf_ents_str; + } + + return true; +} + +bool PrepareAseCtpConfigQos(const std::vector<struct ctp_qos_conf>& confs, + std::vector<uint8_t>& value) { + if (confs.size() == 0) return false; + value.resize(confs.size() * kCtpQosConfMinLen + kAseNumSize + kCtpOpSize); + + uint8_t* msg = value.data(); + UINT8_TO_STREAM(msg, kCtpOpcodeQosConfiguration); + UINT8_TO_STREAM(msg, confs.size()); + + for (const struct ctp_qos_conf& conf : confs) { + UINT8_TO_STREAM(msg, conf.ase_id); + UINT8_TO_STREAM(msg, conf.cig); + UINT8_TO_STREAM(msg, conf.cis); + UINT24_TO_STREAM(msg, conf.sdu_interval); + UINT8_TO_STREAM(msg, conf.framing); + UINT8_TO_STREAM(msg, conf.phy); + UINT16_TO_STREAM(msg, conf.max_sdu); + UINT8_TO_STREAM(msg, conf.retrans_nb); + UINT16_TO_STREAM(msg, conf.max_transport_latency); + UINT24_TO_STREAM(msg, conf.pres_delay); + + LOG(INFO) << __func__ << ", QoS configuration" + << "\n\tAse id: " << loghex(conf.ase_id) + << "\n\tcig: " << loghex(conf.cig) + << "\n\tCis: " << loghex(conf.cis) + << "\n\tSDU interval: " << loghex(conf.sdu_interval) + << "\n\tFraming: " << loghex(conf.framing) + << "\n\tPhy: " << loghex(conf.phy) + << "\n\tMax sdu size: " << loghex(conf.max_sdu) + << "\n\tRetrans nb: " << loghex(conf.retrans_nb) + << "\n\tMax Transport latency: " + << loghex(conf.max_transport_latency) + << "\n\tPres delay: " << loghex(conf.pres_delay); + } + + return true; +} + +bool PrepareAseCtpEnable(const std::vector<struct ctp_enable>& confs, + std::vector<uint8_t>& value) { + if (confs.size() == 0) return false; + + uint16_t msg_len = confs.size() * kCtpEnableMinLen + kAseNumSize + kCtpOpSize; + std::for_each(confs.begin(), confs.end(), + [&msg_len](const struct ctp_enable& conf) { + msg_len += conf.metadata.size(); + }); + value.resize(msg_len); + + uint8_t* msg = value.data(); + UINT8_TO_STREAM(msg, kCtpOpcodeEnable); + UINT8_TO_STREAM(msg, confs.size()); + + for (const struct ctp_enable& conf : confs) { + UINT8_TO_STREAM(msg, conf.ase_id); + UINT8_TO_STREAM(msg, conf.metadata.size()); + ARRAY_TO_STREAM(msg, conf.metadata.data(), + static_cast<int>(conf.metadata.size())); + + LOG(INFO) << __func__ << ", Enable" + << "\n\tAse id: " << loghex(conf.ase_id) << "\n\tMetadata: " + << base::HexEncode(conf.metadata.data(), conf.metadata.size()); + } + + return true; +} + +bool PrepareAseCtpAudioReceiverStartReady(const std::vector<uint8_t>& ase_ids, + std::vector<uint8_t>& value) { + if (ase_ids.size() == 0) return false; + value.resize(ase_ids.size() * kAseIdSize + kAseNumSize + kCtpOpSize); + + uint8_t* msg = value.data(); + UINT8_TO_STREAM(msg, kCtpOpcodeReceiverStartReady); + UINT8_TO_STREAM(msg, ase_ids.size()); + + for (const uint8_t& id : ase_ids) { + UINT8_TO_STREAM(msg, id); + + LOG(INFO) << __func__ << ", ReceiverStartReady" + << "\n\tAse id: " << loghex(id); + } + + return true; +} + +bool PrepareAseCtpDisable(const std::vector<uint8_t>& ase_ids, + std::vector<uint8_t>& value) { + if (ase_ids.size() == 0) return false; + value.resize(ase_ids.size() * kAseIdSize + kAseNumSize + kCtpOpSize); + + uint8_t* msg = value.data(); + UINT8_TO_STREAM(msg, kCtpOpcodeDisable); + UINT8_TO_STREAM(msg, ase_ids.size()); + + for (const uint8_t& id : ase_ids) { + UINT8_TO_STREAM(msg, id); + + LOG(INFO) << __func__ << ", Disable" + << "\n\tAse id: " << loghex(id); + } + + return true; +} + +bool PrepareAseCtpAudioReceiverStopReady(const std::vector<uint8_t>& ase_ids, + std::vector<uint8_t>& value) { + if (ase_ids.size() == 0) return false; + value.resize(ase_ids.size() * kAseIdSize + kAseNumSize + kCtpOpSize); + + uint8_t* msg = value.data(); + UINT8_TO_STREAM(msg, kCtpOpcodeReceiverStopReady); + UINT8_TO_STREAM(msg, ase_ids.size()); + + for (const uint8_t& ase_id : ase_ids) { + UINT8_TO_STREAM(msg, ase_id); + + LOG(INFO) << __func__ << ", ReceiverStopReady" + << "\n\tAse id: " << loghex(ase_id); + } + + return true; +} + +bool PrepareAseCtpUpdateMetadata( + const std::vector<struct ctp_update_metadata>& confs, + std::vector<uint8_t>& value) { + if (confs.size() == 0) return false; + + uint16_t msg_len = + confs.size() * kCtpUpdateMetadataMinLen + kAseNumSize + kCtpOpSize; + std::for_each(confs.begin(), confs.end(), + [&msg_len](const struct ctp_update_metadata& conf) { + msg_len += conf.metadata.size(); + }); + value.resize(msg_len); + + uint8_t* msg = value.data(); + UINT8_TO_STREAM(msg, kCtpOpcodeUpdateMetadata); + UINT8_TO_STREAM(msg, confs.size()); + + for (const struct ctp_update_metadata& conf : confs) { + UINT8_TO_STREAM(msg, conf.ase_id); + UINT8_TO_STREAM(msg, conf.metadata.size()); + ARRAY_TO_STREAM(msg, conf.metadata.data(), + static_cast<int>(conf.metadata.size())); + + LOG(INFO) << __func__ << ", Update Metadata" + << "\n\tAse id: " << loghex(conf.ase_id) << "\n\tMetadata: " + << base::HexEncode(conf.metadata.data(), conf.metadata.size()); + } + + return true; +} + +bool PrepareAseCtpRelease(const std::vector<uint8_t>& ase_ids, + std::vector<uint8_t>& value) { + if (ase_ids.size() == 0) return true; + value.resize(ase_ids.size() * kAseIdSize + kAseNumSize + kCtpOpSize); + + uint8_t* msg = value.data(); + UINT8_TO_STREAM(msg, kCtpOpcodeRelease); + UINT8_TO_STREAM(msg, ase_ids.size()); + + for (const uint8_t& ase_id : ase_ids) { + UINT8_TO_STREAM(msg, ase_id); + + LOG(INFO) << __func__ << ", Release" + << "\n\tAse id: " << loghex(ase_id); + } + + return true; +} +} // namespace ascs + +namespace pacs { + +bool ParsePac(std::vector<struct acs_ac_record>& pac_recs, uint16_t len, + const uint8_t* value) { + if (len < kAcsPacDiscoverRspMinLen) { + LOG(ERROR) << "Wrong len of PAC characteristic"; + return false; + } + + uint8_t pac_rec_nb; + STREAM_TO_UINT8(pac_rec_nb, value); + len -= kAcsPacDiscoverRspMinLen; + + pac_recs.reserve(pac_rec_nb); + for (int i = 0; i < pac_rec_nb; i++) { + struct acs_ac_record rec; + uint8_t codec_spec_cap_len, metadata_len; + + if (len < kAcsPacRecordMinLen) { + LOG(ERROR) << "Wrong len of PAC record"; + pac_recs.clear(); + return false; + } + + STREAM_TO_UINT8(rec.codec_id.coding_format, value); + STREAM_TO_UINT16(rec.codec_id.vendor_company_id, value); + STREAM_TO_UINT16(rec.codec_id.vendor_codec_id, value); + STREAM_TO_UINT8(codec_spec_cap_len, value); + len -= kAcsPacRecordMinLen - kAcsPacMetadataLenLen; + + if (len < codec_spec_cap_len + kAcsPacMetadataLenLen) { + LOG(ERROR) << "Wrong len of PAC record (codec specific capabilities)"; + pac_recs.clear(); + return false; + } + + bool parsed; + rec.codec_spec_caps = + types::LeAudioLtvMap::Parse(value, codec_spec_cap_len, parsed); + if (!parsed) return false; + + value += codec_spec_cap_len; + len -= codec_spec_cap_len; + + STREAM_TO_UINT8(metadata_len, value); + len -= kAcsPacMetadataLenLen; + + if (len < metadata_len) { + LOG(ERROR) << "Wrong len of PAC record (metadata)"; + pac_recs.clear(); + return false; + } + + rec.metadata = std::vector<uint8_t>(value, value + metadata_len); + value += metadata_len; + len -= metadata_len; + + pac_recs.push_back(std::move(rec)); + } + + return true; +} + +bool ParseAudioLocations(types::AudioLocations& audio_locations, uint16_t len, + const uint8_t* value) { + if (len != kAudioLocationsRspMinLen) { + LOG(ERROR) << "Wrong len of Audio Location characteristic"; + return false; + } + + STREAM_TO_UINT32(audio_locations, value); + + LOG(INFO) << "Audio locations: " << audio_locations.to_string(); + + return true; +} + +bool ParseSupportedAudioContexts(struct acs_supported_audio_contexts& contexts, + uint16_t len, const uint8_t* value) { + if (len != kAseAudioSuppContRspMinLen) { + LOG(ERROR) << "Wrong len of Audio Supported Context characteristic"; + return false; + } + + STREAM_TO_UINT16(contexts.snk_supp_cont, value); + STREAM_TO_UINT16(contexts.src_supp_cont, value); + + LOG(INFO) << "Supported Audio Contexts: " + << "\n\tSupported Sink Contexts: " + << contexts.snk_supp_cont.to_string() + << "\n\tSupported Source Contexts: " + << contexts.src_supp_cont.to_string(); + + return true; +} + +bool ParseAvailableAudioContexts(struct acs_available_audio_contexts& contexts, + uint16_t len, const uint8_t* value) { + if (len != kAseAudioAvailRspMinLen) { + LOG(ERROR) << "Wrong len of Audio Availability characteristic"; + return false; + } + + STREAM_TO_UINT16(contexts.snk_avail_cont, value); + STREAM_TO_UINT16(contexts.src_avail_cont, value); + + LOG(INFO) << "Available Audio Contexts: " + << "\n\tAvailable Sink Contexts: " + << contexts.snk_avail_cont.to_string() + << "\n\tAvailable Source Contexts: " + << contexts.src_avail_cont.to_string(); + + return true; +} +} // namespace pacs + +} // namespace client_parser +} // namespace le_audio diff --git a/system/bta/le_audio/client_parser.h b/system/bta/le_audio/client_parser.h new file mode 100644 index 0000000000000000000000000000000000000000..f18d79d2fdee79130318f29048e7c8564caae013 --- /dev/null +++ b/system/bta/le_audio/client_parser.h @@ -0,0 +1,242 @@ +/* + * Copyright 2019 HIMSA II K/S - www.himsa.com. Represented by EHIMA - + * www.ehima.com + * + * 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. + */ + +/* + * This file contains the Audio Stream Control (LE_AUDIO) API function external + * definitions. + */ + +#pragma once + +#include "le_audio_types.h" + +namespace le_audio { +namespace client_parser { +namespace ascs { +/* + * All structures and defines are described in Audio Stream Control Service + * specification + */ + +constexpr uint8_t kCtpResponseCodeSuccess = 0x00; +constexpr uint8_t kCtpResponseCodeUnsupportedOpcode = 0x01; +constexpr uint8_t kCtpResponseCodeInvalidLength = 0x02; +constexpr uint8_t kCtpResponseCodeInvalidAseId = 0x03; +constexpr uint8_t kCtpResponseCodeInvalidAseStateMachineTransition = 0x04; +constexpr uint8_t kCtpResponseCodeInvalidAseDirection = 0x05; +constexpr uint8_t kCtpResponseCodeUnsupportedAudioCapabilities = 0x06; +constexpr uint8_t kCtpResponseCodeUnsupportedConfigurationParameterValue = 0x07; +constexpr uint8_t kCtpResponseCodeRejectedConfigurationParameterValue = 0x08; +constexpr uint8_t kCtpResponseCodeInvalidConfigurationParameterValue = 0x09; +constexpr uint8_t kCtpResponseCodeUnsupportedMetadata = 0x0A; +constexpr uint8_t kCtpResponseCodeRejectedMetadata = 0x0B; +constexpr uint8_t kCtpResponseCodeInvalidMetadata = 0x0C; +constexpr uint8_t kCtpResponseCodeInsufficientResources = 0x0D; +constexpr uint8_t kCtpResponseCodeUnspecifiedError = 0x0E; + +constexpr uint8_t kCtpResponseNoReason = 0x00; +constexpr uint8_t kCtpResponseCodecId = 0x01; +constexpr uint8_t kCtpResponseCodecSpecificConfiguration = 0x02; +constexpr uint8_t kCtpResponseSduInterval = 0x03; +constexpr uint8_t kCtpResponseFraming = 0x04; +constexpr uint8_t kCtpResponsePhy = 0x05; +constexpr uint8_t kCtpResponseMaximumSduSize = 0x06; +constexpr uint8_t kCtpResponseRetransmissionNumber = 0x07; +constexpr uint8_t kCtpResponseMaxTransportLatency = 0x08; +constexpr uint8_t kCtpResponsePresentationDelay = 0x09; +constexpr uint8_t kCtpResponseInvalidAseCisMapping = 0x0A; + +constexpr uint8_t kLeAudioErrorCtpUnsupporterdOpcode = 0xFF; +constexpr uint8_t kLeAudioErrorCtpTruncatedOperation = 0xFE; +constexpr uint8_t kLeAudioErrorCtpCtpErr = 0xFD; + +/* ASE states */ +constexpr uint8_t kAseStateIdle = 0x00; +constexpr uint8_t kAseStateCodecConfigured = 0x01; +constexpr uint8_t kAseStateQosConfigured = 0x02; +constexpr uint8_t kAseStateEnabling = 0x03; +constexpr uint8_t kAseStateStreaming = 0x04; +constexpr uint8_t kAseStateDisabling = 0x05; +constexpr uint8_t kAseStateReleasing = 0x06; + +/* Control point opcodes */ +constexpr uint8_t kCtpOpcodeCodecConfiguration = 0x01; +constexpr uint8_t kCtpOpcodeQosConfiguration = 0x02; +constexpr uint8_t kCtpOpcodeEnable = 0x03; +constexpr uint8_t kCtpOpcodeReceiverStartReady = 0x04; +constexpr uint8_t kCtpOpcodeDisable = 0x05; +constexpr uint8_t kCtpOpcodeReceiverStopReady = 0x06; +constexpr uint8_t kCtpOpcodeUpdateMetadata = 0x07; +constexpr uint8_t kCtpOpcodeRelease = 0x08; + +/* ASE status masks */ +static constexpr uint32_t kAseRspHeaderMaskCtrlStatusFailureOpcode = 0x00FF0000; +static constexpr uint32_t kAseRspHeaderMaskCtrlStatusErrorCode = 0x0000FF00; +static constexpr uint32_t kAseRspHeaderMaskCtrlStatusErrorReason = 0x000000FF; + +constexpr uint16_t kAseStatusCodecConfMinLen = 23; +struct ase_codec_configured_state_params { + uint8_t framing; + uint8_t preferred_phy; + uint8_t preferred_retrans_nb; + uint16_t max_transport_latency; + uint32_t pres_delay_min; + uint32_t pres_delay_max; + uint32_t preferred_pres_delay_min; + uint32_t preferred_pres_delay_max; + types::LeAudioCodecId codec_id; + std::vector<uint8_t> codec_spec_conf; +}; + +constexpr uint16_t kAseStatusCodecQosConfMinLen = 15; +struct ase_qos_configured_state_params { + uint8_t cig_id; + uint8_t cis_id; + uint32_t sdu_interval; + uint8_t framing; + uint8_t phy; + uint16_t max_sdu; + uint8_t retrans_nb; + uint16_t max_transport_latency; + uint32_t pres_delay; +}; + +constexpr uint16_t kAseStatusTransMinLen = 1; +struct ase_transient_state_params { + std::vector<uint8_t> metadata; +}; + +constexpr uint16_t kCtpAseEntryMinLen = 3; +struct ctp_ase_entry { + uint8_t ase_id; + uint8_t response_code; + uint8_t reason; +}; + +constexpr uint16_t kCtpNtfMinLen = 2; +struct ctp_ntf { + uint8_t op; + std::vector<struct ctp_ase_entry> entries; +}; + +constexpr uint16_t kAseRspHdrMinLen = 2; +struct ase_rsp_hdr { + uint8_t id; + uint8_t state; +}; + +constexpr uint8_t kCtpOpSize = 1; +constexpr uint8_t kAseNumSize = 1; +constexpr uint8_t kAseIdSize = 1; + +constexpr uint16_t kCtpCodecConfMinLen = 9; +struct ctp_codec_conf { + uint8_t ase_id; + uint8_t target_latency; + uint8_t target_phy; + types::LeAudioCodecId codec_id; + types::LeAudioLc3Config codec_config; +}; + +constexpr uint16_t kCtpQosConfMinLen = 16; +struct ctp_qos_conf { + uint8_t ase_id; + uint8_t cig; + uint8_t cis; + uint32_t sdu_interval; + uint8_t framing; + uint8_t phy; + uint16_t max_sdu; + uint8_t retrans_nb; + uint16_t max_transport_latency; + uint32_t pres_delay; +}; + +constexpr uint16_t kCtpEnableMinLen = 2; +struct ctp_enable { + uint8_t ase_id; + std::vector<uint8_t> metadata; +}; + +constexpr uint16_t kCtpUpdateMetadataMinLen = 2; +struct ctp_update_metadata { + uint8_t ase_id; + std::vector<uint8_t> metadata; +}; + +/* Device control and common functions */ +bool ParseAseStatusHeader(ase_rsp_hdr& rsp, uint16_t len, const uint8_t* value); +bool ParseAseStatusCodecConfiguredStateParams( + struct ase_codec_configured_state_params& rsp, uint16_t len, + const uint8_t* value); +bool ParseAseStatusQosConfiguredStateParams( + struct ase_qos_configured_state_params& rsp, uint16_t len, + const uint8_t* value); +bool ParseAseStatusTransientStateParams(struct ase_transient_state_params& rsp, + uint16_t len, const uint8_t* value); +bool ParseAseCtpNotification(struct ctp_ntf& ntf, uint16_t len, + const uint8_t* value); +bool PrepareAseCtpCodecConfig(const std::vector<struct ctp_codec_conf>& confs, + std::vector<uint8_t>& value); +bool PrepareAseCtpConfigQos(const std::vector<struct ctp_qos_conf>& confs, + std::vector<uint8_t>& value); +bool PrepareAseCtpEnable(const std::vector<struct ctp_enable>& confs, + std::vector<uint8_t>& value); +bool PrepareAseCtpAudioReceiverStartReady(const std::vector<uint8_t>& ids, + std::vector<uint8_t>& value); +bool PrepareAseCtpDisable(const std::vector<uint8_t>& ids, + std::vector<uint8_t>& value); +bool PrepareAseCtpAudioReceiverStopReady(const std::vector<uint8_t>& ids, + std::vector<uint8_t>& value); +bool PrepareAseCtpUpdateMetadata( + const std::vector<struct ctp_update_metadata>& confs, + std::vector<uint8_t>& value); +bool PrepareAseCtpRelease(const std::vector<uint8_t>& ids, + std::vector<uint8_t>& value); +} // namespace ascs + +namespace pacs { + +constexpr uint16_t kAcsPacRecordMinLen = 7; +constexpr uint8_t kAcsPacMetadataLenLen = 1; +constexpr uint16_t kAcsPacDiscoverRspMinLen = 1; + +constexpr uint16_t kAudioLocationsRspMinLen = 4; + +constexpr uint16_t kAseAudioAvailRspMinLen = 4; +struct acs_available_audio_contexts { + std::bitset<16> snk_avail_cont; + std::bitset<16> src_avail_cont; +}; + +constexpr uint16_t kAseAudioSuppContRspMinLen = 4; +struct acs_supported_audio_contexts { + std::bitset<16> snk_supp_cont; + std::bitset<16> src_supp_cont; +}; + +bool ParsePac(std::vector<struct types::acs_ac_record>& pac_recs, uint16_t len, + const uint8_t* value); +bool ParseAudioLocations(types::AudioLocations& audio_locations, uint16_t len, + const uint8_t* value); +bool ParseAvailableAudioContexts(struct acs_available_audio_contexts& rsp, + uint16_t len, const uint8_t* value); +bool ParseSupportedAudioContexts(struct acs_supported_audio_contexts& rsp, + uint16_t len, const uint8_t* value); +} // namespace pacs +} // namespace client_parser +} // namespace le_audio diff --git a/system/bta/le_audio/client_parser_test.cc b/system/bta/le_audio/client_parser_test.cc new file mode 100644 index 0000000000000000000000000000000000000000..2927792bc5c731289ae823d4874763d0a5fe32eb --- /dev/null +++ b/system/bta/le_audio/client_parser_test.cc @@ -0,0 +1,1647 @@ +/* + * Copyright 2021 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * 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 "client_parser.h" + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include "le_audio_types.h" + +namespace le_audio { +namespace client_parser { +namespace pacs { + +TEST(LeAudioClientParserTest, testParsePacInvalidLength) { + std::vector<struct types::acs_ac_record> pac_recs; + + const uint8_t invalid_num_records[] = {0x01}; + ASSERT_FALSE( + ParsePac(pac_recs, sizeof(invalid_num_records), invalid_num_records)); + + const uint8_t no_caps_len[] = { + // Num records + 0x01, + // Codec_ID + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + }; + ASSERT_FALSE(ParsePac(pac_recs, sizeof(no_caps_len), no_caps_len)); + + const uint8_t no_metalen[] = { + // Num records + 0x01, + // Codec_ID + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + // Codec Spec. Caps. Len + 0x00, + }; + ASSERT_FALSE(ParsePac(pac_recs, sizeof(no_metalen), no_metalen)); +} + +TEST(LeAudioClientParserTest, testParsePacEmpty) { + std::vector<struct types::acs_ac_record> pac_recs; + const uint8_t value[] = {0x00}; + + ASSERT_TRUE(ParsePac(pac_recs, sizeof(value), value)); +} + +TEST(LeAudioClientParserTest, testParsePacEmptyCapsEmptyMeta) { + std::vector<struct types::acs_ac_record> pac_recs; + + const uint8_t value[] = { + // Num records + 0x01, + // Codec_ID + 0x01, + 0x03, + 0x02, + 0x05, + 0x04, + // Codec Spec. Caps. Len + 0x00, + // Metadata Length + 0x00, + }; + ASSERT_TRUE(ParsePac(pac_recs, sizeof(value), value)); + + ASSERT_EQ(pac_recs.size(), 1u); + ASSERT_EQ(pac_recs[0].codec_id.coding_format, 0x01u); + ASSERT_EQ(pac_recs[0].codec_id.vendor_company_id, 0x0203u); + ASSERT_EQ(pac_recs[0].codec_id.vendor_codec_id, 0x0405u); +} + +TEST(LeAudioClientParserTest, testParsePacInvalidCapsLen) { + std::vector<struct types::acs_ac_record> pac_recs; + + const uint8_t bad_capslem[] = { + // Num records + 0x01, + // Codec_ID + 0x01, + 0x03, + 0x02, + 0x05, + 0x04, + // Codec Spec. Caps. Len + 0x05, + // Codec Spec. Caps. + 0x02, // [0].length, + 0x02, // [0].type, + 0x03, // [0].value[0] + 0x03, // [1].length + 0x03, // [1].type + 0x04, // [1].value[0] + 0x05, // [1].value[1] + // Metadata Length + 0x00, + }; + ASSERT_FALSE(ParsePac(pac_recs, sizeof(bad_capslem), bad_capslem)); + + std::vector<struct types::acs_ac_record> pac_recs2; + + const uint8_t bad_capslen2[] = { + // Num records + 0x01, + // Codec_ID + 0x01, + 0x03, + 0x02, + 0x05, + 0x04, + // Codec Spec. Caps. Len + 0x20, + // Codec Spec. Caps. + 0x02, // [0].length + 0x02, // [0].type + 0x03, // [0].value[0] + 0x03, // [1].length + 0x03, // [1].type + 0x04, // [1].value[0] + 0x05, // [1].value[1] + // Metadata Length + 0x00, + }; + ASSERT_FALSE(ParsePac(pac_recs2, sizeof(bad_capslen2), bad_capslen2)); +} + +TEST(LeAudioClientParserTest, testParsePacInvalidCapsLtvLen) { + std::vector<struct types::acs_ac_record> pac_recs; + + const uint8_t bad_ltv_len[] = { + // Num records + 0x01, + // Codec_ID + 0x01, + 0x03, + 0x02, + 0x05, + 0x04, + // Codec Spec. Caps. Len + 0x07, + // Codec Spec. Caps. + 0x02, // [0].length + 0x02, // [0].type + 0x03, // [0].value[0] + 0x06, // [1].bad_length + 0x03, // [1].type + 0x04, // [1].value[0] + 0x05, // [1].value[1] + // Metadata Length + 0x00, + }; + ASSERT_FALSE(ParsePac(pac_recs, sizeof(bad_ltv_len), bad_ltv_len)); + + const uint8_t bad_ltv_len2[] = { + // Num records + 0x01, + // Codec_ID + 0x01, + 0x03, + 0x02, + 0x05, + 0x04, + // Codec Spec. Caps. Len + 0x07, + // Codec Spec. Caps. + 0x02, // [0].length + 0x02, // [0].type + 0x03, // [0].value[0] + 0x04, // [1].bad_length + 0x03, // [1].type + 0x04, // [1].value[0] + 0x05, // [1].value[1] + // Metadata Length + 0x00, + }; + ASSERT_FALSE(ParsePac(pac_recs, sizeof(bad_ltv_len2), bad_ltv_len2)); +} + +TEST(LeAudioClientParserTest, testParsePacNullLtv) { + std::vector<struct types::acs_ac_record> pac_recs; + + const uint8_t value[] = { + // Num records + 0x01, + // Codec_ID + 0x01, + 0x03, + 0x02, + 0x05, + 0x04, + // Codec Spec. Caps. Len + 0x0A, + // Codec Spec. Caps. + 0x02, // [0].length + 0x02, // [0].type + 0x03, // [0].value[0] + 0x03, // [1].length + 0x03, // [1].type + 0x04, // [1].value[0] + 0x05, // [1].value[1] + 0x01, // [2].length <-- a capability without a value + 0x04, // [2].type + 0x00, // [3]length <-- this seems possible although useless + // Metadata Length + 0x00, + }; + ASSERT_TRUE(ParsePac(pac_recs, sizeof(value), value)); + + ASSERT_EQ(pac_recs.size(), 1u); + ASSERT_EQ(pac_recs[0].codec_id.coding_format, 0x01u); + ASSERT_EQ(pac_recs[0].codec_id.vendor_company_id, 0x0203u); + ASSERT_EQ(pac_recs[0].codec_id.vendor_codec_id, 0x0405u); + + auto codec_spec_caps = pac_recs[0].codec_spec_caps.Values(); + ASSERT_EQ(codec_spec_caps.size(), 3u); + ASSERT_EQ(codec_spec_caps.count(0x02u), 1u); + ASSERT_EQ(codec_spec_caps[0x02u].size(), 1u); + ASSERT_EQ(codec_spec_caps[0x02u][0], 0x03u); + ASSERT_EQ(codec_spec_caps.count(0x03u), 1u); + ASSERT_EQ(codec_spec_caps[0x03u].size(), 2u); + ASSERT_EQ(codec_spec_caps[0x03u][0], 0x04u); + ASSERT_EQ(codec_spec_caps[0x03u][1], 0x05u); + ASSERT_EQ(codec_spec_caps.count(0x04u), 1u); + ASSERT_EQ(codec_spec_caps[0x04u].size(), 0u); +} + +TEST(LeAudioClientParserTest, testParsePacEmptyMeta) { + std::vector<struct types::acs_ac_record> pac_recs; + + const uint8_t value[] = { + // Num records + 0x01, + // Codec_ID + 0x01, + 0x03, + 0x02, + 0x05, + 0x04, + // Codec Spec. Caps. Len + 0x07, + // Codec Spec. Caps. + 0x02, // [0].length + 0x02, // [0].type + 0x03, // [0].value[0] + 0x03, // [1].length + 0x03, // [1].type + 0x04, // [1].value[0] + 0x05, // [1].value[1] + // Metadata Length + 0x00, + }; + ASSERT_TRUE(ParsePac(pac_recs, sizeof(value), value)); + + ASSERT_EQ(pac_recs.size(), 1u); + ASSERT_EQ(pac_recs[0].codec_id.coding_format, 0x01u); + ASSERT_EQ(pac_recs[0].codec_id.vendor_company_id, 0x0203u); + ASSERT_EQ(pac_recs[0].codec_id.vendor_codec_id, 0x0405u); + + auto codec_spec_caps = pac_recs[0].codec_spec_caps.Values(); + ASSERT_EQ(codec_spec_caps.size(), 2u); + ASSERT_EQ(codec_spec_caps.count(0x02u), 1u); + ASSERT_EQ(codec_spec_caps[0x02u].size(), 1u); + ASSERT_EQ(codec_spec_caps[0x02u][0], 0x03u); + ASSERT_EQ(codec_spec_caps.count(0x03u), 1u); + ASSERT_EQ(codec_spec_caps[0x03u].size(), 2u); + ASSERT_EQ(codec_spec_caps[0x03u][0], 0x04u); + ASSERT_EQ(codec_spec_caps[0x03u][1], 0x05u); +} + +TEST(LeAudioClientParserTest, testParsePacInvalidMetaLength) { + std::vector<struct types::acs_ac_record> pac_recs; + + const uint8_t value[] = { + // Num records + 0x01, + // Codec_ID + 0x01, 0x03, 0x02, 0x05, 0x04, + // Codec Spec. Caps. Len + 0x07, + // Codec Spec. Caps. + 0x02, // [0].length + 0x02, // [0].type + 0x03, // [0].value[0] + 0x03, // [1].length + 0x03, // [1].type + 0x04, // [1].value[0] + 0x05, // [1].value[1] + // Metadata Length + 0x05, + // Metadata + 0x03, // [0].length + 0x02, // [0].type + 0x01, // [0].value[0] + 0x00, // [0].value[1] + }; + ASSERT_FALSE(ParsePac(pac_recs, sizeof(value), value)); +} + +TEST(LeAudioClientParserTest, testParsePacValidMeta) { + std::vector<struct types::acs_ac_record> pac_recs; + + const uint8_t value[] = { + // Num records + 0x01, + // Codec_ID + 0x01, 0x03, 0x02, 0x05, 0x04, + // Codec Spec. Caps. Len + 0x07, + // Codec Spec. Caps. + 0x02, // [0].length + 0x02, // [0].type + 0x03, // [0].value[0] + 0x03, // [1].length + 0x03, // [1].type + 0x04, // [1].value[0] + 0x05, // [1].value[1] + // Metadata Length + 0x04, + // Metadata + 0x03, // [0].length + 0x02, // [0].type + 0x01, // [0].value[0] + 0x00, // [0].value[1] + }; + ASSERT_TRUE(ParsePac(pac_recs, sizeof(value), value)); + + ASSERT_EQ(pac_recs.size(), 1u); + ASSERT_EQ(pac_recs[0].codec_id.coding_format, 0x01u); + ASSERT_EQ(pac_recs[0].codec_id.vendor_company_id, 0x0203u); + ASSERT_EQ(pac_recs[0].codec_id.vendor_codec_id, 0x0405u); + + auto codec_spec_caps = pac_recs[0].codec_spec_caps.Values(); + ASSERT_EQ(codec_spec_caps.size(), 2u); + ASSERT_EQ(codec_spec_caps.count(0x02u), 1u); + ASSERT_EQ(codec_spec_caps[0x02u].size(), 1u); + ASSERT_EQ(codec_spec_caps[0x02u][0], 0x03u); + ASSERT_EQ(codec_spec_caps.count(0x03u), 1u); + ASSERT_EQ(codec_spec_caps[0x03u].size(), 2u); + ASSERT_EQ(codec_spec_caps[0x03u][0], 0x04u); + ASSERT_EQ(codec_spec_caps[0x03u][1], 0x05u); + + ASSERT_EQ(pac_recs[0].metadata.size(), 4u); + ASSERT_EQ(pac_recs[0].metadata[0], 0x03u); + ASSERT_EQ(pac_recs[0].metadata[1], 0x02u); + ASSERT_EQ(pac_recs[0].metadata[2], 0x01u); + ASSERT_EQ(pac_recs[0].metadata[3], 0x00u); +} + +TEST(LeAudioClientParserTest, testParsePacInvalidNumRecords) { + std::vector<struct types::acs_ac_record> pac_recs; + + const uint8_t value[] = { + // Num records + 0x02, + // Codec_ID + 0x01, 0x03, 0x02, 0x05, 0x04, + // Codec Spec. Caps. Len + 0x07, + // Codec Spec. Caps. + 0x02, // [0].length + 0x02, // [0].type + 0x03, // [0].value[0] + 0x03, // [1].length + 0x03, // [1].type + 0x04, // [1].value[0] + 0x05, // [1].value[1] + // Metadata Length + 0x04, + // Metadata + 0x03, // [0].length + 0x02, // [0].type + 0x01, // [0].value[0] + 0x00, // [0].value[1] + }; + ASSERT_FALSE(ParsePac(pac_recs, sizeof(value), value)); +} + +TEST(LeAudioClientParserTest, testParsePacMultipleRecords) { + std::vector<struct types::acs_ac_record> pac_recs; + + const uint8_t value[] = { + // Num records + 0x03, + // Codec_ID + 0x01, 0x03, 0x02, 0x05, 0x04, + // Codec Spec. Caps. Len + 0x00, + // Metadata Length + 0x00, + // Codec_ID + 0x06, 0x08, 0x07, 0x0A, 0x09, + // Codec Spec. Caps. Len + 0x03, + // Codec Spec. Caps. + 0x02, // [0].length + 0x02, // [0].type + 0x03, // [0].value[0] + // Metadata Length + 0x04, + // Metadata + 0x03, // [0].length + 0x02, // [0].type + 0x01, // [0].value[0] + 0x00, // [0].value[1], + // Codec_ID + 0x11, 0x13, 0x12, 0x15, 0x14, + // Codec Spec. Caps. Len + 0x07, + // Codec Spec. Caps. + 0x02, // [0].length + 0x12, // [0].type + 0x13, // [0].value[0] + 0x03, // [1].length + 0x13, // [1].type + 0x14, // [1].value[0] + 0x15, // [1].value[1] + // Metadata Length + 0x04, + // Metadata + 0x03, // [0].length + 0x12, // [0].type + 0x11, // [0].value[0] + 0x10, // [0].value[1] + }; + ASSERT_TRUE(ParsePac(pac_recs, sizeof(value), value)); + ASSERT_EQ(pac_recs.size(), 3u); + + // Verify 1st record + auto& record0 = pac_recs[0]; + + ASSERT_EQ(record0.codec_id.coding_format, 0x01u); + ASSERT_EQ(record0.codec_id.vendor_company_id, 0x0203u); + ASSERT_EQ(record0.codec_id.vendor_codec_id, 0x0405u); + ASSERT_EQ(record0.codec_spec_caps.Size(), 0u); + ASSERT_EQ(record0.metadata.size(), 0u); + + // Verify 2nd record + auto& record1 = pac_recs[1]; + + ASSERT_EQ(record1.codec_id.coding_format, 0x06u); + ASSERT_EQ(record1.codec_id.vendor_company_id, 0x0708u); + ASSERT_EQ(record1.codec_id.vendor_codec_id, 0x090Au); + + auto codec_spec_caps1 = record1.codec_spec_caps.Values(); + ASSERT_EQ(codec_spec_caps1.size(), 1u); + ASSERT_EQ(codec_spec_caps1.count(0x02u), 1u); + ASSERT_EQ(codec_spec_caps1[0x02u].size(), 1u); + ASSERT_EQ(codec_spec_caps1[0x02u][0], 0x03u); + + ASSERT_EQ(record1.metadata.size(), 4u); + ASSERT_EQ(record1.metadata[0], 0x03u); + ASSERT_EQ(record1.metadata[1], 0x02u); + ASSERT_EQ(record1.metadata[2], 0x01u); + ASSERT_EQ(record1.metadata[3], 0x00u); + + // Verify 3rd record + auto& record2 = pac_recs[2]; + + ASSERT_EQ(record2.codec_id.coding_format, 0x11u); + ASSERT_EQ(record2.codec_id.vendor_company_id, 0x1213u); + ASSERT_EQ(record2.codec_id.vendor_codec_id, 0x1415u); + + auto codec_spec_caps2 = record2.codec_spec_caps.Values(); + ASSERT_EQ(codec_spec_caps2.size(), 2u); + ASSERT_EQ(codec_spec_caps2.count(0x12u), 1u); + ASSERT_EQ(codec_spec_caps2[0x12u].size(), 1u); + ASSERT_EQ(codec_spec_caps2[0x12u][0], 0x13u); + ASSERT_EQ(codec_spec_caps2.count(0x13u), 1u); + ASSERT_EQ(codec_spec_caps2[0x13u].size(), 2u); + ASSERT_EQ(codec_spec_caps2[0x13u][0], 0x14u); + ASSERT_EQ(codec_spec_caps2[0x13u][1], 0x15u); + + ASSERT_EQ(record2.metadata.size(), 4u); + ASSERT_EQ(record2.metadata[0], 0x03u); + ASSERT_EQ(record2.metadata[1], 0x12u); + ASSERT_EQ(record2.metadata[2], 0x11u); + ASSERT_EQ(record2.metadata[3], 0x10u); +} + +TEST(LeAudioClientParserTest, testParseAudioLocationsInvalidLength) { + types::AudioLocations locations = + codec_spec_conf::kLeAudioLocationMonoUnspecified; + const uint8_t value1[] = { + 0x01, + 0x02, + 0x03, + }; + ParseAudioLocations(locations, sizeof(value1), value1); + ASSERT_EQ(locations, 0u); + + const uint8_t value2[] = {0x01, 0x02, 0x03, 0x04, 0x05}; + ParseAudioLocations(locations, sizeof(value2), value2); + ASSERT_EQ(locations, 0u); +} + +TEST(LeAudioClientParserTest, testParseAudioLocations) { + types::AudioLocations locations = + codec_spec_conf::kLeAudioLocationMonoUnspecified; + const uint8_t value1[] = {0x01, 0x02, 0x03, 0x04}; + ParseAudioLocations(locations, sizeof(value1), value1); + ASSERT_EQ(locations, 0x04030201u); +} + +TEST(LeAudioClientParserTest, testParseAvailableAudioContextsInvalidLength) { + acs_available_audio_contexts avail_contexts; + const uint8_t value1[] = { + // Sink available contexts + 0x01, 0x02, + // Missing Source available contexts + }; + + ParseAvailableAudioContexts(avail_contexts, sizeof(value1), value1); + ASSERT_EQ(avail_contexts.snk_avail_cont, 0u); + ASSERT_EQ(avail_contexts.src_avail_cont, 0u); +} + +TEST(LeAudioClientParserTest, testParseAvailableAudioContexts) { + acs_available_audio_contexts avail_contexts; + const uint8_t value1[] = { + // Sink available contexts + 0x01, + 0x02, + // Source available contexts + 0x03, + 0x04, + }; + + ParseAvailableAudioContexts(avail_contexts, sizeof(value1), value1); + ASSERT_EQ(avail_contexts.snk_avail_cont, 0x0201u); + ASSERT_EQ(avail_contexts.src_avail_cont, 0x0403u); +} + +TEST(LeAudioClientParserTest, testParseSupportedAudioContextsInvalidLength) { + acs_supported_audio_contexts supp_contexts; + const uint8_t value1[] = { + // Sink supported contexts + 0x01, 0x02, + // Missing Source supported contexts + }; + + ParseSupportedAudioContexts(supp_contexts, sizeof(value1), value1); + ASSERT_EQ(supp_contexts.snk_supp_cont, 0u); + ASSERT_EQ(supp_contexts.src_supp_cont, 0u); +} + +TEST(LeAudioClientParserTest, testParseSupportedAudioContexts) { + acs_supported_audio_contexts supp_contexts; + const uint8_t value1[] = { + // Sink supported contexts + 0x01, + 0x02, + // Source supported contexts + 0x03, + 0x04, + }; + + ParseSupportedAudioContexts(supp_contexts, sizeof(value1), value1); + ASSERT_EQ(supp_contexts.snk_supp_cont, 0x0201u); + ASSERT_EQ(supp_contexts.src_supp_cont, 0x0403u); +} + +} // namespace pacs + +namespace ascs { + +TEST(LeAudioClientParserTest, testParseAseStatusHeaderInvalidLength) { + ase_rsp_hdr arh; + const uint8_t value1[] = { + // Ase ID + 0x01, + // ASE State is missing here + }; + ASSERT_FALSE(ParseAseStatusHeader(arh, sizeof(value1), value1)); +} + +TEST(LeAudioClientParserTest, testParseAseStatusHeader) { + ase_rsp_hdr arh; + const uint8_t value1[] = { + // Ase ID + 0x01, + // ASE State + 0x00, // 'Idle' state + // No additional ASE Params for the 'Idle' state + }; + ASSERT_TRUE(ParseAseStatusHeader(arh, sizeof(value1), value1)); + ASSERT_EQ(arh.id, 0x01u); + ASSERT_EQ(arh.state, 0x00u); + + const uint8_t value2[] = { + // Ase ID + 0x02, + // ASE State + 0x04, // 'Streaming' state + // Additional ASE Params for the 'Streaming' state + // Metadata Len + 0x03, + // Metadata + 0x03, // [0].length + 0x02, // [0].type + 0x01, // [0].value[0] + 0x00, // [0].value[1] + }; + ASSERT_TRUE(ParseAseStatusHeader(arh, sizeof(value2), value2)); + ASSERT_EQ(arh.id, 0x02u); + ASSERT_EQ(arh.state, 0x04u); + // Currently additional state parameters are not handled +} + +TEST(LeAudioClientParserTest, + testParseAseStatusCodecConfiguredStateParamsInvalidLength) { + ase_codec_configured_state_params codec_configured_state_params; + const uint8_t value1[] = { + // Ase ID + 0x02, + // ASE State + 0x01, // 'Codec Configured' state + // Framing + 0x01, // Unframed + // Peferred PHY + 0x02, // 2M PHY + // Preferred retransimssion Num. + 0x04, + // Max transport Latency + 0x05, 0x00, + // Pressentation delay min. + 0x00, 0x01, 0x02, 0x03, + // Pressentation delay max. + 0x00, 0x01, 0x02, 0x03, + // Preferred presentation delay min. + 0x01, 0x02, 0x03, + // Preferred presentation delay max. + 0x01, 0x02, 0x03, + // Codec ID + 0x01, 0x02, 0x03, 0x04, 0x05, + // Missing Codec spec. conf. length + }; + + ASSERT_FALSE(ParseAseStatusCodecConfiguredStateParams( + codec_configured_state_params, sizeof(value1) - 2, value1 + 2)); +} + +TEST(LeAudioClientParserTest, testParseAseStatusCodecConfiguredStateParams) { + ase_codec_configured_state_params codec_configured_state_params; + const uint8_t value1[] = { + // Ase ID + 0x01, + // ASE State + 0x01, // 'Codec Configured' state + // Framing + 0x01, // Unframed + // Peferred PHY + 0x02, // 2M PHY + // Preferred retransimssion Num. + 0x04, + // Max transport Latency + 0x05, + 0x00, + // Pressentation delay min. + 0x00, + 0x01, + 0x02, + // Pressentation delay max. + 0x10, + 0x11, + 0x12, + // Preferred presentation delay min. + 0x01, + 0x02, + 0x03, + // Preferred presentation delay max. + 0x09, + 0x10, + 0x11, + // Codec ID + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + // Codec spec. conf. length + 0x00, + }; + + // State additional parameters are right after the ASE ID and state bytes + ASSERT_TRUE(ParseAseStatusCodecConfiguredStateParams( + codec_configured_state_params, sizeof(value1) - 2, value1 + 2)); + ASSERT_EQ(codec_configured_state_params.framing, 0x01u); + ASSERT_EQ(codec_configured_state_params.preferred_phy, 0x02u); + ASSERT_EQ(codec_configured_state_params.preferred_retrans_nb, 0x04u); + ASSERT_EQ(codec_configured_state_params.max_transport_latency, 0x0005u); + ASSERT_EQ(codec_configured_state_params.pres_delay_min, 0x020100u); + ASSERT_EQ(codec_configured_state_params.pres_delay_max, 0x121110u); + ASSERT_EQ(codec_configured_state_params.preferred_pres_delay_min, 0x030201u); + ASSERT_EQ(codec_configured_state_params.preferred_pres_delay_max, 0x111009u); + ASSERT_EQ(codec_configured_state_params.codec_id.coding_format, 0x01u); + ASSERT_EQ(codec_configured_state_params.codec_id.vendor_company_id, 0x0302u); + ASSERT_EQ(codec_configured_state_params.codec_id.vendor_codec_id, 0x0504u); + ASSERT_EQ(codec_configured_state_params.codec_spec_conf.size(), 0u); + + const uint8_t value2[] = { + // Ase ID + 0x02, + // ASE State + 0x01, // 'Codec Configured' state + // Framing + 0x01, // Unframed + // Peferred PHY + 0x02, // 2M PHY + // Preferred retransimssion Num. + 0x04, + // Max transport Latency + 0x05, + 0x00, + // Pressentation delay min. + 0x00, + 0x01, + 0x02, + // Pressentation delay max. + 0x10, + 0x11, + 0x12, + // Preferred presentation delay min. + 0x01, + 0x02, + 0x03, + // Preferred presentation delay max. + 0x09, + 0x10, + 0x11, + // Codec ID + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + // Codec spec. conf. length + 0x05, + // Codec spec. conf. + 0x0A, + 0x0B, + 0x0C, + 0x0D, + 0x0E, + }; + + // State additional parameters are right after the ASE ID and state bytes + ASSERT_TRUE(ParseAseStatusCodecConfiguredStateParams( + codec_configured_state_params, sizeof(value2) - 2, value2 + 2)); + ASSERT_EQ(codec_configured_state_params.framing, 0x01u); + ASSERT_EQ(codec_configured_state_params.preferred_phy, 0x02u); + ASSERT_EQ(codec_configured_state_params.preferred_retrans_nb, 0x04u); + ASSERT_EQ(codec_configured_state_params.max_transport_latency, 0x0005u); + ASSERT_EQ(codec_configured_state_params.pres_delay_min, 0x020100u); + ASSERT_EQ(codec_configured_state_params.pres_delay_max, 0x121110u); + ASSERT_EQ(codec_configured_state_params.preferred_pres_delay_min, 0x030201u); + ASSERT_EQ(codec_configured_state_params.preferred_pres_delay_max, 0x111009u); + ASSERT_EQ(codec_configured_state_params.codec_id.coding_format, 0x01u); + ASSERT_EQ(codec_configured_state_params.codec_id.vendor_company_id, 0x0302u); + ASSERT_EQ(codec_configured_state_params.codec_id.vendor_codec_id, 0x0504u); + ASSERT_EQ(codec_configured_state_params.codec_spec_conf.size(), 5u); + ASSERT_EQ(codec_configured_state_params.codec_spec_conf[0], 0x0Au); + ASSERT_EQ(codec_configured_state_params.codec_spec_conf[1], 0x0Bu); + ASSERT_EQ(codec_configured_state_params.codec_spec_conf[2], 0x0Cu); + ASSERT_EQ(codec_configured_state_params.codec_spec_conf[3], 0x0Du); + ASSERT_EQ(codec_configured_state_params.codec_spec_conf[4], 0x0Eu); +} + +TEST(LeAudioClientParserTest, + testParseAseStatusQosConfiguredStateParamsInvalidLength) { + struct ase_qos_configured_state_params rsp { + .cig_id = 0, .cis_id = 0 + }; + const uint8_t value1[] = { + // Ase ID + 0x01, + // ASE State + 0x02, // 'QoS Configured' state + 0x03, // CIG_ID + 0x04, // CIS_ID + }; + + ParseAseStatusQosConfiguredStateParams(rsp, sizeof(value1) - 2, value1 + 2); + ASSERT_EQ(rsp.cig_id, 0); + ASSERT_EQ(rsp.cis_id, 0); + + const uint8_t value2[] = { + // Ase ID + 0x01, + // ASE State + 0x02, // 'QoS Configured' state + // CIG_ID + 0x03, + // CIS_ID + 0x04, + // SDU Interval + 0x05, 0x06, 0x07, + // Framing + 0x01, + // PHY + 0x02, + // Max SDU + 0x08, 0x09, + // Retransmission Num. + 0x0A, + // Max Transport Latency + 0x0B, 0x0C, + // Presentation Delay + 0x0D, 0x0E, + // Missing Byte + }; + + ParseAseStatusQosConfiguredStateParams(rsp, sizeof(value2) - 2, value2 + 2); + ASSERT_EQ(rsp.cig_id, 0); + ASSERT_EQ(rsp.cis_id, 0); +} + +TEST(LeAudioClientParserTest, testParseAseStatusQosConfiguredStateParams) { + struct ase_qos_configured_state_params rsp; + const uint8_t value[] = { + // Ase ID + 0x01, + // ASE State - 'QoS Configured' + 0x02, + // CIG_ID + 0x03, + // CIS_ID + 0x04, + // SDU Interval + 0x05, + 0x06, + 0x07, + // Framing + 0x01, + // PHY + 0x02, + // Max SDU + 0x18, + 0x19, + // Retransmission Num. + 0x1A, + // Max Transport Latency + 0x1B, + 0x1C, + // Presentation Delay + 0x1D, + 0x1E, + 0x1F, + }; + + ParseAseStatusQosConfiguredStateParams(rsp, sizeof(value) - 2, value + 2); + ASSERT_EQ(rsp.cig_id, 0x03u); + ASSERT_EQ(rsp.cis_id, 0x04u); + ASSERT_EQ(rsp.sdu_interval, 0x070605u); + ASSERT_EQ(rsp.framing, 0x01u); + ASSERT_EQ(rsp.phy, 0x02u); + ASSERT_EQ(rsp.max_sdu, 0x1918u); + ASSERT_EQ(rsp.retrans_nb, 0x1Au); + ASSERT_EQ(rsp.max_transport_latency, 0x1C1Bu); + ASSERT_EQ(rsp.pres_delay, 0x1F1E1Du); +} + +TEST(LeAudioClientParserTest, + testParseAseStatusTransientStateParamsInvalidLength) { + ase_transient_state_params params; + const uint8_t value1[] = { + // Ase ID + 0x01, + // ASE State + 0x03, // 'Enabling' state + // missing Metadata length + // missing Metadata + }; + ParseAseStatusTransientStateParams(params, sizeof(value1) - 2, value1 + 2); +} + +TEST(LeAudioClientParserTest, testParseAseStatusTransientStateParams) { + ase_transient_state_params params; + const uint8_t value1[] = { + // Ase ID + 0x01, + // ASE State + 0x03, // 'Enabling' state + // Metadata length + 0x00, + }; + ParseAseStatusTransientStateParams(params, sizeof(value1) - 2, value1 + 2); + ASSERT_EQ(params.metadata.size(), 0u); + + const uint8_t value2[] = { + // Ase ID + 0x01, + // ASE State + 0x03, // 'Enabling' state + // Metadata length + 0x03, + // Metadata + 0x02, // [0].length + 0x01, // [0].type + 0x00, // [0].value[0] + }; + ParseAseStatusTransientStateParams(params, sizeof(value2) - 2, value2 + 2); + + ASSERT_EQ(params.metadata.size(), 3u); + ASSERT_EQ(params.metadata[0], 0x02u); + ASSERT_EQ(params.metadata[1], 0x01u); + ASSERT_EQ(params.metadata[2], 0x00u); +} + +TEST(LeAudioClientParserTest, testParseAseCtpNotificationInvalidLength) { + ctp_ntf ntf; + const uint8_t value1[] = { + // Opcode + 0x01, + // Number of ASEs + 0x02, + // ASE ID + 0x01, + // Response Code + 0x01, + // Reason + 0x01, + // ASE ID + 0x02, + // Response Code + 0x02, + // Missing Reason + }; + ParseAseCtpNotification(ntf, sizeof(value1), value1); + + // In case of invalid payload at least we get the opcode + ASSERT_EQ(ntf.op, 0x01u); + ASSERT_EQ(ntf.entries.size(), 0u); + + const uint8_t value2[] = { + // Opcode + 0x01, + // Missing Number of ASEs + // Missing ASE ID + // Missing Response Code + // Missing Reason + // Missing ASE ID + // Missing Response Code + // Missing Reason + }; + ntf.entries.clear(); + ParseAseCtpNotification(ntf, sizeof(value2), value2); + + // In case of invalid payload at least we get the opcode + ASSERT_EQ(ntf.op, 0x01u); + ASSERT_EQ(ntf.entries.size(), 0u); + + const uint8_t value3[] = { + // Opcode + 0x01, + // Number of ASEs + 0x03, + // ASE ID + 0x01, + // Response Code + 0x01, + // Reason + 0x01, + // ASE ID + 0x02, + // Response Code + 0x02, + // Reason + 0x03, + // Missing the entire ASE entry + }; + + ntf.entries.clear(); + ParseAseCtpNotification(ntf, sizeof(value3), value3); + // In case of invalid payload at least we get the opcode + ASSERT_EQ(ntf.op, 0x01u); + ASSERT_EQ(ntf.entries.size(), 0u); +} + +TEST(LeAudioClientParserTest, testParseAseCtpNotification) { + ctp_ntf ntf; + const uint8_t value1[] = { + // Opcode + 0x01, + // Number of ASEs + 0x02, + // ASE ID + 0x01, + // Response Code + 0x01, + // Reason + 0x01, + // ASE ID + 0x03, + // Response Code + 0x02, + // Reason + 0x03, + }; + ParseAseCtpNotification(ntf, sizeof(value1), value1); + + ASSERT_EQ(ntf.op, 0x01u); + ASSERT_EQ(ntf.entries.size(), 2u); + ASSERT_EQ(ntf.entries[0].ase_id, 0x01u); + ASSERT_EQ(ntf.entries[0].response_code, 0x01u); + ASSERT_EQ(ntf.entries[0].reason, 0x01); + ASSERT_EQ(ntf.entries[1].ase_id, 0x03u); + ASSERT_EQ(ntf.entries[1].response_code, 0x02u); + ASSERT_EQ(ntf.entries[1].reason, 0x03); +} + +TEST(LeAudioClientParserTest, testPrepareAseCtpCodecConfigEmpty) { + std::vector<struct ctp_codec_conf> confs; + std::vector<uint8_t> value; + + PrepareAseCtpCodecConfig(confs, value); + + ASSERT_EQ(value.size(), 0u); +} + +TEST(LeAudioClientParserTest, testPrepareAseCtpCodecConfigSingle) { + std::vector<struct ctp_codec_conf> confs; + std::vector<uint8_t> value; + + types::LeAudioCodecId codec_id{.coding_format = 0x06, + .vendor_company_id = 0x0203, + .vendor_codec_id = 0x0405}; + types::LeAudioLc3Config codec_conf{.sampling_frequency = 0x10, + .frame_duration = 0x03, + .audio_channel_allocation = 0x04050607, + .octets_per_codec_frame = 0x0203}; + + confs.push_back(ctp_codec_conf{ + .ase_id = 0x05, + .target_latency = 0x03, + .target_phy = 0x02, + .codec_id = codec_id, + .codec_config = codec_conf, + }); + PrepareAseCtpCodecConfig(confs, value); + + uint8_t i = 0; + ASSERT_NE(value.size(), 0u); + ASSERT_EQ(value[i++], 0x01); // Config Codec Opcode + ASSERT_EQ(value[i++], 0x01); // Number of ASEs + ASSERT_EQ(value[i++], 0x05); // ASE[0] ASE ID + ASSERT_EQ(value[i++], 0x03); // ASE[0] Target Latency + ASSERT_EQ(value[i++], 0x02); // ASE[0] Target Phy + ASSERT_EQ(value[i++], 0x06); // ASE[0].CodecID Coding Format + ASSERT_EQ(value[i++], 0x03); // ASE[0].CodecID Company ID LSB + ASSERT_EQ(value[i++], 0x02); // ASE[0].CodecID Company ID MSB + ASSERT_EQ(value[i++], 0x05); // ASE[0].CodecID Codec ID LSB + ASSERT_EQ(value[i++], 0x04); // ASE[0].CodecID Codec ID MSB + + // ASE[0].Codec Spec. Conf. Length - LC3 specific + ASSERT_EQ(value[i++], 8 + 8); // * 4*2 bytes for 4 LTV types and lengths + 8 + // bytes for the values + ASSERT_EQ(value[i++], 0x02); // Sampling Freq. Length + ASSERT_EQ(value[i++], 0x01); // Sampling Freq. Type + ASSERT_EQ(value[i++], 0x10); // Sampling Freq. Value + ASSERT_EQ(value[i++], 0x02); // Frame Duration. Length + ASSERT_EQ(value[i++], 0x02); // Frame Duration. Type + ASSERT_EQ(value[i++], 0x03); // Frame Duration. Value + ASSERT_EQ(value[i++], 0x05); // Audio Channel Allocations Length + ASSERT_EQ(value[i++], 0x03); // Audio Channel Allocations Type + ASSERT_EQ(value[i++], 0x07); // Audio Channel Allocations Value[0] + ASSERT_EQ(value[i++], 0x06); // Audio Channel Allocations Value[1] + ASSERT_EQ(value[i++], 0x05); // Audio Channel Allocations Value[2] + ASSERT_EQ(value[i++], 0x04); // Audio Channel Allocations Value[3] + ASSERT_EQ(value[i++], 0x03); // Octets Per Frame Length + ASSERT_EQ(value[i++], 0x04); // Octets Per Frame Type + ASSERT_EQ(value[i++], 0x03); // Octets Per Frame Value[0] + ASSERT_EQ(value[i++], 0x02); // Octets Per Frame Value[1] + ASSERT_EQ(value.size(), i); +} + +TEST(LeAudioClientParserTest, testPrepareAseCtpCodecConfigMultiple) { + std::vector<struct ctp_codec_conf> confs; + std::vector<uint8_t> value; + + types::LeAudioCodecId codec_id{.coding_format = 0x06, + .vendor_company_id = 0x0203, + .vendor_codec_id = 0x0405}; + types::LeAudioLc3Config codec_conf{.sampling_frequency = 0x10, + .frame_duration = 0x03, + .audio_channel_allocation = 0x04050607, + .octets_per_codec_frame = 0x0203}; + + confs.push_back(ctp_codec_conf{ + .ase_id = 0x05, + .target_latency = 0x03, + .target_phy = 0x02, + .codec_id = codec_id, + .codec_config = codec_conf, + }); + PrepareAseCtpCodecConfig(confs, value); + + uint8_t i = 0; + ASSERT_NE(value.size(), 0u); + ASSERT_EQ(value[i++], 0x01); // Config Codec Opcode + ASSERT_EQ(value[i++], 0x01); // Number of ASEs + ASSERT_EQ(value[i++], 0x05); // ASE[0] ASE ID + ASSERT_EQ(value[i++], 0x03); // ASE[0] Target Latency + ASSERT_EQ(value[i++], 0x02); // ASE[0] Target Phy + ASSERT_EQ(value[i++], 0x06); // ASE[0].CodecID Coding Format + ASSERT_EQ(value[i++], 0x03); // ASE[0].CodecID Company ID LSB + ASSERT_EQ(value[i++], 0x02); // ASE[0].CodecID Company ID MSB + ASSERT_EQ(value[i++], 0x05); // ASE[0].CodecID Codec ID LSB + ASSERT_EQ(value[i++], 0x04); // ASE[0].CodecID Codec ID MSB + + // ASE[0].Codec Spec. Conf. Length - LC3 specific + ASSERT_EQ(value[i++], 8 + 8); // * 4*2 bytes for 4 LTV types and lengths + 8 + // bytes for the values + ASSERT_EQ(value[i++], 0x02); // Sampling Freq. Length + ASSERT_EQ(value[i++], 0x01); // Sampling Freq. Type + ASSERT_EQ(value[i++], 0x10); // Sampling Freq. Value + ASSERT_EQ(value[i++], 0x02); // Frame Duration. Length + ASSERT_EQ(value[i++], 0x02); // Frame Duration. Type + ASSERT_EQ(value[i++], 0x03); // Frame Duration. Value + ASSERT_EQ(value[i++], 0x05); // Audio Channel Allocations Length + ASSERT_EQ(value[i++], 0x03); // Audio Channel Allocations Type + ASSERT_EQ(value[i++], 0x07); // Audio Channel Allocations Value[0] + ASSERT_EQ(value[i++], 0x06); // Audio Channel Allocations Value[1] + ASSERT_EQ(value[i++], 0x05); // Audio Channel Allocations Value[2] + ASSERT_EQ(value[i++], 0x04); // Audio Channel Allocations Value[3] + ASSERT_EQ(value[i++], 0x03); // Octets Per Frame Length + ASSERT_EQ(value[i++], 0x04); // Octets Per Frame Type + ASSERT_EQ(value[i++], 0x03); // Octets Per Frame Value[0] + ASSERT_EQ(value[i++], 0x02); // Octets Per Frame Value[1] + ASSERT_EQ(value.size(), i); + + types::LeAudioCodecId codec_id2{.coding_format = 0x16, + .vendor_company_id = 0x1213, + .vendor_codec_id = 0x1415}; + types::LeAudioLc3Config codec_conf2{.sampling_frequency = 0x11, + .frame_duration = 0x13, + .audio_channel_allocation = 0x14151617, + .octets_per_codec_frame = 0x1213}; + + confs.push_back(ctp_codec_conf{ + .ase_id = 0x15, + .target_latency = 0x13, + .target_phy = 0x01, + .codec_id = codec_id2, + .codec_config = codec_conf2, + }); + PrepareAseCtpCodecConfig(confs, value); + + i = 0; + ASSERT_NE(value.size(), 0u); + ASSERT_EQ(value[i++], 0x01); // Config Codec Opcode + ASSERT_EQ(value[i++], 0x02); // Number of ASEs + ASSERT_EQ(value[i++], 0x05); // ASE[0] ASE ID + ASSERT_EQ(value[i++], 0x03); // ASE[0] Target Latency + ASSERT_EQ(value[i++], 0x02); // ASE[0] Target Phy + ASSERT_EQ(value[i++], 0x06); // ASE[0].CodecID Coding Format + ASSERT_EQ(value[i++], 0x03); // ASE[0].CodecID Company ID LSB + ASSERT_EQ(value[i++], 0x02); // ASE[0].CodecID Company ID MSB + ASSERT_EQ(value[i++], 0x05); // ASE[0].CodecID Codec ID LSB + ASSERT_EQ(value[i++], 0x04); // ASE[0].CodecID Codec ID MSB + + // ASE[0].Codec Spec. Conf. Length - LC3 specific + ASSERT_EQ(value[i++], 8 + 8); // * 4*2 bytes for 4 LTV types and lengths + 8 + // bytes for the values + ASSERT_EQ(value[i++], 0x02); // Sampling Freq. Length + ASSERT_EQ(value[i++], 0x01); // Sampling Freq. Type + ASSERT_EQ(value[i++], 0x10); // Sampling Freq. Value + ASSERT_EQ(value[i++], 0x02); // Frame Duration. Length + ASSERT_EQ(value[i++], 0x02); // Frame Duration. Type + ASSERT_EQ(value[i++], 0x03); // Frame Duration. Value + ASSERT_EQ(value[i++], 0x05); // Audio Channel Allocations Length + ASSERT_EQ(value[i++], 0x03); // Audio Channel Allocations Type + ASSERT_EQ(value[i++], 0x07); // Audio Channel Allocations Value[0] + ASSERT_EQ(value[i++], 0x06); // Audio Channel Allocations Value[1] + ASSERT_EQ(value[i++], 0x05); // Audio Channel Allocations Value[2] + ASSERT_EQ(value[i++], 0x04); // Audio Channel Allocations Value[3] + ASSERT_EQ(value[i++], 0x03); // Octets Per Frame Length + ASSERT_EQ(value[i++], 0x04); // Octets Per Frame Type + ASSERT_EQ(value[i++], 0x03); // Octets Per Frame Value[0] + ASSERT_EQ(value[i++], 0x02); // Octets Per Frame Value[1] + + ASSERT_EQ(value[i++], 0x15); // ASE[1] ASE ID + ASSERT_EQ(value[i++], 0x13); // ASE[1] Target Latency + ASSERT_EQ(value[i++], 0x01); // ASE[1] Target Phy + ASSERT_EQ(value[i++], 0x16); // ASE[1].CodecID Coding Format + ASSERT_EQ(value[i++], 0x13); // ASE[1].CodecID Company ID LSB + ASSERT_EQ(value[i++], 0x12); // ASE[1].CodecID Company ID MSB + ASSERT_EQ(value[i++], 0x15); // ASE[1].CodecID Codec ID LSB + ASSERT_EQ(value[i++], 0x14); // ASE[1].CodecID Codec ID MSB + + // ASE[1].Codec Spec. Conf. Length - LC3 specific + ASSERT_EQ(value[i++], 8 + 8); // * 4*2 bytes for 4 LTV types and lengths + 8 + // bytes for the values + ASSERT_EQ(value[i++], 0x02); // Sampling Freq. Length + ASSERT_EQ(value[i++], 0x01); // Sampling Freq. Type + ASSERT_EQ(value[i++], 0x11); // Sampling Freq. Value + ASSERT_EQ(value[i++], 0x02); // Frame Duration. Length + ASSERT_EQ(value[i++], 0x02); // Frame Duration. Type + ASSERT_EQ(value[i++], 0x13); // Frame Duration. Value + ASSERT_EQ(value[i++], 0x05); // Audio Channel Allocations Length + ASSERT_EQ(value[i++], 0x03); // Audio Channel Allocations Type + ASSERT_EQ(value[i++], 0x17); // Audio Channel Allocations Value[0] + ASSERT_EQ(value[i++], 0x16); // Audio Channel Allocations Value[1] + ASSERT_EQ(value[i++], 0x15); // Audio Channel Allocations Value[2] + ASSERT_EQ(value[i++], 0x14); // Audio Channel Allocations Value[3] + ASSERT_EQ(value[i++], 0x03); // Octets Per Frame Length + ASSERT_EQ(value[i++], 0x04); // Octets Per Frame Type + ASSERT_EQ(value[i++], 0x13); // Octets Per Frame Value[0] + ASSERT_EQ(value[i++], 0x12); // Octets Per Frame Value[1] + + ASSERT_EQ(value.size(), i); +} + +TEST(LeAudioClientParserTest, testPrepareAseCtpConfigQosEmpty) { + std::vector<struct ctp_qos_conf> confs; + std::vector<uint8_t> value; + + PrepareAseCtpConfigQos(confs, value); + ASSERT_EQ(value.size(), 0u); +} + +TEST(LeAudioClientParserTest, testPrepareAseCtpConfigQosSingle) { + std::vector<struct ctp_qos_conf> confs; + std::vector<uint8_t> value; + + const ctp_qos_conf conf{.ase_id = 0x01, + .cig = 0x11, + .cis = 0x12, + .sdu_interval = 0x00131415, + .framing = 0x01, + .phy = 0x01, + .max_sdu = 0x0203, + .retrans_nb = 0x04, + .max_transport_latency = 0x0302, + .pres_delay = 0x00121314}; + confs.push_back(conf); + + PrepareAseCtpConfigQos(confs, value); + ASSERT_NE(value.size(), 0u); + + uint8_t i = 0; + ASSERT_EQ(value[i++], 0x02u); // Config QOS Opcode + ASSERT_EQ(value[i++], 0x01u); // Number of ASE + ASSERT_EQ(value[i++], 0x01u); // ASE ID + ASSERT_EQ(value[i++], 0x11u); // CIG ID + ASSERT_EQ(value[i++], 0x12u); // CIS ID + ASSERT_EQ(value[i++], 0x15u); // SDU Interval [0] + ASSERT_EQ(value[i++], 0x14u); // SDU Interval [1] + ASSERT_EQ(value[i++], 0x13u); // SDU Interval [2] + ASSERT_EQ(value[i++], 0x01u); // Framing + ASSERT_EQ(value[i++], 0x01u); // Phy + ASSERT_EQ(value[i++], 0x03u); // Max SDU LSB + ASSERT_EQ(value[i++], 0x02u); // Max SDU MSB + ASSERT_EQ(value[i++], 0x04u); // Retransmission + ASSERT_EQ(value[i++], 0x02u); // Max. Trans. Latency LSB + ASSERT_EQ(value[i++], 0x03u); // Max. Trans. Latency MSB + ASSERT_EQ(value[i++], 0x14u); // Pres. Delay[0] + ASSERT_EQ(value[i++], 0x13u); // Pres. Delay[1] + ASSERT_EQ(value[i++], 0x12u); // Pres. Delay[2] + ASSERT_EQ(value.size(), i); +} + +TEST(LeAudioClientParserTest, testPrepareAseCtpConfigQosMultiple) { + std::vector<struct ctp_qos_conf> confs; + std::vector<uint8_t> value; + + const ctp_qos_conf conf{.ase_id = 0x01, + .cig = 0x11, + .cis = 0x12, + .sdu_interval = 0x131415, + .framing = 0x01, + .phy = 0x01, + .max_sdu = 0x0203, + .retrans_nb = 0x04, + .max_transport_latency = 0x0302, + .pres_delay = 0x121314}; + confs.push_back(conf); + + const ctp_qos_conf conf2{.ase_id = 0x11, + .cig = 0x21, + .cis = 0x22, + .sdu_interval = 0x232425, + .framing = 0x02, + .phy = 0x02, + .max_sdu = 0x2223, + .retrans_nb = 0x24, + .max_transport_latency = 0x2322, + .pres_delay = 0x222324}; + confs.push_back(conf2); + + PrepareAseCtpConfigQos(confs, value); + ASSERT_NE(value.size(), 0u); + + uint8_t i = 0; + ASSERT_EQ(value[i++], 0x02u); // Config QOS Opcode + ASSERT_EQ(value[i++], 0x02u); // Number of ASE + // 1st ASE Config + ASSERT_EQ(value[i++], 0x01u); // ASE ID + ASSERT_EQ(value[i++], 0x11u); // CIG ID + ASSERT_EQ(value[i++], 0x12u); // CIS ID + ASSERT_EQ(value[i++], 0x15u); // SDU Interval [0] + ASSERT_EQ(value[i++], 0x14u); // SDU Interval [1] + ASSERT_EQ(value[i++], 0x13u); // SDU Interval [2] + ASSERT_EQ(value[i++], 0x01u); // Framing + ASSERT_EQ(value[i++], 0x01u); // Phy + ASSERT_EQ(value[i++], 0x03u); // Max SDU LSB + ASSERT_EQ(value[i++], 0x02u); // Max SDU MSB + ASSERT_EQ(value[i++], 0x04u); // Retransmission + ASSERT_EQ(value[i++], 0x02u); // Max. Trans. Latency LSB + ASSERT_EQ(value[i++], 0x03u); // Max. Trans. Latency MSB + ASSERT_EQ(value[i++], 0x14u); // Pres. Delay[0] + ASSERT_EQ(value[i++], 0x13u); // Pres. Delay[1] + ASSERT_EQ(value[i++], 0x12u); // Pres. Delay[2] + // 2nd ASE Config + ASSERT_EQ(value[i++], 0x11u); // ASE ID + ASSERT_EQ(value[i++], 0x21u); // CIG ID + ASSERT_EQ(value[i++], 0x22u); // CIS ID + ASSERT_EQ(value[i++], 0x25u); // SDU Interval [0] + ASSERT_EQ(value[i++], 0x24u); // SDU Interval [1] + ASSERT_EQ(value[i++], 0x23u); // SDU Interval [2] + ASSERT_EQ(value[i++], 0x02u); // Framing + ASSERT_EQ(value[i++], 0x02u); // Phy + ASSERT_EQ(value[i++], 0x23u); // Max SDU LSB + ASSERT_EQ(value[i++], 0x22u); // Max SDU MSB + ASSERT_EQ(value[i++], 0x24u); // Retransmission + ASSERT_EQ(value[i++], 0x22u); // Max. Trans. Latency LSB + ASSERT_EQ(value[i++], 0x23u); // Max. Trans. Latency MSB + ASSERT_EQ(value[i++], 0x24u); // Pres. Delay[0] + ASSERT_EQ(value[i++], 0x23u); // Pres. Delay[1] + ASSERT_EQ(value[i++], 0x22u); // Pres. Delay[2] + + ASSERT_EQ(value.size(), i); +} + +TEST(LeAudioClientParserTest, testPrepareAseCtpEnableEmpty) { + std::vector<struct ctp_enable> confs; + std::vector<uint8_t> value; + + PrepareAseCtpEnable(confs, value); + ASSERT_EQ(value.size(), 0u); +} + +TEST(LeAudioClientParserTest, testPrepareAseCtpEnableSingle) { + std::vector<struct ctp_enable> confs; + std::vector<uint8_t> value; + + ctp_enable conf{.ase_id = 0x11, .metadata = {0x02, 0x22, 0x21}}; + confs.push_back(conf); + + PrepareAseCtpEnable(confs, value); + ASSERT_NE(value.size(), 0u); + + uint8_t i = 0; + ASSERT_EQ(value[i++], 0x03u); // Enable Opcode + ASSERT_EQ(value[i++], 0x01u); // Number of ASEs + ASSERT_EQ(value[i++], 0x11u); // ASE[0] ID + ASSERT_EQ(value[i++], 0x03u); // Metadata Len + ASSERT_EQ(value[i++], 0x02u); // Metadata[0] + ASSERT_EQ(value[i++], 0x22u); // Metadata[1] + ASSERT_EQ(value[i++], 0x21u); // Metadata[2] + ASSERT_EQ(value.size(), i); +} + +TEST(LeAudioClientParserTest, testPrepareAseCtpEnableMultiple) { + std::vector<struct ctp_enable> confs; + std::vector<uint8_t> value; + + ctp_enable conf{.ase_id = 0x11, .metadata = {0x02, 0x22, 0x21}}; + confs.push_back(conf); + + ctp_enable conf2{.ase_id = 0x21, .metadata = {0x03, 0x35, 0x36, 0x37}}; + confs.push_back(conf2); + + PrepareAseCtpEnable(confs, value); + ASSERT_NE(value.size(), 0u); + + uint8_t i = 0; + ASSERT_EQ(value[i++], 0x03u); // Enable Opcode + ASSERT_EQ(value[i++], 0x02u); // Number of ASEs + ASSERT_EQ(value[i++], 0x11u); // ASE[0] ID + ASSERT_EQ(value[i++], 0x03u); // ASE[0] Metadata Len + ASSERT_EQ(value[i++], 0x02u); // ASE[0] Metadata[0] + ASSERT_EQ(value[i++], 0x22u); // ASE[0] Metadata[1] + ASSERT_EQ(value[i++], 0x21u); // ASE[0] Metadata[2] + ASSERT_EQ(value[i++], 0x21u); // ASE[1] ID + ASSERT_EQ(value[i++], 0x04u); // ASE[1] Metadata Len + ASSERT_EQ(value[i++], 0x03u); // ASE[1] Metadata[0] + ASSERT_EQ(value[i++], 0x35u); // ASE[1] Metadata[1] + ASSERT_EQ(value[i++], 0x36u); // ASE[1] Metadata[2] + ASSERT_EQ(value[i++], 0x37u); // ASE[1] Metadata[3] + ASSERT_EQ(value.size(), i); +} + +TEST(LeAudioClientParserTest, testPrepareAseCtpAudioReceiverStartReadyEmpty) { + std::vector<uint8_t> ase_ids; + std::vector<uint8_t> value; + + PrepareAseCtpAudioReceiverStartReady(ase_ids, value); + ASSERT_EQ(value.size(), 0u); +} + +TEST(LeAudioClientParserTest, testPrepareAseCtpAudioReceiverStartReadySingle) { + std::vector<uint8_t> ase_ids; + std::vector<uint8_t> value; + + ase_ids.push_back(0x11); + + PrepareAseCtpAudioReceiverStartReady(ase_ids, value); + + uint8_t i = 0; + ASSERT_NE(value.size(), 0u); + ASSERT_EQ(value[i++], 0x04u); // Receiver Start Ready Opcode + ASSERT_EQ(value[i++], 1u); // Number of ASEs + ASSERT_EQ(value[i++], 0x11u); // ASE[0] ID + ASSERT_EQ(i, value.size()); +} + +TEST(LeAudioClientParserTest, + testPrepareAseCtpAudioReceiverStartReadyMultiple) { + std::vector<uint8_t> ase_ids; + std::vector<uint8_t> value; + + ase_ids.push_back(0x11); + ase_ids.push_back(0x36); + + PrepareAseCtpAudioReceiverStartReady(ase_ids, value); + + uint8_t i = 0; + ASSERT_NE(value.size(), 0u); + ASSERT_EQ(value[i++], 0x04u); // Receiver Start Ready Opcode + ASSERT_EQ(value[i++], 2u); // Number of ASEs + ASSERT_EQ(value[i++], 0x11u); // ASE[0] ID + ASSERT_EQ(value[i++], 0x36u); // ASE[0] ID + ASSERT_EQ(i, value.size()); +} + +TEST(LeAudioClientParserTest, testPrepareAseCtpDisableEmpty) { + std::vector<uint8_t> ase_ids; + std::vector<uint8_t> value; + + PrepareAseCtpDisable(ase_ids, value); + ASSERT_EQ(value.size(), 0u); +} + +TEST(LeAudioClientParserTest, testPrepareAseCtpDisableSingle) { + std::vector<uint8_t> ase_ids; + std::vector<uint8_t> value; + + ase_ids.push_back(0x11); + + PrepareAseCtpDisable(ase_ids, value); + + uint8_t i = 0; + ASSERT_NE(value.size(), 0u); + ASSERT_EQ(value[i++], 0x05u); // Disable Opcode + ASSERT_EQ(value[i++], 1u); // Number of ASEs + ASSERT_EQ(value[i++], 0x11u); // ASE[0] ID + ASSERT_EQ(i, value.size()); +} + +TEST(LeAudioClientParserTest, testPrepareAseCtpDisableMultiple) { + std::vector<uint8_t> ase_ids; + std::vector<uint8_t> value; + + ase_ids.push_back(0x11); + ase_ids.push_back(0x36); + + PrepareAseCtpDisable(ase_ids, value); + + uint8_t i = 0; + ASSERT_NE(value.size(), 0u); + ASSERT_EQ(value[i++], 0x05u); // Disable Opcode + ASSERT_EQ(value[i++], 2u); // Number of ASEs + ASSERT_EQ(value[i++], 0x11u); // ASE[0] ID + ASSERT_EQ(value[i++], 0x36u); // ASE[0] ID + ASSERT_EQ(i, value.size()); +} + +TEST(LeAudioClientParserTest, testPrepareAseCtpAudioReceiverStopReadyEmpty) { + std::vector<uint8_t> ase_ids; + std::vector<uint8_t> value; + + PrepareAseCtpAudioReceiverStopReady(ase_ids, value); + ASSERT_EQ(value.size(), 0u); +} + +TEST(LeAudioClientParserTest, testPrepareAseCtpAudioReceiverStopReadySingle) { + std::vector<uint8_t> ase_ids; + std::vector<uint8_t> value; + + ase_ids.push_back(0x11); + + PrepareAseCtpAudioReceiverStopReady(ase_ids, value); + + uint8_t i = 0; + ASSERT_NE(value.size(), 0u); + ASSERT_EQ(value[i++], 0x06u); // Reveicer Stop Ready Opcode + ASSERT_EQ(value[i++], 1u); // Number of ASEs + ASSERT_EQ(value[i++], 0x11u); // ASE[0] ID + ASSERT_EQ(i, value.size()); +} + +TEST(LeAudioClientParserTest, testPrepareAseCtpAudioReceiverStopReadyMultiple) { + std::vector<uint8_t> ase_ids; + std::vector<uint8_t> value; + + ase_ids.push_back(0x11); + ase_ids.push_back(0x36); + + PrepareAseCtpAudioReceiverStopReady(ase_ids, value); + + uint8_t i = 0; + ASSERT_NE(value.size(), 0u); + ASSERT_EQ(value[i++], 0x06u); // Reveicer Stop Ready Opcode + ASSERT_EQ(value[i++], 2u); // Number of ASEs + ASSERT_EQ(value[i++], 0x11u); // ASE[0] ID + ASSERT_EQ(value[i++], 0x36u); // ASE[0] ID + ASSERT_EQ(i, value.size()); +} + +TEST(LeAudioClientParserTest, testPrepareAseCtpUpdateMetadataEmpty) { + std::vector<struct ctp_update_metadata> confs; + std::vector<uint8_t> value; + + PrepareAseCtpUpdateMetadata(confs, value); + ASSERT_EQ(value.size(), 0u); +} + +TEST(LeAudioClientParserTest, testPrepareAseCtpUpdateMetadataSingle) { + std::vector<struct ctp_update_metadata> confs; + std::vector<uint8_t> value; + + ctp_update_metadata conf{.ase_id = 0x11, .metadata = {0x02, 0x22, 0x21}}; + confs.push_back(conf); + + PrepareAseCtpUpdateMetadata(confs, value); + ASSERT_NE(value.size(), 0u); + + uint8_t i = 0; + ASSERT_EQ(value[i++], 0x07u); // Update Metadata Opcode + ASSERT_EQ(value[i++], 0x01u); // Number of ASEs + ASSERT_EQ(value[i++], 0x11u); // ASE[0] ID + ASSERT_EQ(value[i++], 0x03u); // Metadata Len + ASSERT_EQ(value[i++], 0x02u); // Metadata[0] + ASSERT_EQ(value[i++], 0x22u); // Metadata[1] + ASSERT_EQ(value[i++], 0x21u); // Metadata[2] + ASSERT_EQ(i, value.size()); +} + +TEST(LeAudioClientParserTest, testPrepareAseCtpUpdateMetadataMultiple) { + std::vector<struct ctp_update_metadata> confs; + std::vector<uint8_t> value; + + ctp_update_metadata conf{.ase_id = 0x11, .metadata = {0x02, 0x22, 0x21}}; + confs.push_back(conf); + + ctp_update_metadata conf2{.ase_id = 0x21, + .metadata = {0x03, 0x35, 0x36, 0x37}}; + confs.push_back(conf2); + + PrepareAseCtpUpdateMetadata(confs, value); + ASSERT_NE(value.size(), 0u); + + uint8_t i = 0; + ASSERT_EQ(value[i++], 0x07u); // Update Metadata Opcode + ASSERT_EQ(value[i++], 0x02u); // Number of ASEs + ASSERT_EQ(value[i++], 0x11u); // ASE[0] ID + ASSERT_EQ(value[i++], 0x03u); // ASE[0] Metadata Len + ASSERT_EQ(value[i++], 0x02u); // ASE[0] Metadata[0] + ASSERT_EQ(value[i++], 0x22u); // ASE[0] Metadata[1] + ASSERT_EQ(value[i++], 0x21u); // ASE[0] Metadata[2] + ASSERT_EQ(value[i++], 0x21u); // ASE[1] ID + ASSERT_EQ(value[i++], 0x04u); // ASE[1] Metadata Len + ASSERT_EQ(value[i++], 0x03u); // ASE[1] Metadata[0] + ASSERT_EQ(value[i++], 0x35u); // ASE[1] Metadata[1] + ASSERT_EQ(value[i++], 0x36u); // ASE[1] Metadata[2] + ASSERT_EQ(value[i++], 0x37u); // ASE[1] Metadata[2] + ASSERT_EQ(i, value.size()); +} + +TEST(LeAudioClientParserTest, testPrepareAseCtpReleaseEmpty) { + std::vector<uint8_t> ase_ids; + std::vector<uint8_t> value; + + PrepareAseCtpRelease(ase_ids, value); + ASSERT_EQ(value.size(), 0u); +} + +TEST(LeAudioClientParserTest, testPrepareAseCtpReleaseSingle) { + std::vector<uint8_t> ase_ids; + std::vector<uint8_t> value; + + ase_ids.push_back(0x11); + + PrepareAseCtpRelease(ase_ids, value); + + uint8_t i = 0; + ASSERT_NE(value.size(), 0u); + ASSERT_EQ(value[i++], 0x08u); // Release Opcode + ASSERT_EQ(value[i++], 1u); // Number of ASEs + ASSERT_EQ(value[i++], 0x11u); // ASE[0] ID + ASSERT_EQ(i, value.size()); +} + +TEST(LeAudioClientParserTest, testPrepareAseCtpReleaseMultiple) { + std::vector<uint8_t> ase_ids; + std::vector<uint8_t> value; + + ase_ids.push_back(0x11); + ase_ids.push_back(0x36); + + PrepareAseCtpRelease(ase_ids, value); + + uint8_t i = 0; + ASSERT_NE(value.size(), 0u); + ASSERT_EQ(value[i++], 0x08u); // Release Opcode + ASSERT_EQ(value[i++], 2u); // Number of ASEs + ASSERT_EQ(value[i++], 0x11u); // ASE[0] ID + ASSERT_EQ(value[i++], 0x36u); // ASE[0] ID + ASSERT_EQ(i, value.size()); +} + +} // namespace ascs + +} // namespace client_parser +} // namespace le_audio diff --git a/system/bta/le_audio/devices.cc b/system/bta/le_audio/devices.cc new file mode 100644 index 0000000000000000000000000000000000000000..1fd6f6170ddca291f875c08b212c63179e550f7a --- /dev/null +++ b/system/bta/le_audio/devices.cc @@ -0,0 +1,1778 @@ +/* + * Copyright 2020 HIMSA II K/S - www.himsa.com. Represented by EHIMA + * - www.ehima.com + * + * 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 "devices.h" + +#include <base/strings/string_number_conversions.h> + +#include <map> + +#include "bta_gatt_queue.h" +#include "bta_groups.h" +#include "bta_le_audio_api.h" +#include "btm_iso_api.h" +#include "btm_iso_api_types.h" +#include "client_audio.h" +#include "device/include/controller.h" +#include "stack/btm/btm_int.h" + +using bluetooth::hci::kIsoCigFramingFramed; +using bluetooth::hci::kIsoCigFramingUnframed; +using bluetooth::hci::kIsoCigPackingSequential; +using bluetooth::hci::kIsoCigPhy1M; +using bluetooth::hci::kIsoCigPhy2M; +using bluetooth::hci::iso_manager::kIsoSca0To20Ppm; +using le_audio::set_configurations::CodecCapabilitySetting; +using le_audio::types::ase; +using le_audio::types::AseState; +using le_audio::types::AudioContexts; +using le_audio::types::AudioLocations; +using le_audio::types::AudioStreamDataPathState; +using le_audio::types::BidirectAsesPair; +using le_audio::types::LeAudioCodecId; +using le_audio::types::LeAudioContextType; +using le_audio::types::LeAudioLc3Config; + +namespace le_audio { +/* LeAudioDeviceGroup Class methods implementation */ +void LeAudioDeviceGroup::AddNode( + const std::shared_ptr<LeAudioDevice>& leAudioDevice) { + leAudioDevice->group_id_ = group_id_; + leAudioDevices_.push_back(std::weak_ptr<LeAudioDevice>(leAudioDevice)); +} + +void LeAudioDeviceGroup::RemoveNode( + const std::shared_ptr<LeAudioDevice>& leAudioDevice) { + leAudioDevice->group_id_ = bluetooth::groups::kGroupUnknown; + leAudioDevices_.erase( + std::remove_if( + leAudioDevices_.begin(), leAudioDevices_.end(), + [&leAudioDevice](auto& d) { return d.lock() == leAudioDevice; }), + leAudioDevices_.end()); +} + +bool LeAudioDeviceGroup::IsEmpty(void) { return leAudioDevices_.size() == 0; } + +bool LeAudioDeviceGroup::IsAnyDeviceConnected(void) { + return (NumOfConnected() != 0); +} + +int LeAudioDeviceGroup::Size(void) { return leAudioDevices_.size(); } + +int LeAudioDeviceGroup::NumOfConnected(types::LeAudioContextType context_type) { + if (leAudioDevices_.empty()) return 0; + + bool check_context_type = (context_type != LeAudioContextType::RFU); + AudioContexts type_set = static_cast<uint16_t>(context_type); + + /* return number of connected devices from the set*/ + return std::count_if( + leAudioDevices_.begin(), leAudioDevices_.end(), + [type_set, check_context_type](auto& iter) { + if (iter.expired()) return false; + if (iter.lock()->conn_id_ == GATT_INVALID_CONN_ID) return false; + + if (!check_context_type) return true; + + return (iter.lock()->GetAvailableContexts() & type_set).any(); + }); +} + +void LeAudioDeviceGroup::Cleanup(void) { leAudioDevices_.clear(); } + +void LeAudioDeviceGroup::Deactivate(void) { + for (auto* leAudioDevice = GetFirstActiveDevice(); leAudioDevice; + leAudioDevice = GetNextActiveDevice(leAudioDevice)) { + for (auto* ase = leAudioDevice->GetFirstActiveAse(); ase; + ase = leAudioDevice->GetNextActiveAse(ase)) { + ase->active = false; + } + } +} + +LeAudioDevice* LeAudioDeviceGroup::GetFirstDevice(void) { + return (leAudioDevices_.front().lock()).get(); +} + +LeAudioDevice* LeAudioDeviceGroup::GetFirstDeviceWithActiveContext( + types::LeAudioContextType context_type) { + AudioContexts type_set = static_cast<uint16_t>(context_type); + + auto iter = std::find_if( + leAudioDevices_.begin(), leAudioDevices_.end(), [&type_set](auto& iter) { + if (iter.expired()) return false; + return (iter.lock()->GetAvailableContexts() & type_set).any(); + }); + + if ((iter == leAudioDevices_.end()) || (iter->expired())) return nullptr; + + return (iter->lock()).get(); +} + +LeAudioDevice* LeAudioDeviceGroup::GetNextDevice(LeAudioDevice* leAudioDevice) { + auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), + [&leAudioDevice](auto& d) { + if (d.expired()) + return false; + else + return (d.lock()).get() == leAudioDevice; + }); + + /* If reference device not found */ + if (iter == leAudioDevices_.end()) return nullptr; + + std::advance(iter, 1); + /* If reference device is last in group */ + if (iter == leAudioDevices_.end()) return nullptr; + + if (iter->expired()) return nullptr; + + return (iter->lock()).get(); +} + +LeAudioDevice* LeAudioDeviceGroup::GetNextDeviceWithActiveContext( + LeAudioDevice* leAudioDevice, types::LeAudioContextType context_type) { + AudioContexts type_set = static_cast<uint16_t>(context_type); + + auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), + [&leAudioDevice](auto& d) { + if (d.expired()) + return false; + else + return (d.lock()).get() == leAudioDevice; + }); + + /* If reference device not found */ + if (iter == leAudioDevices_.end()) return nullptr; + + std::advance(iter, 1); + /* If reference device is last in group */ + if (iter == leAudioDevices_.end()) return nullptr; + + iter = std::find_if(iter, leAudioDevices_.end(), [&type_set](auto& d) { + if (d.expired()) + return false; + else + return (d.lock()->GetAvailableContexts() & type_set).any(); + ; + }); + + return (iter == leAudioDevices_.end()) ? nullptr : (iter->lock()).get(); +} + +bool LeAudioDeviceGroup::IsDeviceInTheGroup(LeAudioDevice* leAudioDevice) { + auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), + [&leAudioDevice](auto& d) { + if (d.expired()) + return false; + else + return (d.lock()).get() == leAudioDevice; + }); + + if ((iter == leAudioDevices_.end()) || (iter->expired())) return false; + + return true; +} + +bool LeAudioDeviceGroup::HaveAllActiveDevicesAsesTheSameState(AseState state) { + auto iter = std::find_if( + leAudioDevices_.begin(), leAudioDevices_.end(), [&state](auto& d) { + if (d.expired()) + return false; + else + return !(((d.lock()).get())->HaveAllActiveAsesSameState(state)); + }); + + return iter == leAudioDevices_.end(); +} + +LeAudioDevice* LeAudioDeviceGroup::GetFirstActiveDevice(void) { + auto iter = + std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [](auto& d) { + if (d.expired()) + return false; + else + return ((d.lock()).get())->HaveActiveAse(); + }); + + if (iter == leAudioDevices_.end() || iter->expired()) return nullptr; + + return (iter->lock()).get(); +} + +LeAudioDevice* LeAudioDeviceGroup::GetNextActiveDevice( + LeAudioDevice* leAudioDevice) { + auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), + [&leAudioDevice](auto& d) { + if (d.expired()) + return false; + else + return (d.lock()).get() == leAudioDevice; + }); + + if (iter == leAudioDevices_.end() || + std::distance(iter, leAudioDevices_.end()) < 1) + return nullptr; + + iter = std::find_if(std::next(iter, 1), leAudioDevices_.end(), [](auto& d) { + if (d.expired()) + return false; + else + return ((d.lock()).get())->HaveActiveAse(); + }); + + return (iter == leAudioDevices_.end()) ? nullptr : (iter->lock()).get(); +} + +bool LeAudioDeviceGroup::SetContextType(LeAudioContextType context_type) { + /* XXX: group context policy ? / may it disallow to change type ?) */ + context_type_ = context_type; + + return true; +} + +LeAudioContextType LeAudioDeviceGroup::GetContextType(void) { + return context_type_; +} + +uint32_t LeAudioDeviceGroup::GetSduInterval(uint8_t direction) { + for (LeAudioDevice* leAudioDevice = GetFirstActiveDevice(); + leAudioDevice != nullptr; + leAudioDevice = GetNextActiveDevice(leAudioDevice)) { + struct ase* ase = leAudioDevice->GetFirstActiveAseByDirection(direction); + if (!ase) continue; + + return ase->codec_config.GetFrameDurationUs(); + } + + return 0; +} + +uint8_t LeAudioDeviceGroup::GetSCA(void) { + uint8_t sca = kIsoSca0To20Ppm; + + for (const auto& leAudioDevice : leAudioDevices_) { + uint8_t dev_sca = + BTM_GetPeerSCA(leAudioDevice.lock()->address_, BT_TRANSPORT_LE); + + /* If we could not read SCA from the peer device or sca is 0, + * then there is no reason to continue. + */ + if ((dev_sca == 0xFF) || (dev_sca == 0)) return 0; + + /* The Slaves_Clock_Accuracy parameter shall be the worst-case sleep clock + *accuracy of all the slaves that will participate in the CIG. + */ + if (dev_sca < sca) { + sca = dev_sca; + } + } + + return sca; +} + +uint8_t LeAudioDeviceGroup::GetPacking(void) { + /* TODO: Decide about packing */ + return kIsoCigPackingSequential; +} + +uint8_t LeAudioDeviceGroup::GetFraming(void) { + LeAudioDevice* leAudioDevice = GetFirstActiveDevice(); + LOG_ASSERT(leAudioDevice) + << __func__ << " Shouldn't be called without an active device."; + + do { + struct ase* ase = leAudioDevice->GetFirstActiveAse(); + if (!ase) continue; + + do { + if (ase->framing == types::kFramingUnframedPduUnsupported) + return kIsoCigFramingFramed; + } while ((ase = leAudioDevice->GetNextActiveAse(ase))); + } while ((leAudioDevice = GetNextActiveDevice(leAudioDevice))); + + return kIsoCigFramingUnframed; +} + +uint8_t LeAudioDeviceGroup::GetTargetLatency(void) { + /* TODO: Decide about target latency */ + return types::kTargetLatencyBalancedLatencyReliability; +} + +/* TODO: Preferred parameter may be other than minimum */ +static uint16_t find_max_transport_latency(LeAudioDeviceGroup* group, + uint8_t direction) { + uint16_t max_transport_latency = 0; + + for (LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice(); + leAudioDevice != nullptr; + leAudioDevice = group->GetNextActiveDevice(leAudioDevice)) { + for (ase* ase = leAudioDevice->GetFirstActiveAseByDirection(direction); + ase != nullptr; + ase = leAudioDevice->GetNextActiveAseWithSameDirection(ase)) { + if (!ase) break; + + if (!max_transport_latency) + // first assignment + max_transport_latency = ase->max_transport_latency; + else if (ase->max_transport_latency < max_transport_latency) + max_transport_latency = ase->max_transport_latency; + } + } + + if (max_transport_latency < types::kMaxTransportLatencyMin) + max_transport_latency = types::kMaxTransportLatencyMin; + else if (max_transport_latency > types::kMaxTransportLatencyMax) + max_transport_latency = types::kMaxTransportLatencyMax; + + return max_transport_latency; +} + +uint16_t LeAudioDeviceGroup::GetMaxTransportLatencyStom(void) { + return find_max_transport_latency(this, types::kLeAudioDirectionSource); +} + +uint16_t LeAudioDeviceGroup::GetMaxTransportLatencyMtos(void) { + return find_max_transport_latency(this, types::kLeAudioDirectionSink); +} + +uint16_t LeAudioDeviceGroup::GetTransportLatency(uint8_t direction) { + if (direction == types::kLeAudioDirectionSink) { + return transport_latency_mtos_; + } else if (direction == types::kLeAudioDirectionSource) { + return transport_latency_stom_; + } else { + LOG(ERROR) << __func__ << ", invalid direction"; + return 0; + } +} + +void LeAudioDeviceGroup::SetTransportLatency(uint8_t direction, + uint16_t new_transport_latency) { + uint16_t* transport_latency; + + if (direction == types::kLeAudioDirectionSink) { + transport_latency = &transport_latency_mtos_; + } else if (direction == types::kLeAudioDirectionSource) { + transport_latency = &transport_latency_stom_; + } else { + LOG(ERROR) << __func__ << ", invalid direction"; + return; + } + + if (*transport_latency == new_transport_latency) return; + + if ((*transport_latency != 0) && + (*transport_latency != new_transport_latency)) { + LOG(WARNING) << __func__ << ", Different transport latency for group: " + << " old: " << static_cast<int>(*transport_latency) + << " [ms], new: " << static_cast<int>(new_transport_latency) + << " [ms]"; + return; + } + + LOG(INFO) << __func__ << ", updated group " << static_cast<int>(group_id_) + << " transport latency: " << static_cast<int>(new_transport_latency) + << " [ms]"; + *transport_latency = new_transport_latency; +} + +uint8_t LeAudioDeviceGroup::GetPhyBitmask(uint8_t direction) { + LeAudioDevice* leAudioDevice = GetFirstActiveDevice(); + LOG_ASSERT(leAudioDevice) + << __func__ << " Shouldn't be called without an active device."; + + // local supported PHY's + uint8_t phy_bitfield = kIsoCigPhy1M; + if (controller_get_interface()->supports_ble_2m_phy()) + phy_bitfield |= kIsoCigPhy2M; + + if (!leAudioDevice) { + LOG(ERROR) << "No active leaudio device for direction?: " << +direction; + return phy_bitfield; + } + + do { + struct ase* ase = leAudioDevice->GetFirstActiveAseByDirection(direction); + if (!ase) return phy_bitfield; + + do { + if (direction == ase->direction) { + phy_bitfield &= leAudioDevice->GetPhyBitmask(); + + // A value of 0x00 denotes no preference + if (ase->preferred_phy) phy_bitfield &= ase->preferred_phy; + } + } while ((ase = leAudioDevice->GetNextActiveAseWithSameDirection(ase))); + } while ((leAudioDevice = GetNextActiveDevice(leAudioDevice))); + + return phy_bitfield; +} + +uint8_t LeAudioDeviceGroup::GetTargetPhy(uint8_t direction) { + uint8_t phy_bitfield = GetPhyBitmask(direction); + + // prefer to use 2M if supported + if (phy_bitfield & kIsoCigPhy2M) + return types::kTargetPhy2M; + else if (phy_bitfield & kIsoCigPhy1M) + return types::kTargetPhy1M; + else + return 0; +} + +bool LeAudioDeviceGroup::GetPresentationDelay(uint32_t* delay, + uint8_t direction) { + uint32_t delay_min = 0; + uint32_t delay_max = UINT32_MAX; + uint32_t preferred_delay_min = delay_min; + uint32_t preferred_delay_max = delay_max; + + LeAudioDevice* leAudioDevice = GetFirstActiveDevice(); + LOG_ASSERT(leAudioDevice) + << __func__ << " Shouldn't be called without an active device."; + + do { + struct ase* ase = leAudioDevice->GetFirstActiveAseByDirection(direction); + if (!ase) continue; // device has no active ASEs in this direction + + do { + /* No common range check */ + if (ase->pres_delay_min > delay_max || ase->pres_delay_max < delay_min) + return false; + + if (ase->pres_delay_min > delay_min) delay_min = ase->pres_delay_min; + if (ase->pres_delay_max < delay_max) delay_max = ase->pres_delay_max; + if (ase->preferred_pres_delay_min > preferred_delay_min) + preferred_delay_min = ase->preferred_pres_delay_min; + if (ase->preferred_pres_delay_max < preferred_delay_max && + ase->preferred_pres_delay_max != types::kPresDelayNoPreference) + preferred_delay_max = ase->preferred_pres_delay_max; + } while ((ase = leAudioDevice->GetNextActiveAseWithSameDirection(ase))); + } while ((leAudioDevice = GetNextActiveDevice(leAudioDevice))); + + if (preferred_delay_min <= preferred_delay_max && + preferred_delay_min > delay_min && preferred_delay_min < delay_max) { + *delay = preferred_delay_min; + } else { + *delay = delay_min; + } + + return true; +} + +uint16_t LeAudioDeviceGroup::GetRemoteDelay(uint8_t direction) { + uint16_t remote_delay_ms = 0; + uint32_t presentation_delay; + + if (!GetPresentationDelay(&presentation_delay, direction)) { + /* This should never happens at stream request time but to be safe return + * some sample value to not break streaming + */ + return 100; + } + + /* us to ms */ + remote_delay_ms = presentation_delay / 1000; + remote_delay_ms += GetTransportLatency(direction) / 1000; + + return remote_delay_ms; +} + +/* This method returns AudioContext value if support for any type has changed */ +std::optional<AudioContexts> LeAudioDeviceGroup::UpdateActiveContextsMap(void) { + DLOG(INFO) << __func__ << " group id: " << group_id_ << " active contexts: " + << loghex(active_contexts_mask_.to_ulong()); + return UpdateActiveContextsMap(active_contexts_mask_); +} + +/* This method returns AudioContext value if support for any type has changed */ +std::optional<AudioContexts> LeAudioDeviceGroup::UpdateActiveContextsMap( + AudioContexts update_contexts) { + AudioContexts contexts = 0x0000; + bool active_contexts_has_been_modified = false; + + for (LeAudioContextType ctx_type : types::kLeAudioContextAllTypesArray) { + AudioContexts type_set = static_cast<uint16_t>(ctx_type); + + if ((type_set & update_contexts).none()) { + /* Fill context bitset for possible returned value if updated */ + if (active_context_to_configuration_map.count(ctx_type) > 0) + contexts |= type_set; + + continue; + } + + auto new_conf = FindFirstSupportedConfiguration(ctx_type); + + /* Check if support for context type has changed */ + if (active_context_to_configuration_map.count(ctx_type) == 0 || + active_context_to_configuration_map[ctx_type] == nullptr) { + /* Current configuration for context type is empty */ + if (new_conf == nullptr) { + /* Configuration remains empty */ + continue; + } else { + /* Configuration changes from empty to some */ + contexts |= type_set; + active_contexts_has_been_modified = true; + } + } else { + /* Current configuration for context type is not empty */ + if (new_conf == nullptr) { + /* Configuration changed to empty */ + contexts &= ~type_set; + active_contexts_has_been_modified = true; + } else if (new_conf != active_context_to_configuration_map[ctx_type]) { + /* Configuration changed to any other */ + contexts |= type_set; + active_contexts_has_been_modified = true; + } else { + /* Configuration is the same */ + continue; + } + } + + LOG(INFO) << __func__ << ", updated context: " << loghex(int(ctx_type)) + << ", " + << (active_context_to_configuration_map[ctx_type] != nullptr + ? active_context_to_configuration_map[ctx_type]->name + : "empty") + << " -> " << (new_conf != nullptr ? new_conf->name : "empty"); + active_context_to_configuration_map[ctx_type] = new_conf; + } + + /* Some contexts have changed, return new active context bitset */ + if (active_contexts_has_been_modified) { + active_contexts_mask_ = contexts; + return contexts; + } + + /* Nothing has changed */ + return std::nullopt; +} + +bool LeAudioDeviceGroup::ReloadAudioLocations(void) { + AudioLocations updated_snk_audio_locations_ = + codec_spec_conf::kLeAudioLocationMonoUnspecified; + AudioLocations updated_src_audio_locations_ = + codec_spec_conf::kLeAudioLocationMonoUnspecified; + + for (const auto& device : leAudioDevices_) { + if (device.expired()) continue; + updated_snk_audio_locations_ |= device.lock().get()->snk_audio_locations_; + updated_src_audio_locations_ |= device.lock().get()->src_audio_locations_; + } + + /* Nothing has changed */ + if ((updated_snk_audio_locations_ == snk_audio_locations_) && + (updated_src_audio_locations_ == src_audio_locations_)) + return false; + + snk_audio_locations_ = updated_snk_audio_locations_; + src_audio_locations_ = updated_src_audio_locations_; + + return true; +} + +bool LeAudioDeviceGroup::IsInTransition(void) { + return target_state_ != current_state_; +} + +bool LeAudioDeviceGroup::IsReleasing(void) { + return target_state_ == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE; +} + +bool LeAudioDeviceGroup::IsGroupStreamReady(void) { + auto iter = + std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [](auto& d) { + if (d.expired()) + return false; + else + return !(((d.lock()).get())->HaveAllActiveAsesCisEst()); + }); + + return iter == leAudioDevices_.end(); +} + +bool LeAudioDeviceGroup::HaveAllActiveDevicesCisDisc(void) { + auto iter = + std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [](auto& d) { + if (d.expired()) + return false; + else + return !(((d.lock()).get())->HaveAllAsesCisDisc()); + }); + + return iter == leAudioDevices_.end(); +} + +uint8_t LeAudioDeviceGroup::GetFirstFreeCisId(void) { + for (uint8_t id = 0; id < UINT8_MAX; id++) { + auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), + [id](auto& d) { + if (d.expired()) + return false; + else + return ((d.lock()).get())->HasCisId(id); + }); + + if (iter == leAudioDevices_.end()) return id; + } + + return kInvalidCisId; +} + +bool CheckIfStrategySupported(types::LeAudioConfigurationStrategy strategy, + types::AudioLocations audio_locations, + uint8_t requested_channel_count, + uint8_t channel_count_mask) { + DLOG(INFO) << __func__ << " strategy: " << (int)strategy + << " locations: " << +audio_locations.to_ulong(); + + switch (strategy) { + case types::LeAudioConfigurationStrategy::MONO_ONE_CIS_PER_DEVICE: + return audio_locations.any(); + case types::LeAudioConfigurationStrategy::STEREO_TWO_CISES_PER_DEVICE: + if ((audio_locations.to_ulong() & + codec_spec_conf::kLeAudioLocationAnyLeft) && + (audio_locations.to_ulong() & + codec_spec_conf::kLeAudioLocationAnyRight)) + return true; + else + return false; + case types::LeAudioConfigurationStrategy::STEREO_ONE_CIS_PER_DEVICE: + if (!(audio_locations.to_ulong() & + codec_spec_conf::kLeAudioLocationAnyLeft) || + !(audio_locations.to_ulong() & + codec_spec_conf::kLeAudioLocationAnyRight)) + return false; + + DLOG(INFO) << __func__ << " requested chan cnt " + << +requested_channel_count + << " chan mask: " << loghex(channel_count_mask); + + /* Return true if requested channel count is set in the channel count + * mask. In the channel_count_mask, bit0 is set when 1 channel is + * supported. + */ + return ((1 << (requested_channel_count - 1)) & channel_count_mask); + default: + return false; + } + + return false; +} + +/* This method check if group support given audio configuration + * requirement for connected devices in the group and available ASEs + * (no matter on the ASE state) and for given context type + */ +bool LeAudioDeviceGroup::IsConfigurationSupported( + const set_configurations::AudioSetConfiguration* audio_set_conf, + types::LeAudioContextType context_type) { + if (!set_configurations::check_if_may_cover_scenario( + audio_set_conf, NumOfConnected(context_type))) { + DLOG(INFO) << __func__ << " cannot cover scenario " + << static_cast<int>(context_type) + << " size of for context type: " + << +NumOfConnected(context_type); + return false; + } + + /* TODO For now: set ase if matching with first pac. + * 1) We assume as well that devices will match requirements in order + * e.g. 1 Device - 1 Requirement, 2 Device - 2 Requirement etc. + * 2) ASEs should be active only if best (according to priority list) full + * scenarion will be covered. + * 3) ASEs should be filled according to performance profile. + */ + for (const auto& ent : (*audio_set_conf).confs) { + DLOG(INFO) << __func__ + << " Looking for configuration: " << audio_set_conf->name + << " - " + << (ent.direction == types::kLeAudioDirectionSink ? "snk" + : "src"); + + uint8_t required_device_cnt = ent.device_cnt; + uint8_t max_required_ase_per_dev = + ent.ase_cnt / ent.device_cnt + (ent.ase_cnt % ent.device_cnt); + uint8_t active_ase_num = 0; + auto strategy = ent.strategy; + + DLOG(INFO) << __func__ << " Number of devices: " << +required_device_cnt + << " number of ASEs: " << +ent.ase_cnt + << " Max ASE per device: " << +max_required_ase_per_dev + << " strategy: " << static_cast<int>(strategy); + + for (auto* device = GetFirstDeviceWithActiveContext(context_type); + device != nullptr && required_device_cnt > 0; + device = GetNextDeviceWithActiveContext(device, context_type)) { + /* Skip if device has ASE configured in this direction already */ + + if (device->ases_.empty()) continue; + + if (!device->IsCodecConfigurationSupported(ent.direction, ent.codec)) + continue; + + int needed_ase = std::min(static_cast<int>(max_required_ase_per_dev), + static_cast<int>(ent.ase_cnt - active_ase_num)); + + /* If we required more ASEs per device which means we would like to + * create more CISes to one device, we should also check the allocation + * if it allows us to do this. + */ + + types::AudioLocations audio_locations = 0; + /* Check direction and if audio location allows to create more cise */ + if (ent.direction == types::kLeAudioDirectionSink) + audio_locations = device->snk_audio_locations_; + else + audio_locations = device->src_audio_locations_; + + /* TODO Make it no Lc3 specific */ + if (!CheckIfStrategySupported( + strategy, audio_locations, + std::get<LeAudioLc3Config>(ent.codec.config).GetChannelCount(), + device->GetLc3SupportedChannelCount(ent.direction))) { + DLOG(INFO) << __func__ << " insufficient device audio allocation: " + << audio_locations; + continue; + } + + for (auto& ase : device->ases_) { + if (ase.direction != ent.direction) continue; + + active_ase_num++; + needed_ase--; + + if (needed_ase == 0) break; + } + + required_device_cnt--; + } + + if (required_device_cnt > 0) { + /* Don't left any active devices if requirements are not met */ + DLOG(INFO) << __func__ << " could not configure all the devices"; + return false; + } + } + + DLOG(INFO) << "Choosed ASE Configuration for group: " << this->group_id_ + << " configuration: " << audio_set_conf->name; + return true; +} + +uint32_t GetFirstLeft(const types::AudioLocations audio_locations) { + uint32_t audio_location_ulong = audio_locations.to_ulong(); + + if (audio_location_ulong & codec_spec_conf::kLeAudioLocationFrontLeft) + return codec_spec_conf::kLeAudioLocationFrontLeft; + + if (audio_location_ulong & codec_spec_conf::kLeAudioLocationBackLeft) + return codec_spec_conf::kLeAudioLocationBackLeft; + + if (audio_location_ulong & codec_spec_conf::kLeAudioLocationFrontLeftOfCenter) + return codec_spec_conf::kLeAudioLocationFrontLeftOfCenter; + + if (audio_location_ulong & codec_spec_conf::kLeAudioLocationSideLeft) + return codec_spec_conf::kLeAudioLocationSideLeft; + + if (audio_location_ulong & codec_spec_conf::kLeAudioLocationTopFrontLeft) + return codec_spec_conf::kLeAudioLocationTopFrontLeft; + + if (audio_location_ulong & codec_spec_conf::kLeAudioLocationTopBackLeft) + return codec_spec_conf::kLeAudioLocationTopBackLeft; + + if (audio_location_ulong & codec_spec_conf::kLeAudioLocationTopSideLeft) + return codec_spec_conf::kLeAudioLocationTopSideLeft; + + if (audio_location_ulong & codec_spec_conf::kLeAudioLocationBottomFrontLeft) + return codec_spec_conf::kLeAudioLocationBottomFrontLeft; + + if (audio_location_ulong & codec_spec_conf::kLeAudioLocationFrontLeftWide) + return codec_spec_conf::kLeAudioLocationFrontLeftWide; + + if (audio_location_ulong & codec_spec_conf::kLeAudioLocationLeftSurround) + return codec_spec_conf::kLeAudioLocationLeftSurround; + + LOG_ASSERT(0) << __func__ << " shall not happen"; + return 0; +} + +uint32_t GetFirstRight(const types::AudioLocations audio_locations) { + uint32_t audio_location_ulong = audio_locations.to_ulong(); + + if (audio_location_ulong & codec_spec_conf::kLeAudioLocationFrontRight) + return codec_spec_conf::kLeAudioLocationFrontRight; + + if (audio_location_ulong & codec_spec_conf::kLeAudioLocationBackRight) + return codec_spec_conf::kLeAudioLocationBackRight; + + if (audio_location_ulong & + codec_spec_conf::kLeAudioLocationFrontRightOfCenter) + return codec_spec_conf::kLeAudioLocationFrontRightOfCenter; + + if (audio_location_ulong & codec_spec_conf::kLeAudioLocationSideRight) + return codec_spec_conf::kLeAudioLocationSideRight; + + if (audio_location_ulong & codec_spec_conf::kLeAudioLocationTopFrontRight) + return codec_spec_conf::kLeAudioLocationTopFrontRight; + + if (audio_location_ulong & codec_spec_conf::kLeAudioLocationTopBackRight) + return codec_spec_conf::kLeAudioLocationTopBackRight; + + if (audio_location_ulong & codec_spec_conf::kLeAudioLocationTopSideRight) + return codec_spec_conf::kLeAudioLocationTopSideRight; + + if (audio_location_ulong & codec_spec_conf::kLeAudioLocationBottomFrontRight) + return codec_spec_conf::kLeAudioLocationBottomFrontRight; + + if (audio_location_ulong & codec_spec_conf::kLeAudioLocationFrontRightWide) + return codec_spec_conf::kLeAudioLocationFrontRightWide; + + if (audio_location_ulong & codec_spec_conf::kLeAudioLocationRightSurround) + return codec_spec_conf::kLeAudioLocationRightSurround; + + LOG_ASSERT(0) << __func__ << " shall not happen"; + return 0; +} + +uint32_t PickAudioLocation(types::LeAudioConfigurationStrategy strategy, + types::AudioLocations audio_locations, + types::AudioLocations* group_audio_locations) { + DLOG(INFO) << __func__ << " strategy: " << (int)strategy + << " locations: " << +audio_locations.to_ulong() + << " group locations: " << +group_audio_locations->to_ulong(); + + switch (strategy) { + case types::LeAudioConfigurationStrategy::MONO_ONE_CIS_PER_DEVICE: + case types::LeAudioConfigurationStrategy::STEREO_TWO_CISES_PER_DEVICE: + if ((audio_locations.to_ulong() & + codec_spec_conf::kLeAudioLocationAnyLeft) && + !(group_audio_locations->to_ulong() & + codec_spec_conf::kLeAudioLocationAnyLeft)) { + uint32_t left_location = GetFirstLeft(audio_locations); + *group_audio_locations |= left_location; + return left_location; + } + + if ((audio_locations.to_ulong() & + codec_spec_conf::kLeAudioLocationAnyRight) && + !(group_audio_locations->to_ulong() & + codec_spec_conf::kLeAudioLocationAnyRight)) { + uint32_t right_location = GetFirstRight(audio_locations); + *group_audio_locations |= right_location; + return right_location; + } + break; + case types::LeAudioConfigurationStrategy::STEREO_ONE_CIS_PER_DEVICE: + if ((audio_locations.to_ulong() & + codec_spec_conf::kLeAudioLocationAnyLeft) && + (audio_locations.to_ulong() & + codec_spec_conf::kLeAudioLocationAnyRight)) { + uint32_t left_location = GetFirstLeft(audio_locations); + uint32_t right_location = GetFirstRight(audio_locations); + *group_audio_locations |= left_location | right_location; + return left_location | right_location; + } + break; + default: + LOG_ASSERT(0) << " Shall never happen "; + return 0; + } + + LOG_ASSERT(0) << " Shall never happen "; + return 0; +} + +bool LeAudioDevice::ConfigureAses( + const le_audio::set_configurations::SetConfiguration& ent, + types::LeAudioContextType context_type, + uint8_t* number_of_already_active_group_ase, + types::AudioLocations& group_snk_audio_locations, + types::AudioLocations& group_src_audio_locations, bool reconnect) { + struct ase* ase = GetFirstInactiveAse(ent.direction, reconnect); + if (!ase) return false; + + uint8_t active_ases = *number_of_already_active_group_ase; + uint8_t max_required_ase_per_dev = + ent.ase_cnt / ent.device_cnt + (ent.ase_cnt % ent.device_cnt); + le_audio::types::LeAudioConfigurationStrategy strategy = ent.strategy; + + bool is_codec_supported = + IsCodecConfigurationSupported(ent.direction, ent.codec); + if (!is_codec_supported) return false; + + int needed_ase = std::min((int)(max_required_ase_per_dev), + (int)(ent.ase_cnt - active_ases)); + + types::AudioLocations audio_locations = 0; + types::AudioLocations* group_audio_locations; + /* Check direction and if audio location allows to create more cise */ + if (ent.direction == types::kLeAudioDirectionSink) { + audio_locations = snk_audio_locations_; + group_audio_locations = &group_snk_audio_locations; + } else { + audio_locations = src_audio_locations_; + group_audio_locations = &group_src_audio_locations; + } + + for (; needed_ase && ase; needed_ase--) { + ase->active = true; + active_ases++; + + if (ase->state == AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED) + ase->reconfigure = true; + + ase->codec_id = ent.codec.id; + /* TODO: find better way to not use LC3 explicitly */ + ase->codec_config = std::get<LeAudioLc3Config>(ent.codec.config); + + /*Let's choose audio channel allocation if not set */ + ase->codec_config.audio_channel_allocation = + PickAudioLocation(strategy, audio_locations, group_audio_locations); + + ase->max_sdu_size = codec_spec_caps::GetAudioChannelCounts( + ase->codec_config.audio_channel_allocation) * + ase->codec_config.octets_per_codec_frame; + + /* Append additional metadata */ + std::vector<uint8_t> metadata; + metadata.resize(3 + 1); + uint8_t* metadata_buf = metadata.data(); + UINT8_TO_STREAM(metadata_buf, 3); + UINT8_TO_STREAM(metadata_buf, + types::kLeAudioMetadataTypeStreamingAudioContext); + UINT16_TO_STREAM(metadata_buf, static_cast<uint16_t>(context_type)); + + ase->metadata = std::move(metadata); + + DLOG(INFO) << __func__ << " device=" << address_ + << ", activated ASE id=" << +ase->id + << ", direction=" << +ase->direction + << ", max_sdu_size=" << +ase->max_sdu_size + << ", cis_id=" << +ase->cis_id; + + ase = GetFirstInactiveAse(ent.direction, reconnect); + } + + *number_of_already_active_group_ase = active_ases; + return true; +} + +/* This method should choose aproperiate ASEs to be active and set a cached + * configuration for codec and qos. + */ +bool LeAudioDeviceGroup::ConfigureAses( + const set_configurations::AudioSetConfiguration* audio_set_conf, + types::LeAudioContextType context_type) { + if (!set_configurations::check_if_may_cover_scenario( + audio_set_conf, NumOfConnected(context_type))) + return false; + + /* TODO For now: set ase if matching with first pac. + * 1) We assume as well that devices will match requirements in order + * e.g. 1 Device - 1 Requirement, 2 Device - 2 Requirement etc. + * 2) ASEs should be active only if best (according to priority list) full + * scenarion will be covered. + * 3) ASEs should be filled according to performance profile. + */ + + types::AudioLocations group_snk_audio_locations = 0; + types::AudioLocations group_src_audio_locations = 0; + + for (const auto& ent : (*audio_set_conf).confs) { + DLOG(INFO) << __func__ + << " Looking for requirements: " << audio_set_conf->name << " - " + << (ent.direction == 1 ? "snk" : "src"); + + uint8_t required_device_cnt = ent.device_cnt; + uint8_t max_required_ase_per_dev = + ent.ase_cnt / ent.device_cnt + (ent.ase_cnt % ent.device_cnt); + uint8_t active_ase_num = 0; + le_audio::types::LeAudioConfigurationStrategy strategy = ent.strategy; + + DLOG(INFO) << __func__ << " Number of devices: " << +required_device_cnt + << " number of ASEs: " << +ent.ase_cnt + << " Max ASE per device: " << +max_required_ase_per_dev + << " strategy: " << (int)strategy; + + for (auto* device = GetFirstDeviceWithActiveContext(context_type); + device != nullptr && required_device_cnt > 0; + device = GetNextDeviceWithActiveContext(device, context_type)) { + /* Skip if device has ASE configured in this direction already */ + if (device->GetFirstActiveAseByDirection(ent.direction)) continue; + + if (!device->ConfigureAses(ent, context_type, &active_ase_num, + group_snk_audio_locations, + group_src_audio_locations)) + continue; + + required_device_cnt--; + } + + if (required_device_cnt > 0) { + /* Don't left any active devices if requirements are not met */ + LOG(ERROR) << __func__ << " could not configure all the devices"; + Deactivate(); + return false; + } + } + + LOG(INFO) << "Choosed ASE Configuration for group: " << this->group_id_ + << " configuration: " << audio_set_conf->name; + + active_context_type_ = context_type; + return true; +} + +const set_configurations::AudioSetConfiguration* +LeAudioDeviceGroup::GetActiveConfiguration(void) { + return active_context_to_configuration_map[active_context_type_]; +} +AudioContexts LeAudioDeviceGroup::GetActiveContexts(void) { + return active_contexts_mask_; +} + +std::optional<LeAudioCodecConfiguration> +LeAudioDeviceGroup::GetCodecConfigurationByDirection( + types::LeAudioContextType group_context_type, uint8_t direction) { + const set_configurations::AudioSetConfiguration* audio_set_conf = + active_context_to_configuration_map[group_context_type]; + LeAudioCodecConfiguration group_config = {0, 0, 0, 0}; + if (!audio_set_conf) return std::nullopt; + + for (const auto& conf : audio_set_conf->confs) { + if (conf.direction != direction) continue; + + if (group_config.sample_rate != 0 && + conf.codec.GetConfigSamplingFrequency() != group_config.sample_rate) { + LOG(WARNING) << __func__ + << ", stream configuration could not be " + "determined (sampling frequency differs) for direction: " + << loghex(direction); + return std::nullopt; + } + group_config.sample_rate = conf.codec.GetConfigSamplingFrequency(); + + if (group_config.data_interval_us != 0 && + conf.codec.GetConfigDataIntervalUs() != group_config.data_interval_us) { + LOG(WARNING) << __func__ + << ", stream configuration could not be " + "determined (data interval differs) for direction: " + << loghex(direction); + return std::nullopt; + } + group_config.data_interval_us = conf.codec.GetConfigDataIntervalUs(); + + if (group_config.bits_per_sample != 0 && + conf.codec.GetConfigBitsPerSample() != group_config.bits_per_sample) { + LOG(WARNING) << __func__ + << ", stream configuration could not be " + "determined (bits per sample differs) for direction: " + << loghex(direction); + return std::nullopt; + } + group_config.bits_per_sample = conf.codec.GetConfigBitsPerSample(); + + group_config.num_channels += + conf.codec.GetConfigChannelCount() * conf.device_cnt; + } + + if (group_config.IsInvalid()) return std::nullopt; + + return group_config; +} + +types::LeAudioContextType LeAudioDeviceGroup::GetCurrentContextType(void) { + return active_context_type_; +} + +const set_configurations::AudioSetConfiguration* +LeAudioDeviceGroup::FindFirstSupportedConfiguration( + LeAudioContextType context_type) { + const set_configurations::AudioSetConfigurations* confs = + set_configurations::get_confs_by_type(context_type); + + DLOG(INFO) << __func__ << " context type: " << (int)context_type + << " number of connected devices: " << NumOfConnected(); + + /* Filter out device set for all scenarios */ + if (!set_configurations::check_if_may_cover_scenario(confs, + NumOfConnected())) { + LOG(ERROR) << __func__ << ", group is unable to cover scenario"; + return nullptr; + } + + /* Filter out device set for each end every scenario */ + + for (const auto& conf : *confs) { + if (IsConfigurationSupported(conf, context_type)) { + DLOG(INFO) << __func__ << " found: " << conf->name; + return conf; + } + } + + return nullptr; +} + +/* This method should choose aproperiate ASEs to be active and set a cached + * configuration for codec and qos. + */ +bool LeAudioDeviceGroup::Configure(LeAudioContextType context_type) { + const set_configurations::AudioSetConfiguration* conf = + active_context_to_configuration_map[context_type]; + + DLOG(INFO) << __func__; + + if (!conf) { + LOG(ERROR) << __func__ << ", requested context type: " + << loghex(static_cast<uint16_t>(context_type)) + << ", is in mismatch with cached active contexts"; + return false; + } + + DLOG(INFO) << __func__ << " setting context type: " << int(context_type); + + if (!ConfigureAses(conf, context_type)) { + LOG(ERROR) << __func__ << ", requested pick ASE config context type: " + << loghex(static_cast<uint16_t>(context_type)) + << ", is in mismatch with cached active contexts"; + return false; + } + return true; +} + +LeAudioDeviceGroup::~LeAudioDeviceGroup(void) { this->Cleanup(); } +void LeAudioDeviceGroup::Dump(int fd) { + std::stringstream stream; + auto* active_conf = GetActiveConfiguration(); + + stream << " == Group id: " << group_id_ << " == \n" + << " state: " << GetState() << "\n" + << " target state: " << GetTargetState() << "\n" + << " number of devices: " << Size() << "\n" + << " number of connected devices: " << NumOfConnected() << "\n" + << " active context types: " + << loghex(GetActiveContexts().to_ulong()) << "\n" + << " current context type: " + << static_cast<int>(GetCurrentContextType()) << "\n" + << " active stream configuration name: " + << (active_conf ? active_conf->name : " not set") << "\n" + << " Last used stream configuration: \n" + << " valid: " << (stream_conf.valid ? " Yes " : " No") << "\n" + << " codec id : " << +(stream_conf.id.coding_format) << "\n" + << " name: " + << (stream_conf.conf != nullptr ? stream_conf.conf->name : " null ") + << "\n" + << " number of sinks in the configuration " + << stream_conf.sink_num_of_devices << "\n" + << " number of sink_streams connected: " + << stream_conf.sink_streams.size() << "\n" + << " number of sources in the configuration " + << stream_conf.source_num_of_devices << "\n" + << " number of source_streams connected: " + << stream_conf.source_streams.size() << "\n" + << " === devices: ===\n"; + + dprintf(fd, "%s", stream.str().c_str()); + + for (const auto& device_iter : leAudioDevices_) { + device_iter.lock()->Dump(fd); + } +} + +/* LeAudioDevice Class methods implementation */ +void LeAudioDevice::ClearPACs(void) { + snk_pacs_.clear(); + src_pacs_.clear(); +} + +LeAudioDevice::~LeAudioDevice(void) { + alarm_free(link_quality_timer); + this->ClearPACs(); +} + +void LeAudioDevice::RegisterPACs( + std::vector<struct types::acs_ac_record>* pac_db, + std::vector<struct types::acs_ac_record>* pac_recs) { + /* Clear PAC database for characteristic in case if re-read, indicated */ + if (!pac_db->empty()) { + DLOG(INFO) << __func__ << ", upgrade PACs for characteristic"; + pac_db->clear(); + } + + /* TODO wrap this logging part with debug flag */ + for (const struct types::acs_ac_record& pac : *pac_recs) { + LOG(INFO) << "Registering PAC" + << "\n\tCoding format: " << loghex(pac.codec_id.coding_format) + << "\n\tVendor codec company ID: " + << loghex(pac.codec_id.vendor_company_id) + << "\n\tVendor codec ID: " << loghex(pac.codec_id.vendor_codec_id) + << "\n\tCodec spec caps:\n" + << pac.codec_spec_caps.ToString() << "\n\tMetadata: " + << base::HexEncode(pac.metadata.data(), pac.metadata.size()); + } + + pac_db->insert(pac_db->begin(), pac_recs->begin(), pac_recs->end()); +} + +struct ase* LeAudioDevice::GetAseByValHandle(uint16_t val_hdl) { + auto iter = std::find_if( + ases_.begin(), ases_.end(), + [&val_hdl](const auto& ase) { return ase.hdls.val_hdl == val_hdl; }); + + return (iter == ases_.end()) ? nullptr : &(*iter); +} + +struct ase* LeAudioDevice::GetFirstInactiveAseWithState(uint8_t direction, + AseState state) { + auto iter = std::find_if( + ases_.begin(), ases_.end(), [direction, state](const auto& ase) { + return ((ase.direction == direction) && (ase.state == state)); + }); + + return (iter == ases_.end()) ? nullptr : &(*iter); +} + +struct ase* LeAudioDevice::GetFirstActiveAse(void) { + auto iter = std::find_if(ases_.begin(), ases_.end(), + [](const auto& ase) { return ase.active; }); + + return (iter == ases_.end()) ? nullptr : &(*iter); +} + +struct ase* LeAudioDevice::GetFirstActiveAseByDirection(uint8_t direction) { + auto iter = + std::find_if(ases_.begin(), ases_.end(), [direction](const auto& ase) { + return (ase.active && (ase.direction == direction)); + }); + + return (iter == ases_.end()) ? nullptr : &(*iter); +} + +struct ase* LeAudioDevice::GetNextActiveAseWithSameDirection( + struct ase* base_ase) { + auto iter = std::find_if(ases_.begin(), ases_.end(), + [&base_ase](auto& ase) { return base_ase == &ase; }); + + /* Invalid ase given */ + if (iter == ases_.end() || std::distance(iter, ases_.end()) < 1) + return nullptr; + + iter = + std::find_if(std::next(iter, 1), ases_.end(), [&iter](const auto& ase) { + return ase.active && (*iter).direction == ase.direction; + }); + + return (iter == ases_.end()) ? nullptr : &(*iter); +} + +struct ase* LeAudioDevice::GetFirstActiveAseByDataPathState( + types::AudioStreamDataPathState state) { + auto iter = + std::find_if(ases_.begin(), ases_.end(), [state](const auto& ase) { + return (ase.active && (ase.data_path_state == state)); + }); + + return (iter == ases_.end()) ? nullptr : &(*iter); +} + +struct ase* LeAudioDevice::GetFirstInactiveAse(uint8_t direction, + bool reconnect) { + auto iter = std::find_if(ases_.begin(), ases_.end(), + [direction, reconnect](const auto& ase) { + if (ase.active || (ase.direction != direction)) + return false; + + if (!reconnect) return true; + + return (ase.cis_id != kInvalidCisId); + }); + + return (iter == ases_.end()) ? nullptr : &(*iter); +} + +struct ase* LeAudioDevice::GetNextActiveAse(struct ase* base_ase) { + auto iter = std::find_if(ases_.begin(), ases_.end(), + [&base_ase](auto& ase) { return base_ase == &ase; }); + + /* Invalid ase given */ + if (iter == ases_.end() || std::distance(iter, ases_.end()) < 1) + return nullptr; + + iter = std::find_if(std::next(iter, 1), ases_.end(), + [](const auto& ase) { return ase.active; }); + + return (iter == ases_.end()) ? nullptr : &(*iter); +} + +struct ase* LeAudioDevice::GetAseToMatchBidirectionCis(struct ase* base_ase) { + auto iter = std::find_if(ases_.begin(), ases_.end(), [&base_ase](auto& ase) { + return (base_ase->cis_conn_hdl == ase.cis_conn_hdl) && + (base_ase->direction != ase.direction); + }); + return (iter == ases_.end()) ? nullptr : &(*iter); +} + +BidirectAsesPair LeAudioDevice::GetAsesByCisConnHdl(uint16_t conn_hdl) { + BidirectAsesPair ases; + + for (auto& ase : ases_) { + if (ase.cis_conn_hdl == conn_hdl) { + if (ase.direction == types::kLeAudioDirectionSink) { + ases.sink = &ase; + } else { + ases.source = &ase; + } + } + } + + return ases; +} + +BidirectAsesPair LeAudioDevice::GetAsesByCisId(uint8_t cis_id) { + BidirectAsesPair ases; + + for (auto& ase : ases_) { + if (ase.cis_id == cis_id) { + if (ase.direction == types::kLeAudioDirectionSink) { + ases.sink = &ase; + } else { + ases.source = &ase; + } + } + } + + return ases; +} + +bool LeAudioDevice::HaveActiveAse(void) { + auto iter = std::find_if(ases_.begin(), ases_.end(), + [](const auto& ase) { return ase.active; }); + + return iter != ases_.end(); +} + +bool LeAudioDevice::HaveAnyUnconfiguredAses(void) { + /* In configuring state when active in Idle or Configured and reconfigure */ + auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) { + if (!ase.active) return false; + + if (ase.state == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE || + ((ase.state == AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED) && + ase.reconfigure)) + return true; + + return false; + }); + + return iter != ases_.end(); +} + +bool LeAudioDevice::HaveAllActiveAsesSameState(AseState state) { + auto iter = std::find_if( + ases_.begin(), ases_.end(), + [&state](const auto& ase) { return ase.active && (ase.state != state); }); + + return iter == ases_.end(); +} + +bool LeAudioDevice::IsReadyToCreateStream(void) { + auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) { + if (!ase.active) return false; + + if (ase.direction == types::kLeAudioDirectionSink && + (ase.state != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING && + ase.state != AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING)) + return true; + + if (ase.direction == types::kLeAudioDirectionSource && + ase.state != AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING) + return true; + + return false; + }); + + return iter == ases_.end(); +} + +bool LeAudioDevice::IsReadyToSuspendStream(void) { + auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) { + if (!ase.active) return false; + + if (ase.direction == types::kLeAudioDirectionSink && + ase.state != AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) + return true; + + if (ase.direction == types::kLeAudioDirectionSource && + ase.state != AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING) + return true; + + return false; + }); + + return iter == ases_.end(); +} + +bool LeAudioDevice::HaveAllActiveAsesCisEst(void) { + auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) { + return ase.active && + (ase.data_path_state != AudioStreamDataPathState::CIS_ESTABLISHED); + }); + + return iter == ases_.end(); +} + +bool LeAudioDevice::HaveAllAsesCisDisc(void) { + auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) { + return ase.active && + (ase.data_path_state != AudioStreamDataPathState::CIS_ASSIGNED); + }); + + return iter == ases_.end(); +} + +bool LeAudioDevice::HasCisId(uint8_t id) { + struct ase* ase = GetFirstActiveAse(); + + while (ase) { + if (ase->cis_id == id) return true; + ase = GetNextActiveAse(ase); + } + + return false; +} + +uint8_t LeAudioDevice::GetMatchingBidirectionCisId( + const struct types::ase* base_ase) { + for (auto& ase : ases_) { + auto& cis = ase.cis_id; + if (!ase.active) continue; + + int num_cises = + std::count_if(ases_.begin(), ases_.end(), [&cis](const auto& iter_ase) { + return iter_ase.active && iter_ase.cis_id == cis; + }); + + /* + * If there is only one ASE for device with unique CIS ID and opposite to + * direction - it may be bi-directional/completive. + */ + if (num_cises == 1 && + ((base_ase->direction == types::kLeAudioDirectionSink && + ase.direction == types::kLeAudioDirectionSource) || + (base_ase->direction == types::kLeAudioDirectionSource && + ase.direction == types::kLeAudioDirectionSink))) { + return ase.cis_id; + } + } + + return kInvalidCisId; +} + +uint8_t LeAudioDevice::GetLc3SupportedChannelCount(uint8_t direction) { + auto& pacs = + direction == types::kLeAudioDirectionSink ? snk_pacs_ : src_pacs_; + + if (pacs.size() == 0) { + LOG(ERROR) << __func__ << " missing PAC for direction " << +direction; + return 0; + } + + for (const auto& pac_tuple : pacs) { + /* Get PAC records from tuple as second element from tuple */ + auto& pac_recs = std::get<1>(pac_tuple); + + for (const auto pac : pac_recs) { + if (pac.codec_id.coding_format != types::kLeAudioCodingFormatLC3) + continue; + + auto supported_channel_count_ltv = pac.codec_spec_caps.Find( + codec_spec_caps::kLeAudioCodecLC3TypeAudioChannelCounts); + + return VEC_UINT8_TO_UINT8(supported_channel_count_ltv.value()); + }; + } + + return 0; +} + +bool LeAudioDevice::IsCodecConfigurationSupported( + uint8_t direction, const CodecCapabilitySetting& codec_capability_setting) { + auto& pacs = + direction == types::kLeAudioDirectionSink ? snk_pacs_ : src_pacs_; + + if (pacs.size() == 0) { + LOG(ERROR) << __func__ << " missing PAC for direction " << +direction; + return false; + } + + /* TODO: Validate channel locations */ + + for (const auto& pac_tuple : pacs) { + /* Get PAC records from tuple as second element from tuple */ + auto& pac_recs = std::get<1>(pac_tuple); + + for (const auto pac : pac_recs) { + if (!IsCodecCapabilitySettingSupported(pac, codec_capability_setting)) + continue; + + return true; + }; + } + + /* Doesn't match required configuration with any PAC */ + return false; +} + +/** + * Returns supported PHY's bitfield + */ +uint8_t LeAudioDevice::GetPhyBitmask(void) { + uint8_t phy_bitfield = kIsoCigPhy1M; + + if (BTM_IsPhy2mSupported(address_, BT_TRANSPORT_LE)) + phy_bitfield |= kIsoCigPhy2M; + + return phy_bitfield; +} + +void LeAudioDevice::SetSupportedContexts(AudioContexts snk_contexts, + AudioContexts src_contexts) { + supp_snk_context_ = snk_contexts; + supp_src_context_ = src_contexts; +} + +void LeAudioDevice::Dump(int fd) { + std::stringstream stream; + stream << " address: " << address_ << "\n" + << (conn_id_ == GATT_INVALID_CONN_ID ? " Not connected " + : " Connected conn_id =") + << (conn_id_ == GATT_INVALID_CONN_ID ? "" : std::to_string(conn_id_)) + << "\n" + << " set member: " << (csis_member_ ? " Yes" : " No") << "\n" + << " known_service_handles_: " << known_service_handles_ + << "\n" + << " notify_connected_after_read_: " + << notify_connected_after_read_ << "\n" + << " removing_device_: " << removing_device_ << "\n" + << " first_connection_: " << first_connection_ << "\n" + << " encrypted_: " << encrypted_ << "\n" + << " connecting_actively_: " << connecting_actively_ << "\n"; + + dprintf(fd, "%s", stream.str().c_str()); +} + +AudioContexts LeAudioDevice::GetAvailableContexts(void) { + return avail_snk_contexts_ | avail_src_contexts_; +} + +/* Returns XOR of updated sink and source bitset context types */ +AudioContexts LeAudioDevice::SetAvailableContexts(AudioContexts snk_contexts, + AudioContexts src_contexts) { + AudioContexts updated_contexts; + + updated_contexts = snk_contexts ^ avail_snk_contexts_; + updated_contexts |= src_contexts ^ avail_src_contexts_; + + DLOG(INFO) << __func__ + << "\n\t avail_snk_contexts_: " << avail_snk_contexts_.to_string() + << "\n\t avail_src_contexts_: " << avail_src_contexts_.to_string() + << "\n\t snk_contexts:" << snk_contexts.to_string() + << "\n\t src_contexts: " << src_contexts.to_string() + << "\n\t updated_contexts: " << updated_contexts.to_string(); + + avail_snk_contexts_ = snk_contexts; + avail_src_contexts_ = src_contexts; + + return updated_contexts; +} + +void LeAudioDevice::DeactivateAllAses(void) { + /* Just clear states and keep previous configuration for use + * in case device will get reconnected + */ + for (auto& ase : ases_) { + if (ase.active) { + ase.state = AseState::BTA_LE_AUDIO_ASE_STATE_IDLE; + ase.data_path_state = AudioStreamDataPathState::IDLE; + ase.active = false; + } + } +} + +LeAudioDeviceGroup* LeAudioDeviceGroups::Add(int group_id) { + /* Get first free group id */ + if (FindById(group_id)) { + LOG(ERROR) << __func__ + << ", group already exists, id: " << loghex(group_id); + return nullptr; + } + + return (groups_.emplace_back(std::make_unique<LeAudioDeviceGroup>(group_id))) + .get(); +} + +void LeAudioDeviceGroups::Remove(int group_id) { + auto iter = std::find_if( + groups_.begin(), groups_.end(), + [&group_id](auto const& group) { return group->group_id_ == group_id; }); + + if (iter == groups_.end()) { + LOG(ERROR) << __func__ << ", no such group_id: " << group_id; + return; + } + + groups_.erase(iter); +} + +LeAudioDeviceGroup* LeAudioDeviceGroups::FindById(int group_id) { + auto iter = std::find_if( + groups_.begin(), groups_.end(), + [&group_id](auto const& group) { return group->group_id_ == group_id; }); + + return (iter == groups_.end()) ? nullptr : iter->get(); +} + +void LeAudioDeviceGroups::Cleanup(void) { + for (auto& g : groups_) { + g->Cleanup(); + } + + groups_.clear(); +} + +void LeAudioDeviceGroups::Dump(int fd) { + for (auto& g : groups_) { + g->Dump(fd); + } +} + +bool LeAudioDeviceGroups::IsAnyInTransition(void) { + for (auto& g : groups_) { + if (g->IsInTransition()) { + DLOG(INFO) << __func__ << " group: " << g->group_id_ + << " is in transition"; + return true; + } + } + return false; +} + +size_t LeAudioDeviceGroups::Size() { return (groups_.size()); } + +std::vector<int> LeAudioDeviceGroups::GetGroupsIds(void) { + std::vector<int> result; + + for (auto const& group : groups_) { + result.push_back(group->group_id_); + } + + return result; +} + +/* LeAudioDevices Class methods implementation */ +void LeAudioDevices::Add(const RawAddress& address, bool first_connection, + int group_id) { + auto device = FindByAddress(address); + if (device != nullptr) { + LOG(ERROR) << __func__ << ", address: " << address + << " is already assigned to group: " << device->group_id_; + return; + } + + leAudioDevices_.emplace_back( + std::make_shared<LeAudioDevice>(address, first_connection, group_id)); +} + +void LeAudioDevices::Remove(const RawAddress& address) { + auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), + [&address](auto const& leAudioDevice) { + return leAudioDevice->address_ == address; + }); + + if (iter == leAudioDevices_.end()) { + LOG(ERROR) << __func__ << ", no such address: " << address; + return; + } + + leAudioDevices_.erase(iter); +} + +LeAudioDevice* LeAudioDevices::FindByAddress(const RawAddress& address) { + auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), + [&address](auto const& leAudioDevice) { + return leAudioDevice->address_ == address; + }); + + return (iter == leAudioDevices_.end()) ? nullptr : iter->get(); +} + +std::shared_ptr<LeAudioDevice> LeAudioDevices::GetByAddress( + const RawAddress& address) { + auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), + [&address](auto const& leAudioDevice) { + return leAudioDevice->address_ == address; + }); + + return (iter == leAudioDevices_.end()) ? nullptr : *iter; +} + +LeAudioDevice* LeAudioDevices::FindByConnId(uint16_t conn_id) { + auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), + [&conn_id](auto const& leAudioDevice) { + return leAudioDevice->conn_id_ == conn_id; + }); + + return (iter == leAudioDevices_.end()) ? nullptr : iter->get(); +} + +LeAudioDevice* LeAudioDevices::FindByCisConnHdl(const uint16_t conn_hdl) { + auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), + [&conn_hdl](auto& d) { + LeAudioDevice* dev; + BidirectAsesPair ases; + + dev = d.get(); + ases = dev->GetAsesByCisConnHdl(conn_hdl); + if (ases.sink || ases.source) + return true; + else + return false; + }); + + if (iter == leAudioDevices_.end()) return nullptr; + + return iter->get(); +} + +size_t LeAudioDevices::Size() { return (leAudioDevices_.size()); } + +void LeAudioDevices::Dump(int fd, int group_id) { + for (auto const& device : leAudioDevices_) { + if (device->group_id_ == group_id) { + device->Dump(fd); + } + } +} + +void LeAudioDevices::Cleanup(void) { leAudioDevices_.clear(); } + +} // namespace le_audio diff --git a/system/bta/le_audio/devices.h b/system/bta/le_audio/devices.h new file mode 100644 index 0000000000000000000000000000000000000000..464afcd1421caae978fbc1d445855ad07244121c --- /dev/null +++ b/system/bta/le_audio/devices.h @@ -0,0 +1,315 @@ +/* + * Copyright 2020 HIMSA II K/S - www.himsa.com. Represented by EHIMA + * - www.ehima.com + * + * 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 <map> +#include <memory> +#include <optional> +#include <tuple> +#include <vector> + +#include "bt_types.h" +#include "bta_groups.h" +#include "btm_iso_api_types.h" +#include "client_audio.h" +#include "gatt_api.h" +#include "le_audio_types.h" +#include "osi/include/alarm.h" +#include "raw_address.h" + +namespace le_audio { +/* Class definitions */ + +/* LeAudioDevice class represents GATT server device with ASCS, PAC services as + * mandatory. Device may contain multiple ASEs, PACs, audio locations. ASEs from + * multiple devices may be formed in group. + * + * Device is created after connection or after storage restoration. + * + * Active device means that device has at least one ASE which will participate + * in any state transition of state machine. ASEs and devices will be activated + * according to requested by upper context type. + */ +class LeAudioDevice { + public: + RawAddress address_; + + bool known_service_handles_; + bool notify_connected_after_read_; + bool removing_device_; + + /* we are making active attempt to connect to this device, 'direct connect'. + * This is true only during initial phase of first connection. */ + bool first_connection_; + bool connecting_actively_; + uint16_t conn_id_; + bool encrypted_; + int group_id_; + bool csis_member_; + + uint8_t audio_directions_; + types::AudioLocations snk_audio_locations_; + types::AudioLocations src_audio_locations_; + + types::PublishedAudioCapabilities snk_pacs_; + types::PublishedAudioCapabilities src_pacs_; + + struct types::hdl_pair snk_audio_locations_hdls_; + struct types::hdl_pair src_audio_locations_hdls_; + struct types::hdl_pair audio_avail_hdls_; + struct types::hdl_pair audio_supp_cont_hdls_; + std::vector<struct types::ase> ases_; + struct types::hdl_pair ctp_hdls_; + + alarm_t* link_quality_timer; + uint16_t link_quality_timer_data; + + LeAudioDevice(const RawAddress& address_, bool first_connection, + int group_id = bluetooth::groups::kGroupUnknown) + : address_(address_), + known_service_handles_(false), + notify_connected_after_read_(false), + removing_device_(false), + first_connection_(first_connection), + connecting_actively_(first_connection), + conn_id_(GATT_INVALID_CONN_ID), + encrypted_(false), + group_id_(group_id), + csis_member_(false), + link_quality_timer(nullptr) {} + ~LeAudioDevice(void); + + void ClearPACs(void); + void RegisterPACs(std::vector<struct types::acs_ac_record>* apr_db, + std::vector<struct types::acs_ac_record>* apr); + struct types::ase* GetAseByValHandle(uint16_t val_hdl); + struct types::ase* GetFirstActiveAse(void); + struct types::ase* GetFirstActiveAseByDirection(uint8_t direction); + struct types::ase* GetNextActiveAseWithSameDirection( + struct types::ase* base_ase); + struct types::ase* GetFirstActiveAseByDataPathState( + types::AudioStreamDataPathState state); + struct types::ase* GetFirstInactiveAse(uint8_t direction, + bool reconnect = false); + struct types::ase* GetFirstInactiveAseWithState(uint8_t direction, + types::AseState state); + struct types::ase* GetNextActiveAse(struct types::ase* ase); + struct types::ase* GetAseToMatchBidirectionCis(struct types::ase* ase); + types::BidirectAsesPair GetAsesByCisConnHdl(uint16_t conn_hdl); + types::BidirectAsesPair GetAsesByCisId(uint8_t cis_id); + bool HaveActiveAse(void); + bool HaveAllActiveAsesSameState(types::AseState state); + bool HaveAnyUnconfiguredAses(void); + bool IsReadyToCreateStream(void); + bool IsReadyToSuspendStream(void); + bool HaveAllActiveAsesCisEst(void); + bool HaveAllAsesCisDisc(void); + bool HasCisId(uint8_t id); + uint8_t GetMatchingBidirectionCisId(const struct types::ase* base_ase); + bool IsCodecConfigurationSupported( + uint8_t direction, const set_configurations::CodecCapabilitySetting& + codec_capability_setting); + uint8_t GetLc3SupportedChannelCount(uint8_t direction); + uint8_t GetPhyBitmask(void); + bool ConfigureAses(const le_audio::set_configurations::SetConfiguration& ent, + types::LeAudioContextType context_type, + uint8_t* number_of_already_active_group_ase, + types::AudioLocations& group_snk_audio_locations, + types::AudioLocations& group_src_audio_locations, + bool reconnect = false); + void SetSupportedContexts(types::AudioContexts snk_contexts, + types::AudioContexts src_contexts); + types::AudioContexts GetAvailableContexts(void); + types::AudioContexts SetAvailableContexts(types::AudioContexts snk_cont_val, + types::AudioContexts src_cont_val); + void DeactivateAllAses(void); + void Dump(int fd); + + private: + types::AudioContexts avail_snk_contexts_; + types::AudioContexts avail_src_contexts_; + types::AudioContexts supp_snk_context_; + types::AudioContexts supp_src_context_; +}; + +/* LeAudioDevices class represents a wraper helper over all devices in le audio + * implementation. It allows to operate on device from a list (vector container) + * using determinants like address, connection id etc. + */ +class LeAudioDevices { + public: + void Add(const RawAddress& address, bool first_connection, + int group_id = bluetooth::groups::kGroupUnknown); + void Remove(const RawAddress& address); + LeAudioDevice* FindByAddress(const RawAddress& address); + std::shared_ptr<LeAudioDevice> GetByAddress(const RawAddress& address); + LeAudioDevice* FindByConnId(uint16_t conn_id); + LeAudioDevice* FindByCisConnHdl(const uint16_t conn_hdl); + size_t Size(void); + void Dump(int fd, int group_id); + void Cleanup(void); + + private: + std::vector<std::shared_ptr<LeAudioDevice>> leAudioDevices_; +}; + +/* LeAudioDeviceGroup class represents group of LeAudioDevices and allows to + * perform operations on them. Group states are ASE states due to nature of + * group which operates finally of ASEs. + * + * Group is created after adding a node to new group id (which is not on list). + */ + +class LeAudioDeviceGroup { + public: + const int group_id_; + bool cig_created_; + + struct stream_configuration stream_conf; + + uint8_t audio_directions_; + types::AudioLocations snk_audio_locations_; + types::AudioLocations src_audio_locations_; + + /* The below is used only for Coordinate sets */ + types::CsisDiscoveryState csis_discovery_state_; + explicit LeAudioDeviceGroup(const int group_id) + : group_id_(group_id), + cig_created_(false), + transport_latency_mtos_(0), + transport_latency_stom_(0), + target_state_(types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE), + current_state_(types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) { + stream_conf.valid = false; + stream_conf.conf = nullptr; + stream_conf.sink_num_of_devices = 0; + stream_conf.source_num_of_devices = 0; + } + ~LeAudioDeviceGroup(void); + + void AddNode(const std::shared_ptr<LeAudioDevice>& leAudioDevice); + void RemoveNode(const std::shared_ptr<LeAudioDevice>& leAudioDevice); + bool IsEmpty(void); + bool IsAnyDeviceConnected(void); + int Size(void); + int NumOfConnected( + types::LeAudioContextType context_type = types::LeAudioContextType::RFU); + void Deactivate(void); + void Cleanup(void); + LeAudioDevice* GetFirstDevice(void); + LeAudioDevice* GetFirstDeviceWithActiveContext( + types::LeAudioContextType context_type); + LeAudioDevice* GetNextDevice(LeAudioDevice* leAudioDevice); + LeAudioDevice* GetNextDeviceWithActiveContext( + LeAudioDevice* leAudioDevice, types::LeAudioContextType context_type); + LeAudioDevice* GetFirstActiveDevice(void); + LeAudioDevice* GetNextActiveDevice(LeAudioDevice* leAudioDevice); + bool IsDeviceInTheGroup(LeAudioDevice* leAudioDevice); + bool HaveAllActiveDevicesAsesTheSameState(types::AseState state); + bool IsGroupStreamReady(void); + bool HaveAllActiveDevicesCisDisc(void); + uint8_t GetFirstFreeCisId(void); + bool Configure(types::LeAudioContextType context_type); + bool SetContextType(types::LeAudioContextType context_type); + types::LeAudioContextType GetContextType(void); + uint32_t GetSduInterval(uint8_t direction); + uint8_t GetSCA(void); + uint8_t GetPacking(void); + uint8_t GetFraming(void); + uint8_t GetTargetLatency(void); + uint16_t GetMaxTransportLatencyStom(void); + uint16_t GetMaxTransportLatencyMtos(void); + void SetTransportLatency(uint8_t direction, uint16_t transport_latency); + uint8_t GetPhyBitmask(uint8_t direction); + uint8_t GetTargetPhy(uint8_t direction); + bool GetPresentationDelay(uint32_t* delay, uint8_t direction); + uint16_t GetRemoteDelay(uint8_t direction); + std::optional<types::AudioContexts> UpdateActiveContextsMap( + types::AudioContexts contexts); + std::optional<types::AudioContexts> UpdateActiveContextsMap(void); + bool ReloadAudioLocations(void); + const set_configurations::AudioSetConfiguration* GetActiveConfiguration(void); + types::LeAudioContextType GetCurrentContextType(void); + types::AudioContexts GetActiveContexts(void); + std::optional<LeAudioCodecConfiguration> GetCodecConfigurationByDirection( + types::LeAudioContextType group_context_type, uint8_t direction); + + inline types::AseState GetState(void) const { return current_state_; } + void SetState(types::AseState state) { + LOG(INFO) << __func__ << " current state: " << current_state_ + << " new state: " << state; + current_state_ = state; + } + + inline types::AseState GetTargetState(void) const { return target_state_; } + void SetTargetState(types::AseState state) { + LOG(INFO) << __func__ << " target state: " << target_state_ + << " new target state: " << state; + target_state_ = state; + } + + bool IsInTransition(void); + bool IsReleasing(void); + void Dump(int fd); + + private: + uint16_t transport_latency_mtos_; + uint16_t transport_latency_stom_; + + const set_configurations::AudioSetConfiguration* + FindFirstSupportedConfiguration(types::LeAudioContextType context_type); + bool ConfigureAses( + const set_configurations::AudioSetConfiguration* audio_set_conf, + types::LeAudioContextType context_type); + bool IsConfigurationSupported( + const set_configurations::AudioSetConfiguration* audio_set_configuration, + types::LeAudioContextType context_type); + uint16_t GetTransportLatency(uint8_t direction); + + /* Mask and table of currently supported contexts */ + types::LeAudioContextType active_context_type_; + types::AudioContexts active_contexts_mask_; + std::map<types::LeAudioContextType, + const set_configurations::AudioSetConfiguration*> + active_context_to_configuration_map; + + types::AseState target_state_; + types::AseState current_state_; + types::LeAudioContextType context_type_; + std::vector<std::weak_ptr<LeAudioDevice>> leAudioDevices_; +}; + +/* LeAudioDeviceGroup class represents a wraper helper over all device groups in + * le audio implementation. It allows to operate on device group from a list + * (vector container) using determinants like id. + */ +class LeAudioDeviceGroups { + public: + LeAudioDeviceGroup* Add(int group_id); + void Remove(const int group_id); + LeAudioDeviceGroup* FindById(int group_id); + std::vector<int> GetGroupsIds(void); + size_t Size(); + bool IsAnyInTransition(); + void Cleanup(void); + void Dump(int fd); + + private: + std::vector<std::unique_ptr<LeAudioDeviceGroup>> groups_; +}; +} // namespace le_audio diff --git a/system/bta/le_audio/devices_test.cc b/system/bta/le_audio/devices_test.cc new file mode 100644 index 0000000000000000000000000000000000000000..7b9f5429b0c2eb71205b3ee61b3307d94b702e5f --- /dev/null +++ b/system/bta/le_audio/devices_test.cc @@ -0,0 +1,898 @@ +/* + * Copyright 2020 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * 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 "devices.h" + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include "btm_api_mock.h" +#include "le_audio_types.h" +#include "mock_controller.h" +#include "stack/btm/btm_int_types.h" + +tACL_CONN* btm_bda_to_acl(const RawAddress& bda, tBT_TRANSPORT transport) { + return nullptr; +} + +namespace bluetooth { +namespace le_audio { +namespace internal { +namespace { + +using ::le_audio::LeAudioDevice; +using ::le_audio::LeAudioDeviceGroup; +using ::le_audio::LeAudioDevices; +using ::le_audio::types::AseState; +using ::le_audio::types::AudioContexts; +using ::le_audio::types::LeAudioContextType; +using testing::Test; + +RawAddress GetTestAddress(int index) { + CHECK_LT(index, UINT8_MAX); + RawAddress result = { + {0xC0, 0xDE, 0xC0, 0xDE, 0x00, static_cast<uint8_t>(index)}}; + return result; +} + +class LeAudioDevicesTest : public Test { + protected: + void SetUp() override { + devices_ = new LeAudioDevices(); + bluetooth::manager::SetMockBtmInterface(&btm_interface); + controller::SetMockControllerInterface(&controller_interface_); + } + + void TearDown() override { + controller::SetMockControllerInterface(nullptr); + bluetooth::manager::SetMockBtmInterface(nullptr); + delete devices_; + } + + LeAudioDevices* devices_ = nullptr; + bluetooth::manager::MockBtmInterface btm_interface; + controller::MockControllerInterface controller_interface_; +}; + +TEST_F(LeAudioDevicesTest, test_add) { + RawAddress test_address_0 = GetTestAddress(0); + ASSERT_EQ((size_t)0, devices_->Size()); + devices_->Add(test_address_0, true); + ASSERT_EQ((size_t)1, devices_->Size()); + devices_->Add(GetTestAddress(1), true, 1); + ASSERT_EQ((size_t)2, devices_->Size()); + devices_->Add(test_address_0, true); + ASSERT_EQ((size_t)2, devices_->Size()); + devices_->Add(GetTestAddress(1), true, 2); + ASSERT_EQ((size_t)2, devices_->Size()); +} + +TEST_F(LeAudioDevicesTest, test_remove) { + RawAddress test_address_0 = GetTestAddress(0); + devices_->Add(test_address_0, true); + RawAddress test_address_1 = GetTestAddress(1); + devices_->Add(test_address_1, true); + RawAddress test_address_2 = GetTestAddress(2); + devices_->Add(test_address_2, true); + ASSERT_EQ((size_t)3, devices_->Size()); + devices_->Remove(test_address_0); + ASSERT_EQ((size_t)2, devices_->Size()); + devices_->Remove(GetTestAddress(3)); + ASSERT_EQ((size_t)2, devices_->Size()); + devices_->Remove(test_address_0); + ASSERT_EQ((size_t)2, devices_->Size()); +} + +TEST_F(LeAudioDevicesTest, test_find_by_address_success) { + RawAddress test_address_0 = GetTestAddress(0); + devices_->Add(test_address_0, true); + RawAddress test_address_1 = GetTestAddress(1); + devices_->Add(test_address_1, false); + RawAddress test_address_2 = GetTestAddress(2); + devices_->Add(test_address_2, true); + LeAudioDevice* device = devices_->FindByAddress(test_address_1); + ASSERT_NE(nullptr, device); + ASSERT_EQ(test_address_1, device->address_); +} + +TEST_F(LeAudioDevicesTest, test_find_by_address_failed) { + RawAddress test_address_0 = GetTestAddress(0); + devices_->Add(test_address_0, true); + RawAddress test_address_2 = GetTestAddress(2); + devices_->Add(test_address_2, true); + LeAudioDevice* device = devices_->FindByAddress(GetTestAddress(1)); + ASSERT_EQ(nullptr, device); +} + +TEST_F(LeAudioDevicesTest, test_get_by_address_success) { + RawAddress test_address_0 = GetTestAddress(0); + devices_->Add(test_address_0, true); + RawAddress test_address_1 = GetTestAddress(1); + devices_->Add(test_address_1, false); + RawAddress test_address_2 = GetTestAddress(2); + devices_->Add(test_address_2, true); + std::shared_ptr<LeAudioDevice> device = + devices_->GetByAddress(test_address_1); + ASSERT_NE(nullptr, device); + ASSERT_EQ(test_address_1, device->address_); +} + +TEST_F(LeAudioDevicesTest, test_get_by_address_failed) { + RawAddress test_address_0 = GetTestAddress(0); + devices_->Add(test_address_0, true); + RawAddress test_address_2 = GetTestAddress(2); + devices_->Add(test_address_2, true); + std::shared_ptr<LeAudioDevice> device = + devices_->GetByAddress(GetTestAddress(1)); + ASSERT_EQ(nullptr, device); +} + +TEST_F(LeAudioDevicesTest, test_find_by_conn_id_success) { + devices_->Add(GetTestAddress(1), true); + RawAddress test_address_0 = GetTestAddress(0); + devices_->Add(test_address_0, true); + devices_->Add(GetTestAddress(4), true); + LeAudioDevice* device = devices_->FindByAddress(test_address_0); + device->conn_id_ = 0x0005; + ASSERT_EQ(device, devices_->FindByConnId(0x0005)); +} + +TEST_F(LeAudioDevicesTest, test_find_by_conn_id_failed) { + devices_->Add(GetTestAddress(1), true); + devices_->Add(GetTestAddress(0), true); + devices_->Add(GetTestAddress(4), true); + ASSERT_EQ(nullptr, devices_->FindByConnId(0x0006)); +} + +/* TODO: Add FindByCisConnHdl test cases (ASE) */ + +} // namespace + +namespace { +using namespace ::le_audio::codec_spec_caps; +using namespace ::le_audio::set_configurations; +using namespace ::le_audio::types; + +static const hdl_pair hdl_pair_nil = hdl_pair(0x0000, 0x0000); + +enum class Lc3SettingId { + _BEGIN, + LC3_8_1 = _BEGIN, + LC3_8_2, + LC3_16_1, + LC3_16_2, + LC3_24_1, + LC3_24_2, + LC3_32_1, + LC3_32_2, + LC3_441_1, + LC3_441_2, + LC3_48_1, + LC3_48_2, + LC3_48_3, + LC3_48_4, + LC3_48_5, + LC3_48_6, + _END, + UNSUPPORTED = _END, +}; +static constexpr int Lc3SettingIdBegin = static_cast<int>(Lc3SettingId::_BEGIN); +static constexpr int Lc3SettingIdEnd = static_cast<int>(Lc3SettingId::_END); + +bool IsLc3SettingSupported(LeAudioContextType context_type, Lc3SettingId id) { + /* Update those values, on any change of codec linked with content type */ + switch (context_type) { + case LeAudioContextType::RINGTONE: + case LeAudioContextType::CONVERSATIONAL: + if (id == Lc3SettingId::LC3_16_1 || id == Lc3SettingId::LC3_16_2) + return true; + + break; + + case LeAudioContextType::MEDIA: + if (id == Lc3SettingId::LC3_16_1 || id == Lc3SettingId::LC3_16_2 || + id == Lc3SettingId::LC3_48_4) + return true; + + break; + + default: + if (id == Lc3SettingId::LC3_16_2) return true; + + break; + }; + + return false; +} + +static constexpr uint8_t kLeAudioSamplingFreqRfu = 0x0E; +uint8_t GetSamplingFrequency(Lc3SettingId id) { + switch (id) { + case Lc3SettingId::LC3_8_1: + case Lc3SettingId::LC3_8_2: + return ::le_audio::codec_spec_conf::kLeAudioSamplingFreq8000Hz; + case Lc3SettingId::LC3_16_1: + case Lc3SettingId::LC3_16_2: + return ::le_audio::codec_spec_conf::kLeAudioSamplingFreq16000Hz; + case Lc3SettingId::LC3_24_1: + case Lc3SettingId::LC3_24_2: + return ::le_audio::codec_spec_conf::kLeAudioSamplingFreq24000Hz; + case Lc3SettingId::LC3_32_1: + case Lc3SettingId::LC3_32_2: + return ::le_audio::codec_spec_conf::kLeAudioSamplingFreq32000Hz; + case Lc3SettingId::LC3_441_1: + case Lc3SettingId::LC3_441_2: + return ::le_audio::codec_spec_conf::kLeAudioSamplingFreq44100Hz; + case Lc3SettingId::LC3_48_1: + case Lc3SettingId::LC3_48_2: + case Lc3SettingId::LC3_48_3: + case Lc3SettingId::LC3_48_4: + case Lc3SettingId::LC3_48_5: + case Lc3SettingId::LC3_48_6: + return ::le_audio::codec_spec_conf::kLeAudioSamplingFreq48000Hz; + case Lc3SettingId::UNSUPPORTED: + return kLeAudioSamplingFreqRfu; + } +} + +static constexpr uint8_t kLeAudioCodecLC3FrameDurRfu = 0x02; +uint8_t GetFrameDuration(Lc3SettingId id) { + switch (id) { + case Lc3SettingId::LC3_8_1: + case Lc3SettingId::LC3_16_1: + case Lc3SettingId::LC3_24_1: + case Lc3SettingId::LC3_32_1: + case Lc3SettingId::LC3_441_1: + case Lc3SettingId::LC3_48_1: + case Lc3SettingId::LC3_48_3: + case Lc3SettingId::LC3_48_5: + return ::le_audio::codec_spec_conf::kLeAudioCodecLC3FrameDur7500us; + case Lc3SettingId::LC3_8_2: + case Lc3SettingId::LC3_16_2: + case Lc3SettingId::LC3_24_2: + case Lc3SettingId::LC3_32_2: + case Lc3SettingId::LC3_441_2: + case Lc3SettingId::LC3_48_2: + case Lc3SettingId::LC3_48_4: + case Lc3SettingId::LC3_48_6: + return ::le_audio::codec_spec_conf::kLeAudioCodecLC3FrameDur10000us; + case Lc3SettingId::UNSUPPORTED: + return kLeAudioCodecLC3FrameDurRfu; + } +} + +static constexpr uint8_t kLeAudioCodecLC3OctetsPerCodecFrameInvalid = 0; +uint16_t GetOctetsPerCodecFrame(Lc3SettingId id) { + switch (id) { + case Lc3SettingId::LC3_8_1: + return 26; + case Lc3SettingId::LC3_8_2: + case Lc3SettingId::LC3_16_1: + return 30; + case Lc3SettingId::LC3_16_2: + return 40; + case Lc3SettingId::LC3_24_1: + return 45; + case Lc3SettingId::LC3_24_2: + case Lc3SettingId::LC3_32_1: + return 60; + case Lc3SettingId::LC3_32_2: + return 80; + case Lc3SettingId::LC3_441_1: + return 97; + case Lc3SettingId::LC3_441_2: + return 130; + case Lc3SettingId::LC3_48_1: + return 75; + case Lc3SettingId::LC3_48_2: + return 100; + case Lc3SettingId::LC3_48_3: + return 90; + case Lc3SettingId::LC3_48_4: + return 120; + case Lc3SettingId::LC3_48_5: + return 116; + case Lc3SettingId::LC3_48_6: + return 155; + case Lc3SettingId::UNSUPPORTED: + return kLeAudioCodecLC3OctetsPerCodecFrameInvalid; + } +} + +class PublishedAudioCapabilitiesBuilder { + public: + PublishedAudioCapabilitiesBuilder() {} + + void Add(LeAudioCodecId codec_id, uint8_t conf_sampling_frequency, + uint8_t conf_frame_duration, uint8_t audio_channel_counts, + uint16_t octets_per_frame, uint8_t codec_frames_per_sdu = 0) { + uint16_t sampling_frequencies = + SamplingFreqConfig2Capability(conf_sampling_frequency); + uint8_t frame_durations = + FrameDurationConfig2Capability(conf_frame_duration); + uint8_t max_codec_frames_per_sdu = codec_frames_per_sdu; + uint32_t octets_per_frame_range = + octets_per_frame | (octets_per_frame << 16); + + pac_records_.push_back( + acs_ac_record({.codec_id = codec_id, + .codec_spec_caps = LeAudioLtvMap({ + {kLeAudioCodecLC3TypeSamplingFreq, + UINT16_TO_VEC_UINT8(sampling_frequencies)}, + {kLeAudioCodecLC3TypeFrameDuration, + UINT8_TO_VEC_UINT8(frame_durations)}, + {kLeAudioCodecLC3TypeAudioChannelCounts, + UINT8_TO_VEC_UINT8(audio_channel_counts)}, + {kLeAudioCodecLC3TypeOctetPerFrame, + UINT32_TO_VEC_UINT8(octets_per_frame_range)}, + {kLeAudioCodecLC3TypeMaxCodecFramesPerSdu, + UINT8_TO_VEC_UINT8(max_codec_frames_per_sdu)}, + }), + .metadata = std::vector<uint8_t>(0)})); + } + + void Add(const CodecCapabilitySetting& setting, + uint8_t audio_channel_counts) { + if (setting.id != LeAudioCodecIdLc3) return; + + const LeAudioLc3Config config = std::get<LeAudioLc3Config>(setting.config); + + Add(setting.id, config.sampling_frequency, config.frame_duration, + audio_channel_counts, config.octets_per_codec_frame); + } + + void Reset() { pac_records_.clear(); } + + PublishedAudioCapabilities Get() { + return PublishedAudioCapabilities({{hdl_pair_nil, pac_records_}}); + } + + private: + std::vector<acs_ac_record> pac_records_; +}; + +struct TestGroupAseConfigurationData { + LeAudioDevice* device; + uint8_t audio_channel_counts_snk; + uint8_t audio_channel_counts_src; + + uint8_t active_channel_num_snk; + uint8_t active_channel_num_src; +}; + +class LeAudioAseConfigurationTest : public Test { + protected: + void SetUp() override { + group_ = new LeAudioDeviceGroup(group_id_); + bluetooth::manager::SetMockBtmInterface(&btm_interface_); + controller::SetMockControllerInterface(&controller_interface_); + } + + void TearDown() override { + controller::SetMockControllerInterface(nullptr); + bluetooth::manager::SetMockBtmInterface(nullptr); + devices_.clear(); + delete group_; + } + + LeAudioDevice* AddTestDevice(int snk_ase_num, int src_ase_num, + int snk_ase_num_cached = 0, + int src_ase_num_cached = 0) { + int index = group_->Size() + 1; + auto device = + (std::make_shared<LeAudioDevice>(GetTestAddress(index), false)); + devices_.push_back(device); + group_->AddNode(device); + + for (int i = 0; i < src_ase_num; i++) { + device->ases_.emplace_back(0x0000, 0x0000, kLeAudioDirectionSource); + } + + for (int i = 0; i < snk_ase_num; i++) { + device->ases_.emplace_back(0x0000, 0x0000, kLeAudioDirectionSink); + } + + for (int i = 0; i < src_ase_num_cached; i++) { + struct ase ase(0x0000, 0x0000, kLeAudioDirectionSource); + ase.state = AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED; + device->ases_.push_back(ase); + } + + for (int i = 0; i < snk_ase_num_cached; i++) { + struct ase ase(0x0000, 0x0000, kLeAudioDirectionSink); + ase.state = AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED; + device->ases_.push_back(ase); + } + + device->SetSupportedContexts((uint16_t)kLeAudioContextAllTypes, + (uint16_t)kLeAudioContextAllTypes); + device->SetAvailableContexts((uint16_t)kLeAudioContextAllTypes, + (uint16_t)kLeAudioContextAllTypes); + device->snk_audio_locations_ = + ::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft | + ::le_audio::codec_spec_conf::kLeAudioLocationFrontRight; + device->src_audio_locations_ = + ::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft; + + device->conn_id_ = index; + return device.get(); + } + + void TestGroupAseConfigurationVerdict( + const TestGroupAseConfigurationData& data) { + uint8_t active_channel_num_snk = 0; + uint8_t active_channel_num_src = 0; + + bool have_active_ase = + data.active_channel_num_snk + data.active_channel_num_src; + ASSERT_EQ(have_active_ase, data.device->HaveActiveAse()); + + for (ase* ase = data.device->GetFirstActiveAse(); ase; + ase = data.device->GetNextActiveAse(ase)) { + if (ase->direction == kLeAudioDirectionSink) + active_channel_num_snk += + GetAudioChannelCounts(ase->codec_config.audio_channel_allocation); + else + active_channel_num_src += + GetAudioChannelCounts(ase->codec_config.audio_channel_allocation); + } + + ASSERT_EQ(data.active_channel_num_snk, active_channel_num_snk); + ASSERT_EQ(data.active_channel_num_src, active_channel_num_src); + } + + void SetCisInformationToActiveAse(void) { + uint8_t cis_id = 1; + uint16_t cis_conn_hdl = 0x0060; + + for (auto& device : devices_) { + for (auto& ase : device->ases_) { + if (ase.active) { + ase.cis_id = cis_id++; + ase.cis_conn_hdl = cis_conn_hdl++; + } + } + } + } + + void TestSingleAseConfiguration(LeAudioContextType context_type, + TestGroupAseConfigurationData* data, + uint8_t data_size, + const AudioSetConfiguration* audio_set_conf) { + // the configuration should fail if there are no active ases expected + bool success_expected = data_size > 0; + for (int i = 0; i < data_size; i++) { + success_expected &= + (data[i].active_channel_num_snk + data[i].active_channel_num_src) > 0; + + /* Prepare PAC's */ + PublishedAudioCapabilitiesBuilder snk_pac_builder, src_pac_builder; + for (const auto& entry : (*audio_set_conf).confs) { + if (entry.direction == kLeAudioDirectionSink) { + snk_pac_builder.Add(entry.codec, data[i].audio_channel_counts_snk); + } else { + src_pac_builder.Add(entry.codec, data[i].audio_channel_counts_src); + } + } + + data[i].device->snk_pacs_ = snk_pac_builder.Get(); + data[i].device->src_pacs_ = src_pac_builder.Get(); + } + + /* Stimulate update of active context map */ + group_->UpdateActiveContextsMap(static_cast<uint16_t>(context_type)); + ASSERT_EQ(success_expected, group_->Configure(context_type)); + + for (int i = 0; i < data_size; i++) { + TestGroupAseConfigurationVerdict(data[i]); + } + } + + void TestGroupAseConfiguration(LeAudioContextType context_type, + TestGroupAseConfigurationData* data, + uint8_t data_size) { + const auto* configurations = get_confs_by_type(context_type); + for (const auto& audio_set_conf : *configurations) { + // the configuration should fail if there are no active ases expected + bool success_expected = data_size > 0; + for (int i = 0; i < data_size; i++) { + success_expected &= (data[i].active_channel_num_snk + + data[i].active_channel_num_src) > 0; + + /* Prepare PAC's */ + PublishedAudioCapabilitiesBuilder snk_pac_builder, src_pac_builder; + for (const auto& entry : (*audio_set_conf).confs) { + if (entry.direction == kLeAudioDirectionSink) { + snk_pac_builder.Add(entry.codec, data[i].audio_channel_counts_snk); + } else { + src_pac_builder.Add(entry.codec, data[i].audio_channel_counts_src); + } + } + + data[i].device->snk_pacs_ = snk_pac_builder.Get(); + data[i].device->src_pacs_ = src_pac_builder.Get(); + } + + /* Stimulate update of active context map */ + group_->UpdateActiveContextsMap(static_cast<uint16_t>(context_type)); + ASSERT_EQ(success_expected, group_->Configure(context_type)); + + for (int i = 0; i < data_size; i++) { + TestGroupAseConfigurationVerdict(data[i]); + } + + group_->Deactivate(); + TestAsesInactive(); + } + } + + void TestAsesActive(LeAudioCodecId codec_id, uint8_t sampling_frequency, + uint8_t frame_duration, uint16_t octets_per_frame) { + for (const auto& device : devices_) { + for (const auto& ase : device->ases_) { + ASSERT_TRUE(ase.active); + ASSERT_EQ(ase.codec_id, codec_id); + + /* FIXME: Validate other codec parameters than LC3 if any */ + ASSERT_EQ(ase.codec_id, LeAudioCodecIdLc3); + if (ase.codec_id == LeAudioCodecIdLc3) { + ASSERT_EQ(ase.codec_config.sampling_frequency, sampling_frequency); + ASSERT_EQ(ase.codec_config.frame_duration, frame_duration); + ASSERT_EQ(ase.codec_config.octets_per_codec_frame, octets_per_frame); + } + } + } + } + + void TestActiveAses(void) { + for (auto& device : devices_) { + for (const auto& ase : device->ases_) { + if (ase.active) { + ASSERT_FALSE(ase.cis_id == ::le_audio::kInvalidCisId); + } + } + } + } + + void TestAsesInactivated(const LeAudioDevice* device) { + for (const auto& ase : device->ases_) { + ASSERT_FALSE(ase.active); + } + } + + void TestAsesInactive() { + for (const auto& device : devices_) { + for (const auto& ase : device->ases_) { + ASSERT_FALSE(ase.active); + } + } + } + + void TestLc3CodecConfig(LeAudioContextType context_type) { + for (int i = Lc3SettingIdBegin; i < Lc3SettingIdEnd; i++) { + // test each configuration parameter against valid and invalid value + std::array<Lc3SettingId, 2> test_variants = {static_cast<Lc3SettingId>(i), + Lc3SettingId::UNSUPPORTED}; + + const bool is_lc3_setting_supported = + IsLc3SettingSupported(context_type, static_cast<Lc3SettingId>(i)); + + for (const auto sf_variant : test_variants) { + uint8_t sampling_frequency = GetSamplingFrequency(sf_variant); + for (const auto fd_variant : test_variants) { + uint8_t frame_duration = GetFrameDuration(fd_variant); + for (const auto opcf_variant : test_variants) { + uint16_t octets_per_frame = GetOctetsPerCodecFrame(opcf_variant); + + PublishedAudioCapabilitiesBuilder pac_builder; + pac_builder.Add( + LeAudioCodecIdLc3, sampling_frequency, frame_duration, + kLeAudioCodecLC3ChannelCountSingleChannel, octets_per_frame); + for (auto& device : devices_) { + /* For simplicity configure both PACs with the same + parameters*/ + device->snk_pacs_ = pac_builder.Get(); + device->src_pacs_ = pac_builder.Get(); + } + + bool success_expected = is_lc3_setting_supported; + if (is_lc3_setting_supported && + (sf_variant == Lc3SettingId::UNSUPPORTED || + fd_variant == Lc3SettingId::UNSUPPORTED || + opcf_variant == Lc3SettingId::UNSUPPORTED)) { + success_expected = false; + } + + /* Stimulate update of active context map */ + group_->UpdateActiveContextsMap( + static_cast<uint16_t>(context_type)); + ASSERT_EQ(success_expected, group_->Configure(context_type)); + if (success_expected) { + TestAsesActive(LeAudioCodecIdLc3, sampling_frequency, + frame_duration, octets_per_frame); + group_->Deactivate(); + } + + TestAsesInactive(); + } + } + } + } + } + + const int group_id_ = 6; + std::vector<std::shared_ptr<LeAudioDevice>> devices_; + LeAudioDeviceGroup* group_ = nullptr; + bluetooth::manager::MockBtmInterface btm_interface_; + controller::MockControllerInterface controller_interface_; +}; + +TEST_F(LeAudioAseConfigurationTest, test_mono_speaker_ringtone) { + LeAudioDevice* mono_speaker = AddTestDevice(1, 0); + TestGroupAseConfigurationData data({mono_speaker, + kLeAudioCodecLC3ChannelCountSingleChannel, + kLeAudioCodecLC3ChannelCountNone, 1, 0}); + + TestGroupAseConfiguration(LeAudioContextType::RINGTONE, &data, 1); +} + +TEST_F(LeAudioAseConfigurationTest, test_mono_speaker_conversional) { + LeAudioDevice* mono_speaker = AddTestDevice(1, 0); + TestGroupAseConfigurationData data({mono_speaker, + kLeAudioCodecLC3ChannelCountSingleChannel, + kLeAudioCodecLC3ChannelCountNone, 0, 0}); + + TestGroupAseConfiguration(LeAudioContextType::CONVERSATIONAL, &data, 1); +} + +TEST_F(LeAudioAseConfigurationTest, test_mono_speaker_media) { + LeAudioDevice* mono_speaker = AddTestDevice(1, 0); + TestGroupAseConfigurationData data({mono_speaker, + kLeAudioCodecLC3ChannelCountSingleChannel, + kLeAudioCodecLC3ChannelCountNone, 1, 0}); + + TestGroupAseConfiguration(LeAudioContextType::MEDIA, &data, 1); +} + +TEST_F(LeAudioAseConfigurationTest, test_bounded_headphones_ringtone) { + LeAudioDevice* bounded_headphones = AddTestDevice(2, 0); + TestGroupAseConfigurationData data({bounded_headphones, + kLeAudioCodecLC3ChannelCountTwoChannel, + kLeAudioCodecLC3ChannelCountNone, 2, 0}); + + TestGroupAseConfiguration(LeAudioContextType::RINGTONE, &data, 1); +} + +TEST_F(LeAudioAseConfigurationTest, test_bounded_headphones_conversional) { + LeAudioDevice* bounded_headphones = AddTestDevice(2, 0); + TestGroupAseConfigurationData data({bounded_headphones, + kLeAudioCodecLC3ChannelCountTwoChannel, + kLeAudioCodecLC3ChannelCountNone, 0, 0}); + + TestGroupAseConfiguration(LeAudioContextType::CONVERSATIONAL, &data, 1); +} + +TEST_F(LeAudioAseConfigurationTest, test_bounded_headphones_media) { + LeAudioDevice* bounded_headphones = AddTestDevice(2, 0); + TestGroupAseConfigurationData data({bounded_headphones, + kLeAudioCodecLC3ChannelCountTwoChannel, + kLeAudioCodecLC3ChannelCountNone, 2, 0}); + + TestGroupAseConfiguration(LeAudioContextType::MEDIA, &data, 1); +} + +TEST_F(LeAudioAseConfigurationTest, test_bounded_headset_ringtone) { + LeAudioDevice* bounded_headset = AddTestDevice(2, 1); + TestGroupAseConfigurationData data( + {bounded_headset, kLeAudioCodecLC3ChannelCountTwoChannel, + kLeAudioCodecLC3ChannelCountSingleChannel, 2, 0}); + + TestGroupAseConfiguration(LeAudioContextType::RINGTONE, &data, 1); +} + +TEST_F(LeAudioAseConfigurationTest, test_bounded_headset_conversional) { + LeAudioDevice* bounded_headset = AddTestDevice(2, 1); + TestGroupAseConfigurationData data( + {bounded_headset, kLeAudioCodecLC3ChannelCountTwoChannel, + kLeAudioCodecLC3ChannelCountSingleChannel, 2, 1}); + + TestGroupAseConfiguration(LeAudioContextType::CONVERSATIONAL, &data, 1); +} + +TEST_F(LeAudioAseConfigurationTest, test_bounded_headset_media) { + LeAudioDevice* bounded_headset = AddTestDevice(2, 1); + TestGroupAseConfigurationData data( + {bounded_headset, kLeAudioCodecLC3ChannelCountTwoChannel, + kLeAudioCodecLC3ChannelCountSingleChannel, 2, 0}); + + TestGroupAseConfiguration(LeAudioContextType::MEDIA, &data, 1); +} + +TEST_F(LeAudioAseConfigurationTest, test_earbuds_ringtone) { + LeAudioDevice* left = AddTestDevice(1, 1); + LeAudioDevice* right = AddTestDevice(1, 1); + TestGroupAseConfigurationData data[] = { + {left, kLeAudioCodecLC3ChannelCountSingleChannel, + kLeAudioCodecLC3ChannelCountSingleChannel, 1, 0}, + {right, kLeAudioCodecLC3ChannelCountSingleChannel, + kLeAudioCodecLC3ChannelCountSingleChannel, 1, 0}}; + + TestGroupAseConfiguration(LeAudioContextType::RINGTONE, data, 2); +} + +TEST_F(LeAudioAseConfigurationTest, test_earbuds_conversional) { + LeAudioDevice* left = AddTestDevice(1, 1); + LeAudioDevice* right = AddTestDevice(1, 1); + TestGroupAseConfigurationData data[] = { + {left, kLeAudioCodecLC3ChannelCountSingleChannel, + kLeAudioCodecLC3ChannelCountSingleChannel, 1, 1}, + {right, kLeAudioCodecLC3ChannelCountSingleChannel, + kLeAudioCodecLC3ChannelCountSingleChannel, 1, 0}}; + + TestGroupAseConfiguration(LeAudioContextType::CONVERSATIONAL, data, 2); +} + +TEST_F(LeAudioAseConfigurationTest, test_earbuds_media) { + LeAudioDevice* left = AddTestDevice(1, 1); + LeAudioDevice* right = AddTestDevice(1, 1); + TestGroupAseConfigurationData data[] = { + {left, kLeAudioCodecLC3ChannelCountSingleChannel, + kLeAudioCodecLC3ChannelCountSingleChannel, 1, 0}, + {right, kLeAudioCodecLC3ChannelCountSingleChannel, + kLeAudioCodecLC3ChannelCountSingleChannel, 1, 0}}; + + TestGroupAseConfiguration(LeAudioContextType::MEDIA, data, 2); +} + +TEST_F(LeAudioAseConfigurationTest, test_handsfree_ringtone) { + LeAudioDevice* handsfree = AddTestDevice(1, 1); + TestGroupAseConfigurationData data( + {handsfree, kLeAudioCodecLC3ChannelCountSingleChannel, + kLeAudioCodecLC3ChannelCountSingleChannel, 1, 0}); + + TestGroupAseConfiguration(LeAudioContextType::RINGTONE, &data, 1); +} + +TEST_F(LeAudioAseConfigurationTest, test_handsfree_conversional) { + LeAudioDevice* handsfree = AddTestDevice(1, 1); + TestGroupAseConfigurationData data( + {handsfree, kLeAudioCodecLC3ChannelCountSingleChannel, + kLeAudioCodecLC3ChannelCountSingleChannel, 1, 1}); + + TestGroupAseConfiguration(LeAudioContextType::CONVERSATIONAL, &data, 1); +} + +TEST_F(LeAudioAseConfigurationTest, test_handsfree_full_cached_conversional) { + LeAudioDevice* handsfree = AddTestDevice(0, 0, 1, 1); + TestGroupAseConfigurationData data( + {handsfree, kLeAudioCodecLC3ChannelCountSingleChannel, + kLeAudioCodecLC3ChannelCountSingleChannel, 1, 1}); + + TestGroupAseConfiguration(LeAudioContextType::CONVERSATIONAL, &data, 1); +} + +TEST_F(LeAudioAseConfigurationTest, + test_handsfree_partial_cached_conversional) { + LeAudioDevice* handsfree = AddTestDevice(1, 0, 0, 1); + TestGroupAseConfigurationData data( + {handsfree, kLeAudioCodecLC3ChannelCountSingleChannel, + kLeAudioCodecLC3ChannelCountSingleChannel, 1, 1}); + + TestGroupAseConfiguration(LeAudioContextType::CONVERSATIONAL, &data, 1); +} + +TEST_F(LeAudioAseConfigurationTest, test_handsfree_media) { + LeAudioDevice* handsfree = AddTestDevice(1, 1); + TestGroupAseConfigurationData data( + {handsfree, kLeAudioCodecLC3ChannelCountSingleChannel, + kLeAudioCodecLC3ChannelCountSingleChannel, 1, 0}); + + TestGroupAseConfiguration(LeAudioContextType::MEDIA, &data, 1); +} + +TEST_F(LeAudioAseConfigurationTest, test_lc3_config_ringtone) { + AddTestDevice(1, 0); + + TestLc3CodecConfig(LeAudioContextType::RINGTONE); +} + +TEST_F(LeAudioAseConfigurationTest, test_lc3_config_conversional) { + AddTestDevice(1, 1); + + TestLc3CodecConfig(LeAudioContextType::CONVERSATIONAL); +} + +TEST_F(LeAudioAseConfigurationTest, test_lc3_config_media) { + AddTestDevice(1, 0); + + TestLc3CodecConfig(LeAudioContextType::MEDIA); +} + +TEST_F(LeAudioAseConfigurationTest, test_unsupported_codec) { + const LeAudioCodecId UnsupportedCodecId = { + .coding_format = kLeAudioCodingFormatVendorSpecific, + .vendor_company_id = 0xBAD, + .vendor_codec_id = 0xC0DE, + }; + + LeAudioDevice* device = AddTestDevice(1, 0); + + PublishedAudioCapabilitiesBuilder pac_builder; + pac_builder.Add(UnsupportedCodecId, + GetSamplingFrequency(Lc3SettingId::LC3_16_2), + GetFrameDuration(Lc3SettingId::LC3_16_2), + kLeAudioCodecLC3ChannelCountSingleChannel, + GetOctetsPerCodecFrame(Lc3SettingId::LC3_16_2)); + device->snk_pacs_ = pac_builder.Get(); + device->src_pacs_ = pac_builder.Get(); + + ASSERT_FALSE(group_->Configure(LeAudioContextType::RINGTONE)); + TestAsesInactive(); +} + +TEST_F(LeAudioAseConfigurationTest, test_reconnection_media) { + LeAudioDevice* left = AddTestDevice(2, 1); + LeAudioDevice* right = AddTestDevice(2, 1); + + TestGroupAseConfigurationData data[] = { + {left, kLeAudioCodecLC3ChannelCountSingleChannel, + kLeAudioCodecLC3ChannelCountSingleChannel, 1, 0}, + {right, kLeAudioCodecLC3ChannelCountSingleChannel, + kLeAudioCodecLC3ChannelCountSingleChannel, 1, 0}}; + + TestSingleAseConfiguration(LeAudioContextType::MEDIA, data, 2, + &kDualDev_OneChanStereoSnk_48_4); + + SetCisInformationToActiveAse(); + + /* Left got disconnected */ + left->DeactivateAllAses(); + TestAsesInactivated(left); + + /* Prepare reconfiguration */ + uint8_t number_of_active_ases = 1; // Right one + auto* ase = right->GetFirstActiveAseByDirection(kLeAudioDirectionSink); + ::le_audio::types::AudioLocations group_snk_audio_location = + ase->codec_config.audio_channel_allocation; + ::le_audio::types::AudioLocations group_src_audio_location = + ase->codec_config.audio_channel_allocation; + + /* Get known requirement*/ + auto* configuration = &kDualDev_OneChanStereoSnk_48_4; + + /* Get entry for the sink direction and use it to set configuration */ + for (auto& ent : configuration->confs) { + if (ent.direction == ::le_audio::types::kLeAudioDirectionSink) { + left->ConfigureAses(ent, group_->GetCurrentContextType(), + &number_of_active_ases, group_snk_audio_location, + group_src_audio_location); + } + } + + ASSERT_TRUE(number_of_active_ases == 2); + ASSERT_TRUE(group_snk_audio_location == kChannelAllocationStereo); + + for (int i = 0; i < 2; i++) { + TestGroupAseConfigurationVerdict(data[i]); + } + + TestActiveAses(); +} +} // namespace +} // namespace internal +} // namespace le_audio +} // namespace bluetooth diff --git a/system/bta/le_audio/le_audio_client_test.cc b/system/bta/le_audio/le_audio_client_test.cc new file mode 100644 index 0000000000000000000000000000000000000000..84f2b7f454159369b292ef470361125bf1192272 --- /dev/null +++ b/system/bta/le_audio/le_audio_client_test.cc @@ -0,0 +1,2644 @@ +/* + * Copyright 2021 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * 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 <gmock/gmock.h> +#include <gtest/gtest.h> + +#include <chrono> + +#include "bta/csis/csis_types.h" +#include "bta_gatt_api_mock.h" +#include "bta_gatt_queue_mock.h" +#include "bta_groups.h" +#include "bta_le_audio_api.h" +#include "btif_storage_mock.h" +#include "btm_api_mock.h" +#include "btm_iso_api.h" +#include "common/message_loop_thread.h" +#include "device/include/controller.h" +#include "gatt/database_builder.h" +#include "hardware/bt_gatt_types.h" +#include "le_audio_types.h" +#include "mock_controller.h" +#include "mock_csis_client.h" +#include "mock_device_groups.h" +#include "mock_iso_manager.h" +#include "mock_le_audio_client_audio.h" +#include "mock_state_machine.h" + +using testing::_; +using testing::AnyNumber; +using testing::AtLeast; +using testing::AtMost; +using testing::DoAll; +using testing::Invoke; +using testing::Mock; +using testing::MockFunction; +using testing::NotNull; +using testing::Return; +using testing::SaveArg; +using testing::SetArgPointee; +using testing::Test; +using testing::WithArg; + +using bluetooth::Uuid; + +using namespace bluetooth::le_audio; + +// Disables most likely false-positives from base::SplitString() +extern "C" const char* __asan_default_options() { + return "detect_container_overflow=0"; +} + +std::atomic<int> num_async_tasks; +bluetooth::common::MessageLoopThread message_loop_thread("test message loop"); +bluetooth::common::MessageLoopThread* get_main_thread() { + return &message_loop_thread; +} +bt_status_t do_in_main_thread(const base::Location& from_here, + base::OnceClosure task) { + // Wrap the task with task counter so we could later know if there are + // any callbacks scheduled and we should wait before performing some actions + if (!message_loop_thread.DoInThread( + from_here, + base::BindOnce( + [](base::OnceClosure task, std::atomic<int>& num_async_tasks) { + std::move(task).Run(); + num_async_tasks--; + }, + std::move(task), std::ref(num_async_tasks)))) { + LOG(ERROR) << __func__ << ": failed from " << from_here.ToString(); + return BT_STATUS_FAIL; + } + num_async_tasks++; + return BT_STATUS_SUCCESS; +} + +static base::MessageLoop* message_loop_; +base::MessageLoop* get_main_message_loop() { return message_loop_; } + +static void init_message_loop_thread() { + num_async_tasks = 0; + message_loop_thread.StartUp(); + if (!message_loop_thread.IsRunning()) { + FAIL() << "unable to create message loop thread."; + } + + if (!message_loop_thread.EnableRealTimeScheduling()) + LOG(ERROR) << "Unable to set real time scheduling"; + + message_loop_ = message_loop_thread.message_loop(); + if (message_loop_ == nullptr) FAIL() << "unable to get message loop."; +} + +static void cleanup_message_loop_thread() { + message_loop_ = nullptr; + message_loop_thread.ShutDown(); +} + +namespace le_audio { +namespace { +class MockLeAudioClientCallbacks + : public bluetooth::le_audio::LeAudioClientCallbacks { + public: + MOCK_METHOD((void), OnConnectionState, + (ConnectionState state, const RawAddress& address), (override)); + MOCK_METHOD((void), OnGroupStatus, (int group_id, GroupStatus group_status), + (override)); + MOCK_METHOD((void), OnGroupNodeStatus, + (const RawAddress& bd_addr, int group_id, + GroupNodeStatus node_status), + (override)); + MOCK_METHOD((void), OnAudioConf, + (uint8_t direction, int group_id, uint32_t snk_audio_location, + uint32_t src_audio_location, uint16_t avail_cont), + (override)); +}; + +class UnicastTestNoInit : public Test { + protected: + void SetUpMockAudioHal() { + // Source + is_audio_hal_source_acquired = false; + + ON_CALL(mock_audio_source_, Start(_, _)) + .WillByDefault( + [this](const LeAudioCodecConfiguration& codec_configuration, + LeAudioClientAudioSinkReceiver* audioReceiver) { + audio_sink_receiver_ = audioReceiver; + return true; + }); + ON_CALL(mock_audio_source_, Acquire).WillByDefault([this]() -> void* { + if (!is_audio_hal_source_acquired) { + is_audio_hal_source_acquired = true; + return &mock_audio_source_; + } + + return nullptr; + }); + ON_CALL(mock_audio_source_, Release) + .WillByDefault([this](const void* inst) -> void { + if (is_audio_hal_source_acquired) { + is_audio_hal_source_acquired = false; + } + }); + + MockLeAudioClientAudioSource::SetMockInstanceForTesting( + &mock_audio_source_); + + // Sink + is_audio_hal_sink_acquired = false; + + ON_CALL(mock_audio_sink_, Start(_, _)) + .WillByDefault( + [this](const LeAudioCodecConfiguration& codec_configuration, + LeAudioClientAudioSourceReceiver* audioReceiver) { + audio_source_receiver_ = audioReceiver; + return true; + }); + ON_CALL(mock_audio_sink_, Acquire).WillByDefault([this]() -> void* { + if (!is_audio_hal_sink_acquired) { + is_audio_hal_sink_acquired = true; + return &mock_audio_sink_; + } + + return nullptr; + }); + ON_CALL(mock_audio_sink_, Release) + .WillByDefault([this](const void* inst) -> void { + if (is_audio_hal_sink_acquired) { + is_audio_hal_sink_acquired = false; + } + }); + ON_CALL(mock_audio_sink_, SendData) + .WillByDefault([](uint8_t* data, uint16_t size) { return size; }); + + MockLeAudioClientAudioSink::SetMockInstanceForTesting(&mock_audio_sink_); + + // HAL + ON_CALL(mock_hal_2_1_verifier, Call()).WillByDefault([]() -> bool { + return true; + }); + } + + void InjectGroupDeviceRemoved(const RawAddress& address, int group_id) { + group_callbacks_->OnGroupMemberRemoved(address, group_id); + } + + void InjectGroupDeviceAdded(const RawAddress& address, int group_id) { + bluetooth::Uuid uuid = le_audio::uuid::kCapServiceUuid; + + int group_members_num = 0; + for (const auto& [addr, id] : groups) { + if (id == group_id) group_members_num++; + } + + bool first_device = (group_members_num == 1); + do_in_main_thread( + FROM_HERE, + base::BindOnce( + [](const RawAddress& addr, int group_id, bluetooth::Uuid uuid, + bluetooth::groups::DeviceGroupsCallbacks* group_callbacks, + bool first_device) { + if (first_device) { + group_callbacks->OnGroupAdded(addr, uuid, group_id); + } else { + group_callbacks->OnGroupMemberAdded(addr, group_id); + } + }, + address, group_id, uuid, base::Unretained(this->group_callbacks_), + first_device)); + } + + void InjectConnectedEvent(const RawAddress& address, uint16_t conn_id, + tGATT_STATUS status = GATT_SUCCESS) { + ASSERT_NE(conn_id, GATT_INVALID_CONN_ID); + tBTA_GATTC_OPEN event_data = { + .status = status, + .conn_id = conn_id, + .client_if = gatt_if, + .remote_bda = address, + .transport = GATT_TRANSPORT_LE, + .mtu = 240, + }; + + ASSERT_NE(peer_devices.count(conn_id), 0u); + peer_devices.at(conn_id)->connected = true; + do_in_main_thread( + FROM_HERE, + base::BindOnce( + [](tBTA_GATTC_CBACK* gatt_callback, tBTA_GATTC_OPEN event_data) { + gatt_callback(BTA_GATTC_OPEN_EVT, (tBTA_GATTC*)&event_data); + }, + base::Unretained(this->gatt_callback), event_data)); + } + + void InjectDisconnectedEvent(uint16_t conn_id) { + ASSERT_NE(conn_id, GATT_INVALID_CONN_ID); + ASSERT_NE(peer_devices.count(conn_id), 0u); + + tBTA_GATTC_CLOSE event_data = { + .status = GATT_SUCCESS, + .conn_id = conn_id, + .client_if = gatt_if, + .remote_bda = peer_devices.at(conn_id)->addr, + .reason = GATT_CONN_TERMINATE_PEER_USER, + }; + + peer_devices.at(conn_id)->connected = false; + do_in_main_thread( + FROM_HERE, + base::BindOnce( + [](tBTA_GATTC_CBACK* gatt_callback, tBTA_GATTC_CLOSE event_data) { + gatt_callback(BTA_GATTC_CLOSE_EVT, (tBTA_GATTC*)&event_data); + }, + base::Unretained(this->gatt_callback), event_data)); + } + + void InjectSearchCompleteEvent(uint16_t conn_id) { + ASSERT_NE(conn_id, GATT_INVALID_CONN_ID); + tBTA_GATTC_SEARCH_CMPL event_data = { + .status = GATT_SUCCESS, + .conn_id = conn_id, + }; + + do_in_main_thread(FROM_HERE, + base::BindOnce( + [](tBTA_GATTC_CBACK* gatt_callback, + tBTA_GATTC_SEARCH_CMPL event_data) { + gatt_callback(BTA_GATTC_SEARCH_CMPL_EVT, + (tBTA_GATTC*)&event_data); + }, + base::Unretained(this->gatt_callback), event_data)); + } + + void InjectNotificationEvent(const RawAddress& test_address, uint16_t conn_id, + uint16_t handle, std::vector<uint8_t> value) { + ASSERT_NE(conn_id, GATT_INVALID_CONN_ID); + tBTA_GATTC_NOTIFY event_data = { + .conn_id = conn_id, + .bda = test_address, + .handle = handle, + .len = (uint8_t)value.size(), + .is_notify = true, + }; + + std::copy(value.begin(), value.end(), event_data.value); + do_in_main_thread( + FROM_HERE, + base::BindOnce( + [](tBTA_GATTC_CBACK* gatt_callback, tBTA_GATTC_NOTIFY event_data) { + gatt_callback(BTA_GATTC_NOTIF_EVT, (tBTA_GATTC*)&event_data); + }, + base::Unretained(this->gatt_callback), event_data)); + } + + void SetUpMockGatt() { + // default action for GetCharacteristic function call + ON_CALL(mock_gatt_interface_, GetCharacteristic(_, _)) + .WillByDefault( + Invoke([&](uint16_t conn_id, + uint16_t handle) -> const gatt::Characteristic* { + std::list<gatt::Service>& services = + peer_devices.at(conn_id)->services; + for (auto const& service : services) { + for (auto const& characteristic : service.characteristics) { + if (characteristic.value_handle == handle) { + return &characteristic; + } + } + } + + return nullptr; + })); + + // default action for GetOwningService function call + ON_CALL(mock_gatt_interface_, GetOwningService(_, _)) + .WillByDefault(Invoke( + [&](uint16_t conn_id, uint16_t handle) -> const gatt::Service* { + std::list<gatt::Service>& services = + peer_devices.at(conn_id)->services; + for (auto const& service : services) { + if (service.handle <= handle && service.end_handle >= handle) { + return &service; + } + } + + return nullptr; + })); + + // default action for ServiceSearchRequest function call + ON_CALL(mock_gatt_interface_, ServiceSearchRequest(_, _)) + .WillByDefault(WithArg<0>(Invoke( + [&](uint16_t conn_id) { InjectSearchCompleteEvent(conn_id); }))); + + // default action for GetServices function call + ON_CALL(mock_gatt_interface_, GetServices(_)) + .WillByDefault(WithArg<0>( + Invoke([&](uint16_t conn_id) -> std::list<gatt::Service>* { + return &peer_devices.at(conn_id)->services; + }))); + + // default action for RegisterForNotifications function call + ON_CALL(mock_gatt_interface_, RegisterForNotifications(gatt_if, _, _)) + .WillByDefault(Return(GATT_SUCCESS)); + + // default action for DeregisterForNotifications function call + ON_CALL(mock_gatt_interface_, DeregisterForNotifications(gatt_if, _, _)) + .WillByDefault(Return(GATT_SUCCESS)); + + // default action for WriteDescriptor function call + ON_CALL(mock_gatt_queue_, WriteDescriptor(_, _, _, _, _, _)) + .WillByDefault( + Invoke([](uint16_t conn_id, uint16_t handle, + std::vector<uint8_t> value, tGATT_WRITE_TYPE write_type, + GATT_WRITE_OP_CB cb, void* cb_data) -> void { + if (cb) + do_in_main_thread( + FROM_HERE, base::BindOnce( + [](GATT_WRITE_OP_CB cb, uint16_t conn_id, + uint16_t handle, void* cb_data) { + cb(conn_id, GATT_SUCCESS, handle, cb_data); + }, + cb, conn_id, handle, cb_data)); + })); + + global_conn_id = 1; + ON_CALL(mock_gatt_interface_, Open(_, _, _, _)) + .WillByDefault( + Invoke([&](tGATT_IF client_if, const RawAddress& remote_bda, + bool is_direct, bool opportunistic) { + InjectConnectedEvent(remote_bda, global_conn_id++); + })); + + ON_CALL(mock_gatt_interface_, Close(_)) + .WillByDefault(Invoke( + [&](uint16_t conn_id) { InjectDisconnectedEvent(conn_id); })); + + // default Characteristic read handler dispatches requests to service mocks + ON_CALL(mock_gatt_queue_, ReadCharacteristic(_, _, _, _)) + .WillByDefault(Invoke([&](uint16_t conn_id, uint16_t handle, + GATT_READ_OP_CB cb, void* cb_data) { + do_in_main_thread( + FROM_HERE, + base::BindOnce( + [](std::map<uint16_t, std::unique_ptr<MockDeviceWrapper>>* + peer_devices, + uint16_t conn_id, uint16_t handle, GATT_READ_OP_CB cb, + void* cb_data) -> void { + if (peer_devices->count(conn_id)) { + auto& device = peer_devices->at(conn_id); + auto svc = std::find_if( + device->services.begin(), device->services.end(), + [handle](const gatt::Service& svc) { + return (handle >= svc.handle) && + (handle <= svc.end_handle); + }); + if (svc == device->services.end()) return; + + // Dispatch to mockable handler functions + if (svc->handle == device->csis->start) { + device->csis->OnReadCharacteristic(handle, cb, cb_data); + } else if (svc->handle == device->cas->start) { + device->cas->OnReadCharacteristic(handle, cb, cb_data); + } else if (svc->handle == device->ascs->start) { + device->ascs->OnReadCharacteristic(handle, cb, cb_data); + } else if (svc->handle == device->pacs->start) { + device->pacs->OnReadCharacteristic(handle, cb, cb_data); + } + } + }, + &peer_devices, conn_id, handle, cb, cb_data)); + })); + } + + void SetUpMockGroups() { + MockCsisClient::SetMockInstanceForTesting(&mock_csis_client_module_); + MockDeviceGroups::SetMockInstanceForTesting(&mock_groups_module_); + MockLeAudioGroupStateMachine::SetMockInstanceForTesting( + &mock_state_machine_); + + ON_CALL(mock_csis_client_module_, Get()) + .WillByDefault(Return(&mock_csis_client_module_)); + + // Store group callbacks so that we could inject grouping events + group_callbacks_ = nullptr; + ON_CALL(mock_groups_module_, Initialize(_)) + .WillByDefault(SaveArg<0>(&group_callbacks_)); + + ON_CALL(mock_groups_module_, GetGroupId(_, _)) + .WillByDefault([this](const RawAddress& addr, bluetooth::Uuid uuid) { + if (groups.find(addr) != groups.end()) return groups.at(addr); + return bluetooth::groups::kGroupUnknown; + }); + + ON_CALL(mock_groups_module_, RemoveDevice(_, _)) + .WillByDefault([this](const RawAddress& addr, int group_id_) { + int group_id = -1; + if (groups.find(addr) != groups.end()) { + group_id = groups[addr]; + groups.erase(addr); + } + if (group_id < 0) return; + + do_in_main_thread( + FROM_HERE, + base::BindOnce( + [](const RawAddress& address, int group_id, + bluetooth::groups::DeviceGroupsCallbacks* + group_callbacks) { + group_callbacks->OnGroupMemberRemoved(address, group_id); + }, + addr, group_id, base::Unretained(group_callbacks_))); + }); + + // Our test devices have unique LSB - use it for unique grouping when + // devices added with a non-CIS context and no grouping info + ON_CALL(mock_groups_module_, + AddDevice(_, le_audio::uuid::kCapServiceUuid, _)) + .WillByDefault( + [this](const RawAddress& addr, + bluetooth::Uuid uuid = le_audio::uuid::kCapServiceUuid, + int group_id = bluetooth::groups::kGroupUnknown) -> int { + if (group_id == bluetooth::groups::kGroupUnknown) { + /* Generate group id from address */ + groups[addr] = addr.address[RawAddress::kLength - 1]; + group_id = groups[addr]; + } else { + groups[addr] = group_id; + } + + InjectGroupDeviceAdded(addr, groups[addr]); + return addr.address[RawAddress::kLength - 1]; + }); + + ON_CALL(mock_state_machine_, Initialize(_)) + .WillByDefault(SaveArg<0>(&state_machine_callbacks_)); + + ON_CALL(mock_state_machine_, StartStream(_, _)) + .WillByDefault([this](LeAudioDeviceGroup* group, + types::LeAudioContextType context_type) { + if (group->GetState() == + types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { + if (group->GetContextType() != context_type) { + /* TODO: Switch context of group */ + group->SetContextType(context_type); + } + return true; + } + + group->Configure(context_type); + + // Fake ASE configuration + for (LeAudioDevice* device = group->GetFirstDevice(); + device != nullptr; device = group->GetNextDevice(device)) { + for (auto& ase : device->ases_) { + if (!ase.active) continue; + + // And also skip the ase establishment procedure which should + // be tested as part of the state machine unit tests + ase.data_path_state = + types::AudioStreamDataPathState::DATA_PATH_ESTABLISHED; + ase.cis_conn_hdl = iso_con_counter_++; + ase.active = true; + ase.state = types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING; + } + } + + // Inject the state + group->SetTargetState( + types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); + group->SetState(group->GetTargetState()); + state_machine_callbacks_->StatusReportCb( + group->group_id_, GroupStreamStatus::STREAMING); + streaming_groups[group->group_id_] = group; + return true; + }); + + ON_CALL(mock_state_machine_, SuspendStream(_)) + .WillByDefault([this](LeAudioDeviceGroup* group) { + // Fake ASE state + for (LeAudioDevice* device = group->GetFirstDevice(); + device != nullptr; device = group->GetNextDevice(device)) { + for (auto& ase : device->ases_) { + ase.data_path_state = + types::AudioStreamDataPathState::CIS_ESTABLISHED; + ase.active = false; + ase.state = + types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED; + } + } + + // Inject the state + group->SetTargetState( + types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED); + group->SetState(group->GetTargetState()); + state_machine_callbacks_->StatusReportCb( + group->group_id_, GroupStreamStatus::SUSPENDED); + }); + + ON_CALL(mock_state_machine_, ProcessHciNotifAclDisconnected(_, _)) + .WillByDefault([](LeAudioDeviceGroup* group, + LeAudioDevice* leAudioDevice) { + if (!group) return; + auto* stream_conf = &group->stream_conf; + if (stream_conf->valid) { + stream_conf->sink_streams.erase( + std::remove_if(stream_conf->sink_streams.begin(), + stream_conf->sink_streams.end(), + [leAudioDevice](auto& pair) { + auto ases = leAudioDevice->GetAsesByCisConnHdl( + pair.first); + return ases.sink; + }), + stream_conf->sink_streams.end()); + + stream_conf->source_streams.erase( + std::remove_if(stream_conf->source_streams.begin(), + stream_conf->source_streams.end(), + [leAudioDevice](auto& pair) { + auto ases = leAudioDevice->GetAsesByCisConnHdl( + pair.first); + return ases.source; + }), + stream_conf->source_streams.end()); + + if (stream_conf->sink_streams.empty()) { + LOG(INFO) << __func__ << " stream stopped "; + stream_conf->valid = false; + } + } + }); + + ON_CALL(mock_state_machine_, ProcessHciNotifCisDisconnected(_, _, _)) + .WillByDefault( + [](LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice, + const bluetooth::hci::iso_manager::cis_disconnected_evt* event) { + if (!group) return; + auto ases_pair = + leAudioDevice->GetAsesByCisConnHdl(event->cis_conn_hdl); + if (ases_pair.sink) { + ases_pair.sink->data_path_state = + types::AudioStreamDataPathState::CIS_ASSIGNED; + } + if (ases_pair.source) { + ases_pair.source->data_path_state = + types::AudioStreamDataPathState::CIS_ASSIGNED; + } + /* Invalidate stream configuration if needed */ + auto* stream_conf = &group->stream_conf; + if (stream_conf->valid) { + stream_conf->sink_streams.erase( + std::remove_if( + stream_conf->sink_streams.begin(), + stream_conf->sink_streams.end(), + [leAudioDevice](auto& pair) { + auto ases = + leAudioDevice->GetAsesByCisConnHdl(pair.first); + LOG(INFO) << __func__ + << " sink ase to delete. Cis handle: " + << (int)(pair.first) + << " ase pointer: " << ases.sink; + return ases.sink; + }), + stream_conf->sink_streams.end()); + + stream_conf->source_streams.erase( + std::remove_if( + stream_conf->source_streams.begin(), + stream_conf->source_streams.end(), + [leAudioDevice](auto& pair) { + auto ases = + leAudioDevice->GetAsesByCisConnHdl(pair.first); + LOG(INFO) + << __func__ << " source to delete. Cis handle: " + << (int)(pair.first) + << " ase pointer: " << ases.source; + return ases.source; + }), + stream_conf->source_streams.end()); + + if (stream_conf->sink_streams.empty()) { + LOG(INFO) << __func__ << " stream stopped "; + stream_conf->valid = false; + } + } + }); + + ON_CALL(mock_state_machine_, StopStream(_)) + .WillByDefault([this](LeAudioDeviceGroup* group) { + for (LeAudioDevice* device = group->GetFirstDevice(); + device != nullptr; device = group->GetNextDevice(device)) { + for (auto& ase : device->ases_) { + ase.data_path_state = types::AudioStreamDataPathState::IDLE; + ase.active = false; + ase.state = types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE; + ase.cis_conn_hdl = 0; + } + } + + // Inject the state + group->SetTargetState(types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); + group->SetState(group->GetTargetState()); + state_machine_callbacks_->StatusReportCb(group->group_id_, + GroupStreamStatus::IDLE); + }); + } + + void SetUp() override { + init_message_loop_thread(); + + ON_CALL(controller_interface_, SupportsBleConnectedIsochronousStreamCentral) + .WillByDefault(Return(true)); + ON_CALL(controller_interface_, + SupportsBleConnectedIsochronousStreamPeripheral) + .WillByDefault(Return(true)); + + controller::SetMockControllerInterface(&controller_interface_); + bluetooth::manager::SetMockBtmInterface(&mock_btm_interface_); + gatt::SetMockBtaGattInterface(&mock_gatt_interface_); + gatt::SetMockBtaGattQueue(&mock_gatt_queue_); + bluetooth::storage::SetMockBtifStorageInterface(&mock_btif_storage_); + + iso_manager_ = bluetooth::hci::IsoManager::GetInstance(); + ASSERT_NE(iso_manager_, nullptr); + iso_manager_->Start(); + + mock_iso_manager_ = MockIsoManager::GetInstance(); + ON_CALL(*mock_iso_manager_, RegisterCigCallbacks(_)) + .WillByDefault(SaveArg<0>(&cig_callbacks_)); + + SetUpMockAudioHal(); + SetUpMockGroups(); + SetUpMockGatt(); + + ASSERT_FALSE(LeAudioClient::IsLeAudioClientRunning()); + } + + void TearDown() override { + // Message loop cleanup should wait for all the 'till now' scheduled calls + // so it should be called right at the very begginning of teardown. + cleanup_message_loop_thread(); + + // This is required since Stop() and Cleanup() may trigger some callbacks or + // drop unique pointers to mocks we have raw pointer for and we want to + // verify them all. + Mock::VerifyAndClearExpectations(&mock_client_callbacks_); + + if (LeAudioClient::IsLeAudioClientRunning()) { + EXPECT_CALL(mock_gatt_interface_, AppDeregister(gatt_if)).Times(1); + LeAudioClient::Cleanup(); + ASSERT_FALSE(LeAudioClient::IsLeAudioClientRunning()); + } + + iso_manager_->Stop(); + } + + protected: + class MockDeviceWrapper { + class IGattHandlers { + public: + // IGattHandlers() = default; + virtual ~IGattHandlers() = default; + virtual void OnReadCharacteristic(uint16_t handle, GATT_READ_OP_CB cb, + void* cb_data) = 0; + virtual void OnWriteCharacteristic(uint16_t handle, + std::vector<uint8_t> value, + tGATT_WRITE_TYPE write_type, + GATT_WRITE_OP_CB cb, + void* cb_data) = 0; + }; + + public: + struct csis_mock : public IGattHandlers { + uint16_t start = 0; + uint16_t end = 0; + uint16_t sirk_char = 0; + uint16_t sirk_ccc = 0; + uint16_t size_char = 0; + uint16_t size_ccc = 0; + uint16_t lock_char = 0; + uint16_t lock_ccc = 0; + uint16_t rank_char = 0; + + int rank = 0; + int size = 0; + + MOCK_METHOD((void), OnReadCharacteristic, + (uint16_t handle, GATT_READ_OP_CB cb, void* cb_data), + (override)); + MOCK_METHOD((void), OnWriteCharacteristic, + (uint16_t handle, std::vector<uint8_t> value, + tGATT_WRITE_TYPE write_type, GATT_WRITE_OP_CB cb, + void* cb_data), + (override)); + }; + + struct cas_mock : public IGattHandlers { + uint16_t start = 0; + uint16_t end = 0; + uint16_t csis_include = 0; + + MOCK_METHOD((void), OnReadCharacteristic, + (uint16_t handle, GATT_READ_OP_CB cb, void* cb_data), + (override)); + MOCK_METHOD((void), OnWriteCharacteristic, + (uint16_t handle, std::vector<uint8_t> value, + tGATT_WRITE_TYPE write_type, GATT_WRITE_OP_CB cb, + void* cb_data), + (override)); + }; + + struct pacs_mock : public IGattHandlers { + uint16_t start = 0; + uint16_t sink_pac_char = 0; + uint16_t sink_pac_ccc = 0; + uint16_t sink_audio_loc_char = 0; + uint16_t sink_audio_loc_ccc = 0; + uint16_t source_pac_char = 0; + uint16_t source_pac_ccc = 0; + uint16_t source_audio_loc_char = 0; + uint16_t source_audio_loc_ccc = 0; + uint16_t avail_contexts_char = 0; + uint16_t avail_contexts_ccc = 0; + uint16_t supp_contexts_char = 0; + uint16_t supp_contexts_ccc = 0; + uint16_t end = 0; + + MOCK_METHOD((void), OnReadCharacteristic, + (uint16_t handle, GATT_READ_OP_CB cb, void* cb_data), + (override)); + MOCK_METHOD((void), OnWriteCharacteristic, + (uint16_t handle, std::vector<uint8_t> value, + tGATT_WRITE_TYPE write_type, GATT_WRITE_OP_CB cb, + void* cb_data), + (override)); + }; + + struct ascs_mock : public IGattHandlers { + uint16_t start = 0; + uint16_t sink_ase_char = 0; + uint16_t sink_ase_ccc = 0; + uint16_t source_ase_char = 0; + uint16_t source_ase_ccc = 0; + uint16_t ctp_char = 0; + uint16_t ctp_ccc = 0; + uint16_t end = 0; + + MOCK_METHOD((void), OnReadCharacteristic, + (uint16_t handle, GATT_READ_OP_CB cb, void* cb_data), + (override)); + MOCK_METHOD((void), OnWriteCharacteristic, + (uint16_t handle, std::vector<uint8_t> value, + tGATT_WRITE_TYPE write_type, GATT_WRITE_OP_CB cb, + void* cb_data), + (override)); + }; + + MockDeviceWrapper(RawAddress addr, const std::list<gatt::Service>& services, + std::unique_ptr<MockDeviceWrapper::csis_mock> csis, + std::unique_ptr<MockDeviceWrapper::cas_mock> cas, + std::unique_ptr<MockDeviceWrapper::ascs_mock> ascs, + std::unique_ptr<MockDeviceWrapper::pacs_mock> pacs) + : addr(addr) { + this->services = services; + this->csis = std::move(csis); + this->cas = std::move(cas); + this->ascs = std::move(ascs); + this->pacs = std::move(pacs); + } + + ~MockDeviceWrapper() { + Mock::VerifyAndClearExpectations(csis.get()); + Mock::VerifyAndClearExpectations(cas.get()); + Mock::VerifyAndClearExpectations(ascs.get()); + Mock::VerifyAndClearExpectations(pacs.get()); + } + + RawAddress addr; + bool connected = false; + + // A list of services and their useful params + std::list<gatt::Service> services; + std::unique_ptr<csis_mock> csis; + std::unique_ptr<cas_mock> cas; + std::unique_ptr<ascs_mock> ascs; + std::unique_ptr<pacs_mock> pacs; + }; + + void SyncOnMainLoop() { + // Wait for the main loop to flush + // WARNING: Not tested with Timers pushing periodic tasks to the main loop + while (num_async_tasks > 0) + ; + } + + void ConnectLeAudio(const RawAddress& address, bool isEncrypted = true) { + // by default indicate link as encrypted + ON_CALL(mock_btm_interface_, BTM_IsEncrypted(address, _)) + .WillByDefault(DoAll(Return(isEncrypted))); + + EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, address, true, _)).Times(1); + + do_in_main_thread( + FROM_HERE, base::Bind(&LeAudioClient::Connect, + base::Unretained(LeAudioClient::Get()), address)); + + SyncOnMainLoop(); + } + + void DisconnectLeAudio(const RawAddress& address, uint16_t conn_id) { + SyncOnMainLoop(); + EXPECT_CALL(mock_gatt_interface_, Close(conn_id)).Times(1); + EXPECT_CALL(mock_client_callbacks_, + OnConnectionState(ConnectionState::DISCONNECTED, address)) + .Times(1); + do_in_main_thread( + FROM_HERE, base::Bind(&LeAudioClient::Disconnect, + base::Unretained(LeAudioClient::Get()), address)); + } + + void ConnectCsisDevice(const RawAddress& addr, uint16_t conn_id, + uint32_t sink_audio_allocation, + uint32_t source_audio_allocation, uint8_t group_size, + int group_id, uint8_t rank, + bool connect_through_csis = false) { + SetSampleDatabaseEarbudsValid(conn_id, addr, sink_audio_allocation, + source_audio_allocation, true, /*add_csis*/ + true, /*add_cas*/ + true, /*add_pacs*/ + true, /*add_ascs*/ + group_size, rank); + EXPECT_CALL(mock_client_callbacks_, + OnConnectionState(ConnectionState::CONNECTED, addr)) + .Times(1); + + EXPECT_CALL(mock_client_callbacks_, + OnGroupNodeStatus(addr, group_id, GroupNodeStatus::ADDED)) + .Times(1); + + if (connect_through_csis) { + // Add it the way CSIS would do: add to group and then connect + do_in_main_thread( + FROM_HERE, + base::Bind(&LeAudioClient::GroupAddNode, + base::Unretained(LeAudioClient::Get()), group_id, addr)); + ConnectLeAudio(addr); + } else { + // The usual connect + // Since device has CSIS, lets add it here to groups already now + groups[addr] = group_id; + ConnectLeAudio(addr); + InjectGroupDeviceAdded(addr, group_id); + } + } + + void ConnectNonCsisDevice(const RawAddress& addr, uint16_t conn_id, + uint32_t sink_audio_allocation, + uint32_t source_audio_allocation) { + SetSampleDatabaseEarbudsValid(conn_id, addr, sink_audio_allocation, + source_audio_allocation, false, /*add_csis*/ + true, /*add_cas*/ + true, /*add_pacs*/ + true, /*add_ascs*/ + 0, 0); + EXPECT_CALL(mock_client_callbacks_, + OnConnectionState(ConnectionState::CONNECTED, addr)) + .Times(1); + + ConnectLeAudio(addr); + } + + void UpdateMetadata(audio_usage_t usage, audio_content_type_t content_type) { + std::promise<void> do_metadata_update_promise; + auto do_metadata_update_future = do_metadata_update_promise.get_future(); + audio_sink_receiver_->OnAudioMetadataUpdate( + std::move(do_metadata_update_promise), usage, content_type); + do_metadata_update_future.wait(); + } + + void StartStreaming(audio_usage_t usage, audio_content_type_t content_type, + int group_id, bool reconfigured_sink = false) { + ASSERT_NE(audio_sink_receiver_, nullptr); + + UpdateMetadata(usage, content_type); + + EXPECT_CALL(mock_audio_source_, ConfirmStreamingRequest()).Times(1); + std::promise<void> do_resume_sink_promise; + auto do_resume_sink_future = do_resume_sink_promise.get_future(); + /* It's enough to call only one suspend even if it'll be bi-directional + * streaming. First resume will trigger GroupStream. + * + * There is no - 'only source receiver' scenario (e.g. single microphone). + * If there will be such test oriented scenario, such resume choose logic + * should be applied. + */ + audio_sink_receiver_->OnAudioResume(std::move(do_resume_sink_promise)); + do_resume_sink_future.wait(); + SyncOnMainLoop(); + + if (reconfigured_sink) { + std::promise<void> do_resume_sink_reconf_promise; + auto do_resume_sink_reconf_future = + do_resume_sink_reconf_promise.get_future(); + + audio_sink_receiver_->OnAudioResume( + std::move(do_resume_sink_reconf_promise)); + do_resume_sink_reconf_future.wait(); + } + + if (usage == AUDIO_USAGE_VOICE_COMMUNICATION) { + ASSERT_NE(audio_source_receiver_, nullptr); + + std::promise<void> do_resume_source_promise; + auto do_resume_source_future = do_resume_source_promise.get_future(); + audio_source_receiver_->OnAudioResume( + std::move(do_resume_source_promise)); + do_resume_source_future.wait(); + } + } + + void StopStreaming(int group_id, bool suspend_source = false) { + ASSERT_NE(audio_sink_receiver_, nullptr); + + /* TODO We should have a way to confirm Stop() otherwise, audio framework + * 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. + * + * There is no - 'only source receiver' scenario (e.g. single microphone). + * If there will be such test oriented scenario, such resume choose logic + * should be applied. + */ + audio_sink_receiver_->OnAudioSuspend(std::move(do_suspend_sink_promise)); + do_suspend_sink_future.wait(); + + if (suspend_source) { + ASSERT_NE(audio_source_receiver_, nullptr); + std::promise<void> do_suspend_source_promise; + auto do_suspend_source_future = do_suspend_source_promise.get_future(); + audio_source_receiver_->OnAudioSuspend( + std::move(do_suspend_source_promise)); + do_suspend_source_future.wait(); + } + } + + void set_sample_database(uint16_t conn_id, RawAddress addr, + std::unique_ptr<MockDeviceWrapper::csis_mock> csis, + std::unique_ptr<MockDeviceWrapper::cas_mock> cas, + std::unique_ptr<MockDeviceWrapper::ascs_mock> ascs, + std::unique_ptr<MockDeviceWrapper::pacs_mock> pacs) { + gatt::DatabaseBuilder bob; + + /* Generic Access Service */ + bob.AddService(0x0001, 0x0003, Uuid::From16Bit(0x1800), true); + /* Device Name Char. */ + bob.AddCharacteristic(0x0002, 0x0003, Uuid::From16Bit(0x2a00), + GATT_CHAR_PROP_BIT_READ); + + if (csis->start) { + bool is_primary = true; + bob.AddService(csis->start, csis->end, bluetooth::csis::kCsisServiceUuid, + is_primary); + if (csis->sirk_char) { + bob.AddCharacteristic( + csis->sirk_char, csis->sirk_char + 1, + bluetooth::csis::kCsisSirkUuid, + GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY); + if (csis->sirk_ccc) + bob.AddDescriptor(csis->sirk_ccc, + Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); + } + + if (csis->size_char) { + bob.AddCharacteristic( + csis->size_char, csis->size_char + 1, + bluetooth::csis::kCsisSizeUuid, + GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY); + if (csis->size_ccc) + bob.AddDescriptor(csis->size_ccc, + Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); + } + + if (csis->lock_char) { + bob.AddCharacteristic(csis->lock_char, csis->lock_char + 1, + bluetooth::csis::kCsisLockUuid, + GATT_CHAR_PROP_BIT_READ | + GATT_CHAR_PROP_BIT_NOTIFY | + GATT_CHAR_PROP_BIT_WRITE); + if (csis->lock_ccc) + bob.AddDescriptor(csis->lock_ccc, + Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); + } + + if (csis->rank_char) + bob.AddCharacteristic(csis->rank_char, csis->rank_char + 1, + bluetooth::csis::kCsisRankUuid, + GATT_CHAR_PROP_BIT_READ); + } + + if (cas->start) { + bool is_primary = true; + bob.AddService(cas->start, cas->end, le_audio::uuid::kCapServiceUuid, + is_primary); + // Include CSIS service inside + if (cas->csis_include) + bob.AddIncludedService(cas->csis_include, + bluetooth::csis::kCsisServiceUuid, csis->start, + csis->end); + } + + if (pacs->start) { + bool is_primary = true; + bob.AddService(pacs->start, pacs->end, + le_audio::uuid::kPublishedAudioCapabilityServiceUuid, + is_primary); + + if (pacs->sink_pac_char) { + bob.AddCharacteristic( + pacs->sink_pac_char, pacs->sink_pac_char + 1, + le_audio::uuid::kSinkPublishedAudioCapabilityCharacteristicUuid, + GATT_CHAR_PROP_BIT_READ); + if (pacs->sink_pac_ccc) + bob.AddDescriptor(pacs->sink_pac_ccc, + Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); + } + + if (pacs->sink_audio_loc_char) { + bob.AddCharacteristic( + pacs->sink_audio_loc_char, pacs->sink_audio_loc_char + 1, + le_audio::uuid::kSinkAudioLocationCharacteristicUuid, + GATT_CHAR_PROP_BIT_READ); + if (pacs->sink_audio_loc_ccc) + bob.AddDescriptor(pacs->sink_audio_loc_ccc, + Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); + } + + if (pacs->source_pac_char) { + bob.AddCharacteristic( + pacs->source_pac_char, pacs->source_pac_char + 1, + le_audio::uuid::kSourcePublishedAudioCapabilityCharacteristicUuid, + GATT_CHAR_PROP_BIT_READ); + if (pacs->source_pac_ccc) + bob.AddDescriptor(pacs->source_pac_ccc, + Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); + } + + if (pacs->source_audio_loc_char) { + bob.AddCharacteristic( + pacs->source_audio_loc_char, pacs->source_audio_loc_char + 1, + le_audio::uuid::kSourceAudioLocationCharacteristicUuid, + GATT_CHAR_PROP_BIT_READ); + if (pacs->source_audio_loc_ccc) + bob.AddDescriptor(pacs->source_audio_loc_ccc, + Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); + } + + if (pacs->avail_contexts_char) { + bob.AddCharacteristic( + pacs->avail_contexts_char, pacs->avail_contexts_char + 1, + le_audio::uuid::kAudioContextAvailabilityCharacteristicUuid, + GATT_CHAR_PROP_BIT_READ); + if (pacs->avail_contexts_ccc) + bob.AddDescriptor(pacs->avail_contexts_ccc, + Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); + } + + if (pacs->supp_contexts_char) { + bob.AddCharacteristic( + pacs->supp_contexts_char, pacs->supp_contexts_char + 1, + le_audio::uuid::kAudioSupportedContextCharacteristicUuid, + GATT_CHAR_PROP_BIT_READ); + if (pacs->supp_contexts_ccc) + bob.AddDescriptor(pacs->supp_contexts_ccc, + Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); + } + } + + if (ascs->start) { + bool is_primary = true; + bob.AddService(ascs->start, ascs->end, + le_audio::uuid::kAudioStreamControlServiceUuid, + is_primary); + if (ascs->sink_ase_char) { + bob.AddCharacteristic(ascs->sink_ase_char, ascs->sink_ase_char + 1, + le_audio::uuid::kSinkAudioStreamEndpointUuid, + GATT_CHAR_PROP_BIT_READ); + if (ascs->sink_ase_ccc) + bob.AddDescriptor(ascs->sink_ase_ccc, + Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); + } + if (ascs->source_ase_char) { + bob.AddCharacteristic(ascs->source_ase_char, ascs->source_ase_char + 1, + le_audio::uuid::kSourceAudioStreamEndpointUuid, + GATT_CHAR_PROP_BIT_READ); + if (ascs->source_ase_ccc) + bob.AddDescriptor(ascs->source_ase_ccc, + Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); + } + if (ascs->ctp_char) { + bob.AddCharacteristic( + ascs->ctp_char, ascs->ctp_char + 1, + le_audio::uuid::kAudioStreamEndpointControlPointCharacteristicUuid, + GATT_CHAR_PROP_BIT_READ); + if (ascs->ctp_ccc) + bob.AddDescriptor(ascs->ctp_ccc, + Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); + } + } + + // Assign conn_id to a certain device - this does not mean it is connected + auto dev_wrapper = std::make_unique<MockDeviceWrapper>( + addr, bob.Build().Services(), std::move(csis), std::move(cas), + std::move(ascs), std::move(pacs)); + peer_devices.emplace(conn_id, std::move(dev_wrapper)); + } + + void SetSampleDatabaseEmpty(uint16_t conn_id, RawAddress addr) { + auto csis = std::make_unique<MockDeviceWrapper::csis_mock>(); + auto cas = std::make_unique<MockDeviceWrapper::cas_mock>(); + auto pacs = std::make_unique<MockDeviceWrapper::pacs_mock>(); + auto ascs = std::make_unique<MockDeviceWrapper::ascs_mock>(); + set_sample_database(conn_id, addr, std::move(csis), std::move(cas), + std::move(ascs), std::move(pacs)); + } + + void SetSampleDatabaseEarbudsValid(uint16_t conn_id, RawAddress addr, + uint32_t sink_audio_allocation, + uint32_t source_audio_allocation, + bool add_csis = true, bool add_cas = true, + bool add_pacs = true, bool add_ascs = true, + uint8_t set_size = 2, uint8_t rank = 1) { + auto csis = std::make_unique<MockDeviceWrapper::csis_mock>(); + if (add_csis) { + // attribute handles + csis->start = 0x0010; + csis->sirk_char = 0x0020; + csis->sirk_ccc = 0x0022; + csis->size_char = 0x0023; + csis->size_ccc = 0x0025; + csis->lock_char = 0x0026; + csis->lock_ccc = 0x0028; + csis->rank_char = 0x0029; + csis->end = 0x0030; + // other params + csis->size = set_size; + csis->rank = rank; + } + + auto cas = std::make_unique<MockDeviceWrapper::cas_mock>(); + if (add_cas) { + // attribute handles + cas->start = 0x0040; + if (add_csis) cas->csis_include = 0x0041; + cas->end = 0x0050; + // other params + } + + auto pacs = std::make_unique<MockDeviceWrapper::pacs_mock>(); + if (add_pacs) { + // attribute handles + pacs->start = 0x0060; + pacs->sink_pac_char = 0x0061; + pacs->sink_pac_ccc = 0x0063; + pacs->sink_audio_loc_char = 0x0064; + pacs->sink_audio_loc_ccc = 0x0066; + pacs->source_pac_char = 0x0067; + pacs->source_pac_ccc = 0x0069; + pacs->source_audio_loc_char = 0x0070; + pacs->source_audio_loc_ccc = 0x0072; + pacs->avail_contexts_char = 0x0073; + pacs->avail_contexts_ccc = 0x0075; + pacs->supp_contexts_char = 0x0076; + pacs->supp_contexts_ccc = 0x0078; + pacs->end = 0x0080; + // other params + } + + auto ascs = std::make_unique<MockDeviceWrapper::ascs_mock>(); + if (add_ascs) { + // attribute handles + ascs->start = 0x0090; + ascs->sink_ase_char = 0x0091; + ascs->sink_ase_ccc = 0x0093; + ascs->source_ase_char = 0x0094; + ascs->source_ase_ccc = 0x0096; + ascs->ctp_char = 0x0097; + ascs->ctp_ccc = 0x0099; + ascs->end = 0x00A0; + // other params + } + + set_sample_database(conn_id, addr, std::move(csis), std::move(cas), + std::move(ascs), std::move(pacs)); + + if (add_pacs) { + uint8_t snk_allocation[4]; + uint8_t src_allocation[4]; + + snk_allocation[0] = (uint8_t)(sink_audio_allocation); + snk_allocation[1] = (uint8_t)(sink_audio_allocation >> 8); + snk_allocation[2] = (uint8_t)(sink_audio_allocation >> 16); + snk_allocation[3] = (uint8_t)(sink_audio_allocation >> 24); + + src_allocation[0] = (uint8_t)(source_audio_allocation); + src_allocation[1] = (uint8_t)(source_audio_allocation >> 8); + src_allocation[2] = (uint8_t)(source_audio_allocation >> 16); + src_allocation[3] = (uint8_t)(source_audio_allocation >> 24); + + // Set pacs default read values + ON_CALL(*peer_devices.at(conn_id)->pacs, OnReadCharacteristic(_, _, _)) + .WillByDefault( + [this, conn_id, snk_allocation, src_allocation]( + uint16_t handle, GATT_READ_OP_CB cb, void* cb_data) { + auto& pacs = peer_devices.at(conn_id)->pacs; + std::vector<uint8_t> value; + if (handle == pacs->sink_pac_char + 1) { + value = { + // Num records + 0x02, + // Codec_ID + 0x06, + 0x00, + 0x00, + 0x00, + 0x00, + // Codec Spec. Caps. Len + 0x10, + 0x03, + 0x01, + 0x04, + 0x00, + 0x02, + 0x02, + 0x03, + 0x02, + 0x03, + 0x03, + 0x05, + 0x04, + 0x1E, + 0x00, + 0x28, + 0x00, + // Metadata Length + 0x00, + // Codec_ID + 0x06, + 0x00, + 0x00, + 0x00, + 0x00, + // Codec Spec. Caps. Len + 0x10, + 0x03, + 0x01, + 0x80, + 0x00, + 0x02, + 0x02, + 0x03, + 0x02, + 0x03, + 0x03, + 0x05, + 0x04, + 0x78, + 0x00, + 0x78, + 0x00, + // Metadata Length + 0x00, + }; + } else if (handle == pacs->sink_audio_loc_char + 1) { + value = { + // Audio Locations + snk_allocation[0], + snk_allocation[1], + snk_allocation[2], + snk_allocation[3], + }; + } else if (handle == pacs->source_pac_char + 1) { + value = { + // Num records + 0x02, + // Codec_ID + 0x06, + 0x00, + 0x00, + 0x00, + 0x00, + // Codec Spec. Caps. Len + 0x10, + 0x03, + 0x01, + 0x04, + 0x00, + 0x02, + 0x02, + 0x03, + 0x02, + 0x03, + 0x03, + 0x05, + 0x04, + 0x1E, + 0x00, + 0x28, + 0x00, + // Metadata Length + 0x00, + // Codec_ID + 0x06, + 0x00, + 0x00, + 0x00, + 0x00, + // Codec Spec. Caps. Len + 0x10, + 0x03, + 0x01, + 0x04, + 0x00, + 0x02, + 0x02, + 0x03, + 0x02, + 0x03, + 0x03, + 0x05, + 0x04, + 0x1E, + 0x00, + 0x28, + 0x00, + // Metadata Length + 0x00, + }; + } else if (handle == pacs->source_audio_loc_char + 1) { + value = { + // Audio Locations + src_allocation[0], + src_allocation[1], + src_allocation[2], + src_allocation[3], + }; + } else if (handle == pacs->avail_contexts_char + 1) { + value = { + // Sink Avail Contexts + 0xff, + 0xff, + // Source Avail Contexts + 0xff, + 0xff, + }; + } else if (handle == pacs->supp_contexts_char + 1) { + value = { + // Sink Avail Contexts + 0xff, + 0xff, + // Source Avail Contexts + 0xff, + 0xff, + }; + } + cb(conn_id, GATT_SUCCESS, handle, value.size(), value.data(), + cb_data); + }); + } + + if (add_ascs) { + // Set ascs default read values + ON_CALL(*peer_devices.at(conn_id)->ascs, OnReadCharacteristic(_, _, _)) + .WillByDefault([this, conn_id](uint16_t handle, GATT_READ_OP_CB cb, + void* cb_data) { + auto& ascs = peer_devices.at(conn_id)->ascs; + std::vector<uint8_t> value; + if (handle == ascs->sink_ase_char + 1) { + value = { + // ASE ID + 0x01, + // State + static_cast<uint8_t>( + le_audio::types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE), + // No Additional ASE params for IDLE state + }; + } else if (handle == ascs->source_ase_char + 1) { + value = { + // ASE ID + 0x02, + // State + static_cast<uint8_t>( + le_audio::types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE), + // No Additional ASE params for IDLE state + }; + } + cb(conn_id, GATT_SUCCESS, handle, value.size(), value.data(), + cb_data); + }); + } + } + + void TestAudioDataTransfer(int group_id, uint8_t cis_count_out, + uint8_t cis_count_in, int data_len) { + ASSERT_NE(audio_sink_receiver_, nullptr); + + // Expect two channels ISO Data to be sent + std::vector<uint16_t> handles; + EXPECT_CALL(*mock_iso_manager_, SendIsoData(_, _, _)) + .Times(cis_count_out) + .WillRepeatedly( + [&handles](uint16_t iso_handle, const uint8_t* data, + uint16_t data_len) { handles.push_back(iso_handle); }); + std::vector<uint8_t> data(data_len); + audio_sink_receiver_->OnAudioDataReady(data); + + // Inject microphone data from a single peer device + EXPECT_CALL(mock_audio_sink_, SendData(_, _)).Times(cis_count_in); + ASSERT_EQ(streaming_groups.count(group_id), 1u); + + if (cis_count_in) { + ASSERT_NE(audio_source_receiver_, nullptr); + + auto group = streaming_groups.at(group_id); + for (LeAudioDevice* device = group->GetFirstDevice(); device != nullptr; + device = group->GetNextDevice(device)) { + for (auto& ase : device->ases_) { + if (ase.direction == le_audio::types::kLeAudioDirectionSource) { + InjectIncomingIsoData(group_id, ase.cis_conn_hdl); + --cis_count_in; + if (!cis_count_in) break; + } + } + if (!cis_count_in) break; + } + } + + SyncOnMainLoop(); + std::sort(handles.begin(), handles.end()); + ASSERT_EQ(std::unique(handles.begin(), handles.end()) - handles.begin(), + cis_count_out); + ASSERT_EQ(cis_count_in, 0); + handles.clear(); + + Mock::VerifyAndClearExpectations(mock_iso_manager_); + Mock::VerifyAndClearExpectations(&mock_audio_sink_); + } + + void InjectIncomingIsoData(uint16_t cig_id, uint16_t cis_con_hdl, + size_t payload_size = 40) { + BT_HDR* bt_hdr = (BT_HDR*)malloc(sizeof(BT_HDR) + payload_size); + + bt_hdr->offset = 0; + bt_hdr->len = payload_size; + + bluetooth::hci::iso_manager::cis_data_evt cis_evt; + cis_evt.cig_id = cig_id; + cis_evt.cis_conn_hdl = cis_con_hdl; + cis_evt.ts = 0; + cis_evt.evt_lost = 0; + cis_evt.p_msg = bt_hdr; + + ASSERT_NE(cig_callbacks_, nullptr); + cig_callbacks_->OnCisEvent( + bluetooth::hci::iso_manager::kIsoEventCisDataAvailable, &cis_evt); + free(bt_hdr); + } + + void InjectCisDisconnected(uint16_t cig_id, uint16_t cis_con_hdl, + uint8_t reason = 0) { + bluetooth::hci::iso_manager::cis_disconnected_evt cis_evt; + cis_evt.cig_id = cig_id; + cis_evt.cis_conn_hdl = cis_con_hdl; + cis_evt.reason = reason; + + ASSERT_NE(cig_callbacks_, nullptr); + cig_callbacks_->OnCisEvent( + bluetooth::hci::iso_manager::kIsoEventCisDisconnected, &cis_evt); + } + + MockLeAudioClientCallbacks mock_client_callbacks_; + MockLeAudioClientAudioSource mock_audio_source_; + MockLeAudioClientAudioSink mock_audio_sink_; + LeAudioClientAudioSinkReceiver* audio_sink_receiver_ = nullptr; + LeAudioClientAudioSourceReceiver* audio_source_receiver_ = nullptr; + + bool is_audio_hal_source_acquired; + bool is_audio_hal_sink_acquired; + + MockCsisClient mock_csis_client_module_; + MockDeviceGroups mock_groups_module_; + bluetooth::groups::DeviceGroupsCallbacks* group_callbacks_; + MockLeAudioGroupStateMachine mock_state_machine_; + + MockFunction<void()> mock_storage_load; + MockFunction<bool()> mock_hal_2_1_verifier; + + controller::MockControllerInterface controller_interface_; + bluetooth::manager::MockBtmInterface mock_btm_interface_; + gatt::MockBtaGattInterface mock_gatt_interface_; + gatt::MockBtaGattQueue mock_gatt_queue_; + tBTA_GATTC_CBACK* gatt_callback; + const uint8_t gatt_if = 0xfe; + uint8_t global_conn_id = 1; + le_audio::LeAudioGroupStateMachine::Callbacks* state_machine_callbacks_; + std::map<int, LeAudioDeviceGroup*> streaming_groups; + + bluetooth::hci::IsoManager* iso_manager_; + MockIsoManager* mock_iso_manager_; + bluetooth::hci::iso_manager::CigCallbacks* cig_callbacks_ = nullptr; + uint16_t iso_con_counter_ = 1; + + bluetooth::storage::MockBtifStorageInterface mock_btif_storage_; + + std::map<uint16_t, std::unique_ptr<MockDeviceWrapper>> peer_devices; + std::list<int> group_locks; + std::map<RawAddress, int> groups; +}; + +class UnicastTest : public UnicastTestNoInit { + protected: + void SetUp() override { + UnicastTestNoInit::SetUp(); + + EXPECT_CALL(mock_hal_2_1_verifier, Call()).Times(1); + EXPECT_CALL(mock_storage_load, Call()).Times(1); + + BtaAppRegisterCallback app_register_callback; + EXPECT_CALL(mock_gatt_interface_, AppRegister(_, _, _)) + .WillOnce(DoAll(SaveArg<0>(&gatt_callback), + SaveArg<1>(&app_register_callback))); + LeAudioClient::Initialize( + &mock_client_callbacks_, + base::Bind([](MockFunction<void()>* foo) { foo->Call(); }, + &mock_storage_load), + base::Bind([](MockFunction<bool()>* foo) { return foo->Call(); }, + &mock_hal_2_1_verifier)); + + SyncOnMainLoop(); + ASSERT_TRUE(gatt_callback); + ASSERT_TRUE(group_callbacks_); + ASSERT_TRUE(app_register_callback); + app_register_callback.Run(gatt_if, GATT_SUCCESS); + Mock::VerifyAndClearExpectations(&mock_gatt_interface_); + } + + void TearDown() override { + groups.clear(); + UnicastTestNoInit::TearDown(); + } +}; + +RawAddress GetTestAddress(uint8_t index) { + CHECK_LT(index, UINT8_MAX); + RawAddress result = {{0xC0, 0xDE, 0xC0, 0xDE, 0x00, index}}; + return result; +} + +TEST_F(UnicastTest, Initialize) { + ASSERT_NE(LeAudioClient::Get(), nullptr); + ASSERT_TRUE(LeAudioClient::IsLeAudioClientRunning()); +} + +TEST_F(UnicastTestNoInit, InitializeNoHal_2_1) { + ASSERT_FALSE(LeAudioClient::IsLeAudioClientRunning()); + + // Report False when asked for Audio HAL 2.1 support + ON_CALL(mock_hal_2_1_verifier, Call()).WillByDefault([]() -> bool { + return false; + }); + + BtaAppRegisterCallback app_register_callback; + ON_CALL(mock_gatt_interface_, AppRegister(_, _, _)) + .WillByDefault(DoAll(SaveArg<0>(&gatt_callback), + SaveArg<1>(&app_register_callback))); + + EXPECT_DEATH( + LeAudioClient::Initialize( + &mock_client_callbacks_, + base::Bind([](MockFunction<void()>* foo) { foo->Call(); }, + &mock_storage_load), + base::Bind([](MockFunction<bool()>* foo) { return foo->Call(); }, + &mock_hal_2_1_verifier)), + ", LE Audio Client requires Bluetooth Audio HAL V2.1 at least. Either " + "disable LE Audio Profile, or update your HAL"); +} + +TEST_F(UnicastTest, ConnectOneEarbudEmpty) { + const RawAddress test_address0 = GetTestAddress(0); + SetSampleDatabaseEmpty(1, test_address0); + EXPECT_CALL(mock_client_callbacks_, + OnConnectionState(ConnectionState::DISCONNECTED, test_address0)) + .Times(1); + EXPECT_CALL(mock_gatt_interface_, Close(_)).Times(1); + ConnectLeAudio(test_address0); +} + +TEST_F(UnicastTest, ConnectOneEarbudNoPacs) { + const RawAddress test_address0 = GetTestAddress(0); + SetSampleDatabaseEarbudsValid( + 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, + codec_spec_conf::kLeAudioLocationStereo, true, /*add_csis*/ + true, /*add_cas*/ + false, /*add_pacs*/ + true /*add_ascs*/); + EXPECT_CALL(mock_client_callbacks_, + OnConnectionState(ConnectionState::DISCONNECTED, test_address0)) + .Times(1); + EXPECT_CALL(mock_gatt_interface_, Close(_)).Times(1); + ConnectLeAudio(test_address0); +} + +TEST_F(UnicastTest, ConnectOneEarbudNoAscs) { + const RawAddress test_address0 = GetTestAddress(0); + SetSampleDatabaseEarbudsValid( + 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, + codec_spec_conf::kLeAudioLocationStereo, true, /*add_csis*/ + true, /*add_cas*/ + true, /*add_pacs*/ + false /*add_ascs*/); + EXPECT_CALL(mock_client_callbacks_, + OnConnectionState(ConnectionState::DISCONNECTED, test_address0)) + .Times(1); + EXPECT_CALL(mock_gatt_interface_, Close(_)).Times(1); + ConnectLeAudio(test_address0); +} + +TEST_F(UnicastTest, ConnectOneEarbudNoCas) { + const RawAddress test_address0 = GetTestAddress(0); + uint16_t conn_id = 1; + SetSampleDatabaseEarbudsValid( + conn_id, test_address0, codec_spec_conf::kLeAudioLocationStereo, + codec_spec_conf::kLeAudioLocationStereo, true, /*add_csis*/ + false, /*add_cas*/ + true, /*add_pacs*/ + true /*add_ascs*/); + + EXPECT_CALL(mock_client_callbacks_, + OnConnectionState(ConnectionState::CONNECTED, test_address0)) + .Times(1); + ConnectLeAudio(test_address0); +} + +TEST_F(UnicastTest, ConnectOneEarbudNoCsis) { + const RawAddress test_address0 = GetTestAddress(0); + SetSampleDatabaseEarbudsValid( + 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, + codec_spec_conf::kLeAudioLocationStereo, false, /*add_csis*/ + true, /*add_cas*/ + true, /*add_pacs*/ + true /*add_ascs*/); + EXPECT_CALL(mock_client_callbacks_, + OnConnectionState(ConnectionState::CONNECTED, test_address0)) + .Times(1); + ConnectLeAudio(test_address0); +} + +TEST_F(UnicastTest, ConnectDisconnectOneEarbud) { + const RawAddress test_address0 = GetTestAddress(0); + SetSampleDatabaseEarbudsValid(1, test_address0, + codec_spec_conf::kLeAudioLocationStereo, + codec_spec_conf::kLeAudioLocationStereo); + EXPECT_CALL(mock_client_callbacks_, + OnConnectionState(ConnectionState::CONNECTED, test_address0)) + .Times(1); + ConnectLeAudio(test_address0); + DisconnectLeAudio(test_address0, 1); +} + +TEST_F(UnicastTest, ConnectTwoEarbudsCsisGrouped) { + uint8_t group_size = 2; + int group_id = 2; + + // Report working CSIS + ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) + .WillByDefault(Return(true)); + + // First earbud + const RawAddress test_address0 = GetTestAddress(0); + EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) + .Times(1); + ConnectCsisDevice(test_address0, 1 /*conn_id*/, + codec_spec_conf::kLeAudioLocationFrontLeft, + codec_spec_conf::kLeAudioLocationFrontLeft, group_size, + group_id, 1 /* rank*/); + + // Second earbud + const RawAddress test_address1 = GetTestAddress(1); + EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) + .Times(1); + ConnectCsisDevice(test_address1, 2 /*conn_id*/, + codec_spec_conf::kLeAudioLocationFrontRight, + codec_spec_conf::kLeAudioLocationFrontRight, group_size, + group_id, 2 /* rank*/, true /*connect_through_csis*/); + + // Verify grouping information + std::vector<RawAddress> devs = + LeAudioClient::Get()->GetGroupDevices(group_id); + ASSERT_NE(std::find(devs.begin(), devs.end(), test_address0), devs.end()); + ASSERT_NE(std::find(devs.begin(), devs.end(), test_address1), devs.end()); + + DisconnectLeAudio(test_address0, 1); + DisconnectLeAudio(test_address1, 2); +} + +TEST_F(UnicastTest, ConnectTwoEarbudsCsisGroupUnknownAtConnect) { + uint8_t group_size = 2; + uint8_t group_id = 2; + + // Report working CSIS + ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) + .WillByDefault(Return(true)); + + // First earbud connects without known grouping + const RawAddress test_address0 = GetTestAddress(0); + EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) + .Times(1); + ConnectCsisDevice(test_address0, 1 /*conn_id*/, + codec_spec_conf::kLeAudioLocationFrontLeft, + codec_spec_conf::kLeAudioLocationFrontLeft, group_size, + group_id, 1 /* rank*/); + + // Second earbud + const RawAddress test_address1 = GetTestAddress(1); + EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) + .Times(1); + ConnectCsisDevice(test_address1, 2 /*conn_id*/, + codec_spec_conf::kLeAudioLocationFrontRight, + codec_spec_conf::kLeAudioLocationFrontRight, group_size, + group_id, 2 /* rank*/, true /*connect_through_csis*/); + + // Verify grouping information + std::vector<RawAddress> devs = + LeAudioClient::Get()->GetGroupDevices(group_id); + ASSERT_NE(std::find(devs.begin(), devs.end(), test_address0), devs.end()); + ASSERT_NE(std::find(devs.begin(), devs.end(), test_address1), devs.end()); + + DisconnectLeAudio(test_address0, 1); + DisconnectLeAudio(test_address1, 2); +} + +TEST_F(UnicastTestNoInit, LoadStoredEarbudsCsisGrouped) { + // Prepare two devices + uint8_t group_size = 2; + uint8_t group_id = 2; + + const RawAddress test_address0 = GetTestAddress(0); + SetSampleDatabaseEarbudsValid( + 1, test_address0, codec_spec_conf::kLeAudioLocationFrontLeft, + codec_spec_conf::kLeAudioLocationFrontLeft, true, /*add_csis*/ + true, /*add_cas*/ + true, /*add_pacs*/ + true, /*add_ascs*/ + group_size, 1); + + const RawAddress test_address1 = GetTestAddress(1); + SetSampleDatabaseEarbudsValid( + 2, test_address1, codec_spec_conf::kLeAudioLocationFrontRight, + codec_spec_conf::kLeAudioLocationFrontRight, true, /*add_csis*/ + true, /*add_cas*/ + true, /*add_pacs*/ + true, /*add_ascs*/ + group_size, 2); + + // Load devices from the storage when storage API is called + bool autoconnect = true; + EXPECT_CALL(mock_storage_load, Call()).WillOnce([&]() { + do_in_main_thread(FROM_HERE, base::Bind(&LeAudioClient::AddFromStorage, + test_address0, autoconnect)); + do_in_main_thread(FROM_HERE, base::Bind(&LeAudioClient::AddFromStorage, + test_address1, autoconnect)); + }); + + // Expect stored device0 to connect automatically + EXPECT_CALL(mock_client_callbacks_, + OnConnectionState(ConnectionState::CONNECTED, test_address0)) + .Times(1); + ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address0, _)) + .WillByDefault(DoAll(Return(true))); + EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, true, _)) + .Times(1); + + // Expect stored device1 to connect automatically + EXPECT_CALL(mock_client_callbacks_, + OnConnectionState(ConnectionState::CONNECTED, test_address1)) + .Times(1); + ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address1, _)) + .WillByDefault(DoAll(Return(true))); + EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address1, true, _)) + .Times(1); + + ON_CALL(mock_groups_module_, GetGroupId(_, _)) + .WillByDefault(DoAll(Return(group_id))); + + ON_CALL(mock_btm_interface_, + GetSecurityFlagsByTransport(test_address0, NotNull(), _)) + .WillByDefault( + DoAll(SetArgPointee<1>(BTM_SEC_FLAG_ENCRYPTED), Return(true))); + + // Initialize + BtaAppRegisterCallback app_register_callback; + ON_CALL(mock_gatt_interface_, AppRegister(_, _, _)) + .WillByDefault(DoAll(SaveArg<0>(&gatt_callback), + SaveArg<1>(&app_register_callback))); + LeAudioClient::Initialize( + &mock_client_callbacks_, + base::Bind([](MockFunction<void()>* foo) { foo->Call(); }, + &mock_storage_load), + base::Bind([](MockFunction<bool()>* foo) { return foo->Call(); }, + &mock_hal_2_1_verifier)); + if (app_register_callback) app_register_callback.Run(gatt_if, GATT_SUCCESS); + + // We need to wait for the storage callback before verifying stuff + SyncOnMainLoop(); + ASSERT_TRUE(LeAudioClient::IsLeAudioClientRunning()); + + // Verify if all went well and we got the proper group + std::vector<RawAddress> devs = + LeAudioClient::Get()->GetGroupDevices(group_id); + ASSERT_NE(std::find(devs.begin(), devs.end(), test_address0), devs.end()); + ASSERT_NE(std::find(devs.begin(), devs.end(), test_address1), devs.end()); + + DisconnectLeAudio(test_address0, 1); + DisconnectLeAudio(test_address1, 2); +} + +TEST_F(UnicastTestNoInit, LoadStoredEarbudsCsisGroupedDifferently) { + // Prepare two devices + uint8_t group_size = 1; + + // Device 0 + uint8_t group_id0 = 2; + bool autoconnect0 = true; + const RawAddress test_address0 = GetTestAddress(0); + SetSampleDatabaseEarbudsValid( + 1, test_address0, codec_spec_conf::kLeAudioLocationFrontLeft, + codec_spec_conf::kLeAudioLocationFrontLeft, true, /*add_csis*/ + true, /*add_cas*/ + true, /*add_pacs*/ + true, /*add_ascs*/ + group_size, 1); + + ON_CALL(mock_groups_module_, GetGroupId(test_address0, _)) + .WillByDefault(DoAll(Return(group_id0))); + + // Device 1 + uint8_t group_id1 = 3; + bool autoconnect1 = false; + const RawAddress test_address1 = GetTestAddress(1); + SetSampleDatabaseEarbudsValid( + 2, test_address1, codec_spec_conf::kLeAudioLocationFrontRight, + codec_spec_conf::kLeAudioLocationFrontRight, true, /*add_csis*/ + true, /*add_cas*/ + true, /*add_pacs*/ + true, /*add_ascs*/ + group_size, 2); + + ON_CALL(mock_groups_module_, GetGroupId(test_address1, _)) + .WillByDefault(DoAll(Return(group_id1))); + + // Load devices from the storage when storage API is called + EXPECT_CALL(mock_storage_load, Call()).WillOnce([&]() { + do_in_main_thread(FROM_HERE, base::Bind(&LeAudioClient::AddFromStorage, + test_address0, autoconnect0)); + do_in_main_thread(FROM_HERE, base::Bind(&LeAudioClient::AddFromStorage, + test_address1, autoconnect1)); + }); + + // Expect stored device0 to connect automatically + EXPECT_CALL(mock_client_callbacks_, + OnConnectionState(ConnectionState::CONNECTED, test_address0)) + .Times(1); + ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address0, _)) + .WillByDefault(DoAll(Return(true))); + EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, true, _)) + .Times(1); + + // Expect stored device1 to NOT connect automatically + EXPECT_CALL(mock_client_callbacks_, + OnConnectionState(ConnectionState::CONNECTED, test_address1)) + .Times(0); + ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address1, _)) + .WillByDefault(DoAll(Return(true))); + EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address1, true, _)) + .Times(0); + + // Initialize + BtaAppRegisterCallback app_register_callback; + ON_CALL(mock_gatt_interface_, AppRegister(_, _, _)) + .WillByDefault(DoAll(SaveArg<0>(&gatt_callback), + SaveArg<1>(&app_register_callback))); + LeAudioClient::Initialize( + &mock_client_callbacks_, + base::Bind([](MockFunction<void()>* foo) { foo->Call(); }, + &mock_storage_load), + base::Bind([](MockFunction<bool()>* foo) { return foo->Call(); }, + &mock_hal_2_1_verifier)); + if (app_register_callback) app_register_callback.Run(gatt_if, GATT_SUCCESS); + + // We need to wait for the storage callback before verifying stuff + SyncOnMainLoop(); + ASSERT_TRUE(LeAudioClient::IsLeAudioClientRunning()); + + std::vector<RawAddress> devs = + LeAudioClient::Get()->GetGroupDevices(group_id0); + ASSERT_NE(std::find(devs.begin(), devs.end(), test_address0), devs.end()); + ASSERT_EQ(std::find(devs.begin(), devs.end(), test_address1), devs.end()); + + devs = LeAudioClient::Get()->GetGroupDevices(group_id1); + ASSERT_EQ(std::find(devs.begin(), devs.end(), test_address0), devs.end()); + ASSERT_NE(std::find(devs.begin(), devs.end(), test_address1), devs.end()); + + DisconnectLeAudio(test_address0, 1); +} + +TEST_F(UnicastTest, GroupingAddRemove) { + // Earbud connects without known grouping + uint8_t group_id0 = bluetooth::groups::kGroupUnknown; + const RawAddress test_address0 = GetTestAddress(0); + + EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) + .Times(1); + ConnectNonCsisDevice(test_address0, 1 /*conn_id*/, + codec_spec_conf::kLeAudioLocationFrontLeft, + codec_spec_conf::kLeAudioLocationFrontLeft); + + group_id0 = MockDeviceGroups::DeviceGroups::Get()->GetGroupId(test_address0); + + // Earbud connects without known grouping + uint8_t group_id1 = bluetooth::groups::kGroupUnknown; + const RawAddress test_address1 = GetTestAddress(1); + EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) + .Times(1); + ConnectNonCsisDevice(test_address1, 2 /*conn_id*/, + codec_spec_conf::kLeAudioLocationFrontRight, + codec_spec_conf::kLeAudioLocationFrontRight); + + group_id1 = MockDeviceGroups::DeviceGroups::Get()->GetGroupId(test_address1); + + Mock::VerifyAndClearExpectations(&mock_btif_storage_); + + // Verify individual groups + ASSERT_NE(group_id0, bluetooth::groups::kGroupUnknown); + ASSERT_NE(group_id1, bluetooth::groups::kGroupUnknown); + ASSERT_NE(group_id0, group_id1); + ASSERT_EQ(LeAudioClient::Get()->GetGroupDevices(group_id0).size(), 1u); + ASSERT_EQ(LeAudioClient::Get()->GetGroupDevices(group_id1).size(), 1u); + + // Expectations on reassigning second earbud to the first group + int dev1_storage_group = bluetooth::groups::kGroupUnknown; + int dev1_new_group = bluetooth::groups::kGroupUnknown; + + EXPECT_CALL( + mock_client_callbacks_, + OnGroupNodeStatus(test_address1, group_id1, GroupNodeStatus::REMOVED)) + .Times(AtLeast(1)); + EXPECT_CALL(mock_client_callbacks_, + OnGroupNodeStatus(test_address1, _, GroupNodeStatus::ADDED)) + .WillRepeatedly(SaveArg<1>(&dev1_new_group)); + EXPECT_CALL(mock_groups_module_, RemoveDevice(test_address1, group_id1)) + .Times(AtLeast(1)); + EXPECT_CALL(mock_groups_module_, AddDevice(test_address1, _, _)) + .Times(AnyNumber()); + + LeAudioClient::Get()->GroupRemoveNode(group_id1, test_address1); + SyncOnMainLoop(); + + Mock::VerifyAndClearExpectations(&mock_groups_module_); + + EXPECT_CALL(mock_groups_module_, AddDevice(test_address1, _, group_id0)) + .Times(1); + + LeAudioClient::Get()->GroupAddNode(group_id0, test_address1); + SyncOnMainLoop(); + Mock::VerifyAndClearExpectations(&mock_groups_module_); + + dev1_storage_group = + MockDeviceGroups::DeviceGroups::Get()->GetGroupId(test_address1); + + // Verify regrouping results + EXPECT_EQ(dev1_new_group, group_id0); + EXPECT_EQ(dev1_new_group, dev1_storage_group); + ASSERT_EQ(LeAudioClient::Get()->GetGroupDevices(group_id1).size(), 0u); + ASSERT_EQ(LeAudioClient::Get()->GetGroupDevices(group_id0).size(), 2u); + std::vector<RawAddress> devs = + LeAudioClient::Get()->GetGroupDevices(group_id0); + ASSERT_NE(std::find(devs.begin(), devs.end(), test_address0), devs.end()); + ASSERT_NE(std::find(devs.begin(), devs.end(), test_address1), devs.end()); +} + +TEST_F(UnicastTest, GroupingAddTwiceNoRemove) { + // Earbud connects without known grouping + uint8_t group_id0 = bluetooth::groups::kGroupUnknown; + const RawAddress test_address0 = GetTestAddress(0); + EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) + .WillOnce(Return()) + .RetiresOnSaturation(); + ConnectNonCsisDevice(test_address0, 1 /*conn_id*/, + codec_spec_conf::kLeAudioLocationFrontLeft, + codec_spec_conf::kLeAudioLocationFrontLeft); + + group_id0 = MockDeviceGroups::DeviceGroups::Get()->GetGroupId(test_address0); + + // Earbud connects without known grouping + uint8_t group_id1 = bluetooth::groups::kGroupUnknown; + const RawAddress test_address1 = GetTestAddress(1); + EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) + .WillOnce(Return()) + .RetiresOnSaturation(); + ConnectNonCsisDevice(test_address1, 2 /*conn_id*/, + codec_spec_conf::kLeAudioLocationFrontRight, + codec_spec_conf::kLeAudioLocationFrontRight); + + Mock::VerifyAndClearExpectations(&mock_btif_storage_); + + group_id1 = MockDeviceGroups::DeviceGroups::Get()->GetGroupId(test_address1); + // Verify individual groups + ASSERT_NE(group_id0, bluetooth::groups::kGroupUnknown); + ASSERT_NE(group_id1, bluetooth::groups::kGroupUnknown); + ASSERT_NE(group_id0, group_id1); + ASSERT_EQ(LeAudioClient::Get()->GetGroupDevices(group_id0).size(), 1u); + ASSERT_EQ(LeAudioClient::Get()->GetGroupDevices(group_id1).size(), 1u); + + // Expectations on reassigning second earbud to the first group + int dev1_storage_group = bluetooth::groups::kGroupUnknown; + int dev1_new_group = bluetooth::groups::kGroupUnknown; + + EXPECT_CALL( + mock_client_callbacks_, + OnGroupNodeStatus(test_address1, group_id1, GroupNodeStatus::REMOVED)) + .Times(AtLeast(1)); + EXPECT_CALL(mock_client_callbacks_, + OnGroupNodeStatus(test_address1, _, GroupNodeStatus::ADDED)) + .WillRepeatedly(SaveArg<1>(&dev1_new_group)); + + // FIXME: We should expect removal with group_id context. No such API exists. + EXPECT_CALL(mock_groups_module_, RemoveDevice(test_address1, group_id1)) + .Times(AtLeast(1)); + EXPECT_CALL(mock_groups_module_, AddDevice(test_address1, _, _)) + .Times(AnyNumber()); + EXPECT_CALL(mock_groups_module_, AddDevice(test_address1, _, group_id0)) + .Times(1); + + // Regroup device: assign new group without removing it from the first one + LeAudioClient::Get()->GroupAddNode(group_id0, test_address1); + SyncOnMainLoop(); + Mock::VerifyAndClearExpectations(&mock_groups_module_); + + dev1_storage_group = + MockDeviceGroups::DeviceGroups::Get()->GetGroupId(test_address1); + + // Verify regrouping results + EXPECT_EQ(dev1_new_group, group_id0); + EXPECT_EQ(dev1_new_group, dev1_storage_group); + ASSERT_EQ(LeAudioClient::Get()->GetGroupDevices(group_id1).size(), 0u); + ASSERT_EQ(LeAudioClient::Get()->GetGroupDevices(group_id0).size(), 2u); + std::vector<RawAddress> devs = + LeAudioClient::Get()->GetGroupDevices(group_id0); + ASSERT_NE(std::find(devs.begin(), devs.end(), test_address0), devs.end()); + ASSERT_NE(std::find(devs.begin(), devs.end(), test_address1), devs.end()); +} + +TEST_F(UnicastTest, RemoveTwoEarbudsCsisGrouped) { + uint8_t group_size = 2; + int group_id0 = 2; + int group_id1 = 3; + + // Report working CSIS + ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) + .WillByDefault(Return(true)); + + // First group - First earbud + const RawAddress test_address0 = GetTestAddress(0); + EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) + .Times(1); + ConnectCsisDevice(test_address0, 1 /*conn_id*/, + codec_spec_conf::kLeAudioLocationFrontLeft, + codec_spec_conf::kLeAudioLocationFrontLeft, group_size, + group_id0, 1 /* rank*/); + + // First group - Second earbud + const RawAddress test_address1 = GetTestAddress(1); + EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) + .Times(1); + ConnectCsisDevice(test_address1, 2 /*conn_id*/, + codec_spec_conf::kLeAudioLocationFrontRight, + codec_spec_conf::kLeAudioLocationFrontRight, group_size, + group_id0, 2 /* rank*/, true /*connect_through_csis*/); + + // Second group - First earbud + const RawAddress test_address2 = GetTestAddress(2); + EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address2, true)) + .Times(1); + ConnectCsisDevice(test_address2, 3 /*conn_id*/, + codec_spec_conf::kLeAudioLocationFrontLeft, + codec_spec_conf::kLeAudioLocationFrontLeft, group_size, + group_id1, 1 /* rank*/); + + // Second group - Second earbud + const RawAddress test_address3 = GetTestAddress(3); + EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address3, true)) + .Times(1); + ConnectCsisDevice(test_address3, 4 /*conn_id*/, + codec_spec_conf::kLeAudioLocationFrontRight, + codec_spec_conf::kLeAudioLocationFrontRight, group_size, + group_id1, 2 /* rank*/, true /*connect_through_csis*/); + + // First group - verify grouping information + std::vector<RawAddress> group0_devs = + LeAudioClient::Get()->GetGroupDevices(group_id0); + ASSERT_NE(std::find(group0_devs.begin(), group0_devs.end(), test_address0), + group0_devs.end()); + ASSERT_NE(std::find(group0_devs.begin(), group0_devs.end(), test_address1), + group0_devs.end()); + + // Second group - verify grouping information + std::vector<RawAddress> group1_devs = + LeAudioClient::Get()->GetGroupDevices(group_id1); + ASSERT_NE(std::find(group1_devs.begin(), group1_devs.end(), test_address2), + group1_devs.end()); + ASSERT_NE(std::find(group1_devs.begin(), group1_devs.end(), test_address3), + group1_devs.end()); + Mock::VerifyAndClearExpectations(&mock_btif_storage_); + + // Expect one of the groups to be dropped and devices to be disconnected + EXPECT_CALL(mock_groups_module_, RemoveDevice(test_address0, group_id0)) + .Times(1); + EXPECT_CALL(mock_groups_module_, RemoveDevice(test_address1, group_id0)) + .Times(1); + EXPECT_CALL( + mock_client_callbacks_, + OnGroupNodeStatus(test_address0, group_id0, GroupNodeStatus::REMOVED)); + EXPECT_CALL( + mock_client_callbacks_, + OnGroupNodeStatus(test_address1, group_id0, GroupNodeStatus::REMOVED)); + EXPECT_CALL(mock_client_callbacks_, + OnConnectionState(ConnectionState::DISCONNECTED, test_address0)) + .Times(1); + EXPECT_CALL(mock_client_callbacks_, + OnConnectionState(ConnectionState::DISCONNECTED, test_address1)) + .Times(1); + + // Expect the other groups to be left as is + EXPECT_CALL(mock_client_callbacks_, OnGroupStatus(group_id1, _)).Times(0); + EXPECT_CALL(mock_client_callbacks_, + OnConnectionState(ConnectionState::DISCONNECTED, test_address2)) + .Times(0); + EXPECT_CALL(mock_client_callbacks_, + OnConnectionState(ConnectionState::DISCONNECTED, test_address3)) + .Times(0); + + do_in_main_thread( + FROM_HERE, base::Bind(&LeAudioClient::GroupDestroy, + base::Unretained(LeAudioClient::Get()), group_id0)); + + SyncOnMainLoop(); + Mock::VerifyAndClearExpectations(&mock_btif_storage_); +} + +TEST_F(UnicastTest, SpeakerStreaming) { + const RawAddress test_address0 = GetTestAddress(0); + int group_id = bluetooth::groups::kGroupUnknown; + + SetSampleDatabaseEarbudsValid( + 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, + codec_spec_conf::kLeAudioLocationStereo, false /*add_csis*/, + true /*add_cas*/, true /*add_pacs*/, true /*add_ascs*/, 1 /*set_size*/, + 0 /*rank*/); + EXPECT_CALL(mock_client_callbacks_, + OnConnectionState(ConnectionState::CONNECTED, test_address0)) + .Times(1); + EXPECT_CALL(mock_client_callbacks_, + OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) + .WillOnce(DoAll(SaveArg<1>(&group_id))); + + ConnectLeAudio(test_address0); + ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); + + // Start streaming + uint8_t cis_count_out = 1; + uint8_t cis_count_in = 0; + + EXPECT_CALL(mock_audio_source_, Start(_, _)).Times(1); + LeAudioClient::Get()->GroupSetActive(group_id); + + StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); + + Mock::VerifyAndClearExpectations(&mock_client_callbacks_); + Mock::VerifyAndClearExpectations(&mock_audio_source_); + SyncOnMainLoop(); + + // Verify Data transfer on one audio source cis + TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); + + // Suspend + /*TODO Need a way to verify STOP */ + LeAudioClient::Get()->GroupSuspend(group_id); + Mock::VerifyAndClearExpectations(&mock_client_callbacks_); + Mock::VerifyAndClearExpectations(&mock_audio_source_); + + // Resume + StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); + Mock::VerifyAndClearExpectations(&mock_client_callbacks_); + Mock::VerifyAndClearExpectations(&mock_audio_source_); + + // Stop + StopStreaming(group_id); + Mock::VerifyAndClearExpectations(&mock_client_callbacks_); + + // Release + EXPECT_CALL(mock_audio_source_, Stop()).Times(1); + EXPECT_CALL(mock_audio_source_, Release(_)).Times(1); + LeAudioClient::Get()->GroupSetActive(bluetooth::groups::kGroupUnknown); + Mock::VerifyAndClearExpectations(&mock_audio_source_); +} + +TEST_F(UnicastTest, SpeakerStreamingAutonomousRelease) { + const RawAddress test_address0 = GetTestAddress(0); + int group_id = bluetooth::groups::kGroupUnknown; + + SetSampleDatabaseEarbudsValid( + 1, test_address0, codec_spec_conf::kLeAudioLocationStereo, + codec_spec_conf::kLeAudioLocationStereo, false /*add_csis*/, + true /*add_cas*/, true /*add_pacs*/, true /*add_ascs*/, 1 /*set_size*/, + 0 /*rank*/); + EXPECT_CALL(mock_client_callbacks_, + OnConnectionState(ConnectionState::CONNECTED, test_address0)) + .Times(1); + EXPECT_CALL(mock_client_callbacks_, + OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) + .WillOnce(DoAll(SaveArg<1>(&group_id))); + + ConnectLeAudio(test_address0); + ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); + + // Start streaming + EXPECT_CALL(mock_audio_source_, Start(_, _)).Times(1); + LeAudioClient::Get()->GroupSetActive(group_id); + + StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); + + Mock::VerifyAndClearExpectations(&mock_client_callbacks_); + Mock::VerifyAndClearExpectations(&mock_audio_source_); + SyncOnMainLoop(); + + // Verify Data transfer on one audio source cis + TestAudioDataTransfer(group_id, 1 /* cis_count_out */, 0 /* cis_count_in */, + 1920); + + // Inject the IDLE state as if an autonomous release happened + auto group = streaming_groups.at(group_id); + ASSERT_NE(group, nullptr); + for (LeAudioDevice* device = group->GetFirstDevice(); device != nullptr; + device = group->GetNextDevice(device)) { + for (auto& ase : device->ases_) { + ase.data_path_state = types::AudioStreamDataPathState::IDLE; + ase.state = types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE; + InjectCisDisconnected(group_id, ase.cis_conn_hdl); + } + } + + // Verify no Data transfer after the autonomous release + TestAudioDataTransfer(group_id, 0 /* cis_count_out */, 0 /* cis_count_in */, + 1920); +} + +TEST_F(UnicastTest, TwoEarbudsStreaming) { + uint8_t group_size = 2; + int group_id = 2; + + // Report working CSIS + ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) + .WillByDefault(Return(true)); + + // First earbud + const RawAddress test_address0 = GetTestAddress(0); + EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) + .Times(1); + ConnectCsisDevice(test_address0, 1 /*conn_id*/, + codec_spec_conf::kLeAudioLocationFrontLeft, + codec_spec_conf::kLeAudioLocationFrontLeft, group_size, + group_id, 1 /* rank*/); + + // Second earbud + const RawAddress test_address1 = GetTestAddress(1); + EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) + .Times(1); + ConnectCsisDevice(test_address1, 2 /*conn_id*/, + codec_spec_conf::kLeAudioLocationFrontRight, + codec_spec_conf::kLeAudioLocationFrontRight, group_size, + group_id, 2 /* rank*/, true /*connect_through_csis*/); + + EXPECT_CALL(mock_audio_source_, Start(_, _)).Times(1); + LeAudioClient::Get()->GroupSetActive(group_id); + Mock::VerifyAndClearExpectations(&mock_audio_source_); + + // Start streaming with reconfiguration from default media stream setup + EXPECT_CALL(mock_audio_source_, Start(_, _)).Times(1); + EXPECT_CALL(mock_audio_source_, CancelStreamingRequest()).Times(1); + EXPECT_CALL(mock_audio_source_, Stop()).Times(1); + EXPECT_CALL(mock_audio_sink_, Start(_, _)).Times(1); + + StartStreaming(AUDIO_USAGE_VOICE_COMMUNICATION, AUDIO_CONTENT_TYPE_SPEECH, + group_id, true /* reconfigure */); + + Mock::VerifyAndClearExpectations(&mock_client_callbacks_); + Mock::VerifyAndClearExpectations(&mock_audio_source_); + Mock::VerifyAndClearExpectations(&mock_audio_sink_); + SyncOnMainLoop(); + + // Verify Data transfer on two peer sinks and one source + uint8_t cis_count_out = 2; + uint8_t cis_count_in = 1; + TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 640); + + // Suspend + EXPECT_CALL(mock_audio_source_, Release(_)).Times(0); + EXPECT_CALL(mock_audio_sink_, Release(_)).Times(0); + EXPECT_CALL(mock_audio_source_, Stop()).Times(0); + EXPECT_CALL(mock_audio_sink_, Stop()).Times(0); + LeAudioClient::Get()->GroupSuspend(group_id); + SyncOnMainLoop(); + Mock::VerifyAndClearExpectations(&mock_audio_source_); + Mock::VerifyAndClearExpectations(&mock_audio_sink_); + + // Resume + StartStreaming(AUDIO_USAGE_VOICE_COMMUNICATION, AUDIO_CONTENT_TYPE_SPEECH, + group_id); + SyncOnMainLoop(); + Mock::VerifyAndClearExpectations(&mock_audio_source_); + Mock::VerifyAndClearExpectations(&mock_audio_sink_); + + // Verify Data transfer still works + TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 640); + + // Stop + StopStreaming(group_id, true); + Mock::VerifyAndClearExpectations(&mock_client_callbacks_); + + // Release + EXPECT_CALL(mock_audio_source_, Stop()).Times(1); + EXPECT_CALL(mock_audio_source_, Release(_)).Times(1); + EXPECT_CALL(mock_audio_sink_, Stop()).Times(1); + EXPECT_CALL(mock_audio_sink_, Release(_)).Times(1); + LeAudioClient::Get()->GroupSetActive(bluetooth::groups::kGroupUnknown); + Mock::VerifyAndClearExpectations(&mock_audio_source_); + Mock::VerifyAndClearExpectations(&mock_audio_sink_); +} + +TEST_F(UnicastTest, TwoEarbudsStreamingContextSwitchSimple) { + uint8_t group_size = 2; + int group_id = 2; + + // Report working CSIS + ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) + .WillByDefault(Return(true)); + + // First earbud + const RawAddress test_address0 = GetTestAddress(0); + EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) + .Times(1); + ConnectCsisDevice(test_address0, 1 /*conn_id*/, + codec_spec_conf::kLeAudioLocationFrontLeft, + codec_spec_conf::kLeAudioLocationFrontLeft, group_size, + group_id, 1 /* rank*/); + + // Second earbud + const RawAddress test_address1 = GetTestAddress(1); + EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) + .Times(1); + ConnectCsisDevice(test_address1, 2 /*conn_id*/, + codec_spec_conf::kLeAudioLocationFrontRight, + codec_spec_conf::kLeAudioLocationFrontRight, group_size, + group_id, 2 /* rank*/, true /*connect_through_csis*/); + + EXPECT_CALL(mock_audio_source_, Start(_, _)).Times(1); + LeAudioClient::Get()->GroupSetActive(group_id); + Mock::VerifyAndClearExpectations(&mock_audio_source_); + + // Start streaming with reconfiguration from default media stream setup + EXPECT_CALL( + mock_state_machine_, + StartStream(_, le_audio::types::LeAudioContextType::NOTIFICATIONS)) + .Times(1); + EXPECT_CALL(mock_audio_source_, Start(_, _)).Times(1); + EXPECT_CALL(mock_audio_source_, CancelStreamingRequest()).Times(1); + EXPECT_CALL(mock_audio_source_, Stop()).Times(1); + + StartStreaming(AUDIO_USAGE_NOTIFICATION, AUDIO_CONTENT_TYPE_UNKNOWN, group_id, + true /* reconfigure */); + + Mock::VerifyAndClearExpectations(&mock_client_callbacks_); + Mock::VerifyAndClearExpectations(&mock_audio_source_); + SyncOnMainLoop(); + + // Do a content switch to ALERTS + EXPECT_CALL(mock_audio_source_, Release).Times(0); + EXPECT_CALL(mock_audio_source_, Stop).Times(0); + EXPECT_CALL(mock_audio_source_, Start).Times(0); + EXPECT_CALL(mock_state_machine_, + StartStream(_, le_audio::types::LeAudioContextType::ALERTS)) + .Times(1); + UpdateMetadata(AUDIO_USAGE_ALARM, AUDIO_CONTENT_TYPE_UNKNOWN); + Mock::VerifyAndClearExpectations(&mock_client_callbacks_); + Mock::VerifyAndClearExpectations(&mock_audio_source_); + + // Do a content switch to EMERGENCY + EXPECT_CALL(mock_audio_source_, Release).Times(0); + EXPECT_CALL(mock_audio_source_, Stop).Times(0); + EXPECT_CALL(mock_audio_source_, Start).Times(0); + + EXPECT_CALL( + mock_state_machine_, + StartStream(_, le_audio::types::LeAudioContextType::EMERGENCYALARM)) + .Times(1); + UpdateMetadata(AUDIO_USAGE_EMERGENCY, AUDIO_CONTENT_TYPE_UNKNOWN); + Mock::VerifyAndClearExpectations(&mock_audio_source_); +} + +TEST_F(UnicastTest, TwoEarbudsStreamingContextSwitchReconfigure) { + uint8_t group_size = 2; + int group_id = 2; + + // Report working CSIS + ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) + .WillByDefault(Return(true)); + + // First earbud + const RawAddress test_address0 = GetTestAddress(0); + EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, true)) + .Times(1); + ConnectCsisDevice(test_address0, 1 /*conn_id*/, + codec_spec_conf::kLeAudioLocationFrontLeft, + codec_spec_conf::kLeAudioLocationFrontLeft, group_size, + group_id, 1 /* rank*/); + + // Second earbud + const RawAddress test_address1 = GetTestAddress(1); + EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, true)) + .Times(1); + ConnectCsisDevice(test_address1, 2 /*conn_id*/, + codec_spec_conf::kLeAudioLocationFrontRight, + codec_spec_conf::kLeAudioLocationFrontRight, group_size, + group_id, 2 /* rank*/, true /*connect_through_csis*/); + + // Start streaming MEDIA + EXPECT_CALL(mock_audio_source_, Start(_, _)).Times(1); + LeAudioClient::Get()->GroupSetActive(group_id); + + StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); + + Mock::VerifyAndClearExpectations(&mock_client_callbacks_); + Mock::VerifyAndClearExpectations(&mock_audio_source_); + SyncOnMainLoop(); + + // Verify Data transfer on two peer sinks + uint8_t cis_count_out = 2; + uint8_t cis_count_in = 0; + TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); + + // Stop + StopStreaming(group_id); + Mock::VerifyAndClearExpectations(&mock_client_callbacks_); + + // Start streaming + EXPECT_CALL(mock_audio_source_, Start(_, _)).Times(1); + EXPECT_CALL(mock_audio_source_, CancelStreamingRequest()).Times(1); + EXPECT_CALL(mock_audio_source_, Stop()).Times(1); + EXPECT_CALL(mock_audio_sink_, Start(_, _)).Times(1); + LeAudioClient::Get()->GroupSetActive(group_id); + + StartStreaming(AUDIO_USAGE_VOICE_COMMUNICATION, AUDIO_CONTENT_TYPE_SPEECH, + group_id, true /* reconfigure */); + + Mock::VerifyAndClearExpectations(&mock_client_callbacks_); + Mock::VerifyAndClearExpectations(&mock_audio_source_); + Mock::VerifyAndClearExpectations(&mock_audio_sink_); + SyncOnMainLoop(); + + // Verify Data transfer on two peer sinks and one source + cis_count_out = 2; + cis_count_in = 1; + TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 640); +} + +TEST_F(UnicastTest, TwoEarbuds2ndLateConnect) { + uint8_t group_size = 2; + int group_id = 2; + + // Report working CSIS + ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) + .WillByDefault(Return(true)); + + // First earbud + const RawAddress test_address0 = GetTestAddress(0); + ConnectCsisDevice(test_address0, 1 /*conn_id*/, + codec_spec_conf::kLeAudioLocationFrontLeft, + codec_spec_conf::kLeAudioLocationFrontLeft, group_size, + group_id, 1 /* rank*/); + + // Start streaming + EXPECT_CALL(mock_audio_source_, Start(_, _)).Times(1); + LeAudioClient::Get()->GroupSetActive(group_id); + + StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); + + Mock::VerifyAndClearExpectations(&mock_client_callbacks_); + Mock::VerifyAndClearExpectations(&mock_audio_source_); + SyncOnMainLoop(); + + // Expect one iso channel to be fed with data + uint8_t cis_count_out = 1; + uint8_t cis_count_in = 0; + TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); + + // Second earbud connects during stream + const RawAddress test_address1 = GetTestAddress(1); + ConnectCsisDevice(test_address1, 2 /*conn_id*/, + codec_spec_conf::kLeAudioLocationFrontRight, + codec_spec_conf::kLeAudioLocationFrontRight, group_size, + group_id, 2 /* rank*/, true /*connect_through_csis*/); + + /* We should expect two iso channels to be fed with data, but for now, when + * second device is connected later, we just continue stream to one device. + * TODO: improve it. + */ + cis_count_out = 1; + cis_count_in = 0; + TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); +} + +TEST_F(UnicastTest, TwoEarbuds2ndDisconnect) { + uint8_t group_size = 2; + int group_id = 2; + + // Report working CSIS + ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) + .WillByDefault(Return(true)); + + // First earbud + const RawAddress test_address0 = GetTestAddress(0); + ConnectCsisDevice(test_address0, 1 /*conn_id*/, + codec_spec_conf::kLeAudioLocationFrontLeft, + codec_spec_conf::kLeAudioLocationFrontLeft, group_size, + group_id, 1 /* rank*/); + + // Second earbud + const RawAddress test_address1 = GetTestAddress(1); + ConnectCsisDevice(test_address1, 2 /*conn_id*/, + codec_spec_conf::kLeAudioLocationFrontRight, + codec_spec_conf::kLeAudioLocationFrontRight, group_size, + group_id, 2 /* rank*/, true /*connect_through_csis*/); + + // Start streaming + EXPECT_CALL(mock_audio_source_, Start(_, _)).Times(1); + LeAudioClient::Get()->GroupSetActive(group_id); + + StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); + + Mock::VerifyAndClearExpectations(&mock_client_callbacks_); + Mock::VerifyAndClearExpectations(&mock_audio_source_); + SyncOnMainLoop(); + + // Expect two iso channels to be fed with data + uint8_t cis_count_out = 2; + uint8_t cis_count_in = 0; + TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); + + // Disconnect one device and expect the group to keep on streaming + EXPECT_CALL(mock_state_machine_, StopStream(_)).Times(0); + auto group = streaming_groups.at(group_id); + auto device = group->GetFirstDevice(); + for (auto& ase : device->ases_) { + InjectCisDisconnected(group_id, ase.cis_conn_hdl); + } + DisconnectLeAudio(device->address_, 1); + SyncOnMainLoop(); + Mock::VerifyAndClearExpectations(&mock_client_callbacks_); + + // Expect one channel ISO Data to be sent + cis_count_out = 1; + cis_count_in = 0; + TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); +} + +} // namespace +} // namespace le_audio diff --git a/system/bta/le_audio/le_audio_types.cc b/system/bta/le_audio/le_audio_types.cc new file mode 100644 index 0000000000000000000000000000000000000000..d8286e1acd0e00513c1ca19522b4c76934fe5081 --- /dev/null +++ b/system/bta/le_audio/le_audio_types.cc @@ -0,0 +1,409 @@ +/* + * Copyright 2020 HIMSA II K/S - www.himsa.com. Represented by EHIMA - + * www.ehima.com + * + * 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. + */ + +/* + * This file contains definitions for Basic Audio Profile / Audio Stream Control + * and Published Audio Capabilities definitions, structures etc. + */ + +#include "le_audio_types.h" + +#include <base/strings/string_number_conversions.h> + +#include "bt_types.h" +#include "bta_api.h" +#include "bta_le_audio_api.h" +#include "client_audio.h" +#include "client_parser.h" + +namespace le_audio { +namespace set_configurations { +using set_configurations::CodecCapabilitySetting; +using types::acs_ac_record; +using types::kLeAudioCodingFormatLC3; +using types::kLeAudioDirectionSink; +using types::kLeAudioDirectionSource; +using types::LeAudioContextType; +using types::LeAudioLc3Config; + +static uint8_t min_req_devices_cnt( + const AudioSetConfiguration* audio_set_conf) { + std::pair<uint8_t /* sink */, uint8_t /* source */> snk_src_pair(0, 0); + + for (auto ent : (*audio_set_conf).confs) { + if (ent.direction == kLeAudioDirectionSink) + snk_src_pair.first += ent.device_cnt; + if (ent.direction == kLeAudioDirectionSource) + snk_src_pair.second += ent.device_cnt; + } + + return std::max(snk_src_pair.first, snk_src_pair.second); +} + +static uint8_t min_req_devices_cnt( + const AudioSetConfigurations* audio_set_confs) { + uint8_t curr_min_req_devices_cnt = 0xff; + + for (auto ent : *audio_set_confs) { + uint8_t req_devices_cnt = min_req_devices_cnt(ent); + if (req_devices_cnt < curr_min_req_devices_cnt) + curr_min_req_devices_cnt = req_devices_cnt; + } + + return curr_min_req_devices_cnt; +} + +bool check_if_may_cover_scenario(const AudioSetConfigurations* audio_set_confs, + uint8_t group_size) { + if (!audio_set_confs) { + LOG(ERROR) << __func__ << ", no audio requirements for group"; + return false; + } + + return group_size >= min_req_devices_cnt(audio_set_confs); +} + +bool check_if_may_cover_scenario(const AudioSetConfiguration* audio_set_conf, + uint8_t group_size) { + if (!audio_set_conf) { + LOG(ERROR) << __func__ << ", no audio requirement for group"; + return false; + } + + return group_size >= min_req_devices_cnt(audio_set_conf); +} + +static bool IsCodecConfigurationSupported(const types::LeAudioLtvMap& pacs, + const LeAudioLc3Config& lc3_config) { + const auto& reqs = lc3_config.GetAsLtvMap(); + uint8_t u8_req_val, u8_pac_val; + uint16_t u16_req_val, u16_pac_val; + + /* Sampling frequency */ + auto req = reqs.Find(codec_spec_conf::kLeAudioCodecLC3TypeSamplingFreq); + auto pac = pacs.Find(codec_spec_caps::kLeAudioCodecLC3TypeSamplingFreq); + if (!req || !pac) { + DLOG(ERROR) << __func__ << ", lack of sampling frequency fields"; + return false; + } + + u8_req_val = VEC_UINT8_TO_UINT8(req.value()); + u16_pac_val = VEC_UINT8_TO_UINT16(pac.value()); + + /* + * Note: Requirements are in the codec configuration specification which + * are values coming from BAP Appendix A1.2.1 + */ + DLOG(INFO) << __func__ << " Req:SamplFreq=" << loghex(u8_req_val); + /* NOTE: Below is Codec specific cababilities comes form BAP Appendix A A1.1.1 + * Note this is a bitfield + */ + DLOG(INFO) << __func__ << " Pac:SamplFreq=" << loghex(u16_pac_val); + + /* TODO: Integrate with codec capabilities */ + if ((u8_req_val != codec_spec_conf::kLeAudioSamplingFreq16000Hz && + u8_req_val != codec_spec_conf::kLeAudioSamplingFreq48000Hz) || + !(u16_pac_val & + codec_spec_caps::SamplingFreqConfig2Capability(u8_req_val))) { + DLOG(ERROR) << __func__ << ", sampling frequency not supported"; + return false; + } + + /* Frame duration */ + req = reqs.Find(codec_spec_conf::kLeAudioCodecLC3TypeFrameDuration); + pac = pacs.Find(codec_spec_caps::kLeAudioCodecLC3TypeFrameDuration); + if (!req || !pac) { + DLOG(ERROR) << __func__ << ", lack of frame duration fields"; + return false; + } + + u8_req_val = VEC_UINT8_TO_UINT8(req.value()); + u8_pac_val = VEC_UINT8_TO_UINT8(pac.value()); + DLOG(INFO) << __func__ << " Req:FrameDur=" << loghex(u8_req_val); + DLOG(INFO) << __func__ << " Pac:FrameDur=" << loghex(u8_pac_val); + + if ((u8_req_val != codec_spec_conf::kLeAudioCodecLC3FrameDur7500us && + u8_req_val != codec_spec_conf::kLeAudioCodecLC3FrameDur10000us) || + !(u8_pac_val & + (codec_spec_caps::FrameDurationConfig2Capability(u8_req_val)))) { + DLOG(ERROR) << __func__ << ", frame duration not supported"; + return false; + } + + uint8_t required_audio_chan_num = lc3_config.GetChannelCount(); + pac = pacs.Find(codec_spec_caps::kLeAudioCodecLC3TypeAudioChannelCounts); + + /* + * BAP_Validation_r07 1.9.2 Audio channel support requirements + * "The Unicast Server shall support an Audio_Channel_Counts value of 0x01 + * (0b00000001 = one channel) and may support other values defined by an + * implementation or by a higher-layer specification." + * + * Thus if Audio_Channel_Counts is not present in PAC LTV structure, we assume + * the Unicast Server supports mandatory one channel. + */ + if (!pac) { + DLOG(WARNING) << __func__ << ", no Audio_Channel_Counts field in PAC"; + u8_pac_val = 0x01; + } else { + u8_pac_val = VEC_UINT8_TO_UINT8(pac.value()); + } + + DLOG(INFO) << __func__ << " Pac:AudioChanCnt=" << loghex(u8_pac_val); + if (!((1 << (required_audio_chan_num - 1)) & u8_pac_val)) { + DLOG(ERROR) << __func__ << ", channel count warning"; + return false; + } + + /* Octets per frame */ + req = reqs.Find(codec_spec_conf::kLeAudioCodecLC3TypeOctetPerFrame); + pac = pacs.Find(codec_spec_caps::kLeAudioCodecLC3TypeOctetPerFrame); + + if (!req || !pac) { + DLOG(ERROR) << __func__ << ", lack of octet per frame fields"; + return false; + } + + u16_req_val = VEC_UINT8_TO_UINT16(req.value()); + DLOG(INFO) << __func__ << " Req:OctetsPerFrame=" << int(u16_req_val); + + /* Minimal value 0-1 byte */ + u16_pac_val = VEC_UINT8_TO_UINT16(pac.value()); + DLOG(INFO) << __func__ << " Pac:MinOctetsPerFrame=" << int(u16_pac_val); + if (u16_req_val < u16_pac_val) { + DLOG(ERROR) << __func__ << ", octet per frame below minimum"; + return false; + } + + /* Maximal value 2-3 byte */ + u16_pac_val = OFF_VEC_UINT8_TO_UINT16(pac.value(), 2); + DLOG(INFO) << __func__ << " Pac:MaxOctetsPerFrame=" << int(u16_pac_val); + if (u16_req_val > u16_pac_val) { + DLOG(ERROR) << __func__ << ", octet per frame above maximum"; + return false; + } + + return true; +} + +bool IsCodecCapabilitySettingSupported( + const acs_ac_record& pac, + const CodecCapabilitySetting& codec_capability_setting) { + const auto& codec_id = codec_capability_setting.id; + + if (codec_id != pac.codec_id) return false; + + DLOG(INFO) << __func__ << ": Settings for format " << +codec_id.coding_format; + + switch (codec_id.coding_format) { + case kLeAudioCodingFormatLC3: + return IsCodecConfigurationSupported( + pac.codec_spec_caps, + std::get<LeAudioLc3Config>(codec_capability_setting.config)); + default: + return false; + } +} + +const AudioSetConfigurations* get_confs_by_type(LeAudioContextType type) { + switch (type) { + case LeAudioContextType::MEDIA: + return &audio_set_conf_media; + case LeAudioContextType::CONVERSATIONAL: + return &audio_set_conf_conversational; + case LeAudioContextType::RINGTONE: + return &audio_set_conf_ringtone; + default: + return &audio_set_conf_default; + } +} +uint32_t CodecCapabilitySetting::GetConfigSamplingFrequency() const { + switch (id.coding_format) { + case kLeAudioCodingFormatLC3: + return std::get<types::LeAudioLc3Config>(config).GetSamplingFrequencyHz(); + default: + DLOG(WARNING) << __func__ << ", invalid codec id"; + return 0; + } +}; + +uint32_t CodecCapabilitySetting::GetConfigDataIntervalUs() const { + switch (id.coding_format) { + case kLeAudioCodingFormatLC3: + return std::get<types::LeAudioLc3Config>(config).GetFrameDurationUs(); + default: + DLOG(WARNING) << __func__ << ", invalid codec id"; + return 0; + } +}; + +uint8_t CodecCapabilitySetting::GetConfigBitsPerSample() const { + switch (id.coding_format) { + case kLeAudioCodingFormatLC3: + /* XXX LC3 supports 16, 24, 32 */ + return 16; + default: + DLOG(WARNING) << __func__ << ", invalid codec id"; + return 0; + } +}; + +uint8_t CodecCapabilitySetting::GetConfigChannelCount() const { + switch (id.coding_format) { + case kLeAudioCodingFormatLC3: + DLOG(INFO) << __func__ << ", count = " + << static_cast<int>(std::get<types::LeAudioLc3Config>(config) + .channel_count); + return std::get<types::LeAudioLc3Config>(config).channel_count; + default: + DLOG(WARNING) << __func__ << ", invalid codec id"; + return 0; + } +} +} // namespace set_configurations + +namespace types { +/* Helper map for matching various frequency notations */ +const std::map<uint8_t, uint32_t> LeAudioLc3Config::sampling_freq_map = { + {codec_spec_conf::kLeAudioSamplingFreq8000Hz, + LeAudioCodecConfiguration::kSampleRate8000}, + {codec_spec_conf::kLeAudioSamplingFreq16000Hz, + LeAudioCodecConfiguration::kSampleRate16000}, + {codec_spec_conf::kLeAudioSamplingFreq24000Hz, + LeAudioCodecConfiguration::kSampleRate24000}, + {codec_spec_conf::kLeAudioSamplingFreq32000Hz, + LeAudioCodecConfiguration::kSampleRate32000}, + {codec_spec_conf::kLeAudioSamplingFreq44100Hz, + LeAudioCodecConfiguration::kSampleRate44100}, + {codec_spec_conf::kLeAudioSamplingFreq48000Hz, + LeAudioCodecConfiguration::kSampleRate48000}}; + +/* Helper map for matching various frame durations notations */ +const std::map<uint8_t, uint32_t> LeAudioLc3Config::frame_duration_map = { + {codec_spec_conf::kLeAudioCodecLC3FrameDur7500us, + LeAudioCodecConfiguration::kInterval7500Us}, + {codec_spec_conf::kLeAudioCodecLC3FrameDur10000us, + LeAudioCodecConfiguration::kInterval10000Us}}; + +std::optional<std::vector<uint8_t>> LeAudioLtvMap::Find(uint8_t type) const { + auto iter = + std::find_if(values.cbegin(), values.cend(), + [type](const auto& value) { return value.first == type; }); + + if (iter == values.cend()) return std::nullopt; + + return iter->second; +} + +uint8_t* LeAudioLtvMap::RawPacket(uint8_t* p_buf) const { + for (auto const& value : values) { + UINT8_TO_STREAM(p_buf, value.second.size() + 1); + UINT8_TO_STREAM(p_buf, value.first); + ARRAY_TO_STREAM(p_buf, value.second.data(), + static_cast<int>(value.second.size())); + } + + return p_buf; +} + +LeAudioLtvMap LeAudioLtvMap::Parse(const uint8_t* p_value, uint8_t len, + bool& success) { + LeAudioLtvMap ltv_map; + + if (len > 0) { + const auto p_value_end = p_value + len; + + while ((p_value_end - p_value) > 0) { + uint8_t ltv_len; + STREAM_TO_UINT8(ltv_len, p_value); + + // Unusual, but possible case + if (ltv_len == 0) continue; + + if (p_value_end < (p_value + ltv_len)) { + LOG(ERROR) << __func__ + << " Invalid ltv_len: " << static_cast<int>(ltv_len); + success = false; + return LeAudioLtvMap(); + } + + uint8_t ltv_type; + STREAM_TO_UINT8(ltv_type, p_value); + ltv_len -= sizeof(ltv_type); + + const auto p_temp = p_value; + p_value += ltv_len; + + std::vector<uint8_t> ltv_value(p_temp, p_value); + ltv_map.values.emplace(ltv_type, std::move(ltv_value)); + } + } + + success = true; + return ltv_map; +} + +size_t LeAudioLtvMap::RawPacketSize() const { + size_t bytes = 0; + + for (auto const& value : values) { + bytes += (/* ltv_len + ltv_type */ 2 + value.second.size()); + } + + return bytes; +} + +std::string LeAudioLtvMap::ToString() const { + std::string debug_str; + + for (const auto& value : values) { + std::stringstream sstream; + + sstream << "\ttype: " << std::to_string(value.first) + << "\tlen: " << std::to_string(value.second.size()) << "\tdata: " + << base::HexEncode(value.second.data(), value.second.size()) + "\n"; + + debug_str += sstream.str(); + } + + return debug_str; +} + +} // namespace types +} // namespace le_audio + +std::ostream& operator<<(std::ostream& os, + const le_audio::types::LeAudioLc3Config& config) { + os << " LeAudioLc3Config(SamplFreq=" << loghex(config.sampling_frequency) + << ", FrameDur=" << loghex(config.frame_duration) + << ", OctetsPerFrame=" << int(config.octets_per_codec_frame) + << ", AudioChanLoc=" << loghex(config.audio_channel_allocation) << ")"; + return os; +} + +std::ostream& operator<<(std::ostream& os, + const le_audio::types::AseState& state) { + static const char* char_value_[7] = { + "IDLE", "CODEC_CONFIGURED", "QOS_CONFIGURED", "ENABLING", + "STREAMING", "DISABLING", "RELEASING", + }; + + os << char_value_[static_cast<uint8_t>(state)] << " (" + << "0x" << std::setfill('0') << std::setw(2) << static_cast<int>(state) + << ")"; + return os; +} diff --git a/system/bta/le_audio/le_audio_types.h b/system/bta/le_audio/le_audio_types.h new file mode 100644 index 0000000000000000000000000000000000000000..ec0e2e3bc2703d26abc2ced252b8a75f8df8a224 --- /dev/null +++ b/system/bta/le_audio/le_audio_types.h @@ -0,0 +1,952 @@ +/* + * Copyright 2020 HIMSA II K/S - www.himsa.com. Represented by EHIMA - + * www.ehima.com + * + * 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. + */ + +/* + * This file contains definitions for Basic Audio Profile / Audio Stream Control + * and Published Audio Capabilities definitions, structures etc. + */ + +#pragma once + +#include <stdint.h> + +#include <map> +#include <optional> +#include <string> +#include <variant> +#include <vector> + +#include "bta_groups.h" +#include "bta_le_audio_api.h" +#include "btm_iso_api_types.h" + +namespace le_audio { + +#define UINT8_TO_VEC_UINT8(u8) \ + std::vector<uint8_t> { u8 } +#define UINT16_TO_VEC_UINT8(u16) \ + std::vector<uint8_t>((uint8_t*)&u16, (uint8_t*)&u16 + sizeof(u16)) +#define UINT32_TO_VEC_UINT8(u32) \ + std::vector<uint8_t>((uint8_t*)&u32, (uint8_t*)&u32 + sizeof(u32)) + +#define VEC_UINT8_TO_UINT8(vec) vec.data()[0] +#define VEC_UINT8_TO_UINT16(vec) ((vec.data()[1] << 8) + vec.data()[0]) +#define OFF_VEC_UINT8_TO_UINT16(vec, off) \ + ((vec.data()[1 + off] << 8) + vec.data()[0 + off]) +#define VEC_UINT8_TO_UINT32(vec) \ + ((vec.data()[3] << 24) + (vec.data()[2] << 16) + (vec.data()[1] << 8) + \ + vec.data()[0]) + +namespace uuid { +/* CAP service + * This service is used to identify peer role (which we are not using for now) + * and to wrap CSIS service as this is required to understand the context of the + * CSIS + * Place holder + */ +static const bluetooth::Uuid kCapServiceUuid = + bluetooth::Uuid::From16Bit(0xEEEE); + +/* Assigned numbers for attributes */ +static const bluetooth::Uuid kPublishedAudioCapabilityServiceUuid = + bluetooth::Uuid::From16Bit(0x1850); +static const bluetooth::Uuid kAudioStreamControlServiceUuid = + bluetooth::Uuid::From16Bit(0x184E); + +/* Published Audio Capabilities Service Characteristics */ +static const bluetooth::Uuid kSinkPublishedAudioCapabilityCharacteristicUuid = + bluetooth::Uuid::From16Bit(0x2BC9); +static const bluetooth::Uuid kSourcePublishedAudioCapabilityCharacteristicUuid = + bluetooth::Uuid::From16Bit(0x2BCB); +static const bluetooth::Uuid kSinkAudioLocationCharacteristicUuid = + bluetooth::Uuid::From16Bit(0x2BCA); +static const bluetooth::Uuid kSourceAudioLocationCharacteristicUuid = + bluetooth::Uuid::From16Bit(0x2BCC); + +/* Audio Stream Control Service Characteristics */ +static const bluetooth::Uuid kAudioContextAvailabilityCharacteristicUuid = + bluetooth::Uuid::From16Bit(0x2BCD); +static const bluetooth::Uuid kAudioSupportedContextCharacteristicUuid = + bluetooth::Uuid::From16Bit(0x2BCE); + +/* Audio Stream Control Service Characteristics */ +static const bluetooth::Uuid kSinkAudioStreamEndpointUuid = + bluetooth::Uuid::From16Bit(0x2BC4); +static const bluetooth::Uuid kSourceAudioStreamEndpointUuid = + bluetooth::Uuid::From16Bit(0x2BC5); +static const bluetooth::Uuid + kAudioStreamEndpointControlPointCharacteristicUuid = + bluetooth::Uuid::From16Bit(0x2BC6); +} // namespace uuid + +namespace codec_spec_conf { +/* LTV Types */ +constexpr uint8_t kLeAudioCodecLC3TypeSamplingFreq = 0x01; +constexpr uint8_t kLeAudioCodecLC3TypeFrameDuration = 0x02; +constexpr uint8_t kLeAudioCodecLC3TypeAudioChannelAllocation = 0x03; +constexpr uint8_t kLeAudioCodecLC3TypeOctetPerFrame = 0x04; +constexpr uint8_t kLeAudioCodecLC3TypeCodecFrameBlocksPerSdu = 0x05; + +/* Sampling Frequencies */ +constexpr uint8_t kLeAudioSamplingFreq8000Hz = 0x01; +constexpr uint8_t kLeAudioSamplingFreq11025Hz = 0x02; +constexpr uint8_t kLeAudioSamplingFreq16000Hz = 0x03; +constexpr uint8_t kLeAudioSamplingFreq22050Hz = 0x04; +constexpr uint8_t kLeAudioSamplingFreq24000Hz = 0x05; +constexpr uint8_t kLeAudioSamplingFreq32000Hz = 0x06; +constexpr uint8_t kLeAudioSamplingFreq44100Hz = 0x07; +constexpr uint8_t kLeAudioSamplingFreq48000Hz = 0x08; +constexpr uint8_t kLeAudioSamplingFreq88200Hz = 0x09; +constexpr uint8_t kLeAudioSamplingFreq96000Hz = 0x0A; +constexpr uint8_t kLeAudioSamplingFreq176400Hz = 0x0B; +constexpr uint8_t kLeAudioSamplingFreq192000Hz = 0x0C; +constexpr uint8_t kLeAudioSamplingFreq384000Hz = 0x0D; + +/* Frame Durations */ +constexpr uint8_t kLeAudioCodecLC3FrameDur7500us = 0x00; +constexpr uint8_t kLeAudioCodecLC3FrameDur10000us = 0x01; + +/* Audio Allocations */ +constexpr uint32_t kLeAudioLocationMonoUnspecified = 0x00000000; +constexpr uint32_t kLeAudioLocationFrontLeft = 0x00000001; +constexpr uint32_t kLeAudioLocationFrontRight = 0x00000002; +constexpr uint32_t kLeAudioLocationFrontCenter = 0x00000004; +constexpr uint32_t kLeAudioLocationLowFreqEffects1 = 0x00000008; +constexpr uint32_t kLeAudioLocationBackLeft = 0x00000010; +constexpr uint32_t kLeAudioLocationBackRight = 0x00000020; +constexpr uint32_t kLeAudioLocationFrontLeftOfCenter = 0x00000040; +constexpr uint32_t kLeAudioLocationFrontRightOfCenter = 0x00000080; +constexpr uint32_t kLeAudioLocationBackCenter = 0x00000100; +constexpr uint32_t kLeAudioLocationLowFreqEffects2 = 0x00000200; +constexpr uint32_t kLeAudioLocationSideLeft = 0x00000400; +constexpr uint32_t kLeAudioLocationSideRight = 0x00000800; +constexpr uint32_t kLeAudioLocationTopFrontLeft = 0x00001000; +constexpr uint32_t kLeAudioLocationTopFrontRight = 0x00002000; +constexpr uint32_t kLeAudioLocationTopFrontCenter = 0x00004000; +constexpr uint32_t kLeAudioLocationTopCenter = 0x00008000; +constexpr uint32_t kLeAudioLocationTopBackLeft = 0x00010000; +constexpr uint32_t kLeAudioLocationTopBackRight = 0x00020000; +constexpr uint32_t kLeAudioLocationTopSideLeft = 0x00040000; +constexpr uint32_t kLeAudioLocationTopSideRight = 0x00080000; +constexpr uint32_t kLeAudioLocationTopSideCenter = 0x00100000; +constexpr uint32_t kLeAudioLocationBottomFrontCenter = 0x00200000; +constexpr uint32_t kLeAudioLocationBottomFrontLeft = 0x00400000; +constexpr uint32_t kLeAudioLocationBottomFrontRight = 0x00800000; +constexpr uint32_t kLeAudioLocationFrontLeftWide = 0x01000000; +constexpr uint32_t kLeAudioLocationFrontRightWide = 0x02000000; +constexpr uint32_t kLeAudioLocationLeftSurround = 0x04000000; +constexpr uint32_t kLeAudioLocationRightSurround = 0x08000000; + +constexpr uint32_t kLeAudioLocationAnyLeft = + kLeAudioLocationFrontLeft | kLeAudioLocationBackLeft | + kLeAudioLocationFrontLeftOfCenter | kLeAudioLocationSideLeft | + kLeAudioLocationTopFrontLeft | kLeAudioLocationTopBackLeft | + kLeAudioLocationTopSideLeft | kLeAudioLocationBottomFrontLeft | + kLeAudioLocationFrontLeftWide | kLeAudioLocationLeftSurround; + +constexpr uint32_t kLeAudioLocationAnyRight = + kLeAudioLocationFrontRight | kLeAudioLocationBackRight | + kLeAudioLocationFrontRightOfCenter | kLeAudioLocationSideRight | + kLeAudioLocationTopFrontRight | kLeAudioLocationTopBackRight | + kLeAudioLocationTopSideRight | kLeAudioLocationBottomFrontRight | + kLeAudioLocationFrontRightWide | kLeAudioLocationRightSurround; + +constexpr uint32_t kLeAudioLocationStereo = + kLeAudioLocationFrontLeft | kLeAudioLocationFrontRight; + +/* Octets Per Frame */ +constexpr uint16_t kLeAudioCodecLC3FrameLen30 = 30; +constexpr uint16_t kLeAudioCodecLC3FrameLen40 = 40; +constexpr uint16_t kLeAudioCodecLC3FrameLen120 = 120; + +} // namespace codec_spec_conf + +constexpr uint8_t kInvalidCisId = 0xFF; + +namespace codec_spec_caps { +uint16_t constexpr SamplingFreqConfig2Capability(uint8_t conf) { + return (1 << (conf - 1)); +} + +uint8_t constexpr FrameDurationConfig2Capability(uint8_t conf) { + return (0x01 << (conf)); +} + +inline uint8_t GetAudioChannelCounts(std::bitset<32> allocation) { + /* + * BAP d09r07 B4.2.3 Audio_Channel_Allocation + * "(...) Audio_Channel_Allocation bitmap value of all zeros or the + * absence of the Audio_Channel_Allocation LTV structure within a + * Codec_Specific_Configuration field shall be interpreted as defining a + * single audio channel of Mono audio (a single channel of no specified + * Audio Location). + */ + uint8_t audio_channel_counts = allocation.count() ?: 1; + return (0x01 << (audio_channel_counts - 1)); +} + +/* LTV Types - same values as in Codec Specific Configurations but 0x03 is + * named differently. + */ +constexpr uint8_t kLeAudioCodecLC3TypeSamplingFreq = + codec_spec_conf::kLeAudioCodecLC3TypeSamplingFreq; +constexpr uint8_t kLeAudioCodecLC3TypeFrameDuration = + codec_spec_conf::kLeAudioCodecLC3TypeFrameDuration; +constexpr uint8_t kLeAudioCodecLC3TypeAudioChannelCounts = + codec_spec_conf::kLeAudioCodecLC3TypeAudioChannelAllocation; +constexpr uint8_t kLeAudioCodecLC3TypeOctetPerFrame = + codec_spec_conf::kLeAudioCodecLC3TypeOctetPerFrame; +constexpr uint8_t kLeAudioCodecLC3TypeMaxCodecFramesPerSdu = + codec_spec_conf::kLeAudioCodecLC3TypeCodecFrameBlocksPerSdu; + +/* Sampling Frequencies */ +constexpr uint16_t kLeAudioSamplingFreq8000Hz = + SamplingFreqConfig2Capability(codec_spec_conf::kLeAudioSamplingFreq8000Hz); +constexpr uint16_t kLeAudioSamplingFreq16000Hz = + SamplingFreqConfig2Capability(codec_spec_conf::kLeAudioSamplingFreq16000Hz); +constexpr uint16_t kLeAudioSamplingFreq24000Hz = + SamplingFreqConfig2Capability(codec_spec_conf::kLeAudioSamplingFreq24000Hz); +constexpr uint16_t kLeAudioSamplingFreq32000Hz = + SamplingFreqConfig2Capability(codec_spec_conf::kLeAudioSamplingFreq32000Hz); +constexpr uint16_t kLeAudioSamplingFreq44100Hz = + SamplingFreqConfig2Capability(codec_spec_conf::kLeAudioSamplingFreq44100Hz); +constexpr uint16_t kLeAudioSamplingFreq48000Hz = + SamplingFreqConfig2Capability(codec_spec_conf::kLeAudioSamplingFreq48000Hz); + +/* Frame Durations */ +constexpr uint8_t kLeAudioCodecLC3FrameDur7500us = + FrameDurationConfig2Capability( + codec_spec_conf::kLeAudioCodecLC3FrameDur7500us); +constexpr uint8_t kLeAudioCodecLC3FrameDur10000us = + FrameDurationConfig2Capability( + codec_spec_conf::kLeAudioCodecLC3FrameDur10000us); +constexpr uint8_t kLeAudioCodecLC3FrameDurPrefer7500us = 0x10; +constexpr uint8_t kLeAudioCodecLC3FrameDurPrefer10000us = 0x20; + +/* Audio Channel Counts */ +/* Each bit represents support for additional channel: bit 0 - one channel, + * bit 1 - two, bit 3 - four channels. Multiple bits can be enabled at once. + */ +constexpr uint8_t kLeAudioCodecLC3ChannelCountNone = 0x00; +constexpr uint8_t kLeAudioCodecLC3ChannelCountSingleChannel = 0x01; +constexpr uint8_t kLeAudioCodecLC3ChannelCountTwoChannel = 0x02; + +/* Octets Per Frame - same as in Codec Specific Configurations but in + * capabilities we get two values: min and max. + */ +constexpr uint16_t kLeAudioCodecLC3FrameLen30 = + codec_spec_conf::kLeAudioCodecLC3FrameLen30; +constexpr uint16_t kLeAudioCodecLC3FrameLen40 = + codec_spec_conf::kLeAudioCodecLC3FrameLen40; +constexpr uint16_t kLeAudioCodecLC3FrameLen120 = + codec_spec_conf::kLeAudioCodecLC3FrameLen120; + +}; // namespace codec_spec_caps + +namespace types { +constexpr uint8_t kLeAudioCodingFormatLC3 = bluetooth::hci::kIsoCodingFormatLc3; +constexpr uint8_t kLeAudioCodingFormatVendorSpecific = + bluetooth::hci::kIsoCodingFormatVendorSpecific; +constexpr uint16_t kLeAudioVendorCompanyIdUndefined = 0x00; +constexpr uint16_t kLeAudioVendorCodecIdUndefined = 0x00; + +/* Metadata types from Assigned Numbers */ +constexpr uint8_t kLeAudioMetadataTypePreferredAudioContext = 0x01; +constexpr uint8_t kLeAudioMetadataTypeStreamingAudioContext = 0x02; + +/* CSIS Types */ +constexpr uint8_t kDefaultScanDurationS = 5; +constexpr uint8_t kDefaultCsisSetSize = 2; + +constexpr uint8_t kLeAudioDirectionSink = 0x01; +constexpr uint8_t kLeAudioDirectionSource = 0x02; + +/* Audio stream config types */ +constexpr uint8_t kFramingUnframedPduSupported = 0x00; +constexpr uint8_t kFramingUnframedPduUnsupported = 0x01; + +constexpr uint8_t kTargetLatencyLower = 0x01; +constexpr uint8_t kTargetLatencyBalancedLatencyReliability = 0x02; +constexpr uint8_t kTargetLatencyHigherReliability = 0x03; + +constexpr uint8_t kTargetPhy1M = 0x01; +constexpr uint8_t kTargetPhy2M = 0x02; +constexpr uint8_t kTargetPhyCoded = 0x03; + +constexpr uint32_t kPresDelayNoPreference = 0x00000000; + +constexpr uint16_t kMaxTransportLatencyMin = 0x0005; +constexpr uint16_t kMaxTransportLatencyMax = 0x0FA0; + +/* Enums */ +enum class CsisLockState : uint8_t { + CSIS_STATE_UNSET = 0x00, + CSIS_STATE_UNLOCKED, + CSIS_STATE_LOCKED +}; + +enum class CsisDiscoveryState : uint8_t { + CSIS_DISCOVERY_IDLE, + CSIS_DISCOVERY_ONGOING, + CSIS_DISCOVERY_COMPLETED, +}; + +/* ASE states according to BAP defined state machine states */ +enum class AseState : uint8_t { + BTA_LE_AUDIO_ASE_STATE_IDLE = 0x00, + BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED = 0x01, + BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED = 0x02, + BTA_LE_AUDIO_ASE_STATE_ENABLING = 0x03, + BTA_LE_AUDIO_ASE_STATE_STREAMING = 0x04, + BTA_LE_AUDIO_ASE_STATE_DISABLING = 0x05, + BTA_LE_AUDIO_ASE_STATE_RELEASING = 0x06, +}; + +enum class AudioStreamDataPathState { + IDLE, + CIS_DISCONNECTING, + CIS_ASSIGNED, + CIS_PENDING, + CIS_ESTABLISHED, + DATA_PATH_ESTABLISHED, +}; + +/* Context Types */ +enum class LeAudioContextType : uint16_t { + UNSPECIFIED = 0x0001, + CONVERSATIONAL = 0x0002, + MEDIA = 0x0004, + GAME = 0x0008, + INSTRUCTIONAL = 0x0010, + VOICEASSISTANTS = 0x0020, + LIVE = 0x0040, + SOUNDEFFECTS = 0x0080, + NOTIFICATIONS = 0x0100, + RINGTONE = 0x0200, + ALERTS = 0x0400, + EMERGENCYALARM = 0x0800, + RFU = 0x1000, +}; + +/* Configuration strategy */ +enum class LeAudioConfigurationStrategy : uint8_t { + MONO_ONE_CIS_PER_DEVICE = 0x00, /* Common true wireless speakers */ + STEREO_TWO_CISES_PER_DEVICE = + 0x01, /* Requires 2 ASEs and 2 Audio Allocation for left/right */ + STEREO_ONE_CIS_PER_DEVICE = 0x02, /* Requires channel count 2*/ + RFU = 0x03, +}; + +constexpr LeAudioContextType operator|(LeAudioContextType lhs, + LeAudioContextType rhs) { + return static_cast<LeAudioContextType>( + static_cast<std::underlying_type<LeAudioContextType>::type>(lhs) | + static_cast<std::underlying_type<LeAudioContextType>::type>(rhs)); +} + +constexpr LeAudioContextType kLeAudioContextAllTypesArray[] = { + LeAudioContextType::UNSPECIFIED, LeAudioContextType::CONVERSATIONAL, + LeAudioContextType::MEDIA, LeAudioContextType::GAME, + LeAudioContextType::INSTRUCTIONAL, LeAudioContextType::VOICEASSISTANTS, + LeAudioContextType::LIVE, LeAudioContextType::SOUNDEFFECTS, + LeAudioContextType::NOTIFICATIONS, LeAudioContextType::RINGTONE, + LeAudioContextType::ALERTS, LeAudioContextType::EMERGENCYALARM, +}; + +constexpr LeAudioContextType kLeAudioContextAllTypes = + LeAudioContextType::UNSPECIFIED | LeAudioContextType::CONVERSATIONAL | + LeAudioContextType::MEDIA | LeAudioContextType::GAME | + LeAudioContextType::INSTRUCTIONAL | LeAudioContextType::VOICEASSISTANTS | + LeAudioContextType::LIVE | LeAudioContextType::SOUNDEFFECTS | + LeAudioContextType::NOTIFICATIONS | LeAudioContextType::RINGTONE | + LeAudioContextType::ALERTS | LeAudioContextType::EMERGENCYALARM; + +/* Structures */ +class LeAudioLtvMap { + public: + LeAudioLtvMap() {} + LeAudioLtvMap(std::map<uint8_t, std::vector<uint8_t>> values) + : values(std::move(values)) {} + + std::optional<std::vector<uint8_t>> Find(uint8_t type) const; + bool IsEmpty() const { return values.empty(); } + void Clear() { values.clear(); } + size_t Size() const { return values.size(); } + const std::map<uint8_t, std::vector<uint8_t>>& Values() const { + return values; + } + std::string ToString() const; + size_t RawPacketSize() const; + uint8_t* RawPacket(uint8_t* p_buf) const; + static LeAudioLtvMap Parse(const uint8_t* value, uint8_t len, bool& success); + + private: + std::map<uint8_t, std::vector<uint8_t>> values; +}; + +struct LeAudioLc3Config { + static const std::map<uint8_t, uint32_t> sampling_freq_map; + static const std::map<uint8_t, uint32_t> frame_duration_map; + + uint8_t sampling_frequency; + uint8_t frame_duration; + uint16_t octets_per_codec_frame; + uint32_t audio_channel_allocation; + uint8_t channel_count; + + /** Returns the sampling frequency representation in Hz */ + uint32_t GetSamplingFrequencyHz() const { + return sampling_freq_map.count(sampling_frequency) + ? sampling_freq_map.at(sampling_frequency) + : 0; + } + + /** Returns the frame duration representation in us */ + uint32_t GetFrameDurationUs() const { + return frame_duration_map.count(frame_duration) + ? frame_duration_map.at(frame_duration) + : 0; + } + + uint8_t GetChannelCount(void) const { return channel_count; } + + LeAudioLtvMap GetAsLtvMap() const { + return LeAudioLtvMap({ + {codec_spec_conf::kLeAudioCodecLC3TypeSamplingFreq, + UINT8_TO_VEC_UINT8(sampling_frequency)}, + {codec_spec_conf::kLeAudioCodecLC3TypeFrameDuration, + UINT8_TO_VEC_UINT8(frame_duration)}, + {codec_spec_conf::kLeAudioCodecLC3TypeAudioChannelAllocation, + UINT32_TO_VEC_UINT8(audio_channel_allocation)}, + {codec_spec_conf::kLeAudioCodecLC3TypeOctetPerFrame, + UINT16_TO_VEC_UINT8(octets_per_codec_frame)}, + }); + } +}; + +struct LeAudioCodecId { + uint8_t coding_format; + uint16_t vendor_company_id; + uint16_t vendor_codec_id; + + friend bool operator==(const LeAudioCodecId& lhs, const LeAudioCodecId& rhs) { + if (lhs.coding_format != rhs.coding_format) return false; + + if (lhs.coding_format == kLeAudioCodingFormatVendorSpecific && + (lhs.vendor_company_id != rhs.vendor_company_id || + lhs.vendor_codec_id != rhs.vendor_codec_id)) + return false; + + return true; + } + + friend bool operator!=(const LeAudioCodecId& lhs, const LeAudioCodecId& rhs) { + return !(lhs == rhs); + } +}; + +struct hdl_pair { + hdl_pair() = default; + hdl_pair(uint16_t val_hdl, uint16_t ccc_hdl) + : val_hdl(val_hdl), ccc_hdl(ccc_hdl) {} + + uint16_t val_hdl = 0; + uint16_t ccc_hdl = 0; +}; + +struct ase { + static constexpr uint8_t kAseIdInvalid = 0x00; + + ase(uint16_t val_hdl, uint16_t ccc_hdl, uint8_t direction) + : hdls(val_hdl, ccc_hdl), + id(kAseIdInvalid), + cis_id(kInvalidCisId), + direction(direction), + active(false), + reconfigure(false), + data_path_state(AudioStreamDataPathState::IDLE), + preferred_phy(0), + state(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) {} + + struct hdl_pair hdls; + uint8_t id; + uint8_t cis_id; + const uint8_t direction; + uint16_t cis_conn_hdl = 0; + + bool active; + bool reconfigure; + AudioStreamDataPathState data_path_state; + + /* Codec configuration */ + LeAudioCodecId codec_id; + LeAudioLc3Config codec_config; + uint8_t framing; + uint8_t preferred_phy; + + /* Qos configuration */ + uint16_t max_sdu_size; + uint8_t retrans_nb; + uint16_t max_transport_latency; + uint32_t pres_delay_min; + uint32_t pres_delay_max; + uint32_t preferred_pres_delay_min; + uint32_t preferred_pres_delay_max; + + std::vector<uint8_t> metadata; + + AseState state; +}; + +struct BidirectAsesPair { + struct ase* sink; + struct ase* source; +}; + +struct acs_ac_record { + LeAudioCodecId codec_id; + LeAudioLtvMap codec_spec_caps; + std::vector<uint8_t> metadata; +}; + +using PublishedAudioCapabilities = + std::vector<std::tuple<hdl_pair, std::vector<acs_ac_record>>>; +using AudioLocations = std::bitset<32>; +using AudioContexts = std::bitset<16>; + +} // namespace types + +namespace set_configurations { + +struct CodecCapabilitySetting { + types::LeAudioCodecId id; + + /* Codec Specific Configuration variant */ + std::variant<types::LeAudioLc3Config> config; + + /* Sampling freqency requested for codec */ + uint32_t GetConfigSamplingFrequency() const; + /* Data fetch/feed interval for codec in microseconds */ + uint32_t GetConfigDataIntervalUs() const; + /* Audio bit depth required for codec */ + uint8_t GetConfigBitsPerSample() const; + /* Audio channels number for stream */ + uint8_t GetConfigChannelCount() const; +}; + +struct SetConfiguration { + SetConfiguration(uint8_t direction, uint8_t device_cnt, uint8_t ase_cnt, + CodecCapabilitySetting codec, + le_audio::types::LeAudioConfigurationStrategy strategy = + le_audio::types::LeAudioConfigurationStrategy:: + MONO_ONE_CIS_PER_DEVICE) + : direction(direction), + device_cnt(device_cnt), + ase_cnt(ase_cnt), + codec(codec), + strategy(strategy) {} + + uint8_t direction; /* Direction of set */ + uint8_t device_cnt; /* How many devices must be in set */ + uint8_t ase_cnt; /* How many ASE we need in configuration */ + CodecCapabilitySetting codec; + types::LeAudioConfigurationStrategy strategy; +}; + +/* Defined audio scenarios */ +struct AudioSetConfiguration { + std::string name; + std::vector<struct SetConfiguration> confs; +}; + +using AudioSetConfigurations = std::vector<const AudioSetConfiguration*>; + +const types::LeAudioCodecId LeAudioCodecIdLc3 = { + .coding_format = types::kLeAudioCodingFormatLC3, + .vendor_company_id = types::kLeAudioVendorCompanyIdUndefined, + .vendor_codec_id = types::kLeAudioVendorCodecIdUndefined}; + +static constexpr uint32_t kChannelAllocationMono = + codec_spec_conf::kLeAudioLocationMonoUnspecified; +static constexpr uint32_t kChannelAllocationStereo = + codec_spec_conf::kLeAudioLocationFrontLeft | + codec_spec_conf::kLeAudioLocationFrontRight; + +/** + * Supported audio codec capability settings + * + * The subset of capabilities defined in BAP_Validation_r13 Table 3.6. + */ +constexpr CodecCapabilitySetting codec_lc3_16_1(uint8_t channel_count) { + return CodecCapabilitySetting{ + .id = LeAudioCodecIdLc3, + .config = types::LeAudioLc3Config({ + .sampling_frequency = codec_spec_conf::kLeAudioSamplingFreq16000Hz, + .frame_duration = codec_spec_conf::kLeAudioCodecLC3FrameDur7500us, + .octets_per_codec_frame = codec_spec_conf::kLeAudioCodecLC3FrameLen30, + .channel_count = channel_count, + .audio_channel_allocation = 0, + })}; +} + +constexpr CodecCapabilitySetting codec_lc3_16_2(uint8_t channel_count) { + return CodecCapabilitySetting{ + .id = LeAudioCodecIdLc3, + .config = types::LeAudioLc3Config({ + .sampling_frequency = codec_spec_conf::kLeAudioSamplingFreq16000Hz, + .frame_duration = codec_spec_conf::kLeAudioCodecLC3FrameDur10000us, + .octets_per_codec_frame = codec_spec_conf::kLeAudioCodecLC3FrameLen40, + .channel_count = channel_count, + .audio_channel_allocation = 0, + })}; +} + +constexpr CodecCapabilitySetting codec_lc3_48_4(uint8_t channel_count) { + return CodecCapabilitySetting{ + .id = LeAudioCodecIdLc3, + .config = types::LeAudioLc3Config({ + .sampling_frequency = codec_spec_conf::kLeAudioSamplingFreq48000Hz, + .frame_duration = codec_spec_conf::kLeAudioCodecLC3FrameDur10000us, + .octets_per_codec_frame = + codec_spec_conf::kLeAudioCodecLC3FrameLen120, + .channel_count = channel_count, + .audio_channel_allocation = 0, + })}; +} + +/* + * AudioSetConfiguration defines the audio set configuration and codec settings + * to to be used by le audio policy to match the required configuration with + * audio server capabilities. The codec settings are defined with respect to + * "Broadcast Source audio capability configuration support requirements" + * defined in BAP d09r06 + */ +const AudioSetConfiguration kSingleDev_OneChanMonoSnk_16_2 = { + .name = "kSingleDev_OneChanMonoSnk_16_2", + .confs = {SetConfiguration( + types::kLeAudioDirectionSink, 1, 1, + codec_lc3_16_2( + codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}}; + +const AudioSetConfiguration kSingleDev_OneChanMonoSnk_16_1 = { + .name = "kSingleDev_OneChanMonoSnk_16_1", + .confs = {SetConfiguration( + types::kLeAudioDirectionSink, 1, 1, + codec_lc3_16_1( + codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}}; + +const AudioSetConfiguration kSingleDev_TwoChanStereoSnk_16_1 = { + .name = "kSingleDev_TwoChanStereoSnk_16_1", + .confs = {SetConfiguration( + types::kLeAudioDirectionSink, 1, 1, + codec_lc3_16_1(codec_spec_caps::kLeAudioCodecLC3ChannelCountTwoChannel), + le_audio::types::LeAudioConfigurationStrategy:: + STEREO_ONE_CIS_PER_DEVICE)}}; + +const AudioSetConfiguration kSingleDev_OneChanStereoSnk_16_1 = { + .name = "kSingleDev_OneChanStereoSnk_16_1", + .confs = {SetConfiguration( + types::kLeAudioDirectionSink, 1, 2, + codec_lc3_16_1( + codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel), + le_audio::types::LeAudioConfigurationStrategy:: + STEREO_TWO_CISES_PER_DEVICE)}}; + +const AudioSetConfiguration kDualDev_OneChanStereoSnk_16_1 = { + .name = "kDualDev_OneChanStereoSnk_16_1", + .confs = {SetConfiguration( + types::kLeAudioDirectionSink, 2, 2, + codec_lc3_16_1( + codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}}; + +const AudioSetConfiguration kSingleDev_TwoChanStereoSnk_48_4 = { + .name = "kSingleDev_TwoChanStereoSnk_48_4", + .confs = {SetConfiguration( + types::kLeAudioDirectionSink, 1, 1, + codec_lc3_48_4(codec_spec_caps::kLeAudioCodecLC3ChannelCountTwoChannel), + le_audio::types::LeAudioConfigurationStrategy:: + STEREO_ONE_CIS_PER_DEVICE)}}; + +const AudioSetConfiguration kDualDev_OneChanStereoSnk_48_4 = { + .name = "kDualDev_OneChanStereoSnk_48_4", + .confs = {SetConfiguration( + types::kLeAudioDirectionSink, 2, 2, + codec_lc3_48_4( + codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}}; + +const AudioSetConfiguration kSingleDev_OneChanStereoSnk_48_4 = { + .name = "kSingleDev_OneChanStereoSnk_48_4", + .confs = {SetConfiguration( + types::kLeAudioDirectionSink, 1, 2, + codec_lc3_48_4( + codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel), + le_audio::types::LeAudioConfigurationStrategy:: + STEREO_TWO_CISES_PER_DEVICE)}}; + +const AudioSetConfiguration kSingleDev_OneChanMonoSnk_48_4 = { + .name = "kSingleDev_OneChanMonoSnk_48_4", + .confs = {SetConfiguration( + types::kLeAudioDirectionSink, 1, 1, + codec_lc3_48_4( + codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}}; + +const AudioSetConfiguration kSingleDev_TwoChanStereoSnk_16_2 = { + .name = "kSingleDev_TwoChanStereoSnk_16_2", + .confs = {SetConfiguration( + types::kLeAudioDirectionSink, 1, 1, + codec_lc3_16_2(codec_spec_caps::kLeAudioCodecLC3ChannelCountTwoChannel), + le_audio::types::LeAudioConfigurationStrategy:: + STEREO_ONE_CIS_PER_DEVICE)}}; + +const AudioSetConfiguration kSingleDev_OneChanStereoSnk_16_2 = { + .name = "kSingleDev_OneChanStereoSnk_16_2", + .confs = {SetConfiguration( + types::kLeAudioDirectionSink, 1, 2, + codec_lc3_16_2( + codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel), + le_audio::types::LeAudioConfigurationStrategy:: + STEREO_TWO_CISES_PER_DEVICE)}}; + +const AudioSetConfiguration kDualDev_OneChanStereoSnk_16_2 = { + .name = "kDualDev_OneChanStereoSnk_16_2", + .confs = {SetConfiguration( + types::kLeAudioDirectionSink, 2, 2, + codec_lc3_16_2( + codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}}; + +const AudioSetConfiguration kSingleDev_OneChanMonoSnk_OneChanMonoSrc_16_1 = { + .name = "kSingleDev_OneChanMonoSnk_OneChanMonoSrc_16_1", + .confs = { + SetConfiguration( + types::kLeAudioDirectionSink, 1, 1, + codec_lc3_16_1( + codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel)), + SetConfiguration( + types::kLeAudioDirectionSource, 1, 1, + codec_lc3_16_1( + codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}}; + +const AudioSetConfiguration kSingleDev_OneChanMonoSnk_OneChanMonoSrc_16_2 = { + .name = "kSingleDev_OneChanMonoSnk_OneChanMonoSrc_16_2", + .confs = { + SetConfiguration( + types::kLeAudioDirectionSink, 1, 1, + codec_lc3_16_2( + codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel)), + SetConfiguration( + types::kLeAudioDirectionSource, 1, 1, + codec_lc3_16_2( + codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}}; + +const AudioSetConfiguration kSingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_2 = { + .name = "kSingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_2", + .confs = { + SetConfiguration( + types::kLeAudioDirectionSink, 1, 1, + codec_lc3_16_2( + codec_spec_caps::kLeAudioCodecLC3ChannelCountTwoChannel), + le_audio::types::LeAudioConfigurationStrategy:: + STEREO_ONE_CIS_PER_DEVICE), + SetConfiguration( + types::kLeAudioDirectionSource, 1, 1, + codec_lc3_16_2( + codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}}; + +const AudioSetConfiguration + kDualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_2 = { + .name = "kDualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_2", + .confs = { + SetConfiguration( + types::kLeAudioDirectionSink, 2, 4, + codec_lc3_16_2( + codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel), + le_audio::types::LeAudioConfigurationStrategy:: + STEREO_TWO_CISES_PER_DEVICE), + SetConfiguration( + types::kLeAudioDirectionSource, 1, 1, + codec_lc3_16_2( + codec_spec_caps:: + kLeAudioCodecLC3ChannelCountSingleChannel))}}; + +const AudioSetConfiguration kSingleDev_OneChanStereoSnk_OneChanMonoSrc_16_2 = { + .name = "kSingleDev_OneChanStereoSnk_OneChanMonoSrc_16_2", + .confs = { + SetConfiguration( + types::kLeAudioDirectionSink, 1, 2, + codec_lc3_16_2( + codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel), + le_audio::types::LeAudioConfigurationStrategy:: + STEREO_TWO_CISES_PER_DEVICE), + SetConfiguration( + types::kLeAudioDirectionSource, 1, 1, + codec_lc3_16_2( + codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}}; + +const AudioSetConfiguration kDualDev_OneChanStereoSnk_OneChanMonoSrc_16_2 = { + .name = "kDualDev_OneChanStereoSnk_OneChanMonoSrc_16_2", + .confs = { + SetConfiguration( + types::kLeAudioDirectionSink, 2, 2, + codec_lc3_16_2( + codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel)), + SetConfiguration( + types::kLeAudioDirectionSource, 1, 1, + codec_lc3_16_2( + codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}}; + +const AudioSetConfiguration kSingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_1 = { + .name = "kSingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_1", + .confs = { + SetConfiguration( + types::kLeAudioDirectionSink, 1, 1, + codec_lc3_16_1( + codec_spec_caps::kLeAudioCodecLC3ChannelCountTwoChannel), + le_audio::types::LeAudioConfigurationStrategy:: + STEREO_ONE_CIS_PER_DEVICE), + SetConfiguration( + types::kLeAudioDirectionSource, 1, 1, + codec_lc3_16_1( + codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}}; + +const AudioSetConfiguration kSingleDev_OneChanStereoSnk_OneChanMonoSrc_16_1 = { + .name = "kSingleDev_OneChanStereoSnk_OneChanMonoSrc_16_1", + .confs = { + SetConfiguration( + types::kLeAudioDirectionSink, 1, 2, + codec_lc3_16_1( + codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel), + le_audio::types::LeAudioConfigurationStrategy:: + STEREO_TWO_CISES_PER_DEVICE), + SetConfiguration( + types::kLeAudioDirectionSource, 1, 1, + codec_lc3_16_1( + codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}}; + +const AudioSetConfiguration kDualDev_OneChanStereoSnk_OneChanMonoSrc_16_1 = { + .name = "kDualDev_OneChanStereoSnk_OneChanMonoSrc_16_1", + .confs = { + SetConfiguration( + types::kLeAudioDirectionSink, 2, 2, + codec_lc3_16_1( + codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel)), + SetConfiguration( + types::kLeAudioDirectionSource, 1, 1, + codec_lc3_16_1( + codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel))}}; + +const AudioSetConfiguration + kDualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_1 = { + .name = "kDualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_1", + .confs = { + SetConfiguration( + types::kLeAudioDirectionSink, 2, 4, + codec_lc3_16_1( + codec_spec_caps::kLeAudioCodecLC3ChannelCountSingleChannel), + le_audio::types::LeAudioConfigurationStrategy:: + STEREO_TWO_CISES_PER_DEVICE), + SetConfiguration( + types::kLeAudioDirectionSource, 1, 1, + codec_lc3_16_1( + codec_spec_caps:: + kLeAudioCodecLC3ChannelCountSingleChannel))}}; + +/* Defined audio scenario linked with context type, priority sorted */ +const AudioSetConfigurations audio_set_conf_ringtone = { + &kDualDev_OneChanStereoSnk_16_2, &kDualDev_OneChanStereoSnk_16_1, + &kSingleDev_OneChanStereoSnk_16_2, &kSingleDev_OneChanStereoSnk_16_1, + &kSingleDev_TwoChanStereoSnk_16_2, &kSingleDev_TwoChanStereoSnk_16_1, + &kSingleDev_OneChanMonoSnk_16_2, &kSingleDev_OneChanMonoSnk_16_1, +}; + +const AudioSetConfigurations audio_set_conf_conversational = { + &kDualDev_OneChanStereoSnk_OneChanMonoSrc_16_2, + &kDualDev_OneChanStereoSnk_OneChanMonoSrc_16_1, + &kDualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_2, + &kDualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_1, + &kSingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_2, + &kSingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_1, + &kSingleDev_OneChanStereoSnk_OneChanMonoSrc_16_2, + &kSingleDev_OneChanStereoSnk_OneChanMonoSrc_16_1, + &kSingleDev_OneChanMonoSnk_OneChanMonoSrc_16_2, + &kSingleDev_OneChanMonoSnk_OneChanMonoSrc_16_1, +}; + +const AudioSetConfigurations audio_set_conf_media = { + &kDualDev_OneChanStereoSnk_48_4, &kDualDev_OneChanStereoSnk_16_2, + &kDualDev_OneChanStereoSnk_16_1, &kSingleDev_OneChanStereoSnk_48_4, + &kSingleDev_OneChanStereoSnk_16_2, &kSingleDev_OneChanStereoSnk_16_1, + &kSingleDev_TwoChanStereoSnk_48_4, &kSingleDev_TwoChanStereoSnk_16_2, + &kSingleDev_TwoChanStereoSnk_16_1, &kSingleDev_OneChanMonoSnk_48_4, + &kSingleDev_OneChanMonoSnk_16_2, &kSingleDev_OneChanMonoSnk_16_1, +}; + +const AudioSetConfigurations audio_set_conf_default = { + &kDualDev_OneChanStereoSnk_16_2, + &kSingleDev_OneChanStereoSnk_16_2, + &kSingleDev_TwoChanStereoSnk_16_2, + &kSingleDev_OneChanMonoSnk_16_2, +}; + +/* Declarations */ +bool check_if_may_cover_scenario( + const AudioSetConfigurations* audio_set_configurations, uint8_t group_size); +bool check_if_may_cover_scenario( + const AudioSetConfiguration* audio_set_configuration, uint8_t group_size); +bool IsCodecCapabilitySettingSupported( + const types::acs_ac_record& pac_record, + const CodecCapabilitySetting& codec_capability_setting); +const AudioSetConfigurations* get_confs_by_type(types::LeAudioContextType type); +} // namespace set_configurations + +struct stream_configuration { + bool valid; + + types::LeAudioCodecId id; + + /* Pointer to chosen req */ + const le_audio::set_configurations::AudioSetConfiguration* conf; + + /* Sink configuration */ + /* For now we have always same frequency for all the channels */ + uint32_t sink_sample_frequency_hz; + uint32_t sink_frame_duration_us; + uint16_t sink_octets_per_codec_frame; + /* Number of channels is what we will request from audio framework */ + uint8_t sink_num_of_channels; + int sink_num_of_devices; + /* cis_handle, audio location*/ + std::vector<std::pair<uint16_t, uint32_t>> sink_streams; + + /* Source configuration */ + /* For now we have always same frequency for all the channels */ + uint32_t source_sample_frequency_hz; + uint32_t source_frame_duration_us; + uint16_t source_octets_per_codec_frame; + /* Number of channels is what we will request from audio framework */ + uint8_t source_num_of_channels; + int source_num_of_devices; + /* cis_handle, audio location*/ + std::vector<std::pair<uint16_t, uint32_t>> source_streams; +}; + +} // namespace le_audio + +std::ostream& operator<<(std::ostream& os, + const le_audio::types::LeAudioLc3Config& config); + +std::ostream& operator<<(std::ostream& os, + const le_audio::types::AseState& state); diff --git a/system/bta/le_audio/le_audio_types_test.cc b/system/bta/le_audio/le_audio_types_test.cc new file mode 100644 index 0000000000000000000000000000000000000000..0d6f1e475267f67923d5e22b2f2f0b64772859c8 --- /dev/null +++ b/system/bta/le_audio/le_audio_types_test.cc @@ -0,0 +1,162 @@ +/* + * Copyright 2020 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * 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 "le_audio_types.h" + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +namespace le_audio { +namespace types { + +using ::testing::AllOf; +using ::testing::Contains; +using ::testing::ElementsAre; +using ::testing::ElementsAreArray; +using ::testing::Eq; +using ::testing::Pair; +using ::testing::SizeIs; + +TEST(LeAudioLtvMapTest, test_serialization) { + // clang-format off + const std::vector<uint8_t> ltv_test_vec{ + 0x02, 0x01, 0x0a, + 0x03, 0x02, 0xaa, 0xbb, + 0x04, 0x03, 0xde, 0xc0, 0xde, + 0x05, 0x04, 0xc0, 0xde, 0xc0, 0xde, + }; + // clang-format on + + // Parse + bool success; + LeAudioLtvMap ltv_map = + LeAudioLtvMap::Parse(ltv_test_vec.data(), ltv_test_vec.size(), success); + ASSERT_TRUE(success); + ASSERT_FALSE(ltv_map.IsEmpty()); + ASSERT_EQ((size_t)4, ltv_map.Size()); + + ASSERT_TRUE(ltv_map.Find(0x01)); + ASSERT_THAT(*(ltv_map.Find(0x01)), ElementsAre(0x0a)); + ASSERT_TRUE(ltv_map.Find(0x02)); + ASSERT_THAT(*(ltv_map.Find(0x02)), ElementsAre(0xaa, 0xbb)); + ASSERT_TRUE(ltv_map.Find(0x03)); + ASSERT_THAT(*(ltv_map.Find(0x03)), ElementsAre(0xde, 0xc0, 0xde)); + ASSERT_TRUE(ltv_map.Find(0x04)); + ASSERT_THAT(*(ltv_map.Find(0x04)), ElementsAre(0xc0, 0xde, 0xc0, 0xde)); + + // RawPacket + std::vector<uint8_t> serialized(ltv_map.RawPacketSize()); + ASSERT_TRUE(ltv_map.RawPacket(serialized.data())); + ASSERT_THAT(serialized, ElementsAreArray(ltv_test_vec)); +} + +TEST(LeAudioLtvMapTest, test_serialization_ltv_len_is_zero) { + // clang-format off + const std::vector<uint8_t> ltv_test_vec{ + 0x02, 0x01, 0x0a, + 0x03, 0x02, 0xaa, 0xbb, + 0x00, 0x00, 0x00, 0x00, 0x00, // ltv_len == 0 + 0x05, 0x04, 0xc0, 0xde, 0xc0, 0xde, + }; + // clang-format on + + // Parse + bool success; + LeAudioLtvMap ltv_map = + LeAudioLtvMap::Parse(ltv_test_vec.data(), ltv_test_vec.size(), success); + ASSERT_TRUE(success); + ASSERT_FALSE(ltv_map.IsEmpty()); + ASSERT_EQ((size_t)3, ltv_map.Size()); + + ASSERT_TRUE(ltv_map.Find(0x01)); + ASSERT_THAT(*(ltv_map.Find(0x01)), ElementsAre(0x0a)); + ASSERT_TRUE(ltv_map.Find(0x02)); + ASSERT_THAT(*(ltv_map.Find(0x02)), ElementsAre(0xaa, 0xbb)); + ASSERT_TRUE(ltv_map.Find(0x04)); + ASSERT_THAT(*(ltv_map.Find(0x04)), ElementsAre(0xc0, 0xde, 0xc0, 0xde)); + + // RawPacket + std::vector<uint8_t> serialized(ltv_map.RawPacketSize()); + ASSERT_TRUE(ltv_map.RawPacket(serialized.data())); + ASSERT_THAT(serialized, ElementsAre(0x02, 0x01, 0x0a, 0x03, 0x02, 0xaa, 0xbb, + 0x05, 0x04, 0xc0, 0xde, 0xc0, 0xde)); +} + +TEST(LeAudioLtvMapTest, test_serialization_ltv_len_is_one) { + // clang-format off + const std::vector<uint8_t> ltv_test_vec{ + 0x02, 0x01, 0x0a, + 0x01, 0x02, + }; + // clang-format on + + // Parse + bool success; + LeAudioLtvMap ltv_map = + LeAudioLtvMap::Parse(ltv_test_vec.data(), ltv_test_vec.size(), success); + ASSERT_TRUE(success); + ASSERT_FALSE(ltv_map.IsEmpty()); + ASSERT_EQ((size_t)2, ltv_map.Size()); + + ASSERT_TRUE(ltv_map.Find(0x01)); + ASSERT_THAT(*(ltv_map.Find(0x01)), ElementsAre(0x0a)); + ASSERT_TRUE(ltv_map.Find(0x02)); + ASSERT_THAT(*(ltv_map.Find(0x02)), SizeIs(0)); + + // RawPacket + std::vector<uint8_t> serialized(ltv_map.RawPacketSize()); + ASSERT_TRUE(ltv_map.RawPacket(serialized.data())); + ASSERT_THAT(serialized, ElementsAreArray(ltv_test_vec)); +} + +TEST(LeAudioLtvMapTest, test_serialization_ltv_len_is_invalid) { + // clang-format off + const std::vector<uint8_t> ltv_test_vec_1{ + 0x02, 0x01, 0x0a, + 0x04, 0x02, 0xaa, 0xbb, // one byte missing + }; + const std::vector<uint8_t> ltv_test_vec_2{ + 0x02, 0x01, 0x0a, + 0x03, 0x02, 0xaa, 0xbb, + 0x01, + }; + const std::vector<uint8_t> ltv_test_vec_3{ + 0x02, 0x01, 0x0a, + 0x03, 0x02, 0xaa, 0xbb, + 0x02, 0x03, + }; + // clang-format on + + // Parse + bool success = true; + LeAudioLtvMap ltv_map; + + ltv_map = LeAudioLtvMap::Parse(ltv_test_vec_1.data(), ltv_test_vec_1.size(), + success); + ASSERT_FALSE(success); + + ltv_map = LeAudioLtvMap::Parse(ltv_test_vec_2.data(), ltv_test_vec_2.size(), + success); + ASSERT_FALSE(success); + + ltv_map = LeAudioLtvMap::Parse(ltv_test_vec_3.data(), ltv_test_vec_3.size(), + success); + ASSERT_FALSE(success); +} + +} // namespace types +} // namespace le_audio diff --git a/system/bta/le_audio/mock_iso_manager.cc b/system/bta/le_audio/mock_iso_manager.cc new file mode 100644 index 0000000000000000000000000000000000000000..89579e7108ce2689b08baa153afe2b0fd06b5a25 --- /dev/null +++ b/system/bta/le_audio/mock_iso_manager.cc @@ -0,0 +1,155 @@ +/* + * Copyright 2020 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * 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 "mock_iso_manager.h" + +MockIsoManager* mock_pimpl_; +MockIsoManager* MockIsoManager::GetInstance() { + bluetooth::hci::IsoManager::GetInstance(); + return mock_pimpl_; +} + +namespace bluetooth { +namespace hci { + +struct IsoManager::impl : public MockIsoManager { + public: + impl() = default; + ~impl() = default; +}; + +IsoManager::IsoManager() {} + +void IsoManager::RegisterCigCallbacks( + iso_manager::CigCallbacks* callbacks) const { + if (!pimpl_) return; + pimpl_->RegisterCigCallbacks(callbacks); +} + +void IsoManager::RegisterBigCallbacks( + iso_manager::BigCallbacks* callbacks) const { + if (!pimpl_) return; + pimpl_->RegisterBigCallbacks(callbacks); +} + +void IsoManager::CreateCig(uint8_t cig_id, + struct iso_manager::cig_create_params cig_params) { + if (!pimpl_) return; + pimpl_->CreateCig(cig_id, std::move(cig_params)); +} + +void IsoManager::ReconfigureCig( + uint8_t cig_id, struct iso_manager::cig_create_params cig_params) { + if (!pimpl_) return; + pimpl_->ReconfigureCig(cig_id, std::move(cig_params)); +} + +void IsoManager::RemoveCig(uint8_t cig_id) { pimpl_->RemoveCig(cig_id); } + +void IsoManager::EstablishCis( + struct iso_manager::cis_establish_params conn_params) { + if (!pimpl_) return; + pimpl_->EstablishCis(std::move(conn_params)); +} + +void IsoManager::DisconnectCis(uint16_t cis_handle, uint8_t reason) { + if (!pimpl_) return; + pimpl_->DisconnectCis(cis_handle, reason); +} + +void IsoManager::SetupIsoDataPath( + uint16_t iso_handle, struct iso_manager::iso_data_path_params path_params) { + if (!pimpl_) return; + pimpl_->SetupIsoDataPath(iso_handle, std::move(path_params)); +} + +void IsoManager::RemoveIsoDataPath(uint16_t iso_handle, uint8_t data_path_dir) { + if (!pimpl_) return; + pimpl_->RemoveIsoDataPath(iso_handle, data_path_dir); +} + +void IsoManager::ReadIsoLinkQuality(uint16_t iso_handle) { + if (!pimpl_) return; + pimpl_->ReadIsoLinkQuality(iso_handle); +} + +void IsoManager::SendIsoData(uint16_t iso_handle, const uint8_t* data, + uint16_t data_len) { + if (!pimpl_) return; + pimpl_->SendIsoData(iso_handle, data, data_len); +} + +void IsoManager::CreateBig(uint8_t big_id, + struct iso_manager::big_create_params big_params) { + if (!pimpl_) return; + pimpl_->CreateBig(big_id, std::move(big_params)); +} + +void IsoManager::TerminateBig(uint8_t big_id, uint8_t reason) { + if (!pimpl_) return; + pimpl_->TerminateBig(big_id, reason); +} + +void IsoManager::HandleIsoData(void* p_msg) { + if (!pimpl_) return; + pimpl_->HandleIsoData(static_cast<BT_HDR*>(p_msg)); +} + +void IsoManager::HandleDisconnect(uint16_t handle, uint8_t reason) { + if (!pimpl_) return; + pimpl_->HandleDisconnect(handle, reason); +} + +void IsoManager::HandleNumComplDataPkts(uint8_t* p, uint8_t evt_len) { + if (!pimpl_) return; + pimpl_->HandleNumComplDataPkts(p, evt_len); +} + +void IsoManager::HandleGdNumComplDataPkts(uint16_t handle, uint16_t credits) {} + +void IsoManager::HandleHciEvent(uint8_t sub_code, uint8_t* params, + uint16_t length) { + if (!pimpl_) return; + pimpl_->HandleHciEvent(sub_code, params, length); +} + +void IsoManager::Start() { + // It is needed here as IsoManager which is a singleton creates it, but in + // this mock we want to destroy and recreate the mock on each test case. + if (!pimpl_) { + pimpl_ = std::make_unique<impl>(); + } + + mock_pimpl_ = pimpl_.get(); + pimpl_->Start(); +} + +void IsoManager::Stop() { + // It is needed here as IsoManager which is a singleton creates it, but in + // this mock we want to destroy and recreate the mock on each test case. + if (pimpl_) { + pimpl_->Stop(); + pimpl_.reset(); + } + + mock_pimpl_ = nullptr; +} + +IsoManager::~IsoManager() = default; + +} // namespace hci +} // namespace bluetooth diff --git a/system/bta/le_audio/mock_iso_manager.h b/system/bta/le_audio/mock_iso_manager.h new file mode 100644 index 0000000000000000000000000000000000000000..e4a10abccbd33cf158e5e9bd507f21eeb4df319e --- /dev/null +++ b/system/bta/le_audio/mock_iso_manager.h @@ -0,0 +1,74 @@ +/* + * Copyright 2020 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <gmock/gmock.h> + +#include "btm_iso_api.h" + +struct MockIsoManager { + public: + static MockIsoManager* GetInstance(); + + MockIsoManager() = default; + virtual ~MockIsoManager() = default; + + MOCK_METHOD((void), RegisterCigCallbacks, + (bluetooth::hci::iso_manager::CigCallbacks * callbacks), (const)); + MOCK_METHOD((void), RegisterBigCallbacks, + (bluetooth::hci::iso_manager::BigCallbacks * callbacks), (const)); + MOCK_METHOD( + (void), CreateCig, + (uint8_t cig_id, + struct bluetooth::hci::iso_manager::cig_create_params cig_params)); + MOCK_METHOD( + (void), ReconfigureCig, + (uint8_t cig_id, + struct bluetooth::hci::iso_manager::cig_create_params cig_params)); + MOCK_METHOD((void), RemoveCig, (uint8_t cig_id)); + MOCK_METHOD( + (void), EstablishCis, + (struct bluetooth::hci::iso_manager::cis_establish_params conn_params)); + MOCK_METHOD((void), DisconnectCis, (uint16_t cis_handle, uint8_t reason)); + MOCK_METHOD( + (void), SetupIsoDataPath, + (uint16_t iso_handle, + struct bluetooth::hci::iso_manager::iso_data_path_params path_params)); + MOCK_METHOD((void), RemoveIsoDataPath, + (uint16_t iso_handle, uint8_t data_path_dir)); + MOCK_METHOD((void), SendIsoData, + (uint16_t iso_handle, const uint8_t* data, uint16_t data_len)); + MOCK_METHOD((void), ReadIsoLinkQuality, (uint16_t iso_handle)); + MOCK_METHOD( + (void), CreateBig, + (uint8_t big_id, + struct bluetooth::hci::iso_manager::big_create_params big_params)); + MOCK_METHOD((void), TerminateBig, (uint8_t big_id, uint8_t reason)); + MOCK_METHOD((void), HandleIsoData, (void* p_msg)); + MOCK_METHOD((void), HandleDisconnect, (uint16_t handle, uint8_t reason)); + MOCK_METHOD((void), HandleNumComplDataPkts, (uint8_t * p, uint8_t evt_len)); + MOCK_METHOD((void), HandleGdNumComplDataPkts, (uint8_t * p, uint8_t evt_len)); + MOCK_METHOD((void), HandleHciEvent, + (uint8_t sub_code, uint8_t* params, uint16_t length)); + + MOCK_METHOD((void), Start, ()); + MOCK_METHOD((void), Stop, ()); + + private: + DISALLOW_COPY_AND_ASSIGN(MockIsoManager); +}; diff --git a/system/bta/le_audio/mock_le_audio_client_audio.cc b/system/bta/le_audio/mock_le_audio_client_audio.cc new file mode 100644 index 0000000000000000000000000000000000000000..5279b4c79dc721567eb5436074efc13a36624d36 --- /dev/null +++ b/system/bta/le_audio/mock_le_audio_client_audio.cc @@ -0,0 +1,135 @@ +/* + * Copyright 2020 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * 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 "mock_le_audio_client_audio.h" + +#include <base/logging.h> + +/* Source mock */ +static MockLeAudioClientAudioSource* source_instance = nullptr; +void MockLeAudioClientAudioSource::SetMockInstanceForTesting( + MockLeAudioClientAudioSource* mock) { + source_instance = mock; +} + +bool LeAudioClientAudioSource::Start( + const LeAudioCodecConfiguration& codecConfiguration, + LeAudioClientAudioSinkReceiver* audioReceiver) { + LOG_ASSERT(source_instance) + << "Mock LeAudioClientAudioSource interface not set!"; + return source_instance->Start(codecConfiguration, audioReceiver); +} + +void LeAudioClientAudioSource::Stop() { + LOG_ASSERT(source_instance) + << "Mock LeAudioClientAudioSource interface not set!"; + source_instance->Stop(); +} + +// FIXME: This is wrong! we will return a different class object - not even in +// inheritance hierarchy +const void* LeAudioClientAudioSource::Acquire() { + LOG_ASSERT(source_instance) + << "Mock LeAudioClientAudioSource interface not set!"; + return source_instance->Acquire(); +} + +void LeAudioClientAudioSource::Release(const void* inst) { + LOG_ASSERT(source_instance) + << "Mock LeAudioClientAudioSource interface not set!"; + source_instance->Release(inst); +} + +void LeAudioClientAudioSource::ConfirmStreamingRequest() { + LOG_ASSERT(source_instance) + << "Mock LeAudioClientAudioSink interface not set!"; + source_instance->ConfirmStreamingRequest(); +} + +void LeAudioClientAudioSource::CancelStreamingRequest() { + LOG_ASSERT(source_instance) + << "Mock LeAudioClientAudioSink interface not set!"; + source_instance->CancelStreamingRequest(); +} + +void LeAudioClientAudioSource::UpdateRemoteDelay(uint16_t delay) { + LOG_ASSERT(source_instance) + << "Mock LeAudioClientAudioSource interface not set!"; + source_instance->UpdateRemoteDelay(delay); +} + +void LeAudioClientAudioSource::DebugDump(int fd) { + LOG_ASSERT(source_instance) + << "Mock LeAudioClientAudioSource interface not set!"; + source_instance->DebugDump(fd); +} + +/* Sink mock */ +static MockLeAudioClientAudioSink* sink_instance = nullptr; +void MockLeAudioClientAudioSink::SetMockInstanceForTesting( + MockLeAudioClientAudioSink* mock) { + sink_instance = mock; +} + +bool LeAudioClientAudioSink::Start( + const LeAudioCodecConfiguration& codecConfiguration, + LeAudioClientAudioSourceReceiver* audioReceiver) { + LOG_ASSERT(sink_instance) << "Mock LeAudioClientAudioSink interface not set!"; + return sink_instance->Start(codecConfiguration, audioReceiver); +} + +void LeAudioClientAudioSink::Stop() { + LOG_ASSERT(sink_instance) << "Mock LeAudioClientAudioSink interface not set!"; + sink_instance->Stop(); +} + +// FIXME: This is wrong! we will return a different class object - not even in +// inheritance hierarchy +const void* LeAudioClientAudioSink::Acquire() { + LOG_ASSERT(sink_instance) << "Mock LeAudioClientAudioSink interface not set!"; + return sink_instance->Acquire(); +} + +void LeAudioClientAudioSink::Release(const void* inst) { + LOG_ASSERT(sink_instance) << "Mock LeAudioClientAudioSink interface not set!"; + sink_instance->Release(inst); +} + +void LeAudioClientAudioSink::UpdateRemoteDelay(uint16_t delay) { + LOG_ASSERT(sink_instance) << "Mock LeAudioClientAudioSink interface not set!"; + sink_instance->UpdateRemoteDelay(delay); +} + +void LeAudioClientAudioSink::DebugDump(int fd) { + LOG_ASSERT(sink_instance) << "Mock LeAudioClientAudioSink interface not set!"; + sink_instance->DebugDump(fd); +} + +size_t LeAudioClientAudioSink::SendData(uint8_t* data, uint16_t size) { + LOG_ASSERT(sink_instance) << "Mock LeAudioClientAudioSink interface not set!"; + return sink_instance->SendData(data, size); +} + +void LeAudioClientAudioSink::ConfirmStreamingRequest() { + LOG_ASSERT(sink_instance) << "Mock LeAudioClientAudioSink interface not set!"; + sink_instance->ConfirmStreamingRequest(); +} + +void LeAudioClientAudioSink::CancelStreamingRequest() { + LOG_ASSERT(sink_instance) << "Mock LeAudioClientAudioSink interface not set!"; + sink_instance->CancelStreamingRequest(); +} diff --git a/system/bta/le_audio/mock_le_audio_client_audio.h b/system/bta/le_audio/mock_le_audio_client_audio.h new file mode 100644 index 0000000000000000000000000000000000000000..fbae8f70c0a09aeac153a10c87c32f3ea220b7b3 --- /dev/null +++ b/system/bta/le_audio/mock_le_audio_client_audio.h @@ -0,0 +1,53 @@ +/* + * Copyright 2020 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <gmock/gmock.h> + +#include "client_audio.h" + +class MockLeAudioClientAudioSource { + public: + static void SetMockInstanceForTesting(MockLeAudioClientAudioSource* mock); + MOCK_METHOD((bool), Start, + (const LeAudioCodecConfiguration& codecConfiguration, + LeAudioClientAudioSinkReceiver* audioReceiver)); + MOCK_METHOD((void), Stop, ()); + MOCK_METHOD((const void*), Acquire, ()); + MOCK_METHOD((void), Release, (const void*)); + MOCK_METHOD((void), ConfirmStreamingRequest, ()); + MOCK_METHOD((void), CancelStreamingRequest, ()); + MOCK_METHOD((void), UpdateRemoteDelay, (uint16_t delay)); + MOCK_METHOD((void), DebugDump, (int fd)); +}; + +class MockLeAudioClientAudioSink { + public: + static void SetMockInstanceForTesting(MockLeAudioClientAudioSink* mock); + MOCK_METHOD((bool), Start, + (const LeAudioCodecConfiguration& codecConfiguration, + LeAudioClientAudioSourceReceiver* audioReceiver)); + MOCK_METHOD((void), Stop, ()); + MOCK_METHOD((const void*), Acquire, ()); + MOCK_METHOD((void), Release, (const void*)); + MOCK_METHOD((size_t), SendData, (uint8_t * data, uint16_t size)); + MOCK_METHOD((void), ConfirmStreamingRequest, ()); + MOCK_METHOD((void), CancelStreamingRequest, ()); + MOCK_METHOD((void), UpdateRemoteDelay, (uint16_t delay)); + MOCK_METHOD((void), DebugDump, (int fd)); +}; diff --git a/system/bta/le_audio/mock_le_audio_client_audio_source.cc b/system/bta/le_audio/mock_le_audio_client_audio_source.cc new file mode 100644 index 0000000000000000000000000000000000000000..014d74be0777722bcbf25fca631ee938f522c720 --- /dev/null +++ b/system/bta/le_audio/mock_le_audio_client_audio_source.cc @@ -0,0 +1,50 @@ +/* + * Copyright 2020 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * 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 "mock_le_audio_client_audio_source.h" + +static MockLeAudioClientAudioSource* instance; +void MockLeAudioClientAudioSource::SetMockInstanceForTesting( + MockLeAudioClientAudioSource* mock) { + instance = mock; +} + +bool LeAudioClientAudioSource::Start( + const LeAudioCodecConfiguration& codecConfiguration, + LeAudioClientAudioSinkReceiver* audioReceiver, uint16_t remote_delay_ms) { + return instance->Start(codecConfiguration, audioReceiver, remote_delay_ms); +} + +void LeAudioClientAudioSource::Stop() { instance->Stop(); } + +// FIXME: This is wrong! we will return a different class object - not even in +// inheritance hierarchy +const void* LeAudioClientAudioSource::Acquire() { return instance->Acquire(); } + +void LeAudioClientAudioSource::Release(const void* inst) { + instance->Release(inst); +} + +void LeAudioClientAudioSource::ConfirmStreamingRequest() { + instance->ConfirmStreamingRequest(); +} + +void LeAudioClientAudioSource::CancelStreamingRequest() { + instance->CancelStreamingRequest(); +} + +void LeAudioClientAudioSource::DebugDump(int fd) { instance->DebugDump(fd); } diff --git a/system/bta/le_audio/mock_le_audio_client_audio_source.h b/system/bta/le_audio/mock_le_audio_client_audio_source.h new file mode 100644 index 0000000000000000000000000000000000000000..59646a56671273d6bf378cc7bb28bf80163c4eec --- /dev/null +++ b/system/bta/le_audio/mock_le_audio_client_audio_source.h @@ -0,0 +1,35 @@ +/* + * Copyright 2020 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <gmock/gmock.h> + +#include "client_audio.h" + +class MockLeAudioClientAudioSource { + public: + static void SetMockInstanceForTesting(MockLeAudioClientAudioSource* mock); + MOCK_METHOD((bool), Start, + (const LeAudioCodecConfiguration& codecConfiguration, + LeAudioClientAudioSinkReceiver* audioReceiver, + uint16_t remote_delay_ms)); + MOCK_METHOD((void), Stop, ()); + MOCK_METHOD((const void*), Acquire, ()); + MOCK_METHOD((void), Release, (const void*)); + MOCK_METHOD((void), DebugDump, (int fd)); +}; diff --git a/system/bta/le_audio/mock_state_machine.cc b/system/bta/le_audio/mock_state_machine.cc new file mode 100644 index 0000000000000000000000000000000000000000..dbff4f427005c0df2c062a97ee0e9569afad147c --- /dev/null +++ b/system/bta/le_audio/mock_state_machine.cc @@ -0,0 +1,43 @@ +/* + * Copyright 2021 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * 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 "mock_state_machine.h" + +#include <base/logging.h> + +static MockLeAudioGroupStateMachine* mock_machine = nullptr; + +void le_audio::LeAudioGroupStateMachine::Initialize( + le_audio::LeAudioGroupStateMachine::Callbacks* state_machine_callbacks) { + LOG_ASSERT(mock_machine) << "Mock State Machine not set!"; + mock_machine->Initialize(state_machine_callbacks); +} + +void le_audio::LeAudioGroupStateMachine::Cleanup(void) { + LOG_ASSERT(mock_machine) << "Mock State Machine not set!"; + mock_machine->Cleanup(); +} + +le_audio::LeAudioGroupStateMachine* le_audio::LeAudioGroupStateMachine::Get( + void) { + return mock_machine; +} + +void MockLeAudioGroupStateMachine::SetMockInstanceForTesting( + MockLeAudioGroupStateMachine* machine) { + mock_machine = machine; +} diff --git a/system/bta/le_audio/mock_state_machine.h b/system/bta/le_audio/mock_state_machine.h new file mode 100644 index 0000000000000000000000000000000000000000..61a0b4f0b9f613249a6bbdd08b4222115f3556d2 --- /dev/null +++ b/system/bta/le_audio/mock_state_machine.h @@ -0,0 +1,90 @@ +/* + * Copyright 2021 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <gmock/gmock.h> + +#include "state_machine.h" + +class MockLeAudioGroupStateMachine : public le_audio::LeAudioGroupStateMachine { + public: + MOCK_METHOD((bool), StartStream, + (le_audio::LeAudioDeviceGroup * group, + le_audio::types::LeAudioContextType context_type), + (override)); + MOCK_METHOD((bool), AttachToStream, + (le_audio::LeAudioDeviceGroup * group, + le_audio::LeAudioDevice* leAudioDevice), + (override)); + MOCK_METHOD((void), SuspendStream, (le_audio::LeAudioDeviceGroup * group), + (override)); + MOCK_METHOD((void), StopStream, (le_audio::LeAudioDeviceGroup * group), + (override)); + MOCK_METHOD((void), ProcessGattNotifEvent, + (uint8_t * value, uint16_t len, le_audio::types::ase* ase, + le_audio::LeAudioDevice* leAudioDevice, + le_audio::LeAudioDeviceGroup* group), + (override)); + + MOCK_METHOD((void), ProcessHciNotifOnCigCreate, + (le_audio::LeAudioDeviceGroup * group, uint8_t status, + uint8_t cig_id, std::vector<uint16_t> conn_handles), + (override)); + MOCK_METHOD((void), ProcessHciNotifOnCigRemove, + (uint8_t status, le_audio::LeAudioDeviceGroup* group), + (override)); + MOCK_METHOD( + (void), ProcessHciNotifCisEstablished, + (le_audio::LeAudioDeviceGroup * group, + le_audio::LeAudioDevice* leAudioDevice, + const bluetooth::hci::iso_manager::cis_establish_cmpl_evt* event), + (override)); + MOCK_METHOD((void), ProcessHciNotifCisDisconnected, + (le_audio::LeAudioDeviceGroup * group, + le_audio::LeAudioDevice* leAudioDevice, + const bluetooth::hci::iso_manager::cis_disconnected_evt* event), + (override)); + MOCK_METHOD((void), ProcessHciNotifSetupIsoDataPath, + (le_audio::LeAudioDeviceGroup * group, + le_audio::LeAudioDevice* leAudioDevice, uint8_t status, + uint16_t conn_hdl), + (override)); + MOCK_METHOD((void), ProcessHciNotifRemoveIsoDataPath, + (le_audio::LeAudioDeviceGroup * group, + le_audio::LeAudioDevice* leAudioDevice, uint8_t status, + uint16_t conn_hdl), + (override)); + MOCK_METHOD((void), Initialize, + (le_audio::LeAudioGroupStateMachine::Callbacks * + state_machine_callbacks)); + MOCK_METHOD((void), Cleanup, ()); + MOCK_METHOD((void), ProcessHciNotifIsoLinkQualityRead, + (le_audio::LeAudioDeviceGroup * group, + le_audio::LeAudioDevice* leAudioDevice, uint8_t conn_handle, + uint32_t txUnackedPackets, uint32_t txFlushedPackets, + uint32_t txLastSubeventPackets, uint32_t retransmittedPackets, + uint32_t crcErrorPackets, uint32_t rxUnreceivedPackets, + uint32_t duplicatePackets), + (override)); + MOCK_METHOD((void), ProcessHciNotifAclDisconnected, + (le_audio::LeAudioDeviceGroup * group, + le_audio::LeAudioDevice* leAudioDevice), + (override)); + + static void SetMockInstanceForTesting(MockLeAudioGroupStateMachine* machine); +}; diff --git a/system/bta/le_audio/state_machine.cc b/system/bta/le_audio/state_machine.cc new file mode 100644 index 0000000000000000000000000000000000000000..6529dcdea752d966165beeb3eb808f79acb8d03e --- /dev/null +++ b/system/bta/le_audio/state_machine.cc @@ -0,0 +1,1832 @@ +/* + * Copyright 2021 HIMSA II K/S - www.himsa.com. Represented by EHIMA - + * www.ehima.com + * + * 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 "state_machine.h" + +#include <base/bind.h> +#include <base/callback.h> + +#include <map> + +#include "bt_types.h" +#include "bta_gatt_queue.h" +#include "bta_le_audio_api.h" +#include "btm_iso_api.h" +#include "client_parser.h" +#include "devices.h" +#include "hcimsgs.h" +#include "le_audio_types.h" +#include "osi/include/alarm.h" +#include "osi/include/osi.h" +#include "osi/include/properties.h" + +// clang-format off +/* ASCS state machine 1.0 + * + * State machine manages group of ASEs to make transition from one state to + * another according to specification and keeping involved necessary externals + * like: ISO, CIG, ISO data path, audio path form/to upper layer. + * + * GroupStream (API): GroupStream method of this le audio implementation class + * object should allow transition from Idle (No Caching), + * Codec Configured (Caching after release) state to + * Streaming for all ASEs in group within time limit. Time + * limit should keep safe whole state machine from being + * stucked in any in-middle state, which is not a destination + * state. + * + * TODO Second functionality of streaming should be switch + * context which will base on previous state, context type. + * + * GroupStop (API): GroupStop method of this le audio implementation class + * object should allow safe transition from any state to Idle + * or Codec Configured (if caching supported). + * + * â•”â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•¦â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•¦â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•¦â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•¦â•â•â•â•â•â•â•— + * â•‘ Current State â•‘ ASE Control Point Operation â•‘ Result â•‘ Next State â•‘ Note â•‘ + * â• â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•¬â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•¬â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•¬â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•¬â•â•â•â•â•â•â•£ + * â•‘ Idle â•‘ Config Codec â•‘ Success â•‘ Codec Configured â•‘ + â•‘ + * â•‘ Codec Configured â•‘ Config Codec â•‘ Success â•‘ Codec Configured â•‘ - â•‘ + * â•‘ Codec Configured â•‘ Release â•‘ Success â•‘ Releasing â•‘ + â•‘ + * â•‘ Codec Configured â•‘ Config QoS â•‘ Success â•‘ QoS Configured â•‘ + â•‘ + * â•‘ QoS Configured â•‘ Config Codec â•‘ Success â•‘ Codec Configured â•‘ - â•‘ + * â•‘ QoS Configured â•‘ Config QoS â•‘ Success â•‘ QoS Configured â•‘ - â•‘ + * â•‘ QoS Configured â•‘ Release â•‘ Success â•‘ Releasing â•‘ + â•‘ + * â•‘ QoS Configured â•‘ Enable â•‘ Success â•‘ Enabling â•‘ + â•‘ + * â•‘ Enabling â•‘ Release â•‘ Success â•‘ Releasing â•‘ + â•‘ + * â•‘ Enabling â•‘ Update Metadata â•‘ Success â•‘ Enabling â•‘ - â•‘ + * â•‘ Enabling â•‘ Disable â•‘ Success â•‘ Disabling â•‘ - â•‘ + * â•‘ Enabling â•‘ Receiver Start Ready â•‘ Success â•‘ Streaming â•‘ + â•‘ + * â•‘ Streaming â•‘ Update Metadata â•‘ Success â•‘ Streaming â•‘ - â•‘ + * â•‘ Streaming â•‘ Disable â•‘ Success â•‘ Disabling â•‘ + â•‘ + * â•‘ Streaming â•‘ Release â•‘ Success â•‘ Releasing â•‘ + â•‘ + * â•‘ Disabling â•‘ Receiver Stop Ready â•‘ Success â•‘ QoS Configured â•‘ + â•‘ + * â•‘ Disabling â•‘ Release â•‘ Success â•‘ Releasing â•‘ + â•‘ + * â•‘ Releasing â•‘ Released (no caching) â•‘ Success â•‘ Idle â•‘ + â•‘ + * â•‘ Releasing â•‘ Released (caching) â•‘ Success â•‘ Codec Configured â•‘ - â•‘ + * â•šâ•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•©â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•©â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•©â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•©â•â•â•â•â•â•â• + * + * + - supported transition + * - - not supported + */ +// clang-format on + +using bluetooth::hci::IsoManager; +using bluetooth::le_audio::GroupStreamStatus; +using le_audio::LeAudioDevice; +using le_audio::LeAudioDeviceGroup; +using le_audio::LeAudioGroupStateMachine; + +using le_audio::types::ase; +using le_audio::types::AseState; +using le_audio::types::AudioStreamDataPathState; + +namespace { + +constexpr int linkQualityCheckInterval = 4000; + +static void link_quality_cb(void* data) { + // very ugly, but we need to pass just two bytes + uint16_t cis_conn_handle = *((uint16_t*)data); + + IsoManager::GetInstance()->ReadIsoLinkQuality(cis_conn_handle); +} + +class LeAudioGroupStateMachineImpl; +LeAudioGroupStateMachineImpl* instance; + +class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine { + public: + LeAudioGroupStateMachineImpl(Callbacks* state_machine_callbacks_) + : state_machine_callbacks_(state_machine_callbacks_), + watchdog_(alarm_new("LeAudioStateMachineTimer")) {} + + ~LeAudioGroupStateMachineImpl() { + alarm_free(watchdog_); + watchdog_ = nullptr; + } + + bool AttachToStream(LeAudioDeviceGroup* group, + LeAudioDevice* leAudioDevice) override { + LOG(INFO) << __func__ << " group id: " << group->group_id_ + << " device: " << leAudioDevice->address_; + + /* This function is used to attach the device to the stream. + * Limitation here is that device should be previously in the streaming + * group and just got reconnected. + */ + if (group->GetState() != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { + LOG(ERROR) << __func__ + << " group not in the streaming state: " << group->GetState(); + return false; + } + + PrepareAndSendCodecConfigure(group, leAudioDevice); + return true; + } + + bool StartStream(LeAudioDeviceGroup* group, + le_audio::types::LeAudioContextType context_type) override { + LOG(INFO) << __func__ << " current state: " << group->GetState(); + + switch (group->GetState()) { + case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED: + case AseState::BTA_LE_AUDIO_ASE_STATE_IDLE: + if (!group->Configure(context_type)) { + LOG(ERROR) << __func__ << ", failed to set ASE configuration"; + return false; + } + + /* All ASEs should aim to achieve target state */ + SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); + PrepareAndSendCodecConfigure(group, group->GetFirstActiveDevice()); + break; + + case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED: { + LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice(); + if (!leAudioDevice) { + LOG(ERROR) << __func__ << ", group has no active devices"; + return false; + } + + /* All ASEs should aim to achieve target state */ + SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); + PrepareAndSendEnable(leAudioDevice); + break; + } + + case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING: + if (group->GetContextType() != context_type) { + /* TODO: Switch context of group */ + group->SetContextType(context_type); + } + return true; + + default: + LOG(ERROR) << "Unable to transit from " << group->GetState(); + return false; + } + + return true; + } + + void SuspendStream(LeAudioDeviceGroup* group) override { + LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice(); + LOG_ASSERT(leAudioDevice) + << __func__ << " Shouldn't be called without an active device."; + + /* All ASEs should aim to achieve target state */ + SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED); + PrepareAndSendDisable(leAudioDevice); + } + + void StopStream(LeAudioDeviceGroup* group) override { + if (group->IsReleasing()) { + LOG(INFO) << __func__ << ", group already in releasing process"; + return; + } + + LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice(); + if (leAudioDevice == nullptr) { + LOG(ERROR) << __func__ + << " Shouldn't be called without an active device."; + return; + } + + /* All Ases should aim to achieve target state */ + SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); + PrepareAndSendRelease(leAudioDevice); + } + + void ProcessGattNotifEvent(uint8_t* value, uint16_t len, struct ase* ase, + LeAudioDevice* leAudioDevice, + LeAudioDeviceGroup* group) override { + struct le_audio::client_parser::ascs::ase_rsp_hdr arh; + + ParseAseStatusHeader(arh, len, value); + + LOG(INFO) << __func__ << " " << leAudioDevice->address_ + << ", ASE id: " << +ase->id << " state changed " << ase->state + << " -> " << AseState(arh.state); + + switch (static_cast<AseState>(arh.state)) { + case AseState::BTA_LE_AUDIO_ASE_STATE_IDLE: + AseStateMachineProcessIdle(arh, ase, group, leAudioDevice); + break; + case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED: + AseStateMachineProcessCodecConfigured( + arh, ase, value + le_audio::client_parser::ascs::kAseRspHdrMinLen, + len - le_audio::client_parser::ascs::kAseRspHdrMinLen, group, + leAudioDevice); + break; + case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED: + AseStateMachineProcessQosConfigured(arh, ase, group, leAudioDevice); + break; + case AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING: + AseStateMachineProcessEnabling(arh, ase, group, leAudioDevice); + break; + case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING: + AseStateMachineProcessStreaming(arh, ase, group, leAudioDevice); + break; + case AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING: + AseStateMachineProcessDisabling(arh, ase, group, leAudioDevice); + break; + case AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING: + AseStateMachineProcessReleasing(arh, ase, group, leAudioDevice); + break; + default: + LOG(ERROR) << __func__ + << ", Wrong AES status: " << static_cast<int>(arh.state); + StopStream(group); + break; + } + } + + void ProcessHciNotifOnCigCreate(LeAudioDeviceGroup* group, uint8_t status, + uint8_t cig_id, + std::vector<uint16_t> conn_handles) override { + uint8_t i = 0; + LeAudioDevice* leAudioDevice; + struct le_audio::types::ase* ase; + + /* TODO: What if not all cises will be configured ? + * conn_handle.size() != active ases in group + */ + + if (!group) { + LOG(ERROR) << __func__ << ", invalid cig"; + return; + } + + if (status != HCI_SUCCESS) { + LOG(ERROR) << __func__ + << ", failed to create CIG, reason: " << loghex(status); + StopStream(group); + return; + } + + group->cig_created_ = true; + + LOG(INFO) << __func__ << "Group id: " << +group->group_id_ + << " conn_handle size " << +conn_handles.size(); + + /* Assign all connection handles to ases. CIS ID order is represented by the + * order of active ASEs in active leAudioDevices + */ + + leAudioDevice = group->GetFirstActiveDevice(); + LOG_ASSERT(leAudioDevice) + << __func__ << " Shouldn't be called without an active device."; + + /* Assign all connection handles to ases */ + do { + ase = leAudioDevice->GetFirstActiveAseByDataPathState( + AudioStreamDataPathState::IDLE); + LOG_ASSERT(ase) << __func__ + << " shouldn't be called without an active ASE"; + do { + auto ases_pair = leAudioDevice->GetAsesByCisId(ase->cis_id); + + if (ases_pair.sink) { + ases_pair.sink->cis_conn_hdl = conn_handles[i]; + ases_pair.sink->data_path_state = + AudioStreamDataPathState::CIS_ASSIGNED; + } + if (ases_pair.source) { + ases_pair.source->cis_conn_hdl = conn_handles[i]; + ases_pair.source->data_path_state = + AudioStreamDataPathState::CIS_ASSIGNED; + } + i++; + } while ((ase = leAudioDevice->GetFirstActiveAseByDataPathState( + AudioStreamDataPathState::IDLE)) && + (i < conn_handles.size())); + } while ((leAudioDevice = group->GetNextActiveDevice(leAudioDevice)) && + (i < conn_handles.size())); + + /* Last node configured, process group to codec configured state */ + group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED); + + if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { + leAudioDevice = group->GetFirstActiveDevice(); + LOG_ASSERT(leAudioDevice) + << __func__ << " Shouldn't be called without an active device."; + PrepareAndSendEnable(leAudioDevice); + } else { + LOG(ERROR) << __func__ + << ", invalid state transition, from: " << group->GetState() + << ", to: " << group->GetTargetState(); + StopStream(group); + return; + } + } + + void ProcessHciNotifOnCigRemove(uint8_t status, + LeAudioDeviceGroup* group) override { + if (status) { + LOG(ERROR) << __func__ + << ", failed to remove cig, id: " << loghex(group->group_id_) + << ", status: " << loghex(status); + return; + } + + group->cig_created_ = false; + + LeAudioDevice* leAudioDevice = group->GetFirstDevice(); + if (!leAudioDevice) return; + + do { + alarm_free(leAudioDevice->link_quality_timer); + leAudioDevice->link_quality_timer = nullptr; + + for (auto& ase : leAudioDevice->ases_) { + ase.data_path_state = AudioStreamDataPathState::IDLE; + ase.cis_id = le_audio::kInvalidCisId; + } + } while ((leAudioDevice = group->GetNextDevice(leAudioDevice))); + } + + void ProcessHciNotifSetupIsoDataPath(LeAudioDeviceGroup* group, + LeAudioDevice* leAudioDevice, + uint8_t status, + uint16_t conn_handle) override { + if (status) { + LOG(ERROR) << __func__ << ", failed to setup data path"; + StopStream(group); + + return; + } + + /* Update state for the given cis.*/ + auto ase = leAudioDevice->GetFirstActiveAseByDataPathState( + AudioStreamDataPathState::CIS_ESTABLISHED); + + if (ase->cis_conn_hdl != conn_handle) { + LOG(ERROR) << __func__ << " Cannot find ase by handle " << +conn_handle; + return; + } + + ase->data_path_state = AudioStreamDataPathState::DATA_PATH_ESTABLISHED; + + if (group->GetTargetState() != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { + LOG(WARNING) << __func__ << " Group " << group->group_id_ + << " is not targeting streaming state any more"; + return; + } + + ase = leAudioDevice->GetNextActiveAse(ase); + if (!ase) { + leAudioDevice = group->GetNextActiveDevice(leAudioDevice); + + if (!leAudioDevice) { + state_machine_callbacks_->StatusReportCb(group->group_id_, + GroupStreamStatus::STREAMING); + return; + } + + ase = leAudioDevice->GetFirstActiveAse(); + } + + LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE"; + if (ase->data_path_state == AudioStreamDataPathState::CIS_ESTABLISHED) + PrepareDataPath(ase); + else + LOG(ERROR) << __func__ + << " CIS got disconnected? handle: " << +ase->cis_conn_hdl; + } + + void ProcessHciNotifRemoveIsoDataPath(LeAudioDeviceGroup* group, + LeAudioDevice* leAudioDevice, + uint8_t status, + uint16_t conn_hdl) override { + if (status != HCI_SUCCESS) { + LOG(ERROR) << __func__ << ", failed to remove ISO data path, reason: " + << loghex(status); + StopStream(group); + + return; + } + + bool do_disconnect = false; + + auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(conn_hdl); + if (ases_pair.sink && (ases_pair.sink->data_path_state == + AudioStreamDataPathState::DATA_PATH_ESTABLISHED)) { + ases_pair.sink->data_path_state = + AudioStreamDataPathState::CIS_DISCONNECTING; + do_disconnect = true; + } + + if (ases_pair.source && + ases_pair.source->data_path_state == + AudioStreamDataPathState::DATA_PATH_ESTABLISHED) { + ases_pair.source->data_path_state = + AudioStreamDataPathState::CIS_DISCONNECTING; + do_disconnect = true; + } + + if (do_disconnect) + IsoManager::GetInstance()->DisconnectCis(conn_hdl, HCI_ERR_PEER_USER); + } + + void ProcessHciNotifIsoLinkQualityRead( + LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice, + uint8_t conn_handle, uint32_t txUnackedPackets, uint32_t txFlushedPackets, + uint32_t txLastSubeventPackets, uint32_t retransmittedPackets, + uint32_t crcErrorPackets, uint32_t rxUnreceivedPackets, + uint32_t duplicatePackets) { + LOG(INFO) << "conn_handle: " << loghex(conn_handle) + << ", txUnackedPackets: " << loghex(txUnackedPackets) + << ", txFlushedPackets: " << loghex(txFlushedPackets) + << ", txLastSubeventPackets: " << loghex(txLastSubeventPackets) + << ", retransmittedPackets: " << loghex(retransmittedPackets) + << ", crcErrorPackets: " << loghex(crcErrorPackets) + << ", rxUnreceivedPackets: " << loghex(rxUnreceivedPackets) + << ", duplicatePackets: " << loghex(duplicatePackets); + } + + void ProcessHciNotifAclDisconnected(LeAudioDeviceGroup* group, + LeAudioDevice* leAudioDevice) { + if (leAudioDevice->link_quality_timer) { + alarm_free(leAudioDevice->link_quality_timer); + leAudioDevice->link_quality_timer = nullptr; + } + + leAudioDevice->conn_id_ = GATT_INVALID_CONN_ID; + + if (!group) { + LOG(ERROR) << __func__ + << " group is null for device: " << leAudioDevice->address_ + << " group_id: " << leAudioDevice->group_id_; + return; + } + + auto* stream_conf = &group->stream_conf; + if (stream_conf->valid) { + stream_conf->sink_streams.erase( + std::remove_if(stream_conf->sink_streams.begin(), + stream_conf->sink_streams.end(), + [leAudioDevice](auto& pair) { + auto ases = + leAudioDevice->GetAsesByCisConnHdl(pair.first); + return ases.sink; + }), + stream_conf->sink_streams.end()); + + stream_conf->source_streams.erase( + std::remove_if(stream_conf->source_streams.begin(), + stream_conf->source_streams.end(), + [leAudioDevice](auto& pair) { + auto ases = + leAudioDevice->GetAsesByCisConnHdl(pair.first); + return ases.source; + }), + stream_conf->source_streams.end()); + + if (stream_conf->sink_streams.empty() && + stream_conf->source_streams.empty()) { + LOG(INFO) << __func__ << " stream stopped "; + stream_conf->valid = false; + } + } + + /* mark ASEs as not used. */ + leAudioDevice->DeactivateAllAses(); + + DLOG(INFO) << __func__ << " device: " << leAudioDevice->address_ + << " group connected: " << group->IsAnyDeviceConnected() + << " all active ase disconnected: " + << group->HaveAllActiveDevicesCisDisc(); + + /* Group has changed. Lets update available contexts */ + group->UpdateActiveContextsMap(); + + /* ACL of one of the device has been dropped. + * If there is active CIS, do nothing here. Just update the active contexts + * table + */ + if (group->IsAnyDeviceConnected() && + !group->HaveAllActiveDevicesCisDisc()) { + return; + } + + /* Group is not connected and all the CISes are down. + * If group is in Idle there is nothing to do here */ + if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) { + LOG(INFO) << __func__ << " group: " << group->group_id_ << " is in IDLE"; + return; + } + + /* Clean states and destroy HCI group */ + group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); + group->SetTargetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); + if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_); + state_machine_callbacks_->StatusReportCb(group->group_id_, + GroupStreamStatus::IDLE); + + if (!group->cig_created_) return; + + IsoManager::GetInstance()->RemoveCig(group->group_id_); + } + + void ProcessHciNotifCisEstablished( + LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice, + const bluetooth::hci::iso_manager::cis_establish_cmpl_evt* event) + override { + std::vector<uint8_t> value; + + auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(event->cis_conn_hdl); + + if (event->status) { + if (ases_pair.sink) + ases_pair.sink->data_path_state = + AudioStreamDataPathState::CIS_ASSIGNED; + if (ases_pair.source) + ases_pair.source->data_path_state = + AudioStreamDataPathState::CIS_ASSIGNED; + + /* CIS establishment failed. Remove CIG if no other CIS is already created + * or pending. If CIS is established, this will be handled in disconnected + * complete event + */ + if (group->HaveAllActiveDevicesCisDisc()) { + IsoManager::GetInstance()->RemoveCig(group->group_id_); + } + + LOG(ERROR) << __func__ + << ", failed to create CIS, status: " << loghex(event->status); + + StopStream(group); + return; + } + + if (group->GetTargetState() != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { + LOG(ERROR) << __func__ << ", Unintended CIS establishement event came"; + StopStream(group); + return; + } + + if (ases_pair.sink) + ases_pair.sink->data_path_state = + AudioStreamDataPathState::CIS_ESTABLISHED; + if (ases_pair.source) + ases_pair.source->data_path_state = + AudioStreamDataPathState::CIS_ESTABLISHED; + + leAudioDevice->link_quality_timer = + alarm_new_periodic("le_audio_cis_link_quality"); + leAudioDevice->link_quality_timer_data = event->cis_conn_hdl; + alarm_set_on_mloop(leAudioDevice->link_quality_timer, + linkQualityCheckInterval, link_quality_cb, + &leAudioDevice->link_quality_timer_data); + + if (!leAudioDevice->HaveAllActiveAsesCisEst()) { + /* More cis established event has to come */ + return; + } + + std::vector<uint8_t> ids; + + /* All CISes created. Send start ready for source ASE before we can go + * to streaming state. + */ + struct ase* ase = leAudioDevice->GetFirstActiveAse(); + LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE"; + do { + if (ase->direction == le_audio::types::kLeAudioDirectionSource) + ids.push_back(ase->id); + } while ((ase = leAudioDevice->GetNextActiveAse(ase))); + + if (ids.size() > 0) { + le_audio::client_parser::ascs::PrepareAseCtpAudioReceiverStartReady( + ids, value); + + BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_, + leAudioDevice->ctp_hdls_.val_hdl, value, + GATT_WRITE_NO_RSP, NULL, NULL); + + return; + } + + /* Cis establishment may came after setting group state to streaming, e.g. + * for autonomous scenario when ase is sink */ + if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING && + group->IsGroupStreamReady()) { + /* No more transition for group */ + alarm_cancel(watchdog_); + PrepareDataPath(group); + } + } + + void ProcessHciNotifCisDisconnected( + LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice, + const bluetooth::hci::iso_manager::cis_disconnected_evt* event) override { + /* Reset the disconnected CIS states */ + + alarm_free(leAudioDevice->link_quality_timer); + leAudioDevice->link_quality_timer = nullptr; + auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(event->cis_conn_hdl); + if (ases_pair.sink) { + ases_pair.sink->data_path_state = AudioStreamDataPathState::CIS_ASSIGNED; + } + if (ases_pair.source) { + ases_pair.source->data_path_state = + AudioStreamDataPathState::CIS_ASSIGNED; + } + + /* Invalidate stream configuration if needed */ + auto* stream_conf = &group->stream_conf; + if (stream_conf->valid) { + if (ases_pair.sink) { + stream_conf->sink_streams.erase( + std::remove_if(stream_conf->sink_streams.begin(), + stream_conf->sink_streams.end(), + [&event](auto& pair) { + return event->cis_conn_hdl == pair.first; + }), + stream_conf->sink_streams.end()); + } + + if (ases_pair.source) { + stream_conf->source_streams.erase( + std::remove_if(stream_conf->source_streams.begin(), + stream_conf->source_streams.end(), + [&event](auto& pair) { + return event->cis_conn_hdl == pair.first; + }), + stream_conf->source_streams.end()); + } + + if (stream_conf->sink_streams.empty() && + stream_conf->source_streams.empty()) { + LOG(INFO) << __func__ << " stream stopped "; + stream_conf->valid = false; + } + } + + auto target_state = group->GetTargetState(); + switch (target_state) { + case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING: + /* Something wrong happen when streaming or when creating stream. + * If there is other device connected and streaming, just leave it as it + * is, otherwise stop the stream. + */ + if (!group->HaveAllActiveDevicesCisDisc()) { + /* TODO: Reconfigure LC3 codec from here or maybe other place?*/ + return; + } + + /*If there is no more ase to stream. Suspend audio and clear state + * machine -> go to Idle */ + state_machine_callbacks_->StatusReportCb(group->group_id_, + GroupStreamStatus::SUSPENDED); + StopStream(group); + return; + + case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED: + /* Intentional group disconnect has finished, but the last CIS in the + * event came after the ASE notification. + * If group is already suspended and all CIS are disconnected, we can + * report SUSPENDED state. + */ + if ((group->GetState() == + AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) && + group->HaveAllActiveDevicesCisDisc()) { + /* No more transition for group */ + alarm_cancel(watchdog_); + + state_machine_callbacks_->StatusReportCb( + group->group_id_, GroupStreamStatus::SUSPENDED); + return; + } + break; + case AseState::BTA_LE_AUDIO_ASE_STATE_IDLE: + case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED: + /* Those two are used when closing the stream and CIS disconnection is + * expected */ + if (group->HaveAllActiveDevicesCisDisc()) { + IsoManager::GetInstance()->RemoveCig(group->group_id_); + return; + } + + break; + default: + break; + } + + /* We should send Receiver Stop Ready when acting as a source */ + if (ases_pair.source && + ases_pair.source->state == AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING) { + std::vector<uint8_t> ids = {ases_pair.source->id}; + std::vector<uint8_t> value; + + le_audio::client_parser::ascs::PrepareAseCtpAudioReceiverStopReady(ids, + value); + BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_, + leAudioDevice->ctp_hdls_.val_hdl, value, + GATT_WRITE_NO_RSP, NULL, NULL); + } + + /* Tear down CIS's data paths within the group */ + struct ase* ase = leAudioDevice->GetFirstActiveAseByDataPathState( + AudioStreamDataPathState::DATA_PATH_ESTABLISHED); + if (!ase) { + leAudioDevice = group->GetNextActiveDevice(leAudioDevice); + /* No more ASEs to disconnect their CISes */ + if (!leAudioDevice) return; + + ase = leAudioDevice->GetFirstActiveAse(); + } + + LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE"; + ases_pair = leAudioDevice->GetAsesByCisConnHdl(ase->cis_conn_hdl); + + if (ase->data_path_state == + AudioStreamDataPathState::DATA_PATH_ESTABLISHED) { + IsoManager::GetInstance()->RemoveIsoDataPath( + ase->cis_conn_hdl, + (ases_pair.sink + ? bluetooth::hci::iso_manager::kIsoDataPathDirectionOut + : 0x00) | + (ases_pair.source + ? bluetooth::hci::iso_manager::kIsoDataPathDirectionIn + : 0x00)); + } + } + + private: + static constexpr uint64_t kStateTransitionTimeoutMs = 5000; + static constexpr char kStateTransitionTimeoutMsProp[] = + "persist.bluetooth.leaudio.device.set.state.timeoutms"; + Callbacks* state_machine_callbacks_; + alarm_t* watchdog_; + + /* This callback is called on timeout during transition to target state */ + void OnStateTransitionTimeout(int group_id) { + state_machine_callbacks_->OnStateTransitionTimeout(group_id); + } + + void SetTargetState(LeAudioDeviceGroup* group, AseState state) { + group->SetTargetState(state); + + /* Group should tie in time to get requested status */ + uint64_t timeoutMs = kStateTransitionTimeoutMs; + timeoutMs = + osi_property_get_int32(kStateTransitionTimeoutMsProp, timeoutMs); + + if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_); + + alarm_set_on_mloop( + watchdog_, timeoutMs, + [](void* data) { + if (instance) instance->OnStateTransitionTimeout(PTR_TO_INT(data)); + }, + INT_TO_PTR(group->group_id_)); + } + + void CigCreate(LeAudioDeviceGroup* group) { + LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice(); + struct ase* ase; + uint32_t sdu_interval_mtos, sdu_interval_stom; + uint8_t packing, framing, sca; + std::vector<EXT_CIS_CFG> cis_cfgs; + + if (group->cig_created_) { + LOG(ERROR) << __func__ << " group id " << group->group_id_ + << " is already created in the controller. "; + return; + } + + if (!leAudioDevice) { + LOG(ERROR) << __func__ << ", no active devices in group"; + + return; + } + + sdu_interval_mtos = + group->GetSduInterval(le_audio::types::kLeAudioDirectionSink); + sdu_interval_stom = + group->GetSduInterval(le_audio::types::kLeAudioDirectionSource); + sca = group->GetSCA(); + packing = group->GetPacking(); + framing = group->GetFraming(); + uint16_t max_trans_lat_mtos = group->GetMaxTransportLatencyMtos(); + uint16_t max_trans_lat_stom = group->GetMaxTransportLatencyStom(); + + do { + ase = leAudioDevice->GetFirstActiveAse(); + LOG_ASSERT(ase) << __func__ + << " shouldn't be called without an active ASE"; + do { + auto& cis = ase->cis_id; + auto iter = + find_if(cis_cfgs.begin(), cis_cfgs.end(), + [&cis](auto const& cfg) { return cis == cfg.cis_id; }); + + /* CIS configuration already on list */ + if (iter != cis_cfgs.end()) continue; + + auto ases_pair = leAudioDevice->GetAsesByCisId(cis); + EXT_CIS_CFG cis_cfg; + cis_cfg.cis_id = ase->cis_id; + cis_cfg.phy_mtos = + group->GetPhyBitmask(le_audio::types::kLeAudioDirectionSink); + cis_cfg.phy_stom = + group->GetPhyBitmask(le_audio::types::kLeAudioDirectionSource); + + if (ases_pair.sink) { + /* TODO: config should be previously adopted */ + cis_cfg.max_sdu_size_mtos = ase->max_sdu_size; + cis_cfg.rtn_mtos = ase->retrans_nb; + } + if (ases_pair.source) { + /* TODO: config should be previously adopted */ + cis_cfg.max_sdu_size_stom = ase->max_sdu_size; + cis_cfg.rtn_stom = ase->retrans_nb; + } + + cis_cfgs.push_back(cis_cfg); + } while ((ase = leAudioDevice->GetNextActiveAse(ase))); + } while ((leAudioDevice = group->GetNextActiveDevice(leAudioDevice))); + + bluetooth::hci::iso_manager::cig_create_params param = { + .sdu_itv_mtos = sdu_interval_mtos, + .sdu_itv_stom = sdu_interval_stom, + .sca = sca, + .packing = packing, + .framing = framing, + .max_trans_lat_stom = max_trans_lat_stom, + .max_trans_lat_mtos = max_trans_lat_mtos, + .cis_cfgs = std::move(cis_cfgs), + }; + IsoManager::GetInstance()->CreateCig(group->group_id_, std::move(param)); + } + + static void CisCreateForDevice(LeAudioDevice* leAudioDevice) { + std::vector<EXT_CIS_CREATE_CFG> conn_pairs; + struct ase* ase = leAudioDevice->GetFirstActiveAse(); + do { + /* First in ase pair is Sink, second Source */ + auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(ase->cis_conn_hdl); + + /* Already in pending state - bi-directional CIS */ + if (ase->data_path_state == AudioStreamDataPathState::CIS_PENDING) + continue; + + if (ases_pair.sink) + ases_pair.sink->data_path_state = AudioStreamDataPathState::CIS_PENDING; + if (ases_pair.source) + ases_pair.source->data_path_state = + AudioStreamDataPathState::CIS_PENDING; + + uint16_t acl_handle = + BTM_GetHCIConnHandle(leAudioDevice->address_, BT_TRANSPORT_LE); + conn_pairs.push_back({.cis_conn_handle = ase->cis_conn_hdl, + .acl_conn_handle = acl_handle}); + LOG(INFO) << __func__ << " cis handle: " << +ase->cis_conn_hdl + << " acl handle : " << loghex(+acl_handle); + + } while ((ase = leAudioDevice->GetNextActiveAse(ase))); + + IsoManager::GetInstance()->EstablishCis( + {.conn_pairs = std::move(conn_pairs)}); + } + + static void CisCreate(LeAudioDeviceGroup* group) { + LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice(); + struct ase* ase; + std::vector<EXT_CIS_CREATE_CFG> conn_pairs; + + LOG_ASSERT(leAudioDevice) + << __func__ << " Shouldn't be called without an active device."; + + do { + ase = leAudioDevice->GetFirstActiveAse(); + LOG_ASSERT(ase) << __func__ + << " shouldn't be called without an active ASE"; + do { + /* First is ase pair is Sink, second Source */ + auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(ase->cis_conn_hdl); + + /* Already in pending state - bi-directional CIS */ + if (ase->data_path_state == AudioStreamDataPathState::CIS_PENDING) + continue; + + if (ases_pair.sink) + ases_pair.sink->data_path_state = + AudioStreamDataPathState::CIS_PENDING; + if (ases_pair.source) + ases_pair.source->data_path_state = + AudioStreamDataPathState::CIS_PENDING; + + uint16_t acl_handle = + BTM_GetHCIConnHandle(leAudioDevice->address_, BT_TRANSPORT_LE); + conn_pairs.push_back({.cis_conn_handle = ase->cis_conn_hdl, + .acl_conn_handle = acl_handle}); + DLOG(INFO) << __func__ << " cis handle: " << +ase->cis_conn_hdl + << " acl handle : " << loghex(+acl_handle); + + } while ((ase = leAudioDevice->GetNextActiveAse(ase))); + } while ((leAudioDevice = group->GetNextActiveDevice(leAudioDevice))); + + IsoManager::GetInstance()->EstablishCis( + {.conn_pairs = std::move(conn_pairs)}); + } + + static void PrepareDataPath(const struct ase* ase) { + /* TODO Handle HW offloading as we handle here only HCI for now */ + bluetooth::hci::iso_manager::iso_data_path_params param = { + .data_path_dir = + ase->direction == le_audio::types::kLeAudioDirectionSink + ? bluetooth::hci::iso_manager::kIsoDataPathDirectionIn + : bluetooth::hci::iso_manager::kIsoDataPathDirectionOut, + .data_path_id = bluetooth::hci::iso_manager::kIsoDataPathHci, + .codec_id_format = ase->codec_id.coding_format, + .codec_id_company = ase->codec_id.vendor_company_id, + .codec_id_vendor = ase->codec_id.vendor_codec_id, + .controller_delay = 0x00000000, + .codec_conf = std::vector<uint8_t>(), + }; + IsoManager::GetInstance()->SetupIsoDataPath(ase->cis_conn_hdl, + std::move(param)); + } + + static inline void PrepareDataPath(LeAudioDeviceGroup* group) { + auto* leAudioDevice = group->GetFirstActiveDevice(); + LOG_ASSERT(leAudioDevice) + << __func__ << " Shouldn't be called without an active device."; + + auto* ase = leAudioDevice->GetFirstActiveAse(); + LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE"; + PrepareDataPath(ase); + } + + static void ReleaseDataPath(LeAudioDeviceGroup* group) { + LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice(); + LOG_ASSERT(leAudioDevice) + << __func__ << " Shouldn't be called without an active device."; + + auto ase = leAudioDevice->GetFirstActiveAseByDataPathState( + AudioStreamDataPathState::DATA_PATH_ESTABLISHED); + LOG_ASSERT(ase) << __func__ + << " Shouldn't be called without an active ASE."; + + auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(ase->cis_conn_hdl); + + IsoManager::GetInstance()->RemoveIsoDataPath( + ase->cis_conn_hdl, + (ases_pair.sink ? bluetooth::hci::iso_manager::kIsoDataPathDirectionOut + : 0x00) | + (ases_pair.source + ? bluetooth::hci::iso_manager::kIsoDataPathDirectionIn + : 0x00)); + } + + void AseStateMachineProcessIdle( + struct le_audio::client_parser::ascs::ase_rsp_hdr& arh, struct ase* ase, + LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice) { + switch (ase->state) { + case AseState::BTA_LE_AUDIO_ASE_STATE_IDLE: + if (ase->id == 0x00) { + /* Initial state of Ase - update id */ + LOG(INFO) << __func__ + << ", discovered ase id: " << static_cast<int>(arh.id); + ase->id = arh.id; + } + break; + case AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING: { + LeAudioDevice* leAudioDeviceNext; + ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_IDLE; + ase->active = false; + + if (!leAudioDevice->HaveAllActiveAsesSameState( + AseState::BTA_LE_AUDIO_ASE_STATE_IDLE)) { + /* More ASEs notification from this device has to come for this group + */ + return; + } + + leAudioDeviceNext = group->GetNextActiveDevice(leAudioDevice); + + /* Configure ASEs for next device in group */ + if (leAudioDeviceNext) { + PrepareAndSendRelease(leAudioDeviceNext); + } else { + /* Last node is in releasing state*/ + if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_); + + group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); + + state_machine_callbacks_->StatusReportCb(group->group_id_, + GroupStreamStatus::IDLE); + } + break; + } + default: + LOG(ERROR) << __func__ << ", invalid state transition, from: " + << static_cast<int>(ase->state) << ", to: " + << static_cast<int>(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); + StopStream(group); + break; + } + } + + void StartConfigQoSForTheGroup(LeAudioDeviceGroup* group) { + LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice(); + if (!leAudioDevice) { + LOG(ERROR) << __func__ << ", no active devices in group"; + StopStream(group); + return; + } + + PrepareAndSendConfigQos(group, leAudioDevice); + } + + void PrepareAndSendCodecConfigure(LeAudioDeviceGroup* group, + LeAudioDevice* leAudioDevice) { + struct le_audio::client_parser::ascs::ctp_codec_conf conf; + std::vector<struct le_audio::client_parser::ascs::ctp_codec_conf> confs; + struct ase* ase; + + ase = leAudioDevice->GetFirstActiveAse(); + LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE"; + do { + conf.ase_id = ase->id; + conf.target_latency = group->GetTargetLatency(); + conf.target_phy = group->GetTargetPhy(ase->direction); + conf.codec_id = ase->codec_id; + conf.codec_config = ase->codec_config; + confs.push_back(conf); + } while ((ase = leAudioDevice->GetNextActiveAse(ase))); + + std::vector<uint8_t> value; + le_audio::client_parser::ascs::PrepareAseCtpCodecConfig(confs, value); + BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_, + leAudioDevice->ctp_hdls_.val_hdl, value, + GATT_WRITE_NO_RSP, NULL, NULL); + } + + void AseStateMachineProcessCodecConfigured( + struct le_audio::client_parser::ascs::ase_rsp_hdr& arh, struct ase* ase, + uint8_t* data, uint16_t len, LeAudioDeviceGroup* group, + LeAudioDevice* leAudioDevice) { + if (!group) { + LOG(ERROR) << __func__ << ", leAudioDevice doesn't belong to any group"; + + return; + } + + /* ase contain current ASE state. New state is in "arh" */ + switch (ase->state) { + case AseState::BTA_LE_AUDIO_ASE_STATE_IDLE: { + if (ase->id == 0x00) { + /* Initial state of Ase - update id */ + LOG(INFO) << __func__ + << ", discovered ase id: " << static_cast<int>(arh.id); + ase->id = arh.id; + } + + LeAudioDevice* leAudioDeviceNext; + + struct le_audio::client_parser::ascs::ase_codec_configured_state_params + rsp; + + /* Cache codec configured status values for further + * configuration/reconfiguration + */ + if (!ParseAseStatusCodecConfiguredStateParams(rsp, len, data)) { + StopStream(group); + return; + } + ase->framing = rsp.framing; + ase->preferred_phy = rsp.preferred_phy; + ase->max_transport_latency = rsp.max_transport_latency; + ase->pres_delay_min = rsp.pres_delay_min; + ase->pres_delay_max = rsp.pres_delay_max; + ase->preferred_pres_delay_min = rsp.preferred_pres_delay_min; + ase->preferred_pres_delay_max = rsp.preferred_pres_delay_max; + ase->retrans_nb = rsp.preferred_retrans_nb; + + ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED; + + if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) { + /* This is autonomus change of the remote device */ + LOG(INFO) << __func__ << " Autonomus change. Just store it. "; + return; + } + + if (leAudioDevice->HaveAnyUnconfiguredAses()) { + /* More ASEs notification from this device has to come for this group + */ + return; + } + + if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { + /* We are here because of the reconnection of the single device. */ + PrepareAndSendConfigQos(group, leAudioDevice); + return; + } + + leAudioDeviceNext = group->GetNextActiveDevice(leAudioDevice); + + /* Configure ASEs for next device in group */ + if (leAudioDeviceNext) { + PrepareAndSendCodecConfigure(group, leAudioDeviceNext); + } else { + /* Last node configured, process group to codec configured state */ + group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED); + + if (group->GetTargetState() == + AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { + StartConfigQoSForTheGroup(group); + return; + } else { + LOG(ERROR) << __func__ << ", invalid state transition, from: " + << group->GetState() + << ", to: " << group->GetTargetState(); + StopStream(group); + return; + } + } + + break; + } + case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED: { + /* Received Configured in Configured state. This could be done + * autonomously because of the reconfiguration done by us + */ + + struct le_audio::client_parser::ascs::ase_codec_configured_state_params + rsp; + + /* Cache codec configured status values for further + * configuration/reconfiguration + */ + if (!ParseAseStatusCodecConfiguredStateParams(rsp, len, data)) { + StopStream(group); + return; + } + + ase->framing = rsp.framing; + ase->preferred_phy = rsp.preferred_phy; + ase->max_transport_latency = rsp.max_transport_latency; + ase->pres_delay_min = rsp.pres_delay_min; + ase->pres_delay_max = rsp.pres_delay_max; + ase->preferred_pres_delay_min = rsp.preferred_pres_delay_min; + ase->preferred_pres_delay_max = rsp.preferred_pres_delay_max; + ase->retrans_nb = rsp.preferred_retrans_nb; + + /* This may be a notification from a re-configured ASE */ + ase->reconfigure = false; + + if (leAudioDevice->HaveAnyUnconfiguredAses()) { + /* Waiting for others to be reconfigured */ + return; + } + + if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { + /* We are here because of the reconnection of the single device. */ + PrepareAndSendConfigQos(group, leAudioDevice); + return; + } + + LeAudioDevice* leAudioDeviceNext = + group->GetNextActiveDevice(leAudioDevice); + + /* Configure ASEs for next device in group */ + if (leAudioDeviceNext) { + PrepareAndSendCodecConfigure(group, leAudioDeviceNext); + } else { + /* Last node configured, process group to codec configured state */ + group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED); + + if (group->GetTargetState() == + AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { + StartConfigQoSForTheGroup(group); + return; + } else { + LOG(ERROR) << __func__ + << ", Autonomouse change ?: " << group->GetState() + << ", to: " << group->GetTargetState(); + } + } + + break; + } + case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED: + /* TODO: Config Codec */ + break; + case AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING: + LeAudioDevice* leAudioDeviceNext; + ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED; + ase->active = false; + + if (!leAudioDevice->HaveAllActiveAsesSameState( + AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED)) { + /* More ASEs notification from this device has to come for this group + */ + return; + } + + leAudioDeviceNext = group->GetNextActiveDevice(leAudioDevice); + + /* Configure ASEs for next device in group */ + if (leAudioDeviceNext) { + PrepareAndSendRelease(leAudioDeviceNext); + } else { + /* Last node is in releasing state*/ + if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_); + + group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED); + /* Remote device has cache and keep staying in configured state after + * release. Therefore, we assume this is a target state requested by + * remote device. + */ + group->SetTargetState(group->GetState()); + state_machine_callbacks_->StatusReportCb(group->group_id_, + GroupStreamStatus::IDLE); + } + break; + default: + LOG(ERROR) << __func__ << ", invalid state transition, from: " + << static_cast<int>(ase->state) << ", to: " + << static_cast<int>( + AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED); + StopStream(group); + break; + } + } + + void AseStateMachineProcessQosConfigured( + struct le_audio::client_parser::ascs::ase_rsp_hdr& arh, struct ase* ase, + LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice) { + if (!group) { + LOG(ERROR) << __func__ << ", leAudioDevice doesn't belong to any group"; + + return; + } + + switch (ase->state) { + case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED: { + ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED; + + if (!leAudioDevice->HaveAllActiveAsesSameState( + AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED)) { + /* More ASEs notification from this device has to come for this group + */ + return; + } + + if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { + /* We are here because of the reconnection of the single device. */ + PrepareAndSendEnable(leAudioDevice); + return; + } + + LeAudioDevice* leAudioDeviceNext = + group->GetNextActiveDevice(leAudioDevice); + + /* Configure ASEs qos for next device in group */ + if (leAudioDeviceNext) { + PrepareAndSendConfigQos(group, leAudioDeviceNext); + } else { + CigCreate(group); + } + + break; + } + case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED: + /* TODO: Config Codec error/Config Qos/Config QoS error/Enable error */ + break; + case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING: + if (ase->direction == le_audio::types::kLeAudioDirectionSource) { + /* Source ASE cannot go from Streaming to QoS Configured state */ + LOG(ERROR) << __func__ << ", invalid state transition, from: " + << static_cast<int>(ase->state) << ", to: " + << static_cast<int>( + AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED); + StopStream(group); + return; + } + + ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED; + + /* Process the Disable Transition of the rest of group members if no + * more ASE notifications has to come from this device. */ + if (leAudioDevice->IsReadyToSuspendStream()) + ProcessGroupDisable(group, leAudioDevice); + + break; + + case AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING: { + ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED; + + /* More ASEs notification from this device has to come for this group */ + if (!group->HaveAllActiveDevicesAsesTheSameState( + AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED)) + return; + + group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED); + + if (!group->HaveAllActiveDevicesCisDisc()) return; + + if (group->GetTargetState() == + AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) { + /* No more transition for group */ + alarm_cancel(watchdog_); + + state_machine_callbacks_->StatusReportCb( + group->group_id_, GroupStreamStatus::SUSPENDED); + } else { + LOG(ERROR) << __func__ << ", invalid state transition, from: " + << group->GetState() + << ", to: " << group->GetTargetState(); + StopStream(group); + return; + } + break; + } + default: + LOG(ERROR) << __func__ << ", invalid state transition, from: " + << static_cast<int>(ase->state) << ", to: " + << static_cast<int>( + AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED); + StopStream(group); + break; + } + } + + void PrepareAndSendEnable(LeAudioDevice* leAudioDevice) { + struct le_audio::client_parser::ascs::ctp_enable conf; + std::vector<struct le_audio::client_parser::ascs::ctp_enable> confs; + std::vector<uint8_t> value; + struct ase* ase; + + ase = leAudioDevice->GetFirstActiveAse(); + LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE"; + do { + conf.ase_id = ase->id; + conf.metadata = ase->metadata; + confs.push_back(conf); + } while ((ase = leAudioDevice->GetNextActiveAse(ase))); + + le_audio::client_parser::ascs::PrepareAseCtpEnable(confs, value); + + BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_, + leAudioDevice->ctp_hdls_.val_hdl, value, + GATT_WRITE_NO_RSP, NULL, NULL); + } + + void PrepareAndSendDisable(LeAudioDevice* leAudioDevice) { + ase* ase = leAudioDevice->GetFirstActiveAse(); + LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE"; + + std::vector<uint8_t> ids; + do { + ids.push_back(ase->id); + } while ((ase = leAudioDevice->GetNextActiveAse(ase))); + + std::vector<uint8_t> value; + le_audio::client_parser::ascs::PrepareAseCtpDisable(ids, value); + + BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_, + leAudioDevice->ctp_hdls_.val_hdl, value, + GATT_WRITE_NO_RSP, NULL, NULL); + } + + void PrepareAndSendRelease(LeAudioDevice* leAudioDevice) { + ase* ase = leAudioDevice->GetFirstActiveAse(); + LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE"; + + std::vector<uint8_t> ids; + do { + ids.push_back(ase->id); + } while ((ase = leAudioDevice->GetNextActiveAse(ase))); + + std::vector<uint8_t> value; + le_audio::client_parser::ascs::PrepareAseCtpRelease(ids, value); + + BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_, + leAudioDevice->ctp_hdls_.val_hdl, value, + GATT_WRITE_NO_RSP, NULL, NULL); + } + + void PrepareAndSendConfigQos(LeAudioDeviceGroup* group, + LeAudioDevice* leAudioDevice) { + std::vector<struct le_audio::client_parser::ascs::ctp_qos_conf> confs; + + for (struct ase* ase = leAudioDevice->GetFirstActiveAse(); ase != nullptr; + ase = leAudioDevice->GetNextActiveAse(ase)) { + /* Get completive (to be bi-directional CIS) CIS ID for ASE */ + uint8_t cis_id = leAudioDevice->GetMatchingBidirectionCisId(ase); + if (cis_id == le_audio::kInvalidCisId) { + /* Get next free CIS ID for group */ + cis_id = group->GetFirstFreeCisId(); + if (cis_id == le_audio::kInvalidCisId) { + LOG(ERROR) << __func__ << ", failed to get free CIS ID"; + StopStream(group); + return; + } + } + + ase->cis_id = cis_id; + + /* TODO: Configure first ASE qos according to context type */ + struct le_audio::client_parser::ascs::ctp_qos_conf conf; + conf.ase_id = ase->id; + conf.cig = group->group_id_; + conf.cis = ase->cis_id; + conf.framing = group->GetFraming(); + conf.phy = group->GetPhyBitmask(ase->direction); + conf.max_sdu = ase->max_sdu_size; + conf.retrans_nb = ase->retrans_nb; + if (!group->GetPresentationDelay(&conf.pres_delay, ase->direction)) { + LOG(ERROR) << __func__ << ", inconsistent presentation delay for group"; + StopStream(group); + return; + } + + conf.sdu_interval = group->GetSduInterval(ase->direction); + if (!conf.sdu_interval) { + LOG(ERROR) << __func__ << ", unsupported SDU interval for group"; + StopStream(group); + return; + } + + if (ase->direction == le_audio::types::kLeAudioDirectionSink) { + conf.max_transport_latency = group->GetMaxTransportLatencyMtos(); + } else { + conf.max_transport_latency = group->GetMaxTransportLatencyStom(); + } + confs.push_back(conf); + } + + LOG_ASSERT(confs.size() > 0) + << __func__ << " shouldn't be called without an active ASE"; + + std::vector<uint8_t> value; + le_audio::client_parser::ascs::PrepareAseCtpConfigQos(confs, value); + + BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_, + leAudioDevice->ctp_hdls_.val_hdl, value, + GATT_WRITE_NO_RSP, NULL, NULL); + } + + void AseStateMachineProcessEnabling( + struct le_audio::client_parser::ascs::ase_rsp_hdr& arh, struct ase* ase, + LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice) { + if (!group) { + LOG(ERROR) << __func__ << ", leAudioDevice doesn't belong to any group"; + return; + } + + if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { + /* We are here because of the reconnection of the single device. */ + CisCreateForDevice(leAudioDevice); + return; + } + + switch (ase->state) { + case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED: + ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING; + + if (leAudioDevice->IsReadyToCreateStream()) + ProcessGroupEnable(group, leAudioDevice); + + break; + + case AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING: + /* Enable/Switch Content */ + break; + default: + LOG(ERROR) << __func__ << ", invalid state transition, from: " + << static_cast<int>(ase->state) << ", to: " + << static_cast<int>( + AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING); + StopStream(group); + break; + } + } + + void AseStateMachineProcessStreaming( + struct le_audio::client_parser::ascs::ase_rsp_hdr& arh, struct ase* ase, + LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice) { + if (!group) { + LOG(ERROR) << __func__ << ", leAudioDevice doesn't belong to any group"; + + return; + } + + switch (ase->state) { + case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED: + /* As per ASCS 1.0 : + * If a CIS has been established and the server is acting as Audio Sink + * for the ASE, and if the server is ready to receive audio data + * transmitted by the client, the server may autonomously initiate the + * Receiver Start Ready, as defined in Section 5.4, without first + * sending a notification of the ASE characteristic value in the + * Enabling state. + */ + if (ase->direction != le_audio::types::kLeAudioDirectionSink) { + LOG(ERROR) << __func__ << ", invalid state transition, from: " + << static_cast<int>(ase->state) << ", to: " + << static_cast<int>( + AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); + StopStream(group); + return; + } + + ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING; + + if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { + /* We are here because of the reconnection of the single device. */ + CisCreateForDevice(leAudioDevice); + return; + } + + if (leAudioDevice->IsReadyToCreateStream()) + ProcessGroupEnable(group, leAudioDevice); + + break; + + case AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING: { + std::vector<uint8_t> value; + + ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING; + + if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { + /* We are here because of the reconnection of the single device. */ + auto* stream_conf = &group->stream_conf; + if (ase->direction == le_audio::types::kLeAudioDirectionSource) { + stream_conf->source_streams.emplace_back(std::make_pair( + ase->cis_conn_hdl, ase->codec_config.audio_channel_allocation)); + } else { + stream_conf->sink_streams.emplace_back(std::make_pair( + ase->cis_conn_hdl, ase->codec_config.audio_channel_allocation)); + } + } + + if (!group->HaveAllActiveDevicesAsesTheSameState( + AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)) { + /* More ASEs notification form this device has to come for this group + */ + + return; + } + + if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { + /* We are here because of the reconnection of the single device. */ + return; + } + + /* Last node is in streaming state */ + group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); + + /* Not all CISes establish evens came */ + if (!group->IsGroupStreamReady()) return; + + if (group->GetTargetState() == + AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { + /* No more transition for group */ + alarm_cancel(watchdog_); + PrepareDataPath(group); + + return; + } else { + LOG(ERROR) << __func__ << ", invalid state transition, from: " + << group->GetState() + << ", to: " << group->GetTargetState(); + StopStream(group); + return; + } + + break; + } + case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING: + /* TODO: Update metadata/Enable */ + break; + default: + LOG(ERROR) << __func__ << ", invalid state transition, from: " + << static_cast<int>(ase->state) << ", to: " + << static_cast<int>( + AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); + StopStream(group); + break; + } + } + + void AseStateMachineProcessDisabling( + struct le_audio::client_parser::ascs::ase_rsp_hdr& arh, struct ase* ase, + LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice) { + if (!group) { + LOG(ERROR) << __func__ << ", leAudioDevice doesn't belong to any group"; + + return; + } + + if (ase->direction == le_audio::types::kLeAudioDirectionSink) { + /* Sink ASE state machine does not have Disabling state */ + LOG(ERROR) << __func__ + << ", invalid state transition, from: " << group->GetState() + << ", to: " << group->GetTargetState(); + StopStream(group); + return; + } + + switch (ase->state) { + case AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING: + /* TODO: Disable */ + break; + case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING: + ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING; + + /* Process the Disable Transition of the rest of group members if no + * more ASE notifications has to come from this device. */ + if (leAudioDevice->IsReadyToSuspendStream()) + ProcessGroupDisable(group, leAudioDevice); + + break; + + default: + LOG(ERROR) << __func__ << ", invalid state transition, from: " + << static_cast<int>(ase->state) << ", to: " + << static_cast<int>( + AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING); + StopStream(group); + break; + } + } + + void AseStateMachineProcessReleasing( + struct le_audio::client_parser::ascs::ase_rsp_hdr& arh, struct ase* ase, + LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice) { + if (!group) { + LOG(ERROR) << __func__ << ", leAudioDevice doesn't belong to any group"; + + return; + } + + switch (ase->state) { + case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED: + case AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING: { + ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING; + break; + } + case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED: + /* At this point all of the active ASEs within group are released. */ + if (group->cig_created_) + IsoManager::GetInstance()->RemoveCig(group->group_id_); + + ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING; + if (group->HaveAllActiveDevicesAsesTheSameState( + AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING)) + group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING); + + break; + + case AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING: + case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING: { + ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING; + + /* Since single ase gets into Releasing state, lets assume our new + * target state is IDLE + */ + SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); + + /* Happens when bi-directional completive ASE releasing state came */ + if (ase->data_path_state == AudioStreamDataPathState::CIS_DISCONNECTING) + break; + + if (ase->data_path_state == + AudioStreamDataPathState::DATA_PATH_ESTABLISHED) { + auto ases_pair = + leAudioDevice->GetAsesByCisConnHdl(ase->cis_conn_hdl); + IsoManager::GetInstance()->RemoveIsoDataPath( + ase->cis_conn_hdl, + (ases_pair.sink + ? bluetooth::hci::iso_manager::kIsoDataPathDirectionOut + : 0x00) | + (ases_pair.source + ? bluetooth::hci::iso_manager::kIsoDataPathDirectionIn + : 0x00)); + } else if (ase->data_path_state == + AudioStreamDataPathState::CIS_ESTABLISHED || + ase->data_path_state == + AudioStreamDataPathState::CIS_PENDING) { + IsoManager::GetInstance()->DisconnectCis(ase->cis_conn_hdl, + HCI_ERR_PEER_USER); + } else { + DLOG(INFO) << __func__ << ", Nothing to do ase data path state: " + << static_cast<int>(ase->data_path_state); + } + break; + } + default: + LOG(ERROR) << __func__ << ", invalid state transition, from: " + << static_cast<int>(ase->state) << ", to: " + << static_cast<int>( + AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING); + break; + } + } + + void ProcessGroupEnable(LeAudioDeviceGroup* group, LeAudioDevice* device) { + /* Enable ASEs for next device in group. */ + LeAudioDevice* deviceNext = group->GetNextActiveDevice(device); + if (deviceNext) { + PrepareAndSendEnable(deviceNext); + return; + } + + /* At this point all of the active ASEs within group are enabled. The server + * might perform autonomous state transition for Sink ASE and skip Enabling + * state notification and transit to Streaming directly. So check the group + * state, because we might be ready to create CIS. */ + if (group->HaveAllActiveDevicesAsesTheSameState( + AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)) { + group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); + } else { + group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING); + } + + if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { + CisCreate(group); + } else { + LOG(ERROR) << __func__ + << ", invalid state transition, from: " << group->GetState() + << ", to: " << group->GetTargetState(); + StopStream(group); + } + } + + void ProcessGroupDisable(LeAudioDeviceGroup* group, LeAudioDevice* device) { + /* Disable ASEs for next device in group. */ + LeAudioDevice* deviceNext = group->GetNextActiveDevice(device); + if (deviceNext) { + PrepareAndSendDisable(deviceNext); + return; + } + + /* At this point all of the active ASEs within group are disabled. As there + * is no Disabling state for Sink ASE, it might happen that all of the + * active ASEs are Sink ASE and will transit to QoS state. So check + * the group state, because we might be ready to release data path. */ + if (group->HaveAllActiveDevicesAsesTheSameState( + AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED)) { + group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED); + } else { + group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING); + } + + /* Transition to QoS configured is done by CIS disconnection */ + if (group->GetTargetState() == + AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) { + ReleaseDataPath(group); + } else { + LOG(ERROR) << __func__ + << ", invalid state transition, from: " << group->GetState() + << ", to: " << group->GetTargetState(); + StopStream(group); + } + } +}; +} // namespace + +namespace le_audio { +void LeAudioGroupStateMachine::Initialize(Callbacks* state_machine_callbacks_) { + if (instance) { + LOG(ERROR) << "Already initialized"; + return; + } + + instance = new LeAudioGroupStateMachineImpl(state_machine_callbacks_); +} + +void LeAudioGroupStateMachine::Cleanup() { + if (!instance) return; + + LeAudioGroupStateMachineImpl* ptr = instance; + instance = nullptr; + + delete ptr; +} + +LeAudioGroupStateMachine* LeAudioGroupStateMachine::Get() { + CHECK(instance); + return instance; +} +} // namespace le_audio diff --git a/system/bta/le_audio/state_machine.h b/system/bta/le_audio/state_machine.h new file mode 100644 index 0000000000000000000000000000000000000000..21365e89e4207a038c022fff3a5d9b8c8d29f500 --- /dev/null +++ b/system/bta/le_audio/state_machine.h @@ -0,0 +1,87 @@ +/* + * Copyright 2020 HIMSA II K/S - www.himsa.com. Represented by EHIMA - + * www.ehima.com + * + * 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 <vector> + +#include "btm_iso_api.h" +#include "btm_iso_api_types.h" +#include "devices.h" +#include "hardware/bt_le_audio.h" +#include "le_audio_types.h" + +namespace le_audio { + +/* State machine interface */ +class LeAudioGroupStateMachine { + public: + class Callbacks { + public: + virtual ~Callbacks() = default; + + virtual void StatusReportCb( + int group_id, bluetooth::le_audio::GroupStreamStatus status) = 0; + virtual void OnStateTransitionTimeout(int group_id) = 0; + }; + + virtual ~LeAudioGroupStateMachine() = default; + + static void Initialize(Callbacks* state_machine_callbacks); + static void Cleanup(void); + static LeAudioGroupStateMachine* Get(void); + + virtual bool AttachToStream(LeAudioDeviceGroup* group, + LeAudioDevice* leAudioDevice) = 0; + virtual bool StartStream(LeAudioDeviceGroup* group, + types::LeAudioContextType context_type) = 0; + virtual void SuspendStream(LeAudioDeviceGroup* group) = 0; + virtual void StopStream(LeAudioDeviceGroup* group) = 0; + virtual void ProcessGattNotifEvent(uint8_t* value, uint16_t len, + struct types::ase* ase, + LeAudioDevice* leAudioDevice, + LeAudioDeviceGroup* group) = 0; + + virtual void ProcessHciNotifOnCigCreate( + LeAudioDeviceGroup* group, uint8_t status, uint8_t cig_id, + std::vector<uint16_t> conn_handles) = 0; + virtual void ProcessHciNotifOnCigRemove(uint8_t status, + LeAudioDeviceGroup* group) = 0; + virtual void ProcessHciNotifCisEstablished( + LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice, + const bluetooth::hci::iso_manager::cis_establish_cmpl_evt* event) = 0; + virtual void ProcessHciNotifCisDisconnected( + LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice, + const bluetooth::hci::iso_manager::cis_disconnected_evt* event) = 0; + virtual void ProcessHciNotifSetupIsoDataPath(LeAudioDeviceGroup* group, + LeAudioDevice* leAudioDevice, + uint8_t status, + uint16_t conn_hdl) = 0; + virtual void ProcessHciNotifRemoveIsoDataPath(LeAudioDeviceGroup* group, + LeAudioDevice* leAudioDevice, + uint8_t status, + uint16_t conn_hdl) = 0; + virtual void ProcessHciNotifIsoLinkQualityRead( + LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice, + uint8_t conn_handle, uint32_t txUnackedPackets, uint32_t txFlushedPackets, + uint32_t txLastSubeventPackets, uint32_t retransmittedPackets, + uint32_t crcErrorPackets, uint32_t rxUnreceivedPackets, + uint32_t duplicatePackets) = 0; + virtual void ProcessHciNotifAclDisconnected(LeAudioDeviceGroup* group, + LeAudioDevice* leAudioDevice) = 0; +}; +} // namespace le_audio diff --git a/system/bta/le_audio/state_machine_test.cc b/system/bta/le_audio/state_machine_test.cc new file mode 100644 index 0000000000000000000000000000000000000000..9bd339bc6570cbaea5bf78b79a22dc4e2bd9ccf5 --- /dev/null +++ b/system/bta/le_audio/state_machine_test.cc @@ -0,0 +1,2108 @@ +/* + * Copyright 2021 HIMSA II K/S - www.himsa.com. Represented by EHIMA - + * www.ehima.com + * + * 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 "state_machine.h" + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include <functional> + +#include "bta_gatt_api_mock.h" +#include "bta_gatt_queue_mock.h" +#include "btm_api_mock.h" +#include "client_parser.h" +#include "fake_osi.h" +#include "mock_controller.h" +#include "mock_iso_manager.h" +#include "types/bt_transport.h" + +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::AtLeast; +using ::testing::DoAll; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::SaveArg; +using ::testing::Test; + +std::map<std::string, int> mock_function_count_map; + +extern struct fake_osi_alarm_set_on_mloop fake_osi_alarm_set_on_mloop_; + +namespace le_audio { +namespace internal { + +// Just some arbitrary initial handles - it has no real meaning +#define ATTR_HANDLE_ASCS_POOL_START (0x0000 | 32) +#define ATTR_HANDLE_PACS_POOL_START (0xFF00 | 64) + +constexpr uint16_t kContextTypeUnspecified = 0x0001; +constexpr uint16_t kContextTypeConversational = 0x0002; +constexpr uint16_t kContextTypeMedia = 0x0004; +// constexpr uint16_t kContextTypeInstructional = 0x0008; +// constexpr uint16_t kContextTypeAttentionSeeking = 0x0010; +// constexpr uint16_t kContextTypeImmediateAllert = 0x0020; +// constexpr uint16_t kContextTypeManMachine = 0x0040; +// constexpr uint16_t kContextTypeEmergencyAlert = 0x0080; +constexpr uint16_t kContextTypeRingtone = 0x0100; +// constexpr uint16_t kContextTypeTV = 0x0200; +// constexpr uint16_t kContextTypeRFULive = 0x0400; + +namespace codec_specific { + +constexpr uint8_t kLc3CodingFormat = 0x06; + +// Reference Codec Capabilities values to test against +constexpr uint8_t kCapTypeSupportedSamplingFrequencies = 0x01; +constexpr uint8_t kCapTypeSupportedFrameDurations = 0x02; +constexpr uint8_t kCapTypeAudioChannelCount = 0x03; +constexpr uint8_t kCapTypeSupportedOctetsPerCodecFrame = 0x04; +// constexpr uint8_t kCapTypeSupportedLc3CodecFramesPerSdu = 0x05; + +// constexpr uint8_t kCapSamplingFrequency8000Hz = 0x0001; +// constexpr uint8_t kCapSamplingFrequency11025Hz = 0x0002; +constexpr uint8_t kCapSamplingFrequency16000Hz = 0x0004; +// constexpr uint8_t kCapSamplingFrequency22050Hz = 0x0008; +// constexpr uint8_t kCapSamplingFrequency24000Hz = 0x0010; +// constexpr uint8_t kCapSamplingFrequency32000Hz = 0x0020; +// constexpr uint8_t kCapSamplingFrequency44100Hz = 0x0040; +// constexpr uint8_t kCapSamplingFrequency48000Hz = 0x0080; +// constexpr uint8_t kCapSamplingFrequency88200Hz = 0x0100; +// constexpr uint8_t kCapSamplingFrequency96000Hz = 0x0200; +// constexpr uint8_t kCapSamplingFrequency176400Hz = 0x0400; +// constexpr uint8_t kCapSamplingFrequency192000Hz = 0x0800; +// constexpr uint8_t kCapSamplingFrequency384000Hz = 0x1000; + +constexpr uint8_t kCapFrameDuration7p5ms = 0x01; +constexpr uint8_t kCapFrameDuration10ms = 0x02; +// constexpr uint8_t kCapFrameDuration7p5msPreferred = 0x10; +constexpr uint8_t kCapFrameDuration10msPreferred = 0x20; +} // namespace codec_specific + +namespace ascs { +constexpr uint8_t kAseStateIdle = 0x00; +constexpr uint8_t kAseStateCodecConfigured = 0x01; +constexpr uint8_t kAseStateQoSConfigured = 0x02; +constexpr uint8_t kAseStateEnabling = 0x03; +constexpr uint8_t kAseStateStreaming = 0x04; +constexpr uint8_t kAseStateDisabling = 0x05; +constexpr uint8_t kAseStateReleasing = 0x06; + +// constexpr uint8_t kAseParamDirectionServerIsAudioSink = 0x01; +// constexpr uint8_t kAseParamDirectionServerIsAudioSource = 0x02; + +constexpr uint8_t kAseParamFramingUnframedSupported = 0x00; +// constexpr uint8_t kAseParamFramingUnframedNotSupported = 0x01; + +// constexpr uint8_t kAseParamPreferredPhy1M = 0x01; +// constexpr uint8_t kAseParamPreferredPhy2M = 0x02; +// constexpr uint8_t kAseParamPreferredPhyCoded = 0x04; + +constexpr uint8_t kAseCtpOpcodeConfigureCodec = 0x01; +constexpr uint8_t kAseCtpOpcodeConfigureQos = 0x02; +constexpr uint8_t kAseCtpOpcodeEnable = 0x03; +constexpr uint8_t kAseCtpOpcodeReceiverStartReady = 0x04; +constexpr uint8_t kAseCtpOpcodeDisable = 0x05; +constexpr uint8_t kAseCtpOpcodeReceiverStopReady = 0x06; +// constexpr uint8_t kAseCtpOpcodeUpdateMetadata = 0x07; +constexpr uint8_t kAseCtpOpcodeRelease = 0x08; +constexpr uint8_t kAseCtpOpcodeMaxVal = kAseCtpOpcodeRelease; + +} // namespace ascs + +static RawAddress GetTestAddress(uint8_t index) { + return {{0xC0, 0xDE, 0xC0, 0xDE, 0x00, index}}; +} + +static uint8_t ase_id_last_assigned; + +class MockLeAudioGroupStateMachineCallbacks + : public LeAudioGroupStateMachine::Callbacks { + public: + MockLeAudioGroupStateMachineCallbacks() = default; + ~MockLeAudioGroupStateMachineCallbacks() override = default; + MOCK_METHOD((void), StatusReportCb, + (int group_id, bluetooth::le_audio::GroupStreamStatus status), + (override)); + MOCK_METHOD((void), OnStateTransitionTimeout, (int group_id), (override)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockLeAudioGroupStateMachineCallbacks); +}; + +class StateMachineTest : public Test { + protected: + void SetUp() override { + mock_function_count_map.clear(); + controller::SetMockControllerInterface(&mock_controller_); + bluetooth::manager::SetMockBtmInterface(&btm_interface); + gatt::SetMockBtaGattInterface(&gatt_interface); + gatt::SetMockBtaGattQueue(&gatt_queue); + + ase_id_last_assigned = types::ase::kAseIdInvalid; + LeAudioGroupStateMachine::Initialize(&mock_callbacks_); + + // Support 2M Phy + ON_CALL(mock_controller_, SupportsBle2mPhy()).WillByDefault(Return(true)); + ON_CALL(btm_interface, IsPhy2mSupported(_, _)).WillByDefault(Return(true)); + ON_CALL(btm_interface, GetHCIConnHandle(_, _)) + .WillByDefault( + Invoke([](RawAddress const& remote_bda, tBT_TRANSPORT transport) { + return remote_bda.IsEmpty() + ? HCI_INVALID_HANDLE + : ((uint16_t)(remote_bda.address[0] ^ + remote_bda.address[1] ^ + remote_bda.address[2])) + << 8 || + (remote_bda.address[3] ^ remote_bda.address[4] ^ + remote_bda.address[5]); + })); + + ON_CALL(gatt_queue, WriteCharacteristic(_, _, _, GATT_WRITE_NO_RSP, _, _)) + .WillByDefault(Invoke([this](uint16_t conn_id, uint16_t handle, + std::vector<uint8_t> value, + tGATT_WRITE_TYPE write_type, + GATT_WRITE_OP_CB cb, void* cb_data) { + for (auto& dev : le_audio_devices_) { + if (dev->conn_id_ == conn_id) { + // Control point write handler + if (dev->ctp_hdls_.val_hdl == handle) { + HandleCtpOperation(dev.get(), value, cb, cb_data); + } + break; + } + } + })); + + ConfigureIsoManagerMock(); + } + + void HandleCtpOperation(LeAudioDevice* device, std::vector<uint8_t> value, + GATT_WRITE_OP_CB cb, void* cb_data) { + auto opcode = value[0]; + + // Verify against valid opcode range + ASSERT_LT(opcode, ascs::kAseCtpOpcodeMaxVal + 1); + ASSERT_NE(opcode, 0); + + if (ase_ctp_handlers[opcode]) + ase_ctp_handlers[opcode](device, std::move(value), cb, cb_data); + } + +/* Helper function to make a deterministic (and unique on the entire device) + * connection handle for a given cis. + */ +#define UNIQUE_CIS_CONN_HANDLE(cig_id, cis_index) (cig_id << 8 | cis_index) + + void ConfigureIsoManagerMock() { + iso_manager_ = bluetooth::hci::IsoManager::GetInstance(); + ASSERT_NE(iso_manager_, nullptr); + iso_manager_->Start(); + + mock_iso_manager_ = MockIsoManager::GetInstance(); + ASSERT_NE(mock_iso_manager_, nullptr); + + ON_CALL(*mock_iso_manager_, CreateCig) + .WillByDefault( + [this](uint8_t cig_id, + bluetooth::hci::iso_manager::cig_create_params p) { + DLOG(INFO) << "CreateCig"; + + auto& group = le_audio_device_groups_[cig_id]; + if (group) { + std::vector<uint16_t> conn_handles; + // Fake connection ID for each cis in a request + for (auto i = 0u; i < p.cis_cfgs.size(); ++i) { + conn_handles.push_back(UNIQUE_CIS_CONN_HANDLE(cig_id, i)); + } + LeAudioGroupStateMachine::Get()->ProcessHciNotifOnCigCreate( + group.get(), 0, cig_id, conn_handles); + } + }); + + ON_CALL(*mock_iso_manager_, RemoveCig) + .WillByDefault([this](uint8_t cig_id) { + DLOG(INFO) << "CreateRemove"; + + auto& group = le_audio_device_groups_[cig_id]; + if (group) { + // Fake connection ID for each cis in a request + LeAudioGroupStateMachine::Get()->ProcessHciNotifOnCigRemove( + 0, group.get()); + } + }); + + ON_CALL(*mock_iso_manager_, SetupIsoDataPath) + .WillByDefault([this](uint16_t conn_handle, + bluetooth::hci::iso_manager::iso_data_path_params + p) { + DLOG(INFO) << "SetupIsoDataPath"; + + auto dev_it = + std::find_if(le_audio_devices_.begin(), le_audio_devices_.end(), + [&conn_handle](auto& dev) { + auto ases = dev->GetAsesByCisConnHdl(conn_handle); + return (ases.sink || ases.source); + }); + if (dev_it == le_audio_devices_.end()) { + DLOG(ERROR) << "Device not found"; + return; + } + + for (auto& kv_pair : le_audio_device_groups_) { + auto& group = kv_pair.second; + if (group->IsDeviceInTheGroup(dev_it->get())) { + LeAudioGroupStateMachine::Get()->ProcessHciNotifSetupIsoDataPath( + group.get(), dev_it->get(), 0, conn_handle); + return; + } + } + }); + + ON_CALL(*mock_iso_manager_, RemoveIsoDataPath) + .WillByDefault([this](uint16_t conn_handle, uint8_t iso_direction) { + DLOG(INFO) << "RemoveIsoDataPath"; + + auto dev_it = + std::find_if(le_audio_devices_.begin(), le_audio_devices_.end(), + [&conn_handle](auto& dev) { + auto ases = dev->GetAsesByCisConnHdl(conn_handle); + return (ases.sink || ases.source); + }); + if (dev_it == le_audio_devices_.end()) { + DLOG(ERROR) << "Device not found"; + return; + } + + for (auto& kv_pair : le_audio_device_groups_) { + auto& group = kv_pair.second; + if (group->IsDeviceInTheGroup(dev_it->get())) { + LeAudioGroupStateMachine::Get()->ProcessHciNotifRemoveIsoDataPath( + group.get(), dev_it->get(), 0, conn_handle); + return; + } + } + }); + + ON_CALL(*mock_iso_manager_, EstablishCis) + .WillByDefault([this](bluetooth::hci::iso_manager::cis_establish_params + conn_params) { + DLOG(INFO) << "EstablishCis"; + + for (auto& pair : conn_params.conn_pairs) { + auto dev_it = std::find_if( + le_audio_devices_.begin(), le_audio_devices_.end(), + [&pair](auto& dev) { + auto ases = dev->GetAsesByCisConnHdl(pair.cis_conn_handle); + return (ases.sink || ases.source); + }); + if (dev_it == le_audio_devices_.end()) { + DLOG(ERROR) << "Device not found"; + return; + } + + for (auto& kv_pair : le_audio_device_groups_) { + auto& group = kv_pair.second; + if (group->IsDeviceInTheGroup(dev_it->get())) { + bluetooth::hci::iso_manager::cis_establish_cmpl_evt evt; + + // Fill proper values if needed + evt.status = 0x00; + evt.cig_id = group->group_id_; + evt.cis_conn_hdl = pair.cis_conn_handle; + evt.cig_sync_delay = 0; + evt.cis_sync_delay = 0; + evt.trans_lat_mtos = 0; + evt.trans_lat_stom = 0; + evt.phy_mtos = 0; + evt.phy_stom = 0; + evt.nse = 0; + evt.bn_mtos = 0; + evt.bn_stom = 0; + evt.ft_mtos = 0; + evt.ft_stom = 0; + evt.max_pdu_mtos = 0; + evt.max_pdu_stom = 0; + evt.iso_itv = 0; + + LeAudioGroupStateMachine::Get()->ProcessHciNotifCisEstablished( + group.get(), dev_it->get(), &evt); + break; + } + } + } + }); + + ON_CALL(*mock_iso_manager_, DisconnectCis) + .WillByDefault([this](uint16_t cis_handle, uint8_t reason) { + DLOG(INFO) << "DisconnectCis"; + + auto dev_it = + std::find_if(le_audio_devices_.begin(), le_audio_devices_.end(), + [&cis_handle](auto& dev) { + auto ases = dev->GetAsesByCisConnHdl(cis_handle); + return (ases.sink || ases.source); + }); + if (dev_it == le_audio_devices_.end()) { + DLOG(ERROR) << "Device not found"; + return; + } + + for (auto& kv_pair : le_audio_device_groups_) { + auto& group = kv_pair.second; + if (group->IsDeviceInTheGroup(dev_it->get())) { + bluetooth::hci::iso_manager::cis_disconnected_evt evt{ + .reason = reason, + .cig_id = static_cast<uint8_t>(group->group_id_), + .cis_conn_hdl = cis_handle, + }; + LeAudioGroupStateMachine::Get()->ProcessHciNotifCisDisconnected( + group.get(), dev_it->get(), &evt); + return; + } + } + }); + } + + void TearDown() override { + iso_manager_->Stop(); + mock_iso_manager_ = nullptr; + + gatt::SetMockBtaGattQueue(nullptr); + gatt::SetMockBtaGattInterface(nullptr); + bluetooth::manager::SetMockBtmInterface(nullptr); + controller::SetMockControllerInterface(nullptr); + + for (auto i = 0u; i <= ascs::kAseCtpOpcodeMaxVal; ++i) + ase_ctp_handlers[i] = nullptr; + + le_audio_devices_.clear(); + cached_codec_configuration_map_.clear(); + LeAudioGroupStateMachine::Cleanup(); + } + + std::shared_ptr<LeAudioDevice> PrepareConnectedDevice(uint8_t id, + bool first_connection, + uint8_t num_ase_snk, + uint8_t num_ase_src) { + auto leAudioDevice = + std::make_shared<LeAudioDevice>(GetTestAddress(id), first_connection); + leAudioDevice->conn_id_ = id; + + uint16_t attr_handle = ATTR_HANDLE_ASCS_POOL_START; + leAudioDevice->snk_audio_locations_hdls_.val_hdl = attr_handle++; + leAudioDevice->snk_audio_locations_hdls_.ccc_hdl = attr_handle++; + leAudioDevice->src_audio_locations_hdls_.val_hdl = attr_handle++; + leAudioDevice->src_audio_locations_hdls_.ccc_hdl = attr_handle++; + leAudioDevice->audio_avail_hdls_.val_hdl = attr_handle++; + leAudioDevice->audio_avail_hdls_.ccc_hdl = attr_handle++; + leAudioDevice->audio_supp_cont_hdls_.val_hdl = attr_handle++; + leAudioDevice->audio_supp_cont_hdls_.ccc_hdl = attr_handle++; + leAudioDevice->ctp_hdls_.val_hdl = attr_handle++; + leAudioDevice->ctp_hdls_.ccc_hdl = attr_handle++; + + // Add some Sink ASEs + while (num_ase_snk) { + types::ase ase(0, 0, 0x01); + ase.hdls.val_hdl = attr_handle++; + ase.hdls.ccc_hdl = attr_handle++; + + leAudioDevice->ases_.emplace_back(std::move(ase)); + num_ase_snk--; + } + + // Add some Source ASEs + while (num_ase_src) { + types::ase ase(0, 0, 0x02); + ase.hdls.val_hdl = attr_handle++; + ase.hdls.ccc_hdl = attr_handle++; + + leAudioDevice->ases_.emplace_back(std::move(ase)); + num_ase_src--; + } + + le_audio_devices_.push_back(leAudioDevice); + + return std::move(leAudioDevice); + } + + LeAudioDeviceGroup* GroupTheDevice( + int group_id, const std::shared_ptr<LeAudioDevice>& leAudioDevice) { + if (le_audio_device_groups_.count(group_id) == 0) { + le_audio_device_groups_[group_id] = + std::make_unique<LeAudioDeviceGroup>(group_id); + } + + auto& group = le_audio_device_groups_[group_id]; + + group->AddNode(leAudioDevice); + if (group->IsEmpty()) return nullptr; + + return &(*group); + } + + static void InjectAseStateNotification(types::ase* ase, LeAudioDevice* device, + LeAudioDeviceGroup* group, + uint8_t new_state, + void* new_state_params) { + // Prepare additional params + switch (new_state) { + case ascs::kAseStateCodecConfigured: { + client_parser::ascs::ase_codec_configured_state_params* conf = + static_cast< + client_parser::ascs::ase_codec_configured_state_params*>( + new_state_params); + std::vector<uint8_t> notif_value(25 + conf->codec_spec_conf.size()); + auto* p = notif_value.data(); + + UINT8_TO_STREAM(p, ase->id == types::ase::kAseIdInvalid + ? ++ase_id_last_assigned + : ase->id); + UINT8_TO_STREAM(p, new_state); + + UINT8_TO_STREAM(p, conf->framing); + UINT8_TO_STREAM(p, conf->preferred_phy); + UINT8_TO_STREAM(p, conf->preferred_retrans_nb); + UINT16_TO_STREAM(p, conf->max_transport_latency); + UINT24_TO_STREAM(p, conf->pres_delay_min); + UINT24_TO_STREAM(p, conf->pres_delay_max); + UINT24_TO_STREAM(p, conf->preferred_pres_delay_min); + UINT24_TO_STREAM(p, conf->preferred_pres_delay_max); + + // CodecID: + UINT8_TO_STREAM(p, conf->codec_id.coding_format); + UINT16_TO_STREAM(p, conf->codec_id.vendor_company_id); + UINT16_TO_STREAM(p, conf->codec_id.vendor_codec_id); + + // Codec Spec. Conf. Length and Data + UINT8_TO_STREAM(p, conf->codec_spec_conf.size()); + memcpy(p, conf->codec_spec_conf.data(), conf->codec_spec_conf.size()); + + LeAudioGroupStateMachine::Get()->ProcessGattNotifEvent( + notif_value.data(), notif_value.size(), ase, device, group); + } break; + + case ascs::kAseStateQoSConfigured: { + client_parser::ascs::ase_qos_configured_state_params* conf = + static_cast<client_parser::ascs::ase_qos_configured_state_params*>( + new_state_params); + std::vector<uint8_t> notif_value(17); + auto* p = notif_value.data(); + + // Prepare header + UINT8_TO_STREAM(p, ase->id); + UINT8_TO_STREAM(p, new_state); + + UINT8_TO_STREAM(p, conf->cig_id); + UINT8_TO_STREAM(p, conf->cis_id); + UINT24_TO_STREAM(p, conf->sdu_interval); + UINT8_TO_STREAM(p, conf->framing); + UINT8_TO_STREAM(p, conf->phy); + UINT16_TO_STREAM(p, conf->max_sdu); + UINT8_TO_STREAM(p, conf->retrans_nb); + UINT16_TO_STREAM(p, conf->max_transport_latency); + UINT24_TO_STREAM(p, conf->pres_delay); + + LeAudioGroupStateMachine::Get()->ProcessGattNotifEvent( + notif_value.data(), notif_value.size(), ase, device, group); + } break; + + case ascs::kAseStateEnabling: + // fall-through + case ascs::kAseStateStreaming: + // fall-through + case ascs::kAseStateDisabling: { + client_parser::ascs::ase_transient_state_params* params = + static_cast<client_parser::ascs::ase_transient_state_params*>( + new_state_params); + std::vector<uint8_t> notif_value(5 + params->metadata.size()); + auto* p = notif_value.data(); + + // Prepare header + UINT8_TO_STREAM(p, ase->id); + UINT8_TO_STREAM(p, new_state); + + UINT8_TO_STREAM(p, group->group_id_); + UINT8_TO_STREAM(p, ase->cis_id); + UINT8_TO_STREAM(p, params->metadata.size()); + memcpy(p, params->metadata.data(), params->metadata.size()); + + LeAudioGroupStateMachine::Get()->ProcessGattNotifEvent( + notif_value.data(), notif_value.size(), ase, device, group); + } break; + + case ascs::kAseStateReleasing: + // fall-through + case ascs::kAseStateIdle: { + std::vector<uint8_t> notif_value(2); + auto* p = notif_value.data(); + + // Prepare header + UINT8_TO_STREAM(p, ase->id == types::ase::kAseIdInvalid + ? ++ase_id_last_assigned + : ase->id); + UINT8_TO_STREAM(p, new_state); + + LeAudioGroupStateMachine::Get()->ProcessGattNotifEvent( + notif_value.data(), notif_value.size(), ase, device, group); + } break; + + default: + break; + }; + } + + static void InsertPacRecord( + std::vector<types::acs_ac_record>& recs, + uint16_t sampling_frequencies_bitfield, + uint8_t supported_frame_durations_bitfield, + uint8_t audio_channel_count_bitfield, + uint16_t supported_octets_per_codec_frame_min, + uint16_t supported_octets_per_codec_frame_max, + uint8_t coding_format = codec_specific::kLc3CodingFormat, + uint16_t vendor_company_id = 0x0000, uint16_t vendor_codec_id = 0x0000, + std::vector<uint8_t> metadata = {}) { + recs.push_back({ + .codec_id = + { + .coding_format = coding_format, + .vendor_company_id = vendor_company_id, + .vendor_codec_id = vendor_codec_id, + }, + .codec_spec_caps = types::LeAudioLtvMap({ + {codec_specific::kCapTypeSupportedSamplingFrequencies, + {(uint8_t)(sampling_frequencies_bitfield), + (uint8_t)(sampling_frequencies_bitfield >> 8)}}, + {codec_specific::kCapTypeSupportedFrameDurations, + {supported_frame_durations_bitfield}}, + {codec_specific::kCapTypeAudioChannelCount, + {audio_channel_count_bitfield}}, + {codec_specific::kCapTypeSupportedOctetsPerCodecFrame, + { + // Min + (uint8_t)(supported_octets_per_codec_frame_min), + (uint8_t)(supported_octets_per_codec_frame_min >> 8), + // Max + (uint8_t)(supported_octets_per_codec_frame_max), + (uint8_t)(supported_octets_per_codec_frame_max >> 8), + }}, + }), + .metadata = std::move(metadata), + }); + } + + static void InjectInitialIdleNotification(LeAudioDeviceGroup* group) { + for (auto* device = group->GetFirstDevice(); device != nullptr; + device = group->GetNextDevice(device)) { + for (auto& ase : device->ases_) { + InjectAseStateNotification(&ase, device, group, ascs::kAseStateIdle, + nullptr); + } + } + } + + void MultipleTestDevicePrepare(int leaudio_group_id, uint16_t context_type, + uint16_t device_cnt, + bool insert_default_pac_records = true) { + // Prepare fake connected device group + bool first_connections = true; + int total_devices = device_cnt; + le_audio::LeAudioDeviceGroup* group = nullptr; + + uint8_t num_ase_snk; + uint8_t num_ase_src; + switch (context_type) { + case kContextTypeRingtone: + num_ase_snk = 1; + num_ase_src = 0; + break; + + case kContextTypeMedia: + num_ase_snk = 2; + num_ase_src = 0; + break; + + case kContextTypeConversational: + num_ase_snk = 1; + num_ase_src = 1; + break; + + default: + ASSERT_TRUE(false); + } + + while (device_cnt) { + auto leAudioDevice = PrepareConnectedDevice( + device_cnt--, first_connections, num_ase_snk, num_ase_src); + + if (insert_default_pac_records) { + uint16_t attr_handle = ATTR_HANDLE_PACS_POOL_START; + + /* As per spec, unspecified shall be supported */ + types::AudioContexts snk_context_type = kContextTypeUnspecified; + types::AudioContexts src_context_type = kContextTypeUnspecified; + + // Prepare Sink Published Audio Capability records + if ((context_type & kContextTypeRingtone) || + (context_type & kContextTypeMedia) || + (context_type & kContextTypeConversational)) { + // Set target ASE configurations + std::vector<types::acs_ac_record> pac_recs; + + InsertPacRecord(pac_recs, + codec_specific::kCapSamplingFrequency16000Hz, + codec_specific::kCapFrameDuration10ms | + codec_specific::kCapFrameDuration7p5ms | + codec_specific::kCapFrameDuration10msPreferred, + 0b00000001, 30, 120); + + types::hdl_pair handle_pair; + handle_pair.val_hdl = attr_handle++; + handle_pair.ccc_hdl = attr_handle++; + + leAudioDevice->snk_pacs_.emplace_back( + std::make_tuple(std::move(handle_pair), pac_recs)); + + snk_context_type |= context_type; + leAudioDevice->snk_audio_locations_ = + ::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft | + ::le_audio::codec_spec_conf::kLeAudioLocationFrontRight; + } + + // Prepare Source Published Audio Capability records + if (context_type & kContextTypeConversational) { + // Set target ASE configurations + std::vector<types::acs_ac_record> pac_recs; + + InsertPacRecord(pac_recs, + codec_specific::kCapSamplingFrequency16000Hz, + codec_specific::kCapFrameDuration10ms | + codec_specific::kCapFrameDuration7p5ms | + codec_specific::kCapFrameDuration10msPreferred, + 0b00000001, 30, 120); + + types::hdl_pair handle_pair; + handle_pair.val_hdl = attr_handle++; + handle_pair.ccc_hdl = attr_handle++; + + leAudioDevice->src_pacs_.emplace_back( + std::make_tuple(std::move(handle_pair), pac_recs)); + src_context_type |= kContextTypeConversational; + + leAudioDevice->src_audio_locations_ = + ::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft; + } + + leAudioDevice->SetSupportedContexts(snk_context_type, src_context_type); + leAudioDevice->SetAvailableContexts(snk_context_type, src_context_type); + } + + group = GroupTheDevice(leaudio_group_id, std::move(leAudioDevice)); + } + + /* Stimulate update of active context map */ + types::AudioContexts type_set = static_cast<uint16_t>(context_type); + group->UpdateActiveContextsMap(type_set); + + ASSERT_NE(group, nullptr); + ASSERT_EQ(group->Size(), total_devices); + } + + LeAudioDeviceGroup* PrepareSingleTestDeviceGroup(int leaudio_group_id, + uint16_t context_type, + uint16_t device_cnt = 1) { + MultipleTestDevicePrepare(leaudio_group_id, context_type, device_cnt); + return le_audio_device_groups_.count(leaudio_group_id) + ? le_audio_device_groups_[leaudio_group_id].get() + : nullptr; + } + + void PrepareConfigureCodecHandler(LeAudioDeviceGroup* group, + int verify_ase_count = 0, + bool caching = false) { + ase_ctp_handlers[ascs::kAseCtpOpcodeConfigureCodec] = + [group, verify_ase_count, caching, this]( + LeAudioDevice* device, std::vector<uint8_t> value, + GATT_WRITE_OP_CB cb, void* cb_data) { + auto num_ase = value[1]; + + // Verify ase count if needed + if (verify_ase_count) ASSERT_EQ(verify_ase_count, num_ase); + + // Inject Configured ASE state notification for each requested ASE + auto* ase_p = &value[2]; + for (auto i = 0u; i < num_ase; ++i) { + client_parser::ascs::ase_codec_configured_state_params + codec_configured_state_params; + + /* Check if this is a valid ASE ID */ + auto ase_id = *ase_p++; + auto it = std::find_if( + device->ases_.begin(), device->ases_.end(), + [ase_id](auto& ase) { return (ase.id == ase_id); }); + ASSERT_NE(it, device->ases_.end()); + const auto ase = &(*it); + + // Skip target latency param + ase_p++; + + codec_configured_state_params.preferred_phy = *ase_p++; + codec_configured_state_params.codec_id.coding_format = ase_p[0]; + codec_configured_state_params.codec_id.vendor_company_id = + (uint16_t)(ase_p[1] << 8 | ase_p[2]), + codec_configured_state_params.codec_id.vendor_codec_id = + (uint16_t)(ase_p[3] << 8 | ase_p[4]), + ase_p += 5; + + auto codec_spec_param_len = *ase_p++; + auto num_handled_bytes = ase_p - value.data(); + codec_configured_state_params.codec_spec_conf = + std::vector<uint8_t>( + value.begin() + num_handled_bytes, + value.begin() + num_handled_bytes + codec_spec_param_len); + ase_p += codec_spec_param_len; + + // Some initial QoS settings + codec_configured_state_params.framing = + ascs::kAseParamFramingUnframedSupported; + codec_configured_state_params.preferred_retrans_nb = 0x04; + codec_configured_state_params.max_transport_latency = 0x0005; + codec_configured_state_params.pres_delay_min = 0xABABAB; + codec_configured_state_params.pres_delay_max = 0xCDCDCD; + codec_configured_state_params.preferred_pres_delay_min = + types::kPresDelayNoPreference; + codec_configured_state_params.preferred_pres_delay_max = + types::kPresDelayNoPreference; + + if (caching) { + cached_codec_configuration_map_[ase_id] = + codec_configured_state_params; + } + InjectAseStateNotification(ase, device, group, + ascs::kAseStateCodecConfigured, + &codec_configured_state_params); + } + }; + } + + void PrepareConfigureQosHandler(LeAudioDeviceGroup* group, + int verify_ase_count = 0) { + ase_ctp_handlers[ascs::kAseCtpOpcodeConfigureQos] = + [group, verify_ase_count](LeAudioDevice* device, + std::vector<uint8_t> value, + GATT_WRITE_OP_CB cb, void* cb_data) { + auto num_ase = value[1]; + + // Verify ase count if needed + if (verify_ase_count) ASSERT_EQ(verify_ase_count, num_ase); + + // Inject Configured QoS state notification for each requested ASE + auto* ase_p = &value[2]; + for (auto i = 0u; i < num_ase; ++i) { + client_parser::ascs::ase_qos_configured_state_params + qos_configured_state_params; + + /* Check if this is a valid ASE ID */ + auto ase_id = *ase_p++; + auto it = std::find_if( + device->ases_.begin(), device->ases_.end(), + [ase_id](auto& ase) { return (ase.id == ase_id); }); + ASSERT_NE(it, device->ases_.end()); + const auto ase = &(*it); + + qos_configured_state_params.cig_id = *ase_p++; + qos_configured_state_params.cis_id = *ase_p++; + + qos_configured_state_params.sdu_interval = + (uint32_t)((ase_p[0] << 16) | (ase_p[1] << 8) | ase_p[2]); + ase_p += 3; + + qos_configured_state_params.framing = *ase_p++; + qos_configured_state_params.phy = *ase_p++; + qos_configured_state_params.max_sdu = + (uint16_t)((ase_p[0] << 8) | ase_p[1]); + ase_p += 2; + + qos_configured_state_params.retrans_nb = *ase_p++; + qos_configured_state_params.max_transport_latency = + (uint16_t)((ase_p[0] << 8) | ase_p[1]); + ase_p += 2; + + qos_configured_state_params.pres_delay = + (uint16_t)((ase_p[0] << 16) | (ase_p[1] << 8) | ase_p[2]); + ase_p += 3; + + InjectAseStateNotification(ase, device, group, + ascs::kAseStateQoSConfigured, + &qos_configured_state_params); + } + }; + } + + void PrepareEnableHandler(LeAudioDeviceGroup* group, int verify_ase_count = 0, + bool inject_enabling = true) { + ase_ctp_handlers[ascs::kAseCtpOpcodeEnable] = + [group, verify_ase_count, inject_enabling]( + LeAudioDevice* device, std::vector<uint8_t> value, + GATT_WRITE_OP_CB cb, void* cb_data) { + auto num_ase = value[1]; + + // Verify ase count if needed + if (verify_ase_count) ASSERT_EQ(verify_ase_count, num_ase); + + // Inject Streaming ASE state notification for each requested ASE + auto* ase_p = &value[2]; + for (auto i = 0u; i < num_ase; ++i) { + /* Check if this is a valid ASE ID */ + auto ase_id = *ase_p++; + auto it = std::find_if( + device->ases_.begin(), device->ases_.end(), + [ase_id](auto& ase) { return (ase.id == ase_id); }); + ASSERT_NE(it, device->ases_.end()); + const auto ase = &(*it); + + auto meta_len = *ase_p++; + auto num_handled_bytes = ase_p - value.data(); + ase_p += num_handled_bytes; + + client_parser::ascs::ase_transient_state_params enable_params = { + .metadata = std::vector<uint8_t>( + value.begin() + num_handled_bytes, + value.begin() + num_handled_bytes + meta_len)}; + + // Server does the 'ReceiverStartReady' on its own - goes to + // Streaming, when in Sink role + if (ase->direction & le_audio::types::kLeAudioDirectionSink) { + if (inject_enabling) + InjectAseStateNotification(ase, device, group, + ascs::kAseStateEnabling, + &enable_params); + InjectAseStateNotification( + ase, device, group, ascs::kAseStateStreaming, &enable_params); + } else { + InjectAseStateNotification( + ase, device, group, ascs::kAseStateEnabling, &enable_params); + } + } + }; + } + + void PrepareDisableHandler(LeAudioDeviceGroup* group, + int verify_ase_count = 0) { + ase_ctp_handlers[ascs::kAseCtpOpcodeDisable] = + [group, verify_ase_count](LeAudioDevice* device, + std::vector<uint8_t> value, + GATT_WRITE_OP_CB cb, void* cb_data) { + auto num_ase = value[1]; + + // Verify ase count if needed + if (verify_ase_count) ASSERT_EQ(verify_ase_count, num_ase); + ASSERT_EQ(value.size(), 2ul + num_ase); + + // Inject Disabling & QoS Conf. ASE state notification for each ASE + auto* ase_p = &value[2]; + for (auto i = 0u; i < num_ase; ++i) { + /* Check if this is a valid ASE ID */ + auto ase_id = *ase_p++; + auto it = std::find_if( + device->ases_.begin(), device->ases_.end(), + [ase_id](auto& ase) { return (ase.id == ase_id); }); + ASSERT_NE(it, device->ases_.end()); + const auto ase = &(*it); + + // The Disabling state is present for Source ASE + if (ase->direction & le_audio::types::kLeAudioDirectionSource) { + client_parser::ascs::ase_transient_state_params disabling_params = + {.metadata = {}}; + InjectAseStateNotification(ase, device, group, + ascs::kAseStateDisabling, + &disabling_params); + } + + // Server does the 'ReceiverStopReady' on its own - goes to + // Streaming, when in Sink role + if (ase->direction & le_audio::types::kLeAudioDirectionSink) { + // FIXME: For now our fake peer does not remember qos params + client_parser::ascs::ase_qos_configured_state_params + qos_configured_state_params; + InjectAseStateNotification(ase, device, group, + ascs::kAseStateQoSConfigured, + &qos_configured_state_params); + } + } + }; + } + + void PrepareReceiverStartReady(LeAudioDeviceGroup* group, + int verify_ase_count = 0) { + ase_ctp_handlers[ascs::kAseCtpOpcodeReceiverStartReady] = + [group, verify_ase_count](LeAudioDevice* device, + std::vector<uint8_t> value, + GATT_WRITE_OP_CB cb, void* cb_data) { + auto num_ase = value[1]; + + // Verify ase count if needed + if (verify_ase_count) ASSERT_EQ(verify_ase_count, num_ase); + + // Inject Streaming ASE state notification for each Source ASE + auto* ase_p = &value[2]; + for (auto i = 0u; i < num_ase; ++i) { + /* Check if this is a valid ASE ID */ + auto ase_id = *ase_p++; + auto it = std::find_if( + device->ases_.begin(), device->ases_.end(), + [ase_id](auto& ase) { return (ase.id == ase_id); }); + ASSERT_NE(it, device->ases_.end()); + + // Once we did the 'ReceiverStartReady' the server goes to + // Streaming, when in Source role + auto meta_len = *ase_p++; + auto num_handled_bytes = ase_p - value.data(); + ase_p += num_handled_bytes; + + const auto& ase = &(*it); + client_parser::ascs::ase_transient_state_params enable_params = { + .metadata = std::vector<uint8_t>( + value.begin() + num_handled_bytes, + value.begin() + num_handled_bytes + meta_len)}; + InjectAseStateNotification( + ase, device, group, ascs::kAseStateStreaming, &enable_params); + } + }; + } + + void PrepareReceiverStopReady(LeAudioDeviceGroup* group, + int verify_ase_count = 0) { + ase_ctp_handlers[ascs::kAseCtpOpcodeReceiverStopReady] = + [group, verify_ase_count](LeAudioDevice* device, + std::vector<uint8_t> value, + GATT_WRITE_OP_CB cb, void* cb_data) { + auto num_ase = value[1]; + + // Verify ase count if needed + if (verify_ase_count) ASSERT_EQ(verify_ase_count, num_ase); + + // Inject QoS configured ASE state notification for each Source ASE + auto* ase_p = &value[2]; + for (auto i = 0u; i < num_ase; ++i) { + /* Check if this is a valid ASE ID */ + auto ase_id = *ase_p++; + auto it = std::find_if( + device->ases_.begin(), device->ases_.end(), + [ase_id](auto& ase) { return (ase.id == ase_id); }); + ASSERT_NE(it, device->ases_.end()); + + const auto& ase = &(*it); + + // FIXME: For now our fake peer does not remember qos params + client_parser::ascs::ase_qos_configured_state_params + qos_configured_state_params; + InjectAseStateNotification(ase, device, group, + ascs::kAseStateQoSConfigured, + &qos_configured_state_params); + } + }; + } + + void PrepareReleaseHandler(LeAudioDeviceGroup* group, + int verify_ase_count = 0) { + ase_ctp_handlers[ascs::kAseCtpOpcodeRelease] = + [group, verify_ase_count, this](LeAudioDevice* device, + std::vector<uint8_t> value, + GATT_WRITE_OP_CB cb, void* cb_data) { + auto num_ase = value[1]; + + // Verify ase count if needed + if (verify_ase_count) ASSERT_EQ(verify_ase_count, num_ase); + ASSERT_EQ(value.size(), 2ul + num_ase); + + // Inject Releasing & Idle ASE state notification for each ASE + auto* ase_p = &value[2]; + for (auto i = 0u; i < num_ase; ++i) { + /* Check if this is a valid ASE ID */ + auto ase_id = *ase_p++; + auto it = std::find_if( + device->ases_.begin(), device->ases_.end(), + [ase_id](auto& ase) { return (ase.id == ase_id); }); + ASSERT_NE(it, device->ases_.end()); + const auto ase = &(*it); + + InjectAseStateNotification(ase, device, group, + ascs::kAseStateReleasing, nullptr); + + /* Check if codec configuration is cached */ + if (cached_codec_configuration_map_.count(ase_id) > 0) { + InjectAseStateNotification( + ase, device, group, ascs::kAseStateCodecConfigured, + &cached_codec_configuration_map_[ase_id]); + } else { + // Release - no caching + InjectAseStateNotification(ase, device, group, + ascs::kAseStateIdle, nullptr); + } + } + }; + } + + controller::MockControllerInterface mock_controller_; + bluetooth::manager::MockBtmInterface btm_interface; + gatt::MockBtaGattInterface gatt_interface; + gatt::MockBtaGattQueue gatt_queue; + + bluetooth::hci::IsoManager* iso_manager_; + MockIsoManager* mock_iso_manager_; + + std::function<void(LeAudioDevice* device, std::vector<uint8_t> value, + GATT_WRITE_OP_CB cb, void* cb_data)> + ase_ctp_handlers[ascs::kAseCtpOpcodeMaxVal + 1] = {nullptr}; + std::map<int, client_parser::ascs::ase_codec_configured_state_params> + cached_codec_configuration_map_; + + MockLeAudioGroupStateMachineCallbacks mock_callbacks_; + std::vector<std::shared_ptr<LeAudioDevice>> le_audio_devices_; + std::map<uint8_t, std::unique_ptr<LeAudioDeviceGroup>> + le_audio_device_groups_; +}; + +TEST_F(StateMachineTest, testInit) { + ASSERT_NE(LeAudioGroupStateMachine::Get(), nullptr); +} + +TEST_F(StateMachineTest, testCleanup) { + ASSERT_NE(LeAudioGroupStateMachine::Get(), nullptr); + LeAudioGroupStateMachine::Cleanup(); + EXPECT_DEATH(LeAudioGroupStateMachine::Get(), ""); +} + +TEST_F(StateMachineTest, testConfigureCodecSingle) { + const auto context_type = kContextTypeRingtone; + const int leaudio_group_id = 2; + + // Prepare fake connected device group + auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type); + + /* Since we prepared device with Ringtone context in mind, only one ASE + * should have been configured. + */ + auto* leAudioDevice = group->GetFirstDevice(); + PrepareConfigureCodecHandler(group, 1); + + // Start the configuration and stream Media content + EXPECT_CALL(gatt_queue, + WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _, + GATT_WRITE_NO_RSP, _, _)) + .Times(2); + + InjectInitialIdleNotification(group); + + ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream( + group, static_cast<types::LeAudioContextType>(context_type))); + + // Check if group has transitioned to a proper state + ASSERT_EQ(group->GetState(), + types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED); +} + +TEST_F(StateMachineTest, testConfigureCodecMulti) { + const auto context_type = kContextTypeMedia; + const auto leaudio_group_id = 2; + const auto num_devices = 2; + + // Prepare multiple fake connected devices in a group + auto* group = + PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices); + ASSERT_EQ(group->Size(), num_devices); + + PrepareConfigureCodecHandler(group); + + auto expected_devices_written = 0; + auto* leAudioDevice = group->GetFirstDevice(); + while (leAudioDevice) { + EXPECT_CALL(gatt_queue, + WriteCharacteristic(leAudioDevice->conn_id_, + leAudioDevice->ctp_hdls_.val_hdl, _, + GATT_WRITE_NO_RSP, _, _)) + .Times(AtLeast(1)); + expected_devices_written++; + leAudioDevice = group->GetNextDevice(leAudioDevice); + } + ASSERT_EQ(expected_devices_written, num_devices); + + InjectInitialIdleNotification(group); + + // Start the configuration and stream the content + ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream( + group, static_cast<types::LeAudioContextType>(context_type))); + + // Check if group has transitioned to a proper state + ASSERT_EQ(group->GetState(), + types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED); +} + +TEST_F(StateMachineTest, testConfigureQosSingle) { + const auto context_type = kContextTypeRingtone; + const int leaudio_group_id = 3; + + // Prepare fake connected device group + auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type); + + /* Since we prepared device with Ringtone context in mind, only one ASE + * should have been configured. + */ + auto* leAudioDevice = group->GetFirstDevice(); + PrepareConfigureCodecHandler(group, 1); + PrepareConfigureQosHandler(group, 1); + + // Start the configuration and stream Media content + EXPECT_CALL(gatt_queue, + WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _, + GATT_WRITE_NO_RSP, _, _)) + .Times(3); + + EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(0); + EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0); + EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0); + EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0); + EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0); + + InjectInitialIdleNotification(group); + + ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream( + group, static_cast<types::LeAudioContextType>(context_type))); + + // Check if group has transitioned to a proper state + ASSERT_EQ(group->GetState(), + types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED); +} + +TEST_F(StateMachineTest, testConfigureQosMultiple) { + const auto context_type = kContextTypeMedia; + const auto leaudio_group_id = 3; + const auto num_devices = 2; + + // Prepare multiple fake connected devices in a group + auto* group = + PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices); + ASSERT_EQ(group->Size(), num_devices); + + PrepareConfigureCodecHandler(group); + PrepareConfigureQosHandler(group); + + auto* leAudioDevice = group->GetFirstDevice(); + auto expected_devices_written = 0; + while (leAudioDevice) { + EXPECT_CALL(gatt_queue, + WriteCharacteristic(leAudioDevice->conn_id_, + leAudioDevice->ctp_hdls_.val_hdl, _, + GATT_WRITE_NO_RSP, _, _)) + .Times(AtLeast(2)); + expected_devices_written++; + leAudioDevice = group->GetNextDevice(leAudioDevice); + } + ASSERT_EQ(expected_devices_written, num_devices); + + EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(0); + EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0); + EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0); + EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0); + EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0); + + InjectInitialIdleNotification(group); + + // Start the configuration and stream Media content + ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream( + group, static_cast<types::LeAudioContextType>(context_type))); + + // Check if group has transitioned to a proper state + ASSERT_EQ(group->GetState(), + types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED); +} + +TEST_F(StateMachineTest, testStreamSingle) { + const auto context_type = kContextTypeRingtone; + const int leaudio_group_id = 4; + + // Prepare fake connected device group + auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type); + + /* Since we prepared device with Ringtone context in mind, only one ASE + * should have been configured. + */ + PrepareConfigureCodecHandler(group, 1); + PrepareConfigureQosHandler(group, 1); + PrepareEnableHandler(group, 1); + + auto* leAudioDevice = group->GetFirstDevice(); + EXPECT_CALL(gatt_queue, + WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _, + GATT_WRITE_NO_RSP, _, _)) + .Times(3); + + EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); + EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0); + EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0); + EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0); + + InjectInitialIdleNotification(group); + + // Validate GroupStreamStatus + EXPECT_CALL( + mock_callbacks_, + StatusReportCb(leaudio_group_id, + bluetooth::le_audio::GroupStreamStatus::STREAMING)); + + // Start the configuration and stream Media content + ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream( + group, static_cast<types::LeAudioContextType>(context_type))); + + // Check if group has transitioned to a proper state + ASSERT_EQ(group->GetState(), + types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); +} + +TEST_F(StateMachineTest, testStreamSkipEnablingSink) { + const auto context_type = kContextTypeRingtone; + const int leaudio_group_id = 4; + + // Prepare fake connected device group + auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type); + + /* Since we prepared device with Ringtone context in mind, only one ASE + * should have been configured. + */ + PrepareConfigureCodecHandler(group, 1); + PrepareConfigureQosHandler(group, 1); + PrepareEnableHandler(group, 1, false); + + auto* leAudioDevice = group->GetFirstDevice(); + EXPECT_CALL(gatt_queue, + WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _, + GATT_WRITE_NO_RSP, _, _)) + .Times(3); + + EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); + EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0); + EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0); + EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0); + + InjectInitialIdleNotification(group); + + // Validate GroupStreamStatus + EXPECT_CALL( + mock_callbacks_, + StatusReportCb(leaudio_group_id, + bluetooth::le_audio::GroupStreamStatus::STREAMING)); + + // Start the configuration and stream Media content + ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream( + group, static_cast<types::LeAudioContextType>(context_type))); + + // Check if group has transitioned to a proper state + ASSERT_EQ(group->GetState(), + types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); +} + +TEST_F(StateMachineTest, testStreamSkipEnablingSinkSource) { + const auto context_type = kContextTypeConversational; + const int leaudio_group_id = 4; + + // Prepare fake connected device group + auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type); + + /* Since we prepared device with Conversional context in mind, one Sink ASE + * and one Source ASE should have been configured. + */ + PrepareConfigureCodecHandler(group, 2); + PrepareConfigureQosHandler(group, 2); + PrepareEnableHandler(group, 2, false); + PrepareReceiverStartReady(group, 1); + + auto* leAudioDevice = group->GetFirstDevice(); + EXPECT_CALL(gatt_queue, + WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _, + GATT_WRITE_NO_RSP, _, _)) + .Times(4); + + EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); + EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2); + EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0); + EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0); + EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0); + + InjectInitialIdleNotification(group); + + // Validate GroupStreamStatus + EXPECT_CALL( + mock_callbacks_, + StatusReportCb(leaudio_group_id, + bluetooth::le_audio::GroupStreamStatus::STREAMING)); + + // Start the configuration and stream Media content + ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream( + group, static_cast<types::LeAudioContextType>(context_type))); + + // Check if group has transitioned to a proper state + ASSERT_EQ(group->GetState(), + types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); +} + +TEST_F(StateMachineTest, testStreamMultipleConversational) { + const auto context_type = kContextTypeConversational; + const auto leaudio_group_id = 4; + const auto num_devices = 2; + + // Prepare multiple fake connected devices in a group + auto* group = + PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices); + ASSERT_EQ(group->Size(), num_devices); + + PrepareConfigureCodecHandler(group); + PrepareConfigureQosHandler(group); + PrepareEnableHandler(group); + PrepareReceiverStartReady(group, 1); + + EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(AtLeast(1)); + EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(3); + EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0); + EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0); + EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0); + + InjectInitialIdleNotification(group); + + auto* leAudioDevice = group->GetFirstDevice(); + auto expected_devices_written = 0; + while (leAudioDevice) { + EXPECT_CALL(gatt_queue, + WriteCharacteristic(leAudioDevice->conn_id_, + leAudioDevice->ctp_hdls_.val_hdl, _, + GATT_WRITE_NO_RSP, _, _)) + .Times(AtLeast(3)); + expected_devices_written++; + leAudioDevice = group->GetNextDevice(leAudioDevice); + } + ASSERT_EQ(expected_devices_written, num_devices); + + // Validate GroupStreamStatus + EXPECT_CALL( + mock_callbacks_, + StatusReportCb(leaudio_group_id, + bluetooth::le_audio::GroupStreamStatus::STREAMING)); + + // Start the configuration and stream Media content + ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream( + group, static_cast<types::LeAudioContextType>(context_type))); + + // Check if group has transitioned to a proper state + ASSERT_EQ(group->GetState(), + types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); +} + +TEST_F(StateMachineTest, testStreamMultiple) { + const auto context_type = kContextTypeMedia; + const auto leaudio_group_id = 4; + const auto num_devices = 2; + + // Prepare multiple fake connected devices in a group + auto* group = + PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices); + ASSERT_EQ(group->Size(), num_devices); + + PrepareConfigureCodecHandler(group); + PrepareConfigureQosHandler(group); + PrepareEnableHandler(group); + + EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(AtLeast(1)); + EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2); + EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0); + EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0); + EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0); + + InjectInitialIdleNotification(group); + + auto* leAudioDevice = group->GetFirstDevice(); + auto expected_devices_written = 0; + while (leAudioDevice) { + EXPECT_CALL(gatt_queue, + WriteCharacteristic(leAudioDevice->conn_id_, + leAudioDevice->ctp_hdls_.val_hdl, _, + GATT_WRITE_NO_RSP, _, _)) + .Times(AtLeast(3)); + expected_devices_written++; + leAudioDevice = group->GetNextDevice(leAudioDevice); + } + ASSERT_EQ(expected_devices_written, num_devices); + + // Validate GroupStreamStatus + EXPECT_CALL( + mock_callbacks_, + StatusReportCb(leaudio_group_id, + bluetooth::le_audio::GroupStreamStatus::STREAMING)); + + // Start the configuration and stream Media content + ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream( + group, static_cast<types::LeAudioContextType>(context_type))); + + // Check if group has transitioned to a proper state + ASSERT_EQ(group->GetState(), + types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); +} + +TEST_F(StateMachineTest, testDisableSingle) { + const auto context_type = kContextTypeRingtone; + const int leaudio_group_id = 4; + + // Prepare fake connected device group + auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type); + + /* Since we prepared device with Ringtone context in mind, only one ASE + * should have been configured. + */ + PrepareConfigureCodecHandler(group, 1); + PrepareConfigureQosHandler(group, 1); + PrepareEnableHandler(group, 1); + PrepareDisableHandler(group, 1); + + auto* leAudioDevice = group->GetFirstDevice(); + EXPECT_CALL(gatt_queue, + WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _, + GATT_WRITE_NO_RSP, _, _)) + .Times(4); + + EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); + EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0); + + InjectInitialIdleNotification(group); + + // Start the configuration and stream Media content + LeAudioGroupStateMachine::Get()->StartStream( + group, static_cast<types::LeAudioContextType>(context_type)); + + // Check if group has transitioned to a proper state + ASSERT_EQ(group->GetState(), + types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); + + // Validate GroupStreamStatus + EXPECT_CALL( + mock_callbacks_, + StatusReportCb(leaudio_group_id, + bluetooth::le_audio::GroupStreamStatus::SUSPENDED)); + + // Suspend the stream + LeAudioGroupStateMachine::Get()->SuspendStream(group); + + // Check if group has transition to a proper state + ASSERT_EQ(group->GetState(), + types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED); +} + +TEST_F(StateMachineTest, testDisableMultiple) { + const auto context_type = kContextTypeMedia; + const auto leaudio_group_id = 4; + const auto num_devices = 2; + + // Prepare multiple fake connected devices in a group + auto* group = + PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices); + ASSERT_EQ(group->Size(), num_devices); + + PrepareConfigureCodecHandler(group); + PrepareConfigureQosHandler(group); + PrepareEnableHandler(group); + PrepareDisableHandler(group); + + auto* leAudioDevice = group->GetFirstDevice(); + auto expected_devices_written = 0; + while (leAudioDevice) { + EXPECT_CALL(gatt_queue, + WriteCharacteristic(leAudioDevice->conn_id_, + leAudioDevice->ctp_hdls_.val_hdl, _, + GATT_WRITE_NO_RSP, _, _)) + .Times(AtLeast(4)); + expected_devices_written++; + leAudioDevice = group->GetNextDevice(leAudioDevice); + } + ASSERT_EQ(expected_devices_written, num_devices); + + EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); + EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2); + EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(2); + EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(2); + EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0); + + InjectInitialIdleNotification(group); + + // Start the configuration and stream Media content + LeAudioGroupStateMachine::Get()->StartStream( + group, static_cast<types::LeAudioContextType>(context_type)); + + // Check if group has transitioned to a proper state + ASSERT_EQ(group->GetState(), + types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); + + // Validate GroupStreamStatus + EXPECT_CALL( + mock_callbacks_, + StatusReportCb(leaudio_group_id, + bluetooth::le_audio::GroupStreamStatus::SUSPENDED)); + + // Suspend the stream + LeAudioGroupStateMachine::Get()->SuspendStream(group); + + // Check if group has transitioned to a proper state + ASSERT_EQ(group->GetState(), + types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED); +} + +TEST_F(StateMachineTest, testDisableBidirectional) { + const auto context_type = kContextTypeConversational; + const int leaudio_group_id = 4; + + // Prepare fake connected device group + auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type); + + /* Since we prepared device with Conversional context in mind, Sink and Source + * ASEs should have been configured. + */ + PrepareConfigureCodecHandler(group, 2); + PrepareConfigureQosHandler(group, 2); + PrepareEnableHandler(group, 2); + PrepareDisableHandler(group, 2); + PrepareReceiverStartReady(group, 1); + PrepareReceiverStopReady(group, 1); + + auto* leAudioDevice = group->GetFirstDevice(); + EXPECT_CALL(gatt_queue, + WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _, + GATT_WRITE_NO_RSP, _, _)) + .Times(AtLeast(4)); + + EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); + EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2); + EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0); + + // Start the configuration and stream Media content + LeAudioGroupStateMachine::Get()->StartStream( + group, static_cast<types::LeAudioContextType>(context_type)); + + // Check if group has transitioned to a proper state + ASSERT_EQ(group->GetState(), + types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); + + // Suspend the stream + LeAudioGroupStateMachine::Get()->SuspendStream(group); + + // Check if group has transitioned to a proper state + ASSERT_EQ(group->GetState(), + types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED); +} + +TEST_F(StateMachineTest, testReleaseSingle) { + const auto context_type = kContextTypeRingtone; + const int leaudio_group_id = 4; + + // Prepare fake connected device group + auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type); + + /* Since we prepared device with Ringtone context in mind, only one ASE + * should have been configured. + */ + PrepareConfigureCodecHandler(group, 1); + PrepareConfigureQosHandler(group, 1); + PrepareEnableHandler(group, 1); + PrepareDisableHandler(group, 1); + PrepareReleaseHandler(group, 1); + + auto* leAudioDevice = group->GetFirstDevice(); + EXPECT_CALL(gatt_queue, + WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _, + GATT_WRITE_NO_RSP, _, _)) + .Times(4); + + EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); + EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(1); + + InjectInitialIdleNotification(group); + + // Start the configuration and stream Media content + LeAudioGroupStateMachine::Get()->StartStream( + group, static_cast<types::LeAudioContextType>(context_type)); + + // Check if group has transitioned to a proper state + ASSERT_EQ(group->GetState(), + types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); + + // Validate GroupStreamStatus + EXPECT_CALL(mock_callbacks_, + StatusReportCb(leaudio_group_id, + bluetooth::le_audio::GroupStreamStatus::IDLE)); + + // Stop the stream + LeAudioGroupStateMachine::Get()->StopStream(group); + + // Check if group has transitioned to a proper state + ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); +} + +TEST_F(StateMachineTest, testReleaseCachingSingle) { + const auto context_type = kContextTypeRingtone; + const int leaudio_group_id = 4; + + // Prepare fake connected device group + auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type); + + /* Since we prepared device with Ringtone context in mind, only one ASE + * should have been configured. + */ + PrepareConfigureCodecHandler(group, 1, true); + PrepareConfigureQosHandler(group, 1); + PrepareEnableHandler(group, 1); + PrepareDisableHandler(group, 1); + PrepareReleaseHandler(group, 1); + + auto* leAudioDevice = group->GetFirstDevice(); + EXPECT_CALL(gatt_queue, + WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _, + GATT_WRITE_NO_RSP, _, _)) + .Times(4); + + EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); + EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(1); + + InjectInitialIdleNotification(group); + + // Validate GroupStreamStatus + EXPECT_CALL(mock_callbacks_, + StatusReportCb(leaudio_group_id, + bluetooth::le_audio::GroupStreamStatus::IDLE)); + EXPECT_CALL( + mock_callbacks_, + StatusReportCb(leaudio_group_id, + bluetooth::le_audio::GroupStreamStatus::STREAMING)); + + // Start the configuration and stream Media content + LeAudioGroupStateMachine::Get()->StartStream( + group, static_cast<types::LeAudioContextType>(context_type)); + + // Check if group has transitioned to a proper state + ASSERT_EQ(group->GetState(), + types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); + + // Stop the stream + LeAudioGroupStateMachine::Get()->StopStream(group); + + // Check if group has transitioned to a proper state + ASSERT_EQ(group->GetState(), + types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED); +} + +TEST_F(StateMachineTest, testStreamCachingSingle) { + const auto context_type = kContextTypeRingtone; + const int leaudio_group_id = 4; + + // Prepare fake connected device group + auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type); + + /* Since we prepared device with Ringtone context in mind, only one ASE + * should have been configured. + */ + PrepareConfigureCodecHandler(group, 1, true); + PrepareConfigureQosHandler(group, 1); + PrepareEnableHandler(group, 1); + PrepareDisableHandler(group, 1); + PrepareReleaseHandler(group, 1); + + auto* leAudioDevice = group->GetFirstDevice(); + EXPECT_CALL(gatt_queue, + WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _, + GATT_WRITE_NO_RSP, _, _)) + .Times(4 + 3); + + EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(2); + EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(2); + EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2); + EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(1); + + InjectInitialIdleNotification(group); + + // Validate GroupStreamStatus + EXPECT_CALL(mock_callbacks_, + StatusReportCb(leaudio_group_id, + bluetooth::le_audio::GroupStreamStatus::IDLE)); + + EXPECT_CALL(mock_callbacks_, + StatusReportCb(leaudio_group_id, + bluetooth::le_audio::GroupStreamStatus::STREAMING)) + .Times(2); + + // Start the configuration and stream Media content + LeAudioGroupStateMachine::Get()->StartStream( + group, static_cast<types::LeAudioContextType>(context_type)); + + // Check if group has transitioned to a proper state + ASSERT_EQ(group->GetState(), + types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); + + // Stop the stream + LeAudioGroupStateMachine::Get()->StopStream(group); + + // Check if group has transitioned to a proper state + ASSERT_EQ(group->GetState(), + types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED); + + // Start the configuration and stream Media content + LeAudioGroupStateMachine::Get()->StartStream( + group, static_cast<types::LeAudioContextType>(context_type)); + + // Check if group has transitioned to a proper state + ASSERT_EQ(group->GetState(), + types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); +} + +TEST_F(StateMachineTest, testReleaseMultiple) { + const auto context_type = kContextTypeMedia; + const auto leaudio_group_id = 6; + const auto num_devices = 2; + + // Prepare multiple fake connected devices in a group + auto* group = + PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices); + ASSERT_EQ(group->Size(), num_devices); + + PrepareConfigureCodecHandler(group); + PrepareConfigureQosHandler(group); + PrepareEnableHandler(group); + PrepareDisableHandler(group); + PrepareReleaseHandler(group); + + auto* leAudioDevice = group->GetFirstDevice(); + auto expected_devices_written = 0; + while (leAudioDevice) { + EXPECT_CALL(gatt_queue, + WriteCharacteristic(leAudioDevice->conn_id_, + leAudioDevice->ctp_hdls_.val_hdl, _, + GATT_WRITE_NO_RSP, _, _)) + .Times(AtLeast(4)); + expected_devices_written++; + leAudioDevice = group->GetNextDevice(leAudioDevice); + } + ASSERT_EQ(expected_devices_written, num_devices); + + EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); + EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2); + EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(2); + EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(2); + EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(1); + + InjectInitialIdleNotification(group); + + // Start the configuration and stream Media content + LeAudioGroupStateMachine::Get()->StartStream( + group, static_cast<types::LeAudioContextType>(context_type)); + + // Check if group has transitioned to a proper state + ASSERT_EQ(group->GetState(), + types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); + + // Validate GroupStreamStatus + EXPECT_CALL(mock_callbacks_, + StatusReportCb(leaudio_group_id, + bluetooth::le_audio::GroupStreamStatus::IDLE)); + + // Stop the stream + LeAudioGroupStateMachine::Get()->StopStream(group); + + // Check if group has transitioned to a proper state + ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); +} + +TEST_F(StateMachineTest, testReleaseBidirectional) { + const auto context_type = kContextTypeConversational; + const auto leaudio_group_id = 6; + + // Prepare fake connected device group + auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type); + + /* Since we prepared device with Conversional context in mind, Sink and Source + * ASEs should have been configured. + */ + PrepareConfigureCodecHandler(group, 2); + PrepareConfigureQosHandler(group, 2); + PrepareEnableHandler(group, 2); + PrepareDisableHandler(group, 2); + PrepareReceiverStartReady(group, 1); + PrepareReleaseHandler(group, 2); + + auto* leAudioDevice = group->GetFirstDevice(); + EXPECT_CALL(gatt_queue, + WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _, + GATT_WRITE_NO_RSP, _, _)) + .Times(AtLeast(4)); + + EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); + EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2); + EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(1); + + InjectInitialIdleNotification(group); + + // Start the configuration and stream Media content + LeAudioGroupStateMachine::Get()->StartStream( + group, static_cast<types::LeAudioContextType>(context_type)); + + // Check if group has transitioned to a proper state + ASSERT_EQ(group->GetState(), + types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); + + // Stop the stream + LeAudioGroupStateMachine::Get()->StopStream(group); + + // Check if group has transitioned to a proper state + ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); +} + +TEST_F(StateMachineTest, testDisableAndReleaseBidirectional) { + const auto context_type = kContextTypeConversational; + const int leaudio_group_id = 4; + + // Prepare fake connected device group + auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type); + + /* Since we prepared device with Conversional context in mind, Sink and Source + * ASEs should have been configured. + */ + PrepareConfigureCodecHandler(group, 2); + PrepareConfigureQosHandler(group, 2); + PrepareEnableHandler(group, 2); + PrepareDisableHandler(group, 2); + PrepareReceiverStartReady(group, 1); + PrepareReceiverStopReady(group, 1); + PrepareReleaseHandler(group, 2); + + auto* leAudioDevice = group->GetFirstDevice(); + EXPECT_CALL(gatt_queue, + WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _, + GATT_WRITE_NO_RSP, _, _)) + .Times(AtLeast(4)); + + EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1); + EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2); + EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(1); + EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(1); + + // Start the configuration and stream Media content + LeAudioGroupStateMachine::Get()->StartStream( + group, static_cast<types::LeAudioContextType>(context_type)); + + // Suspend the stream + LeAudioGroupStateMachine::Get()->SuspendStream(group); + + // Stop the stream + LeAudioGroupStateMachine::Get()->StopStream(group); + + // Check if group has transitioned to a proper state + ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); +} + +TEST_F(StateMachineTest, testAseIdAssignmentIdle) { + const auto context_type = kContextTypeConversational; + const auto leaudio_group_id = 6; + const auto num_devices = 1; + + // Prepare multiple fake connected devices in a group + auto* group = + PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices); + ASSERT_EQ(group->Size(), num_devices); + + // Should not trigger any action on our side + EXPECT_CALL(gatt_queue, WriteCharacteristic(_, _, _, _, _, _)).Times(0); + EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(0); + EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(0); + EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0); + EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0); + EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0); + EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0); + + for (auto* device = group->GetFirstDevice(); device != nullptr; + device = group->GetNextDevice(device)) { + for (auto& ase : device->ases_) { + ASSERT_EQ(ase.id, le_audio::types::ase::kAseIdInvalid); + InjectAseStateNotification(&ase, device, group, ascs::kAseStateIdle, + nullptr); + ASSERT_EQ(ase.id, ase_id_last_assigned); + } + } +} + +TEST_F(StateMachineTest, testAseIdAssignmentCodecConfigured) { + const auto context_type = kContextTypeConversational; + const auto leaudio_group_id = 6; + const auto num_devices = 1; + + // Prepare multiple fake connected devices in a group + auto* group = + PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices); + ASSERT_EQ(group->Size(), num_devices); + + // Should not trigger any action on our side + EXPECT_CALL(gatt_queue, WriteCharacteristic(_, _, _, _, _, _)).Times(0); + EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(0); + EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(0); + EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0); + EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0); + EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0); + EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0); + + for (auto* device = group->GetFirstDevice(); device != nullptr; + device = group->GetNextDevice(device)) { + for (auto& ase : device->ases_) { + client_parser::ascs::ase_codec_configured_state_params + codec_configured_state_params; + + ASSERT_EQ(ase.id, le_audio::types::ase::kAseIdInvalid); + InjectAseStateNotification(&ase, device, group, + ascs::kAseStateCodecConfigured, + &codec_configured_state_params); + ASSERT_EQ(ase.id, ase_id_last_assigned); + } + } +} + +TEST_F(StateMachineTest, testAseAutonomousRelease) { + const auto context_type = kContextTypeConversational; + const int leaudio_group_id = 4; + + // Prepare fake connected device group + auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type); + + /* Since we prepared device with Conversional context in mind, Sink and Source + * ASEs should have been configured. + */ + PrepareConfigureCodecHandler(group, 2); + PrepareConfigureQosHandler(group, 2); + PrepareEnableHandler(group, 2); + PrepareDisableHandler(group, 2); + PrepareReceiverStartReady(group, 1); + PrepareReceiverStopReady(group, 1); + PrepareReleaseHandler(group, 2); + + InjectInitialIdleNotification(group); + + // Validate initial GroupStreamStatus + EXPECT_CALL( + mock_callbacks_, + StatusReportCb(leaudio_group_id, + bluetooth::le_audio::GroupStreamStatus::STREAMING)); + + // Start the configuration and stream Media content + ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream( + group, static_cast<types::LeAudioContextType>(context_type))); + + // Validate new GroupStreamStatus + EXPECT_CALL(mock_callbacks_, + StatusReportCb(leaudio_group_id, + bluetooth::le_audio::GroupStreamStatus::IDLE)); + + for (auto* device = group->GetFirstDevice(); device != nullptr; + device = group->GetNextDevice(device)) { + for (auto& ase : device->ases_) { + client_parser::ascs::ase_codec_configured_state_params + codec_configured_state_params; + + ASSERT_EQ(ase.state, types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); + + // Each one does the autonomous release + InjectAseStateNotification(&ase, device, group, ascs::kAseStateReleasing, + &codec_configured_state_params); + InjectAseStateNotification(&ase, device, group, ascs::kAseStateIdle, + &codec_configured_state_params); + } + } + + // Verify we've handled the release and updated all states + for (auto* device = group->GetFirstDevice(); device != nullptr; + device = group->GetNextDevice(device)) { + for (auto& ase : device->ases_) { + ASSERT_EQ(ase.state, types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); + } + } +} + +TEST_F(StateMachineTest, testStateTransitionTimeout) { + const auto context_type = kContextTypeRingtone; + const int leaudio_group_id = 4; + + // Prepare fake connected device group + auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type); + + /* Since we prepared device with Ringtone context in mind, only one ASE + * should have been configured. + */ + PrepareConfigureCodecHandler(group, 1); + PrepareConfigureQosHandler(group, 1); + + auto* leAudioDevice = group->GetFirstDevice(); + EXPECT_CALL(gatt_queue, + WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _, + GATT_WRITE_NO_RSP, _, _)) + .Times(3); + + // Start the configuration and stream Media content + ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream( + group, static_cast<types::LeAudioContextType>(context_type))); + + // Check if timeout is fired + EXPECT_CALL(mock_callbacks_, OnStateTransitionTimeout(leaudio_group_id)); + + // simulate timeout seconds passed, alarm executing + fake_osi_alarm_set_on_mloop_.cb(fake_osi_alarm_set_on_mloop_.data); + ASSERT_EQ(1, mock_function_count_map["alarm_set_on_mloop"]); +} +} // namespace internal +} // namespace le_audio diff --git a/system/bta/test/common/btif_storage_mock.cc b/system/bta/test/common/btif_storage_mock.cc new file mode 100644 index 0000000000000000000000000000000000000000..1d389c68050e2cdd3b52ec85804e60315579cff2 --- /dev/null +++ b/system/bta/test/common/btif_storage_mock.cc @@ -0,0 +1,39 @@ +/* + * Copyright 2021 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * 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 "btif_storage_mock.h" + +#include <base/logging.h> + +static bluetooth::storage::MockBtifStorageInterface* btif_storage_interface = + nullptr; + +void bluetooth::storage::SetMockBtifStorageInterface( + MockBtifStorageInterface* mock_btif_storage_interface) { + btif_storage_interface = mock_btif_storage_interface; +} + +void btif_storage_set_leaudio_autoconnect(RawAddress const& addr, + bool autoconnect) { + LOG_ASSERT(btif_storage_interface) << "Mock storage module not set!"; + btif_storage_interface->AddLeaudioAutoconnect(addr, autoconnect); +} + +void btif_storage_remove_leaudio(RawAddress const& addr) { + LOG_ASSERT(btif_storage_interface) << "Mock storage module not set!"; + btif_storage_interface->RemoveLeaudio(addr); +} \ No newline at end of file diff --git a/system/bta/test/common/btif_storage_mock.h b/system/bta/test/common/btif_storage_mock.h new file mode 100644 index 0000000000000000000000000000000000000000..1087bc9c594509b74254dcd0a4c421f74b65a9f6 --- /dev/null +++ b/system/bta/test/common/btif_storage_mock.h @@ -0,0 +1,51 @@ +/* + * Copyright 2021 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include <gmock/gmock.h> + +#include "types/raw_address.h" + +namespace bluetooth { +namespace storage { + +class BtifStorageInterface { + public: + virtual void AddLeaudioAutoconnect(RawAddress const& addr, + bool autoconnect) = 0; + virtual void RemoveLeaudio(RawAddress const& addr) = 0; + virtual ~BtifStorageInterface() = default; +}; + +class MockBtifStorageInterface : public BtifStorageInterface { + public: + MOCK_METHOD((void), AddLeaudioAutoconnect, + (RawAddress const& addr, bool autoconnect), (override)); + MOCK_METHOD((void), RemoveLeaudio, (RawAddress const& addr), (override)); +}; + +/** + * Set the {@link MockBifStorageInterface} for testing + * + * @param mock_btif_storage_interface pointer to mock btm security + * internal interface, could be null + */ +void SetMockBtifStorageInterface( + MockBtifStorageInterface* mock_btif_storage_interface); + +} // namespace storage +} // namespace bluetooth diff --git a/system/btif/Android.bp b/system/btif/Android.bp index a1cd9759752478bba86ebdd9b0106959e21d8472..d541ee2318503f9a300f2fc7c34c3f9e6be9b36f 100644 --- a/system/btif/Android.bp +++ b/system/btif/Android.bp @@ -178,7 +178,10 @@ cc_library_static { srcs: ["src/btif_avrcp_audio_track.cc"], }, host: { - srcs: ["src/btif_avrcp_audio_track_linux.cc"], + srcs: [ + "src/btif_avrcp_audio_track_linux.cc", + "src/btif_leaudio_hal_version_host.cc", + ], }, }, whole_static_libs: [ @@ -228,6 +231,7 @@ cc_test { "libbt-utils", "libFraunhoferAAC", "libg722codec", + "liblc3codec", "libbtdevice", "libbt-hci", "libudrv-uipc", @@ -405,6 +409,7 @@ cc_test { ":TestMockBtaHf", ":TestMockBtaHh", ":TestMockBtaJv", + ":TestMockBtaLeAudio", ":TestMockBtaPan", ":TestMockBtaSdp", ":TestMockBtaSys", diff --git a/system/btif/include/btif_storage.h b/system/btif/include/btif_storage.h index c5aee96f9c97d8ffd96c520f6fcf7ef6f9fbcd23..a86ad609071f66abaf09cb7ed683e91b5072581b 100644 --- a/system/btif/include/btif_storage.h +++ b/system/btif/include/btif_storage.h @@ -264,6 +264,16 @@ bool btif_storage_get_hearing_aid_prop( const RawAddress& address, uint8_t* capabilities, uint64_t* hi_sync_id, uint16_t* render_delay, uint16_t* preparation_delay, uint16_t* codecs); +/** Store Le Audio device autoconnect flag */ +void btif_storage_set_leaudio_autoconnect(const RawAddress& addr, + bool autoconnect); + +/** Remove Le Audio device from the storage */ +void btif_storage_remove_leaudio(const RawAddress& address); + +/** Load bonded Le Audio devices */ +void btif_storage_load_bonded_leaudio(void); + /******************************************************************************* * * Function btif_storage_is_retricted_device diff --git a/system/btif/src/bluetooth.cc b/system/btif/src/bluetooth.cc index 8df55805dc372edf28929957d565fa01e5523996..1fec5b54aad97ea00b161d840b5985221d71fd00 100644 --- a/system/btif/src/bluetooth.cc +++ b/system/btif/src/bluetooth.cc @@ -51,6 +51,7 @@ #include "bta/include/bta_csis_api.h" #include "bta/include/bta_hearing_aid_api.h" #include "bta/include/bta_hf_client_api.h" +#include "bta/include/bta_le_audio_api.h" #include "btif/avrcp/avrcp_service.h" #include "btif/include/stack_manager.h" #include "btif_a2dp.h" @@ -403,6 +404,7 @@ static void dump(int fd, const char** arguments) { alarm_debug_dump(fd); bluetooth::csis::CsisClient::DebugDump(fd); HearingAid::DebugDump(fd); + LeAudioClient::DebugDump(fd); connection_manager::dump(fd); bluetooth::bqr::DebugDump(fd); if (bluetooth::shim::is_any_gd_enabled()) { diff --git a/system/btif/src/btif_dm.cc b/system/btif/src/btif_dm.cc index 51600ed090ec9a98dce2f48266d4a0c3c189a1cd..f938987c591f7e662c3872cc6d1d400955714be4 100644 --- a/system/btif/src/btif_dm.cc +++ b/system/btif/src/btif_dm.cc @@ -35,6 +35,7 @@ #include <hardware/bluetooth.h> #include <hardware/bt_csis.h> #include <hardware/bt_hearing_aid.h> +#include <hardware/bt_le_audio.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> @@ -49,6 +50,7 @@ #include "bta_csis_api.h" #include "bta_dm_int.h" #include "bta_gatt_api.h" +#include "bta_le_audio_api.h" #include "btif/include/stack_manager.h" #include "btif_api.h" #include "btif_av.h" @@ -86,6 +88,7 @@ using bluetooth::Uuid; const Uuid UUID_HEARING_AID = Uuid::FromString("FDF0"); const Uuid UUID_VC = Uuid::FromString("1844"); const Uuid UUID_CSIS = Uuid::FromString("1846"); +const Uuid UUID_LE_AUDIO = Uuid::FromString("184E"); #define COD_MASK 0x07FF @@ -244,6 +247,8 @@ extern bt_status_t btif_hd_execute_service(bool b_enable); extern bluetooth::hearing_aid::HearingAidInterface* btif_hearing_aid_get_interface(); extern bluetooth::csis::CsisClientInterface* btif_csis_client_get_interface(); +extern bluetooth::le_audio::LeAudioClientInterface* +btif_le_audio_get_interface(); /****************************************************************************** * Functions @@ -1304,7 +1309,7 @@ static void btif_dm_search_devices_evt(tBTA_DM_SEARCH_EVT event, /* Returns true if |uuid| should be passed as device property */ static bool btif_is_interesting_le_service(bluetooth::Uuid uuid) { return (uuid.As16Bit() == UUID_SERVCLASS_LE_HID || uuid == UUID_HEARING_AID || - uuid == UUID_VC || uuid == UUID_CSIS); + uuid == UUID_VC || uuid == UUID_CSIS || uuid == UUID_LE_AUDIO); } /******************************************************************************* @@ -1592,6 +1597,9 @@ static void btif_dm_upstreams_evt(uint16_t event, char* p_param) { if (bluetooth::csis::CsisClient::IsCsisClientRunning()) btif_csis_client_get_interface()->RemoveDevice(bd_addr); + if (LeAudioClient::IsLeAudioClientRunning()) + btif_le_audio_get_interface()->RemoveDevice(bd_addr); + btif_storage_remove_bonded_device(&bd_addr); bond_state_changed(BT_STATUS_SUCCESS, bd_addr, BT_BOND_STATE_NONE); break; diff --git a/system/btif/src/btif_le_audio.cc b/system/btif/src/btif_le_audio.cc index dea691b3e90abad2e7fb0b3012441e6c675da84f..73a8e0fb4651e512e9f9de6b67be88ea914ebae9 100644 --- a/system/btif/src/btif_le_audio.cc +++ b/system/btif/src/btif_le_audio.cc @@ -20,10 +20,11 @@ #include <vector> +#include "audio_hal_interface/hal_version_manager.h" +#include "bta_le_audio_api.h" #include "btif_common.h" #include "btif_storage.h" -#include "hardware/bt_le_audio.h" -#include "types/raw_address.h" +#include "stack/include/btu.h" using base::Bind; using base::Unretained; @@ -70,21 +71,69 @@ class LeAudioClientInterfaceImpl : public LeAudioClientInterface, void Initialize(LeAudioClientCallbacks* callbacks) override { this->callbacks = callbacks; + do_in_main_thread( + FROM_HERE, + Bind(&LeAudioClient::Initialize, this, + jni_thread_wrapper(FROM_HERE, + Bind(&btif_storage_load_bonded_leaudio)), + base::Bind([]() -> bool { + return bluetooth::audio::HalVersionManager::GetHalVersion() == + bluetooth::audio::BluetoothAudioHalVersion::VERSION_2_1; + }))); } - void Cleanup(void) override {} + void Cleanup(void) override { + DVLOG(2) << __func__; + do_in_main_thread(FROM_HERE, Bind(&LeAudioClient::Cleanup)); + } + + void RemoveDevice(const RawAddress& address) override { + DVLOG(2) << __func__ << " address: " << address; + do_in_main_thread(FROM_HERE, + Bind(&LeAudioClient::RemoveDevice, + Unretained(LeAudioClient::Get()), address)); - void RemoveDevice(const RawAddress& address) override {} + do_in_jni_thread(FROM_HERE, Bind(&btif_storage_remove_leaudio, address)); + } - void Connect(const RawAddress& address) override {} + void Connect(const RawAddress& address) override { + DVLOG(2) << __func__ << " address: " << address; + do_in_main_thread(FROM_HERE, + Bind(&LeAudioClient::Connect, + Unretained(LeAudioClient::Get()), address)); + } - void GroupAddNode(const int group_id, const RawAddress& addr) override {} + void Disconnect(const RawAddress& address) override { + DVLOG(2) << __func__ << " address: " << address; + do_in_main_thread(FROM_HERE, + Bind(&LeAudioClient::Disconnect, + Unretained(LeAudioClient::Get()), address)); + do_in_jni_thread( + FROM_HERE, Bind(&btif_storage_set_leaudio_autoconnect, address, false)); + } - void Disconnect(const RawAddress& address) override {} + void GroupAddNode(const int group_id, const RawAddress& address) override { + DVLOG(2) << __func__ << " group_id: " << group_id + << " address: " << address; + do_in_main_thread( + FROM_HERE, Bind(&LeAudioClient::GroupAddNode, + Unretained(LeAudioClient::Get()), group_id, address)); + } - void GroupRemoveNode(const int group_id, const RawAddress& addr) override {} + void GroupRemoveNode(const int group_id, const RawAddress& address) override { + DVLOG(2) << __func__ << " group_id: " << group_id + << " address: " << address; + do_in_main_thread( + FROM_HERE, Bind(&LeAudioClient::GroupRemoveNode, + Unretained(LeAudioClient::Get()), group_id, address)); + } - void GroupSetActive(const int group_id) override {} + void GroupSetActive(const int group_id) override { + DVLOG(2) << __func__ << " group_id: " << group_id; + do_in_main_thread(FROM_HERE, + Bind(&LeAudioClient::GroupSetActive, + Unretained(LeAudioClient::Get()), group_id)); + } private: LeAudioClientCallbacks* callbacks; diff --git a/system/btif/src/btif_leaudio_hal_version_host.cc b/system/btif/src/btif_leaudio_hal_version_host.cc new file mode 100644 index 0000000000000000000000000000000000000000..a093f80ad3f93f58d77de226498c26edd787faca --- /dev/null +++ b/system/btif/src/btif_leaudio_hal_version_host.cc @@ -0,0 +1,30 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <memory> + +#include "embdrv/lc3/Api/Lc3Encoder.hpp" + +namespace bluetooth { +namespace audio { +class HalVersionManager { + static std::unique_ptr<HalVersionManager> instance_ptr; +}; +} // namespace audio +} // namespace bluetooth + +std::unique_ptr<bluetooth::audio::HalVersionManager> + bluetooth::audio::HalVersionManager::instance_ptr; diff --git a/system/btif/src/btif_storage.cc b/system/btif/src/btif_storage.cc index 572005c8a0dbca5f61da2643a3398aa048f86c6d..38640e235d9d070cdd827f23b6774a8425826696 100644 --- a/system/btif/src/btif_storage.cc +++ b/system/btif/src/btif_storage.cc @@ -44,6 +44,7 @@ #include "bta_hd_api.h" #include "bta_hearing_aid_api.h" #include "bta_hh_api.h" +#include "bta_le_audio_api.h" #include "btif_api.h" #include "btif_config.h" #include "btif_hd.h" @@ -94,6 +95,7 @@ using bluetooth::groups::DeviceGroups; #define BTIF_STORAGE_DEVICE_GROUP_BIN "DeviceGroupBin" #define BTIF_STORAGE_CSIS_AUTOCONNECT "CsisAutoconnect" #define BTIF_STORAGE_CSIS_SET_INFO_BIN "CsisSetInfoBin" +#define BTIF_STORAGE_LEAUDIO_AUTOCONNECT "LeAudioAutoconnect" /* This is a local property to add a device found */ #define BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP 0xFF @@ -1735,6 +1737,64 @@ uint8_t btif_storage_get_sr_supp_feat(const RawAddress& bd_addr) { return value; } +/** Set autoconnect information for LeAudio device */ +void btif_storage_set_leaudio_autoconnect(const RawAddress& addr, + bool autoconnect) { + do_in_jni_thread(FROM_HERE, Bind( + [](const RawAddress& addr, bool autoconnect) { + std::string bdstr = addr.ToString(); + VLOG(2) + << "saving le audio device: " << bdstr; + btif_config_set_int( + bdstr, BTIF_STORAGE_LEAUDIO_AUTOCONNECT, + autoconnect); + btif_config_save(); + }, + addr, autoconnect)); +} + +/** Loads information about bonded Le Audio devices */ +void btif_storage_load_bonded_leaudio() { + for (const auto& bd_addr : btif_config_get_paired_devices()) { + auto name = bd_addr.ToString(); + + int size = STORAGE_UUID_STRING_SIZE * BT_MAX_NUM_UUIDS; + char uuid_str[size]; + bool isLeAudioDevice = false; + if (btif_config_get_str(name, BTIF_STORAGE_PATH_REMOTE_SERVICE, uuid_str, + &size)) { + Uuid p_uuid[BT_MAX_NUM_UUIDS]; + size_t num_uuids = + btif_split_uuids_string(uuid_str, p_uuid, BT_MAX_NUM_UUIDS); + for (size_t i = 0; i < num_uuids; i++) { + if (p_uuid[i] == Uuid::FromString("184E")) { + isLeAudioDevice = true; + break; + } + } + } + if (!isLeAudioDevice) { + continue; + } + + BTIF_TRACE_DEBUG("Remote device:%s", name.c_str()); + + int value; + bool autoconnect = false; + if (btif_config_get_int(name, BTIF_STORAGE_LEAUDIO_AUTOCONNECT, &value)) + autoconnect = !!value; + + do_in_main_thread( + FROM_HERE, Bind(&LeAudioClient::AddFromStorage, bd_addr, autoconnect)); + } +} + +/** Remove the Le Audio device from storage */ +void btif_storage_remove_leaudio(const RawAddress& address) { + std::string addrstr = address.ToString(); + btif_config_set_int(addrstr, BTIF_STORAGE_LEAUDIO_AUTOCONNECT, false); +} + /** Adds the bonded Le Audio device grouping info into the NVRAM */ void btif_storage_add_groups(const RawAddress& addr) { std::vector<uint8_t> group_info; diff --git a/system/embdrv/lc3/Android.bp b/system/embdrv/lc3/Android.bp index 2ba76485cb6424f32fb1598dd964bf2dcb5a8ad7..ce1302f85df71e5379de05f1eee4c30b0cbf8fd5 100644 --- a/system/embdrv/lc3/Android.bp +++ b/system/embdrv/lc3/Android.bp @@ -33,13 +33,17 @@ cc_library_static { "-Wno-self-assign", "-Wno-implicit-fallthrough", ], - sanitize: { - misc_undefined:[ - "unsigned-integer-overflow", - "signed-integer-overflow", - "bounds", - ], - cfi: true, + target: { + android: { + sanitize: { + misc_undefined:[ + "unsigned-integer-overflow", + "signed-integer-overflow", + "bounds", + ], + cfi: true, + }, + }, }, shared_libs: [ "liblog", diff --git a/system/gd/rust/topshim/facade/Android.bp b/system/gd/rust/topshim/facade/Android.bp index d7d046cc59fdd36bdb474705d675b551320b528a..8afff7e493da8f0088d49411f9545b746c3845e5 100644 --- a/system/gd/rust/topshim/facade/Android.bp +++ b/system/gd/rust/topshim/facade/Android.bp @@ -43,6 +43,7 @@ rust_binary_host { "libbt-sbc-encoder", "libFraunhoferAAC", "libg722codec", + "liblc3codec", "libudrv-uipc", "libbluetooth_gd", // Gabeldorsche "libbluetooth_rust_interop", diff --git a/system/include/hardware/bluetooth.h b/system/include/hardware/bluetooth.h index 0810af92f5860d1f07a49cf38d5e9af9fff49e17..28069dbeeaeafa266d438e6d7d5fb1330f942f5c 100644 --- a/system/include/hardware/bluetooth.h +++ b/system/include/hardware/bluetooth.h @@ -54,6 +54,7 @@ #define BT_ACTIVITY_ATTRIBUTION_ID "activity_attribution" #define BT_PROFILE_VC_ID "volume_control" #define BT_PROFILE_CSIS_CLIENT_ID "csis_client" +#define BT_PROFILE_LE_AUDIO_ID "le_audio" /** Bluetooth Device Name */ typedef struct { uint8_t name[249]; } __attribute__((packed)) bt_bdname_t; diff --git a/system/main/Android.bp b/system/main/Android.bp index 93e3387edb4e51d80e3adcc436f030ec48acfa83..a03b10d3184056836989b1ce76015e8707fe83db 100644 --- a/system/main/Android.bp +++ b/system/main/Android.bp @@ -103,7 +103,7 @@ cc_library_shared { "libbt-sbc-encoder", "libFraunhoferAAC", "libg722codec", - "liblc3codec", + "liblc3codec", "libudrv-uipc", "libprotobuf-cpp-lite", "libbluetooth_gd", // Gabeldorsche diff --git a/system/stack/test/fuzzers/Android.bp b/system/stack/test/fuzzers/Android.bp index 56a8178d03fb8bac0c50e9f76a02b1687fe7ac2b..27debff9cdfe9389410523ad4f64a50c4ae37c17 100644 --- a/system/stack/test/fuzzers/Android.bp +++ b/system/stack/test/fuzzers/Android.bp @@ -34,6 +34,7 @@ cc_defaults { "libbt-hci", "libbtdevice", "libg722codec", + "liblc3codec", "libosi", "libudrv-uipc", "libbt-protos-lite", diff --git a/system/test/Android.bp b/system/test/Android.bp index a7806d638c156ecb2d980374db8e2939d5b7c52c..3f5fea75464247528be2e6118e895d0aff58c206 100644 --- a/system/test/Android.bp +++ b/system/test/Android.bp @@ -96,6 +96,13 @@ filegroup { ], } +filegroup { + name: "TestMockBtaLeAudio", + srcs: [ + "mock/mock_bta_leaudio*.cc", + ], +} + filegroup { name: "TestMockBtaPan", srcs: [ @@ -139,6 +146,7 @@ filegroup { ":TestMockBtaHf", ":TestMockBtaHh", ":TestMockBtaJv", + ":TestMockBtaLeAudio", ":TestMockBtaPan", ":TestMockBtaSdp", ":TestMockBtaSys", diff --git a/system/test/mock/mock_bta_leaudio.cc b/system/test/mock/mock_bta_leaudio.cc new file mode 100644 index 0000000000000000000000000000000000000000..b44269709c95bd3b0e91fe4513800eaef2598a88 --- /dev/null +++ b/system/test/mock/mock_bta_leaudio.cc @@ -0,0 +1,67 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Generated mock file from original source file + * Functions generated:7 + */ + +#include <map> +#include <memory> +#include <string> + +extern std::map<std::string, int> mock_function_count_map; + +#include <base/bind.h> +#include <base/bind_helpers.h> +#include <hardware/bt_le_audio.h> + +#include "bta/include/bta_le_audio_api.h" +#include "types/raw_address.h" + +#ifndef UNUSED_ATTR +#define UNUSED_ATTR +#endif + +/* Empty class to satisfy compiler */ +namespace bluetooth { +namespace audio { +class HalVersionManager { + static std::unique_ptr<HalVersionManager> instance_ptr; +}; +} // namespace audio +} // namespace bluetooth + +void LeAudioClient::AddFromStorage(const RawAddress& address, + bool auto_connect) { + mock_function_count_map[__func__]++; +} +void LeAudioClient::Cleanup() { mock_function_count_map[__func__]++; } + +LeAudioClient* LeAudioClient::Get(void) { + mock_function_count_map[__func__]++; + return nullptr; +} +bool LeAudioClient::IsLeAudioClientRunning(void) { + mock_function_count_map[__func__]++; + return false; +} +void LeAudioClient::Initialize( + bluetooth::le_audio::LeAudioClientCallbacks* callbacks_, + base::Closure initCb, base::Callback<bool()> hal_2_1_verifier) { + mock_function_count_map[__func__]++; +} +void LeAudioClient::DebugDump(int fd) { mock_function_count_map[__func__]++; } \ No newline at end of file