--- /dev/null
+/*
+ * 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 <set>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "bt_types.h"
+#include "btm_iso_api.h"
+#include "btu.h"
+#include "common/time_util.h"
+#include "device/include/controller.h"
+#include "osi/include/log.h"
+
+namespace bluetooth {
+namespace hci {
+namespace iso_manager {
+static constexpr uint8_t kIsoDataInTsBtHdrOffset = 0x0C;
+static constexpr uint8_t kIsoHeaderWithTsLen = 12;
+static constexpr uint8_t kIsoHeaderWithoutTsLen = 8;
+
+static constexpr uint8_t kStateFlagsNone = 0x00;
+static constexpr uint8_t kStateFlagIsConnected = 0x01;
+static constexpr uint8_t kStateFlagHasDataPathSet = 0x02;
+
+struct iso_sync_info {
+ uint32_t first_sync_ts;
+ uint16_t seq_nb;
+};
+
+struct iso_base {
+ uint8_t cig_id;
+
+ struct iso_sync_info sync_info;
+ uint8_t state_flags;
+ uint32_t sdu_itv;
+};
+
+typedef iso_base iso_cis;
+
+struct iso_impl {
+ iso_impl() {
+ iso_credits_ = controller_get_interface()->get_iso_buffer_count();
+ iso_buffer_size_ = controller_get_interface()->get_iso_data_size();
+ }
+
+ ~iso_impl() {}
+
+ void handle_register_cis_callbacks(CigCallbacks* callbacks) {
+ LOG_ASSERT(callbacks != nullptr) << "Invalid CIG callbacks";
+ cig_callbacks_ = callbacks;
+ }
+
+ void on_set_cig_params(uint8_t cig_id, uint32_t sdu_itv_mtos, uint8_t* stream,
+ uint16_t len) {
+ uint8_t cis_cnt;
+ uint16_t conn_handle;
+ cig_create_cmpl_evt evt;
+
+ LOG_ASSERT(cig_callbacks_ != nullptr) << "Invalid CIG callbacks";
+ LOG_ASSERT(len >= 3) << "Invalid packet length.";
+
+ STREAM_TO_UINT8(evt.status, stream);
+ STREAM_TO_UINT8(evt.cig_id, stream);
+ STREAM_TO_UINT8(cis_cnt, stream);
+
+ uint8_t evt_code = IsCigKnown(cig_id) ? kIsoEventCigOnReconfigureCmpl
+ : kIsoEventCigOnCreateCmpl;
+
+ if (evt.status == HCI_SUCCESS) {
+ LOG_ASSERT(len >= (3) + (cis_cnt * sizeof(uint16_t)))
+ << "Invalid CIS count.";
+
+ /* Remove entries for the reconfigured CIG */
+ if (evt_code == kIsoEventCigOnReconfigureCmpl) {
+ auto cis_it = conn_hdl_to_cis_map_.cbegin();
+ while (cis_it != conn_hdl_to_cis_map_.cend()) {
+ if (cis_it->second->cig_id == evt.cig_id)
+ cis_it = conn_hdl_to_cis_map_.erase(cis_it);
+ else
+ ++cis_it;
+ }
+ }
+
+ evt.conn_handles.reserve(cis_cnt);
+ for (int i = 0; i < cis_cnt; i++) {
+ STREAM_TO_UINT16(conn_handle, stream);
+
+ evt.conn_handles.push_back(conn_handle);
+ conn_hdl_to_cis_map_[conn_handle] = std::unique_ptr<iso_cis>(
+ new iso_cis({.sync_info = {.first_sync_ts = 0, .seq_nb = 0},
+ .cig_id = cig_id,
+ .state_flags = kStateFlagsNone,
+ .sdu_itv = sdu_itv_mtos}));
+ }
+ }
+
+ cig_callbacks_->OnCigEvent(evt_code, &evt);
+ }
+
+ void create_cig(uint8_t cig_id,
+ struct iso_manager::cig_create_params cig_params) {
+ LOG_ASSERT(!IsCigKnown(cig_id)) << "Invalid cig - already exists.";
+
+ btsnd_hcic_set_cig_params(
+ cig_id, cig_params.sdu_itv_mtos, cig_params.sdu_itv_stom,
+ cig_params.sca, cig_params.packing, cig_params.framing,
+ cig_params.max_trans_lat_stom, cig_params.max_trans_lat_mtos,
+ cig_params.cis_cfgs.size(), cig_params.cis_cfgs.data(),
+ base::BindOnce(&iso_impl::on_set_cig_params, base::Unretained(this),
+ cig_id, cig_params.sdu_itv_mtos));
+ }
+
+ void reconfigure_cig(uint8_t cig_id,
+ struct iso_manager::cig_create_params cig_params) {
+ LOG_ASSERT(IsCigKnown(cig_id)) << "No such cig";
+
+ btsnd_hcic_set_cig_params(
+ cig_id, cig_params.sdu_itv_mtos, cig_params.sdu_itv_stom,
+ cig_params.sca, cig_params.packing, cig_params.framing,
+ cig_params.max_trans_lat_stom, cig_params.max_trans_lat_mtos,
+ cig_params.cis_cfgs.size(), cig_params.cis_cfgs.data(),
+ base::BindOnce(&iso_impl::on_set_cig_params, base::Unretained(this),
+ cig_id, cig_params.sdu_itv_mtos));
+ }
+
+ void on_remove_cig(uint8_t* stream, uint16_t len) {
+ cig_remove_cmpl_evt evt;
+
+ LOG_ASSERT(cig_callbacks_ != nullptr) << "Invalid CIG callbacks";
+ LOG_ASSERT(len == 2) << "Invalid packet length.";
+
+ STREAM_TO_UINT8(evt.status, stream);
+ STREAM_TO_UINT8(evt.cig_id, stream);
+
+ if (evt.status == HCI_SUCCESS) {
+ auto cis_it = conn_hdl_to_cis_map_.cbegin();
+ while (cis_it != conn_hdl_to_cis_map_.cend()) {
+ if (cis_it->second->cig_id == evt.cig_id)
+ cis_it = conn_hdl_to_cis_map_.erase(cis_it);
+ else
+ ++cis_it;
+ }
+ }
+
+ cig_callbacks_->OnCigEvent(kIsoEventCigOnRemoveCmpl, &evt);
+ }
+
+ void remove_cig(uint8_t cig_id) {
+ LOG_ASSERT(IsCigKnown(cig_id)) << "No such cig";
+
+ btsnd_hcic_remove_cig(cig_id, base::BindOnce(&iso_impl::on_remove_cig,
+ base::Unretained(this)));
+ }
+
+ void establish_cis(struct iso_manager::cis_establish_params conn_params) {
+ for (auto& el : conn_params.conn_pairs) {
+ auto cis = GetCisIfKnown(el.cis_conn_handle);
+ LOG_ASSERT(cis) << "No such cis";
+ LOG_ASSERT(!(cis->state_flags & kStateFlagIsConnected))
+ << "Already connected";
+ }
+ btsnd_hcic_create_cis(conn_params.conn_pairs.size(),
+ conn_params.conn_pairs.data());
+ }
+
+ void disconnect_cis(uint16_t cis_handle, uint8_t reason) {
+ auto cis = GetCisIfKnown(cis_handle);
+ LOG_ASSERT(cis) << "No such cis";
+ LOG_ASSERT(cis->state_flags & kStateFlagIsConnected) << "Not connected";
+ btsnd_hcic_disconnect(cis_handle, reason);
+ }
+
+ void on_setup_iso_data_path(uint8_t* stream, uint16_t len) {
+ uint8_t status;
+ uint16_t conn_handle;
+
+ STREAM_TO_UINT8(status, stream);
+ STREAM_TO_UINT16(conn_handle, stream);
+
+ iso_base* iso = GetIsoIfKnown(conn_handle);
+ LOG_ASSERT(iso != nullptr) << "Invalid connection handle: " << +conn_handle;
+
+ if (status == HCI_SUCCESS) iso->state_flags |= kStateFlagHasDataPathSet;
+
+ LOG_ASSERT(cig_callbacks_ != nullptr) << "Invalid CIG callbacks";
+ cig_callbacks_->OnSetupIsoDataPath(status, conn_handle, iso->cig_id);
+ }
+
+ void setup_iso_data_path(
+ uint16_t conn_handle,
+ struct iso_manager::iso_data_path_params path_params) {
+ iso_base* iso = GetIsoIfKnown(conn_handle);
+ LOG_ASSERT(iso != nullptr) << "No such iso connection: " << +conn_handle;
+
+ LOG_ASSERT(iso->state_flags & kStateFlagIsConnected)
+ << "CIS not established";
+
+ btsnd_hcic_setup_iso_data_path(
+ conn_handle, path_params.data_path_dir, path_params.data_path_id,
+ path_params.codec_id_format, path_params.codec_id_company,
+ path_params.codec_id_vendor, path_params.controller_delay,
+ std::move(path_params.codec_conf),
+ base::BindOnce(&iso_impl::on_setup_iso_data_path,
+ base::Unretained(this)));
+ }
+
+ void on_remove_iso_data_path(uint8_t* stream, uint16_t len) {
+ uint8_t status;
+ uint16_t conn_handle;
+
+ STREAM_TO_UINT8(status, stream);
+ STREAM_TO_UINT16(conn_handle, stream);
+
+ iso_base* iso = GetIsoIfKnown(conn_handle);
+ LOG_ASSERT(iso != nullptr) << "Invalid connection handle: " << +conn_handle;
+
+ if (status == HCI_SUCCESS) iso->state_flags &= ~kStateFlagHasDataPathSet;
+
+ LOG_ASSERT(cig_callbacks_ != nullptr) << "Invalid CIG callbacks";
+ cig_callbacks_->OnRemoveIsoDataPath(status, conn_handle, iso->cig_id);
+ }
+
+ void remove_iso_data_path(uint16_t iso_handle, uint8_t data_path_dir) {
+ iso_base* iso = GetIsoIfKnown(iso_handle);
+ LOG_ASSERT(iso != nullptr) << "No such iso connection";
+ LOG_ASSERT((iso->state_flags & kStateFlagHasDataPathSet) ==
+ kStateFlagHasDataPathSet)
+ << "Data path not set";
+
+ btsnd_hcic_remove_iso_data_path(
+ iso_handle, data_path_dir,
+ base::BindOnce(&iso_impl::on_remove_iso_data_path,
+ base::Unretained(this)));
+ }
+
+ BT_HDR* prepare_ts_hci_packet(uint16_t iso_handle, uint32_t ts,
+ uint16_t seq_nb, uint16_t data_len) {
+ /* Add 2 for packet seq., 2 for length, 4 for the timestamp */
+ uint16_t iso_data_load_len = data_len + 8;
+
+ /* Add 2 for handle, 2 for length */
+ uint16_t iso_full_len = iso_data_load_len + 4;
+ BT_HDR* packet = (BT_HDR*)osi_malloc(iso_full_len + sizeof(BT_HDR));
+ packet->len = iso_full_len;
+ packet->offset = 0;
+ packet->event = MSG_STACK_TO_HC_HCI_ISO;
+ packet->layer_specific = 0;
+
+ uint8_t* packet_data = packet->data;
+ UINT16_TO_STREAM(packet_data, iso_handle);
+ UINT16_TO_STREAM(packet_data, iso_data_load_len);
+
+ packet->layer_specific |= BT_ISO_HDR_CONTAINS_TS;
+ UINT32_TO_STREAM(packet_data, ts);
+
+ UINT16_TO_STREAM(packet_data, seq_nb);
+ UINT16_TO_STREAM(packet_data, data_len);
+
+ return packet;
+ }
+
+ void send_iso_data_hci_packet(BT_HDR* packet) {
+ bte_main_hci_send(packet, MSG_STACK_TO_HC_HCI_ISO | 0x0001);
+ }
+
+ void send_iso_data(uint16_t iso_handle, const uint8_t* data,
+ uint16_t data_len) {
+ iso_base* iso = GetIsoIfKnown(iso_handle);
+ LOG_ASSERT(iso != nullptr)
+ << "No such iso connection handle: " << +iso_handle;
+
+ LOG_ASSERT(iso->state_flags & kStateFlagIsConnected)
+ << "CIS not established";
+ LOG_ASSERT(iso->state_flags & kStateFlagHasDataPathSet)
+ << "Data path not set for handle: " << +iso_handle;
+
+ /* Calculate sequence number for the ISO data packet.
+ * It should be incremented by 1 every SDU Interval.
+ */
+ uint32_t ts = bluetooth::common::time_get_os_boottime_us();
+ iso->sync_info.seq_nb = (ts - iso->sync_info.first_sync_ts) / iso->sdu_itv;
+
+ if (iso_credits_ == 0 || data_len > iso_buffer_size_) {
+ LOG(WARNING) << __func__ << ", dropping ISO packet, len: "
+ << static_cast<int>(data_len)
+ << ", iso credits: " << static_cast<int>(iso_credits_);
+ return;
+ }
+
+ iso_credits_--;
+
+ BT_HDR* packet =
+ prepare_ts_hci_packet(iso_handle, ts, iso->sync_info.seq_nb, data_len);
+ memcpy(packet->data + kIsoDataInTsBtHdrOffset, data, data_len);
+ send_iso_data_hci_packet(packet);
+ }
+
+ void process_cis_est_pkt(uint8_t len, uint8_t* data) {
+ cis_establish_cmpl_evt evt;
+
+ LOG_ASSERT(len == 28) << "Invalid packet length";
+ LOG_ASSERT(cig_callbacks_ != nullptr) << "Invalid CIG callbacks";
+
+ STREAM_TO_UINT8(evt.status, data);
+ STREAM_TO_UINT16(evt.cis_conn_hdl, data);
+
+ auto cis = GetCisIfKnown(evt.cis_conn_hdl);
+ LOG_ASSERT(cis != nullptr) << "No such cis";
+
+ cis->sync_info.first_sync_ts = bluetooth::common::time_get_os_boottime_us();
+
+ STREAM_TO_UINT24(evt.cig_sync_delay, data);
+ STREAM_TO_UINT24(evt.cis_sync_delay, data);
+ STREAM_TO_UINT24(evt.trans_lat_mtos, data);
+ STREAM_TO_UINT24(evt.trans_lat_stom, data);
+ STREAM_TO_UINT8(evt.phy_mtos, data);
+ STREAM_TO_UINT8(evt.phy_stom, data);
+ STREAM_TO_UINT8(evt.nse, data);
+ STREAM_TO_UINT8(evt.bn_mtos, data);
+ STREAM_TO_UINT8(evt.bn_stom, data);
+ STREAM_TO_UINT8(evt.ft_mtos, data);
+ STREAM_TO_UINT8(evt.ft_stom, data);
+ STREAM_TO_UINT16(evt.max_pdu_mtos, data);
+ STREAM_TO_UINT16(evt.max_pdu_stom, data);
+ STREAM_TO_UINT16(evt.iso_itv, data);
+
+ if (evt.status == HCI_SUCCESS) cis->state_flags |= kStateFlagIsConnected;
+
+ evt.cig_id = cis->cig_id;
+ cig_callbacks_->OnCisEvent(kIsoEventCisEstablishCmpl, &evt);
+ }
+
+ void disconnection_complete(uint16_t handle, uint8_t reason) {
+ /* Check if this is an ISO handle */
+ auto cis = GetCisIfKnown(handle);
+ if (cis == nullptr) return;
+
+ LOG_ASSERT(cig_callbacks_ != nullptr) << "Invalid CIG callbacks";
+
+ LOG_INFO("%s flags: %d", __func__, +cis->state_flags);
+ if (cis->state_flags & kStateFlagIsConnected) {
+ cis_disconnected_evt evt = {
+ .reason = reason,
+ .cis_conn_hdl = handle,
+ .cig_id = cis->cig_id,
+ };
+
+ cig_callbacks_->OnCisEvent(kIsoEventCisDisconnected, &evt);
+ cis->state_flags &= ~kStateFlagIsConnected;
+ /* Data path is considered still valid, but can be reconfigured only once
+ * CIS is reestablished.
+ */
+ }
+ }
+
+ void handle_num_completed_pkts(uint8_t* p, uint8_t evt_len) {
+ uint8_t num_handles;
+
+ STREAM_TO_UINT8(num_handles, p);
+
+ LOG_ASSERT(evt_len == num_handles * 4 + 1);
+
+ for (int i = 0; i < num_handles; i++) {
+ uint16_t handle, num_sent;
+
+ STREAM_TO_UINT16(handle, p);
+ STREAM_TO_UINT16(num_sent, p);
+
+ if (conn_hdl_to_cis_map_.find(handle) == conn_hdl_to_cis_map_.end())
+ continue;
+
+ iso_credits_ += num_sent;
+ }
+ }
+
+ void on_iso_event(uint8_t code, uint8_t* packet, uint16_t packet_len) {
+ switch (code) {
+ case HCI_BLE_CIS_EST_EVT:
+ process_cis_est_pkt(packet_len, packet);
+ break;
+ case HCI_BLE_CREATE_BIG_CPL_EVT:
+ /* TODO: Implement */
+ break;
+ case HCI_BLE_TERM_BIG_CPL_EVT:
+ /* TODO: Implement */
+ break;
+ case HCI_BLE_CIS_REQ_EVT:
+ /* Not supported */
+ break;
+ case HCI_BLE_BIG_SYNC_EST_EVT:
+ /* Not supported */
+ break;
+ case HCI_BLE_BIG_SYNC_LOST_EVT:
+ /* Not supported */
+ break;
+ default:
+ LOG_ERROR("Unhandled event code %d", +code);
+ }
+ }
+
+ void handle_iso_data(BT_HDR* p_msg) {
+ const uint8_t* stream = p_msg->data;
+ cis_data_evt evt;
+ uint16_t handle, seq_nb;
+
+ if (p_msg->len <= ((p_msg->layer_specific & BT_ISO_HDR_CONTAINS_TS)
+ ? kIsoHeaderWithTsLen
+ : kIsoHeaderWithoutTsLen))
+ return;
+
+ LOG_ASSERT(cig_callbacks_ != nullptr) << "Invalid CIG callbacks";
+
+ STREAM_TO_UINT16(handle, stream);
+ evt.cis_conn_hdl = HCID_GET_HANDLE(handle);
+
+ iso_base* iso = GetCisIfKnown(evt.cis_conn_hdl);
+ if (iso == nullptr) {
+ LOG(ERROR) << __func__ << ", received data for the non-registered CIS!";
+ return;
+ }
+
+ STREAM_SKIP_UINT16(stream);
+ if (p_msg->layer_specific & BT_ISO_HDR_CONTAINS_TS)
+ STREAM_TO_UINT32(evt.ts, stream);
+
+ STREAM_TO_UINT16(seq_nb, stream);
+
+ uint32_t ts = bluetooth::common::time_get_os_boottime_us();
+ uint32_t new_calc_seq_nb =
+ (ts - iso->sync_info.first_sync_ts) / iso->sdu_itv;
+ if (new_calc_seq_nb == iso->sync_info.seq_nb) ++new_calc_seq_nb;
+
+ if (iso->sync_info.seq_nb == 0) {
+ evt.evt_lost = 0;
+ } else {
+ evt.evt_lost = new_calc_seq_nb - iso->sync_info.seq_nb - 1;
+ if (evt.evt_lost > 0) {
+ LOG(WARNING) << evt.evt_lost << " packets possibly lost.";
+ }
+
+ if (new_calc_seq_nb != seq_nb) {
+ LOG(WARNING) << "Sequence number mismatch. "
+ "Adjusting own time reference point.";
+ iso->sync_info.first_sync_ts = ts - (seq_nb * iso->sdu_itv);
+ new_calc_seq_nb = seq_nb;
+ }
+ }
+ iso->sync_info.seq_nb = new_calc_seq_nb;
+
+ evt.p_msg = p_msg;
+ evt.cig_id = iso->cig_id;
+ cig_callbacks_->OnCisEvent(kIsoEventCisDataAvailable, &evt);
+ }
+
+ iso_cis* GetCisIfKnown(uint16_t cis_conn_handle) {
+ auto cis_it = conn_hdl_to_cis_map_.find(cis_conn_handle);
+ return (cis_it != conn_hdl_to_cis_map_.end()) ? cis_it->second.get()
+ : nullptr;
+ }
+
+ iso_base* GetIsoIfKnown(uint16_t iso_handle) {
+ return GetCisIfKnown(iso_handle);
+ }
+
+ bool IsCigKnown(uint8_t cig_id) const {
+ auto const cis_it =
+ std::find_if(conn_hdl_to_cis_map_.cbegin(), conn_hdl_to_cis_map_.cend(),
+ [&cig_id](auto& kv_pair) {
+ return (kv_pair.second->cig_id == cig_id);
+ });
+ return (cis_it != conn_hdl_to_cis_map_.cend());
+ }
+
+ std::map<uint16_t, std::unique_ptr<iso_cis>> conn_hdl_to_cis_map_;
+
+ uint16_t iso_credits_;
+ uint16_t iso_buffer_size_;
+
+ CigCallbacks* cig_callbacks_ = nullptr;
+};
+
+} // namespace iso_manager
+} // namespace hci
+} // namespace bluetooth
--- /dev/null
+/*
+ * 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "btm_iso_api.h"
+#include "device/include/controller.h"
+#include "main/shim/shim.h"
+#include "mock_controller.h"
+#include "mock_hcic_layer.h"
+
+using bluetooth::hci::IsoManager;
+using testing::_;
+using testing::AnyNumber;
+using testing::AtLeast;
+using testing::Eq;
+using testing::Matcher;
+using testing::Return;
+using testing::SaveArg;
+using testing::StrictMock;
+using testing::Test;
+
+// Iso Manager currently works on top of the legacy HCI layer
+bool bluetooth::shim::is_gd_shim_enabled() { return false; }
+
+namespace bte {
+class BteInterface {
+ public:
+ virtual void HciSend(BT_HDR* p_msg, uint16_t event) = 0;
+ virtual ~BteInterface() = default;
+};
+
+class MockBteInterface : public BteInterface {
+ public:
+ MOCK_METHOD((void), HciSend, (BT_HDR * p_msg, uint16_t event), (override));
+};
+
+static MockBteInterface* bte_interface = nullptr;
+static void SetMockBteInterface(MockBteInterface* interface) {
+ bte_interface = interface;
+}
+} // namespace bte
+
+void bte_main_hci_send(BT_HDR* p_msg, uint16_t event) {
+ bte::bte_interface->HciSend(p_msg, event);
+}
+
+namespace {
+class MockCigCallbacks : public bluetooth::hci::iso_manager::CigCallbacks {
+ public:
+ MockCigCallbacks() = default;
+ ~MockCigCallbacks() override = default;
+
+ MOCK_METHOD((void), OnSetupIsoDataPath,
+ (uint8_t status, uint16_t conn_handle, uint8_t cig_id),
+ (override));
+ MOCK_METHOD((void), OnRemoveIsoDataPath,
+ (uint8_t status, uint16_t conn_handle, uint8_t cig_id),
+ (override));
+
+ MOCK_METHOD((void), OnCisEvent, (uint8_t event, void* data), (override));
+ MOCK_METHOD((void), OnCigEvent, (uint8_t event, void* data), (override));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCigCallbacks);
+};
+} // namespace
+
+class IsoManagerTest : public Test {
+ protected:
+ void SetUp() override {
+ bte::SetMockBteInterface(&bte_interface_);
+ hcic::SetMockHcicInterface(&hcic_interface_);
+ controller::SetMockControllerInterface(&controller_interface_);
+
+ cig_callbacks_.reset(new MockCigCallbacks());
+
+ EXPECT_CALL(controller_interface_, GetIsoBufferCount())
+ .Times(AtLeast(1))
+ .WillRepeatedly(Return(6));
+ EXPECT_CALL(controller_interface_, GetIsoDataSize())
+ .Times(AtLeast(1))
+ .WillRepeatedly(Return(1024));
+
+ InitIsoManager();
+ }
+
+ void TearDown() override {
+ CleanupIsoManager();
+
+ cig_callbacks_.reset();
+
+ bte::SetMockBteInterface(nullptr);
+ hcic::SetMockHcicInterface(nullptr);
+ controller::SetMockControllerInterface(nullptr);
+ }
+
+ virtual void InitIsoManager() {
+ manager_instance_ = IsoManager::GetInstance();
+ manager_instance_->Start();
+ manager_instance_->RegisterCigCallbacks(cig_callbacks_.get());
+
+ // Default mock SetCigParams action
+ volatile_test_cig_create_cmpl_evt_ = kDefaultCigParamsEvt;
+ ON_CALL(hcic_interface_, SetCigParams)
+ .WillByDefault([this](auto cig_id, auto,
+ base::OnceCallback<void(uint8_t*, uint16_t)> cb) {
+ uint8_t hci_mock_rsp_buffer
+ [3 + sizeof(uint16_t) * this->volatile_test_cig_create_cmpl_evt_
+ .conn_handles.size()];
+ uint8_t* p = hci_mock_rsp_buffer;
+
+ UINT8_TO_STREAM(p, this->volatile_test_cig_create_cmpl_evt_.status);
+ UINT8_TO_STREAM(p, cig_id);
+ UINT8_TO_STREAM(
+ p, this->volatile_test_cig_create_cmpl_evt_.conn_handles.size());
+ for (auto handle :
+ this->volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ UINT16_TO_STREAM(p, handle);
+ }
+
+ std::move(cb).Run(
+ hci_mock_rsp_buffer,
+ 3 + sizeof(uint16_t) * this->volatile_test_cig_create_cmpl_evt_
+ .conn_handles.size());
+ return 0;
+ });
+
+ // Default mock CreateCis action
+ ON_CALL(hcic_interface_, CreateCis)
+ .WillByDefault([](uint8_t num_cis, const EXT_CIS_CREATE_CFG* cis_cfg) {
+ for (const EXT_CIS_CREATE_CFG* cis = cis_cfg; num_cis != 0;
+ num_cis--, cis++) {
+ std::vector<uint8_t> buf(28);
+ uint8_t* p = buf.data();
+ UINT8_TO_STREAM(p, HCI_SUCCESS);
+ UINT16_TO_STREAM(p, cis->cis_conn_handle);
+ UINT24_TO_STREAM(p, 0xEA); // CIG sync delay
+ UINT24_TO_STREAM(p, 0xEB); // CIS sync delay
+ UINT24_TO_STREAM(p, 0xEC); // transport latency mtos
+ UINT24_TO_STREAM(p, 0xED); // transport latency stom
+ UINT8_TO_STREAM(p, 0x01); // phy mtos
+ UINT8_TO_STREAM(p, 0x02); // phy stom
+ UINT8_TO_STREAM(p, 0x01); // nse
+ UINT8_TO_STREAM(p, 0x02); // bn mtos
+ UINT8_TO_STREAM(p, 0x03); // bn stom
+ UINT8_TO_STREAM(p, 0x04); // ft mtos
+ UINT8_TO_STREAM(p, 0x05); // ft stom
+ UINT16_TO_STREAM(p, 0x00FA); // Max PDU mtos
+ UINT16_TO_STREAM(p, 0x00FB); // Max PDU stom
+ UINT16_TO_STREAM(p, 0x0C60); // ISO interval
+
+ IsoManager::GetInstance()->HandleHciEvent(HCI_BLE_CIS_EST_EVT,
+ buf.data(), buf.size());
+ }
+ });
+
+ // Default mock disconnect action
+ ON_CALL(hcic_interface_, Disconnect)
+ .WillByDefault([](uint16_t handle, uint8_t reason) {
+ IsoManager::GetInstance()->HandleDisconnect(handle, reason);
+ });
+
+ // Default mock SetupIsoDataPath HCI action
+ ON_CALL(hcic_interface_, SetupIsoDataPath)
+ .WillByDefault(
+ [](uint16_t iso_handle, uint8_t /* data_path_dir */,
+ uint8_t /* data_path_id */, uint8_t /* codec_id_format */,
+ uint16_t /* codec_id_company */, uint16_t /* codec_id_vendor */,
+ uint32_t /* controller_delay */,
+ std::vector<uint8_t> /* codec_conf */,
+ base::OnceCallback<void(uint8_t*, uint16_t)> cb) {
+ std::vector<uint8_t> buf(3);
+ uint8_t* p = buf.data();
+ UINT8_TO_STREAM(p, HCI_SUCCESS);
+ UINT16_TO_STREAM(p, iso_handle);
+
+ std::move(cb).Run(buf.data(), buf.size());
+ });
+
+ // Default mock RemoveIsoDataPath HCI action
+ ON_CALL(hcic_interface_, RemoveIsoDataPath)
+ .WillByDefault([](uint16_t iso_handle, uint8_t data_path_dir,
+ base::OnceCallback<void(uint8_t*, uint16_t)> cb) {
+ std::vector<uint8_t> buf(3);
+ uint8_t* p = buf.data();
+ UINT8_TO_STREAM(p, HCI_SUCCESS);
+ UINT16_TO_STREAM(p, iso_handle);
+
+ std::move(cb).Run(buf.data(), buf.size());
+ });
+ }
+
+ virtual void CleanupIsoManager() {
+ manager_instance_->Stop();
+ manager_instance_ = nullptr;
+ }
+
+ static const bluetooth::hci::iso_manager::cig_create_params kDefaultCigParams;
+ static const bluetooth::hci::iso_manager::cig_create_params
+ kDefaultCigParams2;
+ static const bluetooth::hci::iso_manager::cig_create_cmpl_evt
+ kDefaultCigParamsEvt;
+ static const bluetooth::hci::iso_manager::iso_data_path_params
+ kDefaultIsoDataPathParams;
+
+ bluetooth::hci::iso_manager::cig_create_cmpl_evt
+ volatile_test_cig_create_cmpl_evt_;
+
+ IsoManager* manager_instance_;
+ bte::MockBteInterface bte_interface_;
+ hcic::MockHcicInterface hcic_interface_;
+ controller::MockControllerInterface controller_interface_;
+
+ std::unique_ptr<MockCigCallbacks> cig_callbacks_;
+};
+
+const bluetooth::hci::iso_manager::cig_create_cmpl_evt
+ IsoManagerTest::kDefaultCigParamsEvt = {
+ .cig_id = 128,
+ .status = 0x00,
+ .conn_handles = std::vector<uint16_t>({0x0EFF, 0x00FF}),
+};
+
+const bluetooth::hci::iso_manager::iso_data_path_params
+ IsoManagerTest::kDefaultIsoDataPathParams = {
+ .data_path_dir = bluetooth::hci::iso_manager::kIsoDataPathDirectionOut,
+ .data_path_id = bluetooth::hci::iso_manager::kIsoDataPathHci,
+ .codec_id_format = 0x06,
+ .codec_id_company = 0,
+ .codec_id_vendor = 0,
+ .controller_delay = 0,
+ .codec_conf = {0x02, 0x01, 0x02},
+};
+
+const bluetooth::hci::iso_manager::cig_create_params
+ IsoManagerTest::kDefaultCigParams = {
+ .sdu_itv_mtos = 0x00002710,
+ .sdu_itv_stom = 0x00002711,
+ .sca = bluetooth::hci::iso_manager::kIsoSca0To20Ppm,
+ .packing = 0x00,
+ .framing = 0x01,
+ .max_trans_lat_stom = 0x000A,
+ .max_trans_lat_mtos = 0x0009,
+ .cis_cfgs =
+ {
+ // CIS #1
+ {
+ .cis_id = 1,
+ .max_sdu_size_mtos = 0x0028,
+ .max_sdu_size_stom = 0x0027,
+ .phy_mtos = 0x04,
+ .phy_stom = 0x03,
+ .rtn_mtos = 0x02,
+ .rtn_stom = 0x01,
+ },
+ // CIS #2
+ {
+ .cis_id = 2,
+ .max_sdu_size_mtos = 0x0029,
+ .max_sdu_size_stom = 0x002A,
+ .phy_mtos = 0x09,
+ .phy_stom = 0x08,
+ .rtn_mtos = 0x07,
+ .rtn_stom = 0x06,
+ },
+ },
+};
+
+const bluetooth::hci::iso_manager::cig_create_params
+ IsoManagerTest::kDefaultCigParams2 = {
+ .sdu_itv_mtos = 0x00002709,
+ .sdu_itv_stom = 0x00002700,
+ .sca = bluetooth::hci::iso_manager::kIsoSca0To20Ppm,
+ .packing = 0x01,
+ .framing = 0x00,
+ .max_trans_lat_stom = 0x000B,
+ .max_trans_lat_mtos = 0x0006,
+ .cis_cfgs =
+ {
+ // CIS #1
+ {
+ .cis_id = 1,
+ .max_sdu_size_mtos = 0x0022,
+ .max_sdu_size_stom = 0x0022,
+ .phy_mtos = 0x01,
+ .phy_stom = 0x02,
+ .rtn_mtos = 0x02,
+ .rtn_stom = 0x01,
+ },
+ // CIS #2
+ {
+ .cis_id = 2,
+ .max_sdu_size_mtos = 0x002A,
+ .max_sdu_size_stom = 0x002B,
+ .phy_mtos = 0x06,
+ .phy_stom = 0x06,
+ .rtn_mtos = 0x07,
+ .rtn_stom = 0x07,
+ },
+ },
+};
+
+class IsoManagerDeathTest : public IsoManagerTest {};
+
+class IsoManagerDeathTestNoInit : public IsoManagerTest {
+ protected:
+ void InitIsoManager() override { /* DO NOTHING */
+ }
+
+ void CleanupIsoManager() override { /* DO NOTHING */
+ }
+};
+
+bool operator==(const EXT_CIS_CFG& x, const EXT_CIS_CFG& y) {
+ return ((x.cis_id == y.cis_id) &&
+ (x.max_sdu_size_mtos == y.max_sdu_size_mtos) &&
+ (x.max_sdu_size_stom == y.max_sdu_size_stom) &&
+ (x.phy_mtos == y.phy_mtos) && (x.phy_stom == y.phy_stom) &&
+ (x.rtn_mtos == y.rtn_mtos) && (x.rtn_stom == y.rtn_stom));
+}
+
+bool operator==(
+ const struct bluetooth::hci::iso_manager::cig_create_params& x,
+ const struct bluetooth::hci::iso_manager::cig_create_params& y) {
+ return ((x.sdu_itv_mtos == y.sdu_itv_mtos) &&
+ (x.sdu_itv_stom == y.sdu_itv_stom) && (x.sca == y.sca) &&
+ (x.packing == y.packing) && (x.framing == y.framing) &&
+ (x.max_trans_lat_stom == y.max_trans_lat_stom) &&
+ (x.max_trans_lat_mtos == y.max_trans_lat_mtos) &&
+ std::is_permutation(x.cis_cfgs.begin(), x.cis_cfgs.end(),
+ y.cis_cfgs.begin()));
+}
+
+namespace iso_matchers {
+MATCHER_P(Eq, value, "") { return (arg == value); }
+MATCHER_P2(EqPointedArray, value, len, "") {
+ return (!std::memcmp(arg, value, len));
+}
+} // namespace iso_matchers
+
+TEST_F(IsoManagerTest, SingletonAccess) {
+ auto* iso_mgr = IsoManager::GetInstance();
+ ASSERT_EQ(manager_instance_, iso_mgr);
+}
+
+TEST_F(IsoManagerTest, RegisterCallbacks) {
+ auto* iso_mgr = IsoManager::GetInstance();
+ ASSERT_EQ(manager_instance_, iso_mgr);
+
+ iso_mgr->RegisterCigCallbacks(new MockCigCallbacks());
+}
+
+TEST_F(IsoManagerDeathTestNoInit, RegisterNullCigCallbacks) {
+ IsoManager::GetInstance()->Start();
+
+ ASSERT_EXIT(IsoManager::GetInstance()->RegisterCigCallbacks(nullptr),
+ ::testing::KilledBySignal(SIGABRT), "Invalid CIG callbacks");
+
+ // Manual cleanup as IsoManagerDeathTest has no 'generic' cleanup
+ IsoManager::GetInstance()->Stop();
+}
+
+TEST_F(IsoManagerDeathTestNoInit, DoubleStart) {
+ IsoManager::GetInstance()->Start();
+
+ ASSERT_EXIT(IsoManager::GetInstance()->Start(),
+ ::testing::KilledBySignal(SIGABRT), "");
+
+ // Manual cleanup as IsoManagerDeathTest has no 'generic' cleanup
+ IsoManager::GetInstance()->Stop();
+}
+
+TEST_F(IsoManagerDeathTestNoInit, DoubleStop) {
+ IsoManager::GetInstance()->Start();
+ IsoManager::GetInstance()->Stop();
+
+ ASSERT_EXIT(IsoManager::GetInstance()->Stop(),
+ ::testing::KilledBySignal(SIGABRT), "");
+}
+
+// Verify hci layer being called by the Iso Manager
+TEST_F(IsoManagerTest, CreateCigHciCall) {
+ for (uint8_t i = 220; i != 60; ++i) {
+ EXPECT_CALL(hcic_interface_,
+ SetCigParams(i, iso_matchers::Eq(kDefaultCigParams), _))
+ .Times(1)
+ .RetiresOnSaturation();
+ IsoManager::GetInstance()->CreateCig(i, kDefaultCigParams);
+ }
+}
+
+// Check handling create cig request twice with the same CIG id
+TEST_F(IsoManagerDeathTest, CreateSameCigTwice) {
+ bluetooth::hci::iso_manager::cig_create_cmpl_evt evt;
+ evt.status = 0x01;
+ EXPECT_CALL(
+ *cig_callbacks_,
+ OnCigEvent(bluetooth::hci::iso_manager::kIsoEventCigOnCreateCmpl, _))
+ .WillOnce([&evt](uint8_t type, void* data) {
+ evt = *static_cast<bluetooth::hci::iso_manager::cig_create_cmpl_evt*>(
+ data);
+ return 0;
+ });
+
+ volatile_test_cig_create_cmpl_evt_.cig_id = 127;
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+ ASSERT_EQ(evt.status, HCI_SUCCESS);
+
+ // Second call with the same CIG ID should fail
+ ASSERT_EXIT(IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams),
+ ::testing::KilledBySignal(SIGABRT), "already exists");
+}
+
+// Check for handling invalid length response from the faulty controller
+TEST_F(IsoManagerDeathTest, CreateCigCallbackInvalidRspPacket) {
+ uint8_t hci_mock_rsp_buffer[] = {0x00, 0x00};
+ ON_CALL(hcic_interface_, SetCigParams)
+ .WillByDefault(
+ [&hci_mock_rsp_buffer](
+ auto, auto, base::OnceCallback<void(uint8_t*, uint16_t)> cb) {
+ std::move(cb).Run(hci_mock_rsp_buffer, sizeof(hci_mock_rsp_buffer));
+ return 0;
+ });
+
+ ASSERT_EXIT(IsoManager::GetInstance()->CreateCig(128, kDefaultCigParams),
+ ::testing::KilledBySignal(SIGABRT), "Invalid packet length");
+}
+
+// Check for handling invalid length response from the faulty controller
+TEST_F(IsoManagerDeathTest, CreateCigCallbackInvalidRspPacket2) {
+ uint8_t hci_mock_rsp_buffer[] = {0x00, 0x00, 0x02, 0x01, 0x00};
+ ON_CALL(hcic_interface_, SetCigParams)
+ .WillByDefault(
+ [&hci_mock_rsp_buffer](
+ auto, auto, base::OnceCallback<void(uint8_t*, uint16_t)> cb) {
+ std::move(cb).Run(hci_mock_rsp_buffer, sizeof(hci_mock_rsp_buffer));
+ return 0;
+ });
+
+ ASSERT_EXIT(IsoManager::GetInstance()->CreateCig(128, kDefaultCigParams),
+ ::testing::KilledBySignal(SIGABRT), "Invalid CIS count");
+}
+
+// Check if IsoManager properly handles error responses from HCI layer
+TEST_F(IsoManagerTest, CreateCigCallbackInvalidStatus) {
+ uint8_t rsp_cig_id = 128;
+ uint8_t rsp_status = 0x01;
+ uint8_t rsp_cis_cnt = 3;
+ uint8_t hci_mock_rsp_buffer[] = {rsp_status, rsp_cig_id, rsp_cis_cnt};
+
+ ON_CALL(hcic_interface_, SetCigParams)
+ .WillByDefault(
+ [&hci_mock_rsp_buffer](
+ auto, auto, base::OnceCallback<void(uint8_t*, uint16_t)> cb) {
+ std::move(cb).Run(hci_mock_rsp_buffer, sizeof(hci_mock_rsp_buffer));
+ return 0;
+ });
+
+ bluetooth::hci::iso_manager::cig_create_cmpl_evt evt;
+ EXPECT_CALL(
+ *cig_callbacks_,
+ OnCigEvent(bluetooth::hci::iso_manager::kIsoEventCigOnCreateCmpl, _))
+ .WillOnce([&evt](uint8_t type, void* data) {
+ evt = *static_cast<bluetooth::hci::iso_manager::cig_create_cmpl_evt*>(
+ data);
+ return 0;
+ });
+
+ IsoManager::GetInstance()->CreateCig(rsp_cig_id, kDefaultCigParams);
+ ASSERT_EQ(evt.cig_id, rsp_cig_id);
+ ASSERT_EQ(evt.status, rsp_status);
+ ASSERT_TRUE(evt.conn_handles.empty());
+}
+
+// Check valid callback response
+TEST_F(IsoManagerTest, CreateCigCallbackValid) {
+ bluetooth::hci::iso_manager::cig_create_cmpl_evt evt;
+ EXPECT_CALL(
+ *cig_callbacks_,
+ OnCigEvent(bluetooth::hci::iso_manager::kIsoEventCigOnCreateCmpl, _))
+ .WillOnce([&evt](uint8_t type, void* data) {
+ evt = *static_cast<bluetooth::hci::iso_manager::cig_create_cmpl_evt*>(
+ data);
+ return 0;
+ });
+
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+ ASSERT_EQ(evt.cig_id, volatile_test_cig_create_cmpl_evt_.cig_id);
+ ASSERT_EQ(evt.status, volatile_test_cig_create_cmpl_evt_.status);
+ ASSERT_EQ(evt.conn_handles.size(), 2u);
+ ASSERT_TRUE(
+ std::is_permutation(evt.conn_handles.begin(), evt.conn_handles.end(),
+ std::vector<uint16_t>({0x0EFF, 0x00FF}).begin()));
+}
+
+// Check if CIG reconfigure triggers HCI layer call
+TEST_F(IsoManagerTest, ReconfigureCigHciCall) {
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ EXPECT_CALL(hcic_interface_,
+ SetCigParams(volatile_test_cig_create_cmpl_evt_.cig_id,
+ iso_matchers::Eq(kDefaultCigParams), _))
+ .Times(1);
+ IsoManager::GetInstance()->ReconfigureCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+}
+
+// Verify handlidng invalid call - reconfiguring invalid CIG
+TEST_F(IsoManagerDeathTest, ReconfigureCigWithNoSuchCig) {
+ ASSERT_EXIT(IsoManager::GetInstance()->ReconfigureCig(128, kDefaultCigParams),
+ ::testing::KilledBySignal(SIGABRT), "No such cig");
+}
+
+TEST_F(IsoManagerDeathTest, ReconfigureCigInvalidRspPacket) {
+ uint8_t hci_mock_rsp_buffer[] = {0x00, 0x00};
+
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ ON_CALL(hcic_interface_, SetCigParams)
+ .WillByDefault(
+ [&hci_mock_rsp_buffer](
+ auto, auto, base::OnceCallback<void(uint8_t*, uint16_t)> cb) {
+ std::move(cb).Run(hci_mock_rsp_buffer, sizeof(hci_mock_rsp_buffer));
+ return 0;
+ });
+ ASSERT_EXIT(IsoManager::GetInstance()->ReconfigureCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams),
+ ::testing::KilledBySignal(SIGABRT), "Invalid packet length");
+}
+
+TEST_F(IsoManagerDeathTest, ReconfigureCigInvalidRspPacket2) {
+ uint8_t hci_mock_rsp_buffer[] = {0x00, 0x00, 0x02, 0x01, 0x00};
+
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ ON_CALL(hcic_interface_, SetCigParams)
+ .WillByDefault(
+ [&hci_mock_rsp_buffer](
+ auto, auto, base::OnceCallback<void(uint8_t*, uint16_t)> cb) {
+ std::move(cb).Run(hci_mock_rsp_buffer, sizeof(hci_mock_rsp_buffer));
+ return 0;
+ });
+ ASSERT_EXIT(
+ IsoManager::GetInstance()->ReconfigureCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams2),
+ ::testing::KilledBySignal(SIGABRT), "Invalid CIS count");
+}
+
+TEST_F(IsoManagerTest, ReconfigureCigInvalidStatus) {
+ uint8_t rsp_cig_id = 128;
+ uint8_t rsp_status = 0x01;
+ uint8_t rsp_cis_cnt = 3;
+ uint8_t hci_mock_rsp_buffer[] = {rsp_status, rsp_cig_id, rsp_cis_cnt};
+
+ IsoManager::GetInstance()->CreateCig(rsp_cig_id, kDefaultCigParams);
+
+ // Set-up the invalid response
+ ON_CALL(hcic_interface_, SetCigParams)
+ .WillByDefault(
+ [&hci_mock_rsp_buffer](
+ auto, auto, base::OnceCallback<void(uint8_t*, uint16_t)> cb) {
+ std::move(cb).Run(hci_mock_rsp_buffer, sizeof(hci_mock_rsp_buffer));
+ return 0;
+ });
+
+ bluetooth::hci::iso_manager::cig_create_cmpl_evt evt;
+ EXPECT_CALL(
+ *cig_callbacks_,
+ OnCigEvent(bluetooth::hci::iso_manager::kIsoEventCigOnReconfigureCmpl, _))
+ .WillOnce([&evt](uint8_t type, void* data) {
+ evt = *static_cast<bluetooth::hci::iso_manager::cig_create_cmpl_evt*>(
+ data);
+ return 0;
+ });
+ IsoManager::GetInstance()->ReconfigureCig(rsp_cig_id, kDefaultCigParams2);
+
+ ASSERT_EQ(evt.cig_id, rsp_cig_id);
+ ASSERT_EQ(evt.status, rsp_status);
+ ASSERT_TRUE(evt.conn_handles.empty());
+}
+
+TEST_F(IsoManagerTest, ReconfigureCigValid) {
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ bluetooth::hci::iso_manager::cig_create_cmpl_evt evt;
+ EXPECT_CALL(
+ *cig_callbacks_,
+ OnCigEvent(bluetooth::hci::iso_manager::kIsoEventCigOnReconfigureCmpl, _))
+ .WillOnce([&evt](uint8_t type, void* data) {
+ evt = *static_cast<bluetooth::hci::iso_manager::cig_create_cmpl_evt*>(
+ data);
+ return 0;
+ });
+
+ // Verify valid reconfiguration request
+ IsoManager::GetInstance()->ReconfigureCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams2);
+ ASSERT_EQ(evt.cig_id, volatile_test_cig_create_cmpl_evt_.cig_id);
+ ASSERT_EQ(evt.status, volatile_test_cig_create_cmpl_evt_.status);
+ ASSERT_TRUE(std::is_permutation(
+ evt.conn_handles.begin(), evt.conn_handles.end(),
+ volatile_test_cig_create_cmpl_evt_.conn_handles.begin()));
+}
+
+TEST_F(IsoManagerTest, RemoveCigHciCall) {
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ EXPECT_CALL(hcic_interface_,
+ RemoveCig(volatile_test_cig_create_cmpl_evt_.cig_id, _))
+ .Times(1);
+ IsoManager::GetInstance()->RemoveCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id);
+}
+
+TEST_F(IsoManagerDeathTest, RemoveCigWithNoSuchCig) {
+ ASSERT_EXIT(IsoManager::GetInstance()->RemoveCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id),
+ ::testing::KilledBySignal(SIGABRT), "No such cig");
+}
+
+TEST_F(IsoManagerDeathTest, RemoveSameCigTwice) {
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ ON_CALL(hcic_interface_, RemoveCig)
+ .WillByDefault(
+ [this](auto, base::OnceCallback<void(uint8_t*, uint16_t)> cb) {
+ uint8_t hci_mock_rsp_buffer[2];
+ uint8_t* p = hci_mock_rsp_buffer;
+
+ UINT8_TO_STREAM(p, HCI_SUCCESS);
+ UINT8_TO_STREAM(p, this->volatile_test_cig_create_cmpl_evt_.cig_id);
+
+ std::move(cb).Run(hci_mock_rsp_buffer, sizeof(hci_mock_rsp_buffer));
+ return 0;
+ });
+
+ IsoManager::GetInstance()->RemoveCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id);
+
+ ASSERT_EXIT(IsoManager::GetInstance()->RemoveCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id),
+ ::testing::KilledBySignal(SIGABRT), "No such cig");
+}
+
+TEST_F(IsoManagerDeathTest, RemoveCigInvalidRspPacket) {
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ ON_CALL(hcic_interface_, RemoveCig)
+ .WillByDefault([](auto, base::OnceCallback<void(uint8_t*, uint16_t)> cb) {
+ uint8_t hci_mock_rsp_buffer[] = {0x00}; // status byte only
+
+ std::move(cb).Run(hci_mock_rsp_buffer, sizeof(hci_mock_rsp_buffer));
+ return 0;
+ });
+ ASSERT_EXIT(IsoManager::GetInstance()->RemoveCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id),
+ ::testing::KilledBySignal(SIGABRT), "Invalid packet length");
+}
+
+TEST_F(IsoManagerTest, RemoveCigInvalidStatus) {
+ uint8_t rsp_status = 0x02;
+ uint8_t hci_mock_rsp_buffer[] = {rsp_status,
+ volatile_test_cig_create_cmpl_evt_.cig_id};
+
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ ON_CALL(hcic_interface_, RemoveCig)
+ .WillByDefault(
+ [&hci_mock_rsp_buffer](
+ auto, base::OnceCallback<void(uint8_t*, uint16_t)> cb) {
+ std::move(cb).Run(hci_mock_rsp_buffer, sizeof(hci_mock_rsp_buffer));
+ return 0;
+ });
+
+ bluetooth::hci::iso_manager::cig_remove_cmpl_evt evt;
+ ON_CALL(*cig_callbacks_,
+ OnCigEvent(bluetooth::hci::iso_manager::kIsoEventCigOnRemoveCmpl, _))
+ .WillByDefault([&evt](uint8_t type, void* data) {
+ evt = *static_cast<bluetooth::hci::iso_manager::cig_remove_cmpl_evt*>(
+ data);
+ return 0;
+ });
+
+ IsoManager::GetInstance()->RemoveCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id);
+ ASSERT_EQ(evt.cig_id, volatile_test_cig_create_cmpl_evt_.cig_id);
+ ASSERT_EQ(evt.status, rsp_status);
+}
+
+TEST_F(IsoManagerTest, RemoveCigValid) {
+ uint8_t hci_mock_rsp_buffer[] = {HCI_SUCCESS,
+ volatile_test_cig_create_cmpl_evt_.cig_id};
+
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ ON_CALL(hcic_interface_, RemoveCig)
+ .WillByDefault(
+ [&hci_mock_rsp_buffer](
+ auto, base::OnceCallback<void(uint8_t*, uint16_t)> cb) {
+ std::move(cb).Run(hci_mock_rsp_buffer, sizeof(hci_mock_rsp_buffer));
+ return 0;
+ });
+
+ bluetooth::hci::iso_manager::cig_remove_cmpl_evt evt;
+ EXPECT_CALL(
+ *cig_callbacks_,
+ OnCigEvent(bluetooth::hci::iso_manager::kIsoEventCigOnRemoveCmpl, _))
+ .WillOnce([&evt](uint8_t type, void* data) {
+ evt = *static_cast<bluetooth::hci::iso_manager::cig_remove_cmpl_evt*>(
+ data);
+ return 0;
+ });
+
+ IsoManager::GetInstance()->RemoveCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id);
+ ASSERT_EQ(evt.cig_id, volatile_test_cig_create_cmpl_evt_.cig_id);
+ ASSERT_EQ(evt.status, HCI_SUCCESS);
+}
+
+TEST_F(IsoManagerTest, EstablishCisHciCall) {
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ bluetooth::hci::iso_manager::cis_establish_params params;
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ params.conn_pairs.push_back({handle, 1});
+ }
+
+ EXPECT_CALL(hcic_interface_,
+ CreateCis(2, iso_matchers::EqPointedArray(
+ params.conn_pairs.data(),
+ params.conn_pairs.size() *
+ sizeof(params.conn_pairs.data()[0]))))
+ .Times(1);
+ IsoManager::GetInstance()->EstablishCis(params);
+}
+
+TEST_F(IsoManagerDeathTest, EstablishCisWithNoSuchCis) {
+ bluetooth::hci::iso_manager::cis_establish_params params;
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ params.conn_pairs.push_back({handle, 1});
+ }
+
+ ASSERT_EXIT(
+ IsoManager::GetInstance()->IsoManager::GetInstance()->EstablishCis(
+ params),
+ ::testing::KilledBySignal(SIGABRT), "No such cis");
+}
+
+TEST_F(IsoManagerDeathTest, ConnectSameCisTwice) {
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ bluetooth::hci::iso_manager::cis_establish_params params;
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ params.conn_pairs.push_back({handle, 1});
+ }
+ IsoManager::GetInstance()->EstablishCis(params);
+
+ ASSERT_EXIT(
+ IsoManager::GetInstance()->IsoManager::GetInstance()->EstablishCis(
+ params),
+ ::testing::KilledBySignal(SIGABRT), "Already connected");
+}
+
+TEST_F(IsoManagerDeathTest, EstablishCisInvalidResponsePacket) {
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ ON_CALL(hcic_interface_, CreateCis)
+ .WillByDefault([this](uint8_t num_cis,
+ const EXT_CIS_CREATE_CFG* cis_cfg) {
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ std::vector<uint8_t> buf(27);
+ uint8_t* p = buf.data();
+ UINT8_TO_STREAM(p, HCI_SUCCESS);
+ UINT16_TO_STREAM(p, handle);
+ UINT24_TO_STREAM(p, 0xEA); // CIG sync delay
+ UINT24_TO_STREAM(p, 0xEB); // CIS sync delay
+ UINT24_TO_STREAM(p, 0xEC); // transport latency mtos
+ UINT24_TO_STREAM(p, 0xED); // transport latency stom
+ UINT8_TO_STREAM(p, 0x01); // phy mtos
+ UINT8_TO_STREAM(p, 0x02); // phy stom
+ UINT8_TO_STREAM(p, 0x01); // nse
+ UINT8_TO_STREAM(p, 0x02); // bn mtos
+ UINT8_TO_STREAM(p, 0x03); // bn stom
+ UINT8_TO_STREAM(p, 0x04); // ft mtos
+ UINT8_TO_STREAM(p, 0x05); // ft stom
+ UINT16_TO_STREAM(p, 0x00FA); // Max PDU mtos
+ UINT16_TO_STREAM(p, 0x00FB); // Max PDU stom
+
+ IsoManager::GetInstance()->HandleHciEvent(HCI_BLE_CIS_EST_EVT,
+ buf.data(), buf.size());
+ }
+ });
+
+ bluetooth::hci::iso_manager::cis_establish_params params;
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ params.conn_pairs.push_back({handle, 1});
+ }
+
+ ASSERT_EXIT(
+ IsoManager::GetInstance()->IsoManager::GetInstance()->EstablishCis(
+ params),
+ ::testing::KilledBySignal(SIGABRT), "Invalid packet length");
+}
+
+TEST_F(IsoManagerTest, EstablishCisInvalidStatus) {
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+ uint8_t invalid_status = 0x01;
+
+ ON_CALL(hcic_interface_, CreateCis)
+ .WillByDefault([this, invalid_status](uint8_t num_cis,
+ const EXT_CIS_CREATE_CFG* cis_cfg) {
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ std::vector<uint8_t> buf(28);
+ uint8_t* p = buf.data();
+ UINT8_TO_STREAM(p, invalid_status);
+ UINT16_TO_STREAM(p, handle);
+ UINT24_TO_STREAM(p, 0xEA); // CIG sync delay
+ UINT24_TO_STREAM(p, 0xEB); // CIS sync delay
+ UINT24_TO_STREAM(p, 0xEC); // transport latency mtos
+ UINT24_TO_STREAM(p, 0xED); // transport latency stom
+ UINT8_TO_STREAM(p, 0x01); // phy mtos
+ UINT8_TO_STREAM(p, 0x02); // phy stom
+ UINT8_TO_STREAM(p, 0x01); // nse
+ UINT8_TO_STREAM(p, 0x02); // bn mtos
+ UINT8_TO_STREAM(p, 0x03); // bn stom
+ UINT8_TO_STREAM(p, 0x04); // ft mtos
+ UINT8_TO_STREAM(p, 0x05); // ft stom
+ UINT16_TO_STREAM(p, 0x00FA); // Max PDU mtos
+ UINT16_TO_STREAM(p, 0x00FB); // Max PDU stom
+ UINT16_TO_STREAM(p, 0x0C60); // ISO interval
+
+ IsoManager::GetInstance()->HandleHciEvent(HCI_BLE_CIS_EST_EVT,
+ buf.data(), buf.size());
+ }
+ });
+
+ EXPECT_CALL(
+ *cig_callbacks_,
+ OnCisEvent(bluetooth::hci::iso_manager::kIsoEventCisEstablishCmpl, _))
+ .Times(kDefaultCigParams.cis_cfgs.size())
+ .WillRepeatedly([this, invalid_status](uint8_t type, void* data) {
+ bluetooth::hci::iso_manager::cis_establish_cmpl_evt* evt =
+ static_cast<bluetooth::hci::iso_manager::cis_establish_cmpl_evt*>(
+ data);
+
+ ASSERT_EQ(evt->status, invalid_status);
+ ASSERT_TRUE(
+ std::find(volatile_test_cig_create_cmpl_evt_.conn_handles.begin(),
+ volatile_test_cig_create_cmpl_evt_.conn_handles.end(),
+ evt->cis_conn_hdl) !=
+ volatile_test_cig_create_cmpl_evt_.conn_handles.end());
+ });
+
+ // Establish all CISes before setting up their data paths
+ bluetooth::hci::iso_manager::cis_establish_params params;
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ params.conn_pairs.push_back({handle, 1});
+ }
+ IsoManager::GetInstance()->EstablishCis(params);
+}
+
+TEST_F(IsoManagerTest, EstablishCisValid) {
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ EXPECT_CALL(
+ *cig_callbacks_,
+ OnCisEvent(bluetooth::hci::iso_manager::kIsoEventCisEstablishCmpl, _))
+ .Times(kDefaultCigParams.cis_cfgs.size())
+ .WillRepeatedly([this](uint8_t type, void* data) {
+ bluetooth::hci::iso_manager::cis_establish_cmpl_evt* evt =
+ static_cast<bluetooth::hci::iso_manager::cis_establish_cmpl_evt*>(
+ data);
+
+ ASSERT_EQ(evt->status, HCI_SUCCESS);
+ ASSERT_TRUE(
+ std::find(volatile_test_cig_create_cmpl_evt_.conn_handles.begin(),
+ volatile_test_cig_create_cmpl_evt_.conn_handles.end(),
+ evt->cis_conn_hdl) !=
+ volatile_test_cig_create_cmpl_evt_.conn_handles.end());
+ });
+
+ // Establish all CISes before setting up their data paths
+ bluetooth::hci::iso_manager::cis_establish_params params;
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ params.conn_pairs.push_back({handle, 1});
+ }
+ IsoManager::GetInstance()->EstablishCis(params);
+}
+
+TEST_F(IsoManagerTest, ReconnectCisValid) {
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ // Establish all CISes before setting up their data paths
+ bluetooth::hci::iso_manager::cis_establish_params params;
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ params.conn_pairs.push_back({handle, 1});
+ }
+ IsoManager::GetInstance()->EstablishCis(params);
+
+ // trigger HCI disconnection event
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ IsoManager::GetInstance()->HandleDisconnect(handle, 0x16);
+ }
+
+ EXPECT_CALL(
+ *cig_callbacks_,
+ OnCisEvent(bluetooth::hci::iso_manager::kIsoEventCisEstablishCmpl, _))
+ .Times(kDefaultCigParams.cis_cfgs.size())
+ .WillRepeatedly([this](uint8_t type, void* data) {
+ bluetooth::hci::iso_manager::cis_establish_cmpl_evt* evt =
+ static_cast<bluetooth::hci::iso_manager::cis_establish_cmpl_evt*>(
+ data);
+
+ ASSERT_EQ(evt->status, HCI_SUCCESS);
+ ASSERT_TRUE(
+ std::find(volatile_test_cig_create_cmpl_evt_.conn_handles.begin(),
+ volatile_test_cig_create_cmpl_evt_.conn_handles.end(),
+ evt->cis_conn_hdl) !=
+ volatile_test_cig_create_cmpl_evt_.conn_handles.end());
+ });
+ IsoManager::GetInstance()->EstablishCis(params);
+}
+
+TEST_F(IsoManagerTest, DisconnectCisHciCall) {
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ // Establish all CISes before setting up their data paths
+ bluetooth::hci::iso_manager::cis_establish_params params;
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ params.conn_pairs.push_back({handle, 1});
+ }
+ IsoManager::GetInstance()->EstablishCis(params);
+
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ EXPECT_CALL(hcic_interface_, Disconnect(handle, 0x16))
+ .Times(1)
+ .RetiresOnSaturation();
+ IsoManager::GetInstance()->IsoManager::GetInstance()->DisconnectCis(handle,
+ 0x16);
+ }
+}
+
+TEST_F(IsoManagerDeathTest, DisconnectCisWithNoSuchCis) {
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ ASSERT_EXIT(
+ IsoManager::GetInstance()->IsoManager::GetInstance()->DisconnectCis(
+ handle, 0x16),
+ ::testing::KilledBySignal(SIGABRT), "No such cis");
+ }
+}
+
+TEST_F(IsoManagerDeathTest, DisconnectSameCisTwice) {
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ // Establish all CISes before setting up their data paths
+ bluetooth::hci::iso_manager::cis_establish_params params;
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ params.conn_pairs.push_back({handle, 1});
+ }
+ IsoManager::GetInstance()->EstablishCis(params);
+
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ IsoManager::GetInstance()->IsoManager::GetInstance()->DisconnectCis(handle,
+ 0x16);
+ }
+
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ ASSERT_EXIT(
+ IsoManager::GetInstance()->IsoManager::GetInstance()->DisconnectCis(
+ handle, 0x16),
+ ::testing::KilledBySignal(SIGABRT), "Not connected");
+ }
+}
+
+TEST_F(IsoManagerTest, DisconnectCisValid) {
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ // Establish all CISes before setting up their data paths
+ bluetooth::hci::iso_manager::cis_establish_params params;
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ params.conn_pairs.push_back({handle, 1});
+ }
+ IsoManager::GetInstance()->EstablishCis(params);
+
+ uint8_t disconnect_reason = 0x16;
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ EXPECT_CALL(*cig_callbacks_, OnCisEvent)
+ .WillOnce([this, handle, disconnect_reason](uint8_t event_code,
+ void* data) {
+ ASSERT_EQ(event_code,
+ bluetooth::hci::iso_manager::kIsoEventCisDisconnected);
+ auto* event =
+ static_cast<bluetooth::hci::iso_manager::cis_disconnected_evt*>(
+ data);
+ ASSERT_EQ(event->reason, disconnect_reason);
+ ASSERT_EQ(event->cig_id, volatile_test_cig_create_cmpl_evt_.cig_id);
+ ASSERT_EQ(event->cis_conn_hdl, handle);
+ })
+ .RetiresOnSaturation();
+ IsoManager::GetInstance()->IsoManager::GetInstance()->DisconnectCis(
+ handle, disconnect_reason);
+ }
+}
+
+// Check if we properly ignore not ISO related disconnect events
+TEST_F(IsoManagerDeathTest, DisconnectCisInvalidResponse) {
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ bluetooth::hci::iso_manager::cis_establish_params params;
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ params.conn_pairs.push_back({handle, 1});
+ }
+ IsoManager::GetInstance()->EstablishCis(params);
+
+ // Make the HCI layer send invalid handles in disconnect event
+ ON_CALL(hcic_interface_, Disconnect)
+ .WillByDefault([](uint16_t handle, uint8_t reason) {
+ IsoManager::GetInstance()->HandleDisconnect(handle + 1, reason);
+ });
+
+ // We don't expect any calls as these are not ISO handles
+ ON_CALL(*cig_callbacks_,
+ OnCisEvent(bluetooth::hci::iso_manager::kIsoEventCisDisconnected, _))
+ .WillByDefault([](uint8_t event_code, void* data) { FAIL(); });
+
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ IsoManager::GetInstance()->IsoManager::GetInstance()->DisconnectCis(handle,
+ 0x16);
+ }
+}
+
+TEST_F(IsoManagerTest, SetupIsoDataPathValid) {
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ // Establish all CISes before setting up their data paths
+ bluetooth::hci::iso_manager::cis_establish_params params;
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ params.conn_pairs.push_back({handle, 1});
+ }
+ IsoManager::GetInstance()->EstablishCis(params);
+
+ bluetooth::hci::iso_manager::iso_data_path_params path_params =
+ kDefaultIsoDataPathParams;
+
+ // Setup data paths for all CISes
+ path_params.data_path_dir =
+ bluetooth::hci::iso_manager::kIsoDataPathDirectionIn;
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ EXPECT_CALL(*cig_callbacks_,
+ OnSetupIsoDataPath(HCI_SUCCESS, handle,
+ volatile_test_cig_create_cmpl_evt_.cig_id))
+ .Times(1)
+ .RetiresOnSaturation();
+
+ path_params.data_path_dir =
+ (bluetooth::hci::iso_manager::kIsoDataPathDirectionIn + handle) % 2;
+
+ IsoManager::GetInstance()->SetupIsoDataPath(handle, path_params);
+ }
+}
+
+TEST_F(IsoManagerTest, SetupIsoDataPathTwice) {
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ // Establish CISes
+ bluetooth::hci::iso_manager::cis_establish_params params;
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ params.conn_pairs.push_back({handle, 1});
+ }
+ IsoManager::GetInstance()->EstablishCis(params);
+
+ // Setup data paths for all CISes twice
+ bluetooth::hci::iso_manager::iso_data_path_params path_params =
+ kDefaultIsoDataPathParams;
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ IsoManager::GetInstance()->SetupIsoDataPath(handle, path_params);
+ // Should be possible to reconfigure
+ IsoManager::GetInstance()->SetupIsoDataPath(handle, path_params);
+ }
+}
+
+TEST_F(IsoManagerTest, SetupIsoDataPathInvalidStatus) {
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ // Establish all CISes before setting up their data paths
+ bluetooth::hci::iso_manager::cis_establish_params params;
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ params.conn_pairs.push_back({handle, 1});
+ }
+ IsoManager::GetInstance()->EstablishCis(params);
+
+ bluetooth::hci::iso_manager::iso_data_path_params path_params =
+ kDefaultIsoDataPathParams;
+
+ uint8_t setup_datapath_rsp_status = HCI_SUCCESS;
+ ON_CALL(hcic_interface_, SetupIsoDataPath)
+ .WillByDefault([&setup_datapath_rsp_status](
+ uint16_t iso_handle, uint8_t, uint8_t, uint8_t,
+ uint16_t, uint16_t, uint32_t, std::vector<uint8_t>,
+ base::OnceCallback<void(uint8_t*, uint16_t)> cb) {
+ std::vector<uint8_t> buf(3);
+ uint8_t* p = buf.data();
+ UINT8_TO_STREAM(p, setup_datapath_rsp_status);
+ UINT16_TO_STREAM(p, iso_handle);
+
+ std::move(cb).Run(buf.data(), buf.size());
+ });
+
+ // Try to setup data paths for all CISes
+ path_params.data_path_dir =
+ bluetooth::hci::iso_manager::kIsoDataPathDirectionIn;
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ // Mock the response with status != HCI_SUCCESS
+ EXPECT_CALL(*cig_callbacks_,
+ OnSetupIsoDataPath(0x11, handle,
+ volatile_test_cig_create_cmpl_evt_.cig_id))
+ .Times(1)
+ .RetiresOnSaturation();
+ setup_datapath_rsp_status = 0x11;
+ IsoManager::GetInstance()->SetupIsoDataPath(handle, path_params);
+
+ // It should be possible to retry on the same handle after the first
+ // failure
+ EXPECT_CALL(*cig_callbacks_,
+ OnSetupIsoDataPath(HCI_SUCCESS, handle,
+ volatile_test_cig_create_cmpl_evt_.cig_id))
+ .Times(1)
+ .RetiresOnSaturation();
+ setup_datapath_rsp_status = HCI_SUCCESS;
+ IsoManager::GetInstance()->SetupIsoDataPath(handle, path_params);
+ }
+}
+
+TEST_F(IsoManagerTest, RemoveIsoDataPathValid) {
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ // Establish all CISes before setting up their data paths
+ bluetooth::hci::iso_manager::cis_establish_params params;
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ params.conn_pairs.push_back({handle, 1});
+ }
+ IsoManager::GetInstance()->EstablishCis(params);
+
+ bluetooth::hci::iso_manager::iso_data_path_params path_params =
+ kDefaultIsoDataPathParams;
+
+ // Setup and remove data paths for all CISes
+ path_params.data_path_dir =
+ bluetooth::hci::iso_manager::kIsoDataPathDirectionIn;
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ IsoManager::GetInstance()->SetupIsoDataPath(handle, path_params);
+
+ EXPECT_CALL(*cig_callbacks_,
+ OnRemoveIsoDataPath(HCI_SUCCESS, handle,
+ volatile_test_cig_create_cmpl_evt_.cig_id))
+ .Times(1)
+ .RetiresOnSaturation();
+ IsoManager::GetInstance()->RemoveIsoDataPath(handle,
+ path_params.data_path_dir);
+ }
+}
+
+TEST_F(IsoManagerDeathTest, RemoveIsoDataPathNoSuchPath) {
+ // Check on CIS
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+ uint16_t iso_handle = volatile_test_cig_create_cmpl_evt_.conn_handles[0];
+ ASSERT_EXIT(
+ IsoManager::GetInstance()->RemoveIsoDataPath(
+ iso_handle, bluetooth::hci::iso_manager::kIsoDataPathDirectionOut),
+ ::testing::KilledBySignal(SIGABRT), "path not set");
+
+ IsoManager::GetInstance()->EstablishCis({.conn_pairs = {{iso_handle, 1}}});
+ ASSERT_EXIT(
+ IsoManager::GetInstance()->RemoveIsoDataPath(
+ iso_handle, bluetooth::hci::iso_manager::kIsoDataPathDirectionOut),
+ ::testing::KilledBySignal(SIGABRT), "path not set");
+}
+
+TEST_F(IsoManagerDeathTest, RemoveIsoDataPathTwice) {
+ // Check on CIS
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+ uint16_t iso_handle = volatile_test_cig_create_cmpl_evt_.conn_handles[0];
+ IsoManager::GetInstance()->EstablishCis({.conn_pairs = {{iso_handle, 1}}});
+ IsoManager::GetInstance()->SetupIsoDataPath(iso_handle,
+ kDefaultIsoDataPathParams);
+ IsoManager::GetInstance()->RemoveIsoDataPath(
+ iso_handle, kDefaultIsoDataPathParams.data_path_dir);
+ ASSERT_EXIT(
+ IsoManager::GetInstance()->RemoveIsoDataPath(
+ iso_handle, bluetooth::hci::iso_manager::kIsoDataPathDirectionOut),
+ ::testing::KilledBySignal(SIGABRT), "path not set");
+}
+
+// Check if HCI status other than HCI_SUCCESS is being propagated to the caller
+TEST_F(IsoManagerTest, RemoveIsoDataPathInvalidStatus) {
+ // Mock invalid status response
+ uint8_t remove_datapath_rsp_status = 0x12;
+ ON_CALL(hcic_interface_, RemoveIsoDataPath)
+ .WillByDefault([&remove_datapath_rsp_status](
+ uint16_t iso_handle, uint8_t data_path_dir,
+ base::OnceCallback<void(uint8_t*, uint16_t)> cb) {
+ std::vector<uint8_t> buf(3);
+ uint8_t* p = buf.data();
+ UINT8_TO_STREAM(p, remove_datapath_rsp_status);
+ UINT16_TO_STREAM(p, iso_handle);
+
+ std::move(cb).Run(buf.data(), buf.size());
+ });
+
+ // Check on CIS
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+ uint16_t iso_handle = volatile_test_cig_create_cmpl_evt_.conn_handles[0];
+ IsoManager::GetInstance()->EstablishCis({.conn_pairs = {{iso_handle, 1}}});
+ IsoManager::GetInstance()->SetupIsoDataPath(iso_handle,
+ kDefaultIsoDataPathParams);
+
+ EXPECT_CALL(*cig_callbacks_,
+ OnRemoveIsoDataPath(remove_datapath_rsp_status, iso_handle,
+ volatile_test_cig_create_cmpl_evt_.cig_id))
+ .Times(1);
+ IsoManager::GetInstance()->RemoveIsoDataPath(
+ iso_handle, kDefaultIsoDataPathParams.data_path_dir);
+}
+
+TEST_F(IsoManagerTest, SendIsoDataCigValid) {
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ bluetooth::hci::iso_manager::cis_establish_params params;
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ params.conn_pairs.push_back({handle, 1});
+ }
+ IsoManager::GetInstance()->EstablishCis(params);
+
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ bluetooth::hci::iso_manager::iso_data_path_params path_params =
+ kDefaultIsoDataPathParams;
+ path_params.data_path_dir =
+ bluetooth::hci::iso_manager::kIsoDataPathDirectionOut;
+ IsoManager::GetInstance()->SetupIsoDataPath(handle, path_params);
+
+ for (uint8_t num_pkts = 2; num_pkts != 0; num_pkts--) {
+ constexpr uint8_t data_len = 108;
+
+ EXPECT_CALL(bte_interface_, HciSend)
+ .WillOnce([handle, data_len](BT_HDR* p_msg, uint16_t event) {
+ uint8_t* p = p_msg->data;
+ uint16_t msg_handle;
+ uint16_t iso_load_len;
+
+ ASSERT_TRUE((event & MSG_STACK_TO_HC_HCI_ISO) != 0);
+ ASSERT_NE(p_msg, nullptr);
+ ASSERT_EQ(p_msg->len, data_len + ((p_msg->layer_specific &
+ BT_ISO_HDR_CONTAINS_TS)
+ ? 12
+ : 8));
+
+ // Verify packet internals
+ STREAM_TO_UINT16(msg_handle, p);
+ ASSERT_EQ(msg_handle, handle);
+
+ STREAM_TO_UINT16(iso_load_len, p);
+ ASSERT_EQ(
+ iso_load_len,
+ data_len +
+ ((p_msg->layer_specific & BT_ISO_HDR_CONTAINS_TS) ? 8 : 4));
+
+ if (p_msg->layer_specific & BT_ISO_HDR_CONTAINS_TS) {
+ STREAM_SKIP_UINT16(p); // skip ts LSB halfword
+ STREAM_SKIP_UINT16(p); // skip ts MSB halfword
+ }
+ STREAM_SKIP_UINT16(p); // skip seq_nb
+
+ uint16_t msg_data_len;
+ STREAM_TO_UINT16(msg_data_len, p);
+ ASSERT_EQ(msg_data_len, data_len);
+ })
+ .RetiresOnSaturation();
+
+ std::vector<uint8_t> data_vec(data_len, 0);
+ IsoManager::GetInstance()->SendIsoData(handle, data_vec.data(),
+ data_vec.size());
+ }
+ }
+}
+
+TEST_F(IsoManagerTest, SendIsoDataNoCredits) {
+ uint8_t num_buffers = controller_interface_.GetIsoBufferCount();
+ std::vector<uint8_t> data_vec(108, 0);
+
+ // Check on CIG
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ bluetooth::hci::iso_manager::cis_establish_params params;
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ params.conn_pairs.push_back({handle, 1});
+ }
+ IsoManager::GetInstance()->EstablishCis(params);
+
+ IsoManager::GetInstance()->SetupIsoDataPath(
+ volatile_test_cig_create_cmpl_evt_.conn_handles[0],
+ kDefaultIsoDataPathParams);
+
+ /* Try sending twice as much data as we can ignoring the credit limits and
+ * expect the redundant packets to be ignored and not propagated down to the
+ * HCI.
+ */
+ EXPECT_CALL(bte_interface_, HciSend).Times(num_buffers);
+ for (uint8_t i = 0; i < (2 * num_buffers); i++) {
+ IsoManager::GetInstance()->SendIsoData(
+ volatile_test_cig_create_cmpl_evt_.conn_handles[0], data_vec.data(),
+ data_vec.size());
+ }
+}
+
+TEST_F(IsoManagerTest, SendIsoDataCreditsReturned) {
+ uint8_t num_buffers = controller_interface_.GetIsoBufferCount();
+ std::vector<uint8_t> data_vec(108, 0);
+
+ // Check on CIG
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ bluetooth::hci::iso_manager::cis_establish_params params;
+ for (auto& handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ params.conn_pairs.push_back({handle, 1});
+ }
+ IsoManager::GetInstance()->EstablishCis(params);
+
+ IsoManager::GetInstance()->SetupIsoDataPath(
+ volatile_test_cig_create_cmpl_evt_.conn_handles[0],
+ kDefaultIsoDataPathParams);
+
+ /* Try sending twice as much data as we can, ignoring the credits limit and
+ * expect the redundant packets to be ignored and not propagated down to the
+ * HCI.
+ */
+ EXPECT_CALL(bte_interface_, HciSend).Times(num_buffers);
+ for (uint8_t i = 0; i < (2 * num_buffers); i++) {
+ IsoManager::GetInstance()->SendIsoData(
+ volatile_test_cig_create_cmpl_evt_.conn_handles[0], data_vec.data(),
+ data_vec.size());
+ }
+
+ // Return all credits for this one handle
+ uint8_t mock_rsp[5];
+ uint8_t* p = mock_rsp;
+ UINT8_TO_STREAM(p, 1);
+ UINT16_TO_STREAM(p, volatile_test_cig_create_cmpl_evt_.conn_handles[0]);
+ UINT16_TO_STREAM(p, num_buffers);
+ IsoManager::GetInstance()->HandleNumComplDataPkts(mock_rsp, sizeof(mock_rsp));
+
+ // Expect some more events go down the HCI
+ EXPECT_CALL(bte_interface_, HciSend).Times(num_buffers);
+ for (uint8_t i = 0; i < (2 * num_buffers); i++) {
+ IsoManager::GetInstance()->SendIsoData(
+ volatile_test_cig_create_cmpl_evt_.conn_handles[0], data_vec.data(),
+ data_vec.size());
+ }
+}
+
+TEST_F(IsoManagerDeathTest, SendIsoDataWithNoDataPath) {
+ std::vector<uint8_t> data_vec(108, 0);
+
+ // Check on CIG
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ bluetooth::hci::iso_manager::cis_establish_params params;
+ for (auto& conn_handle : volatile_test_cig_create_cmpl_evt_.conn_handles) {
+ params.conn_pairs.push_back({conn_handle, 1});
+ }
+ IsoManager::GetInstance()->EstablishCis(params);
+
+ ASSERT_EXIT(IsoManager::GetInstance()->SendIsoData(
+ volatile_test_cig_create_cmpl_evt_.conn_handles[0],
+ data_vec.data(), data_vec.size()),
+ ::testing::KilledBySignal(SIGABRT), "Data path not set");
+}
+
+TEST_F(IsoManagerDeathTest, SendIsoDataWithNoCigHandle) {
+ std::vector<uint8_t> data_vec(108, 0);
+ ASSERT_EXIT(IsoManager::GetInstance()->SendIsoData(134, data_vec.data(),
+ data_vec.size()),
+ ::testing::KilledBySignal(SIGABRT), "No such iso");
+}
+
+TEST_F(IsoManagerDeathTest, SendIsoDataWithNoCigConnected) {
+ std::vector<uint8_t> data_vec(108, 0);
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ auto handle = volatile_test_cig_create_cmpl_evt_.conn_handles[0];
+ ASSERT_EXIT(IsoManager::GetInstance()->SendIsoData(handle, data_vec.data(),
+ data_vec.size()),
+ ::testing::KilledBySignal(SIGABRT), "CIS not established");
+}
+
+TEST_F(IsoManagerTest, HandleDisconnectNoSuchHandle) {
+ // Don't expect any callbacks when connection handle is not for ISO.
+ EXPECT_CALL(*cig_callbacks_, OnCigEvent).Times(0);
+ EXPECT_CALL(*cig_callbacks_, OnCisEvent).Times(0);
+
+ IsoManager::GetInstance()->HandleDisconnect(123, 16);
+}
+
+TEST_F(IsoManagerTest, HandleDisconnectValidCig) {
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ auto handle = volatile_test_cig_create_cmpl_evt_.conn_handles[0];
+ IsoManager::GetInstance()->EstablishCis({{{handle, 1}}});
+
+ EXPECT_CALL(*cig_callbacks_, OnCigEvent).Times(0);
+ EXPECT_CALL(*cig_callbacks_, OnCisEvent).Times(0);
+
+ // Expect disconnect event exactly once
+ EXPECT_CALL(*cig_callbacks_, OnCisEvent)
+ .WillOnce([this, handle](uint8_t event_code, void* data) {
+ ASSERT_EQ(event_code,
+ bluetooth::hci::iso_manager::kIsoEventCisDisconnected);
+ auto* event =
+ static_cast<bluetooth::hci::iso_manager::cis_disconnected_evt*>(
+ data);
+ ASSERT_EQ(event->reason, 16);
+ ASSERT_EQ(event->cig_id, volatile_test_cig_create_cmpl_evt_.cig_id);
+ ASSERT_EQ(event->cis_conn_hdl, handle);
+ });
+
+ IsoManager::GetInstance()->HandleDisconnect(handle, 16);
+}
+
+TEST_F(IsoManagerTest, HandleDisconnectDisconnectedCig) {
+ IsoManager::GetInstance()->CreateCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
+
+ auto handle = volatile_test_cig_create_cmpl_evt_.conn_handles[0];
+ IsoManager::GetInstance()->EstablishCis({{{handle, 1}}});
+
+ EXPECT_CALL(*cig_callbacks_, OnCigEvent).Times(0);
+ EXPECT_CALL(*cig_callbacks_, OnCisEvent).Times(0);
+
+ // Expect disconnect event exactly once
+ EXPECT_CALL(
+ *cig_callbacks_,
+ OnCisEvent(bluetooth::hci::iso_manager::kIsoEventCisDisconnected, _))
+ .Times(1)
+ .RetiresOnSaturation();
+ IsoManager::GetInstance()->HandleDisconnect(handle, 16);
+
+ // This one was once connected - expect no events
+ IsoManager::GetInstance()->HandleDisconnect(handle, 16);
+
+ // This one was never connected - expect no events
+ handle = volatile_test_cig_create_cmpl_evt_.conn_handles[1];
+ IsoManager::GetInstance()->HandleDisconnect(handle, 16);
+}