blob: ed35ef9c237c57445638bd476bfe0264ddace4b4 [file] [log] [blame]
/*
* 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 "../le_audio_types.h"
#include "ble_advertiser.h"
#include "btm_iso_api.h"
#include "mock_ble_advertising_manager.h"
#include "mock_iso_manager.h"
#include "stack/include/ble_advertiser.h"
#include "state_machine.h"
#include "test/common/mock_functions.h"
using namespace bluetooth::hci::iso_manager;
using bluetooth::hci::IsoManager;
using bluetooth::le_audio::BasicAudioAnnouncementData;
using testing::_;
using testing::Mock;
using testing::SaveArg;
using testing::Test;
// Disables most likely false-positives from base::SplitString()
extern "C" const char* __asan_default_options() {
return "detect_container_overflow=0";
}
void btsnd_hcic_ble_rand(base::Callback<void(BT_OCTET8)> cb) {}
namespace le_audio {
namespace broadcaster {
namespace {
// bit 0: encrypted, bit 1: standard quality present
static const uint8_t test_public_broadcast_features = 0x3;
static const std::string test_broadcast_name = "Test";
static const std::vector<uint8_t> default_public_metadata = {
5, le_audio::types::kLeAudioMetadataTypeProgramInfo, 0x1, 0x2, 0x3, 0x4};
class MockBroadcastStatMachineCallbacks
: public IBroadcastStateMachineCallbacks {
public:
MockBroadcastStatMachineCallbacks() = default;
MockBroadcastStatMachineCallbacks(const MockBroadcastStatMachineCallbacks&) =
delete;
MockBroadcastStatMachineCallbacks& operator=(
const MockBroadcastStatMachineCallbacks&) = delete;
~MockBroadcastStatMachineCallbacks() override = default;
MOCK_METHOD((void), OnStateMachineCreateStatus,
(uint32_t broadcast_id, bool initialized), (override));
MOCK_METHOD((void), OnStateMachineDestroyed, (uint32_t broadcast_id),
(override));
MOCK_METHOD((void), OnStateMachineEvent,
(uint32_t broadcast_id, BroadcastStateMachine::State state,
const void* data),
(override));
MOCK_METHOD((void), OnOwnAddressResponse,
(uint32_t broadcast_id, uint8_t addr_type, RawAddress addr),
(override));
MOCK_METHOD((void), OnBigCreated, (const std::vector<uint16_t>& conn_handle),
(override));
};
class MockBroadcastAdvertisingCallbacks : public AdvertisingCallbacks {
public:
MockBroadcastAdvertisingCallbacks() = default;
MockBroadcastAdvertisingCallbacks(const MockBroadcastAdvertisingCallbacks&) =
delete;
MockBroadcastAdvertisingCallbacks& operator=(
const MockBroadcastAdvertisingCallbacks&) = delete;
~MockBroadcastAdvertisingCallbacks() override = default;
MOCK_METHOD((void), OnAdvertisingSetStarted,
(int reg_id, uint8_t advertiser_id, int8_t tx_power,
uint8_t status),
(override));
MOCK_METHOD((void), OnAdvertisingEnabled,
(uint8_t advertiser_id, bool enable, uint8_t status), (override));
MOCK_METHOD((void), OnAdvertisingDataSet,
(uint8_t advertiser_id, uint8_t status), (override));
MOCK_METHOD((void), OnScanResponseDataSet,
(uint8_t advertiser_id, uint8_t status), (override));
MOCK_METHOD((void), OnAdvertisingParametersUpdated,
(uint8_t advertiser_id, int8_t tx_power, uint8_t status),
(override));
MOCK_METHOD((void), OnPeriodicAdvertisingParametersUpdated,
(uint8_t advertiser_id, uint8_t status), (override));
MOCK_METHOD((void), OnPeriodicAdvertisingDataSet,
(uint8_t advertiser_id, uint8_t status), (override));
MOCK_METHOD((void), OnPeriodicAdvertisingEnabled,
(uint8_t advertiser_id, bool enable, uint8_t status), (override));
MOCK_METHOD((void), OnOwnAddressRead,
(uint8_t advertiser_id, uint8_t address_type, RawAddress address),
(override));
};
class StateMachineTest : public Test {
protected:
void SetUp() override {
reset_mock_function_count_map();
MockBleAdvertisingManager::Initialize();
mock_ble_advertising_manager_ = MockBleAdvertisingManager::Get();
sm_callbacks_.reset(new MockBroadcastStatMachineCallbacks());
adv_callbacks_.reset(new MockBroadcastAdvertisingCallbacks());
BroadcastStateMachine::Initialize(sm_callbacks_.get(),
adv_callbacks_.get());
ON_CALL(*mock_ble_advertising_manager_, StartAdvertisingSet)
.WillByDefault(
[this](uint8_t client_id, int reg_id,
BleAdvertiserInterface::IdTxPowerStatusCallback register_cb,
AdvertiseParameters params,
std::vector<uint8_t> advertise_data,
std::vector<uint8_t> scan_response_data,
PeriodicAdvertisingParameters periodic_params,
std::vector<uint8_t> periodic_data, uint16_t duration,
uint8_t maxExtAdvEvents,
BleAdvertiserInterface::IdStatusCallback timeout_cb) {
static uint8_t advertiser_id = 1;
uint8_t tx_power = 32;
uint8_t status = 0;
this->adv_callbacks_->OnAdvertisingSetStarted(
BroadcastStateMachine::kLeAudioBroadcastRegId,
advertiser_id++, tx_power, status);
});
ON_CALL(*mock_ble_advertising_manager_, Enable)
.WillByDefault(
[this](uint8_t advertiser_id, bool enable,
BleAdvertiserInterface::StatusCallback cb, uint16_t duration,
uint8_t maxExtAdvEvents,
BleAdvertiserInterface::StatusCallback timeout_cb) {
uint8_t status = 0;
this->adv_callbacks_->OnAdvertisingEnabled(advertiser_id, enable,
status);
});
ON_CALL(*mock_ble_advertising_manager_, GetOwnAddress)
.WillByDefault(
[](uint8_t inst_id, BleAdvertiserInterface::GetAddressCallback cb) {
uint8_t address_type = 0x02;
RawAddress address;
const uint8_t addr[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66};
address.FromOctets(addr);
cb.Run(address_type, address);
});
ON_CALL(*(sm_callbacks_.get()), OnStateMachineCreateStatus)
.WillByDefault([this](uint32_t broadcast_id, bool initialized) {
auto instance_it =
std::find_if(pending_broadcasts_.begin(),
pending_broadcasts_.end(), [broadcast_id](auto& up) {
return (up->GetBroadcastId() == broadcast_id);
});
if (instance_it != pending_broadcasts_.end()) {
if (initialized) {
broadcasts_[broadcast_id] = std::move(*instance_it);
}
pending_broadcasts_.erase(instance_it);
}
instance_creation_promise_.set_value(broadcast_id);
});
ON_CALL(*(sm_callbacks_.get()), OnStateMachineDestroyed)
.WillByDefault([this](uint32_t broadcast_id) {
if (broadcasts_.count(broadcast_id)) {
instance_destruction_promise_.set_value(broadcast_id);
}
});
ON_CALL(*(adv_callbacks_.get()), OnAdvertisingSetStarted)
.WillByDefault([this](int reg_id, uint8_t advertiser_id,
int8_t tx_power, uint8_t status) {
pending_broadcasts_.back()->OnCreateAnnouncement(advertiser_id,
tx_power, status);
});
ON_CALL(*(adv_callbacks_.get()), OnAdvertisingEnabled)
.WillByDefault(
[this](uint8_t advertiser_id, bool enable, uint8_t status) {
auto const& iter = std::find_if(
broadcasts_.cbegin(), broadcasts_.cend(),
[advertiser_id](auto const& sm) {
return sm.second->GetAdvertisingSid() == advertiser_id;
});
if (iter != broadcasts_.cend()) {
iter->second->OnEnableAnnouncement(enable, status);
}
});
ConfigureIsoManagerMock();
}
void ConfigureIsoManagerMock() {
iso_manager_ = 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_, CreateBig)
.WillByDefault([this](uint8_t big_id, big_create_params p) {
auto bit =
std::find_if(broadcasts_.begin(), broadcasts_.end(),
[big_id](auto const& entry) {
return entry.second->GetAdvertisingSid() == big_id;
});
if (bit == broadcasts_.end()) return;
big_create_cmpl_evt evt;
evt.big_id = big_id;
// For test convenience lets encode big_id into conn_hdl MSB.
// NOTE: In current implementation big_id is equal to advertising SID.
// This is an important detail exploited by the IsoManager mock
static uint8_t conn_lsb = 1;
uint16_t conn_msb = ((uint16_t)big_id) << 8;
for (auto i = 0; i < p.num_bis; ++i) {
evt.conn_handles.push_back(conn_msb | conn_lsb++);
}
bit->second->HandleHciEvent(HCI_BLE_CREATE_BIG_CPL_EVT, &evt);
});
ON_CALL(*mock_iso_manager_, SetupIsoDataPath)
.WillByDefault([this](uint16_t conn_handle, iso_data_path_params p) {
// Get the big_id encoded in conn_handle's MSB
uint8_t big_id = conn_handle >> 8;
auto bit =
std::find_if(broadcasts_.begin(), broadcasts_.end(),
[big_id](auto const& entry) {
return entry.second->GetAdvertisingSid() == big_id;
});
if (bit == broadcasts_.end()) return;
bit->second->OnSetupIsoDataPath(0, conn_handle);
});
ON_CALL(*mock_iso_manager_, RemoveIsoDataPath)
.WillByDefault([this](uint16_t conn_handle, uint8_t iso_direction) {
// Get the big_id encoded in conn_handle's MSB
uint8_t big_id = conn_handle >> 8;
auto bit =
std::find_if(broadcasts_.begin(), broadcasts_.end(),
[big_id](auto const& entry) {
return entry.second->GetAdvertisingSid() == big_id;
});
if (bit == broadcasts_.end()) return;
bit->second->OnRemoveIsoDataPath(0, conn_handle);
});
ON_CALL(*mock_iso_manager_, TerminateBig)
.WillByDefault([this](uint8_t big_id, uint8_t reason) {
// Get the big_id encoded in conn_handle's MSB
auto bit =
std::find_if(broadcasts_.begin(), broadcasts_.end(),
[big_id](auto const& entry) {
return entry.second->GetAdvertisingSid() == big_id;
});
if (bit == broadcasts_.end()) return;
big_terminate_cmpl_evt evt;
evt.big_id = big_id;
evt.reason = reason;
bit->second->HandleHciEvent(HCI_BLE_TERM_BIG_CPL_EVT, &evt);
});
}
void TearDown() override {
iso_manager_->Stop();
mock_iso_manager_ = nullptr;
Mock::VerifyAndClearExpectations(sm_callbacks_.get());
Mock::VerifyAndClearExpectations(adv_callbacks_.get());
pending_broadcasts_.clear();
broadcasts_.clear();
sm_callbacks_.reset();
adv_callbacks_.reset();
MockBleAdvertisingManager::CleanUp();
mock_ble_advertising_manager_ = nullptr;
}
uint32_t InstantiateStateMachine(
le_audio::types::LeAudioContextType context =
le_audio::types::LeAudioContextType::UNSPECIFIED) {
// We will get the state machine create status update in an async callback
// so let's wait for it here.
instance_creation_promise_ = std::promise<uint32_t>();
std::future<uint32_t> instance_future =
instance_creation_promise_.get_future();
static uint8_t broadcast_id_lsb = 1;
auto codec_qos_pair =
getStreamConfigForContext(types::AudioContexts(context));
auto broadcast_id = broadcast_id_lsb++;
pending_broadcasts_.push_back(BroadcastStateMachine::CreateInstance({
.is_public = true,
.broadcast_name = test_broadcast_name,
.broadcast_id = broadcast_id,
// .streaming_phy = ,
.codec_wrapper = codec_qos_pair.first,
.qos_config = codec_qos_pair.second,
// .announcement = ,
// .broadcast_code = ,
}));
pending_broadcasts_.back()->Initialize();
return instance_future.get();
}
MockBleAdvertisingManager* mock_ble_advertising_manager_;
IsoManager* iso_manager_;
MockIsoManager* mock_iso_manager_;
std::map<uint32_t, std::unique_ptr<BroadcastStateMachine>> broadcasts_;
std::vector<std::unique_ptr<BroadcastStateMachine>> pending_broadcasts_;
std::unique_ptr<MockBroadcastStatMachineCallbacks> sm_callbacks_;
std::unique_ptr<MockBroadcastAdvertisingCallbacks> adv_callbacks_;
std::promise<uint32_t> instance_creation_promise_;
std::promise<uint8_t> instance_destruction_promise_;
};
TEST_F(StateMachineTest, CreateInstanceFailed) {
EXPECT_CALL(*mock_ble_advertising_manager_, StartAdvertisingSet)
.WillOnce(
[this](uint8_t client_id, int reg_id,
BleAdvertiserInterface::IdTxPowerStatusCallback register_cb,
AdvertiseParameters params,
std::vector<uint8_t> advertise_data,
std::vector<uint8_t> scan_response_data,
PeriodicAdvertisingParameters periodic_params,
std::vector<uint8_t> periodic_data, uint16_t duration,
uint8_t maxExtAdvEvents,
BleAdvertiserInterface::IdStatusCallback timeout_cb) {
uint8_t advertiser_id = 1;
uint8_t tx_power = 0;
uint8_t status = 1;
this->adv_callbacks_->OnAdvertisingSetStarted(
BroadcastStateMachine::kLeAudioBroadcastRegId, advertiser_id,
tx_power, status);
});
EXPECT_CALL(*(sm_callbacks_.get()), OnStateMachineCreateStatus(_, false))
.Times(1);
auto broadcast_id = InstantiateStateMachine();
ASSERT_NE(broadcast_id, BroadcastStateMachine::kAdvSidUndefined);
ASSERT_TRUE(pending_broadcasts_.empty());
ASSERT_TRUE(broadcasts_.empty());
}
TEST_F(StateMachineTest, CreateInstanceSuccess) {
EXPECT_CALL(*(sm_callbacks_.get()), OnStateMachineCreateStatus(_, true))
.Times(1);
auto broadcast_id = InstantiateStateMachine();
ASSERT_NE(broadcast_id, BroadcastStateMachine::kAdvSidUndefined);
ASSERT_TRUE(pending_broadcasts_.empty());
ASSERT_FALSE(broadcasts_.empty());
ASSERT_EQ(broadcasts_[broadcast_id]->GetBroadcastId(), broadcast_id);
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::CONFIGURED);
}
TEST_F(StateMachineTest, DestroyInstanceSuccess) {
EXPECT_CALL(*(sm_callbacks_.get()), OnStateMachineCreateStatus(_, true))
.Times(1);
auto broadcast_id = InstantiateStateMachine();
ASSERT_NE(broadcast_id, BroadcastStateMachine::kAdvSidUndefined);
ASSERT_FALSE(broadcasts_.empty());
instance_destruction_promise_ = std::promise<uint8_t>();
std::future<uint8_t> instance_future =
instance_destruction_promise_.get_future();
broadcasts_.clear();
EXPECT_EQ(instance_future.get(), broadcast_id);
}
TEST_F(StateMachineTest, GetAdvertisingAddress) {
EXPECT_CALL(*(sm_callbacks_.get()), OnStateMachineCreateStatus(_, true))
.Times(1);
auto broadcast_id = InstantiateStateMachine();
EXPECT_CALL(*(sm_callbacks_.get()), OnOwnAddressResponse(broadcast_id, _, _))
.Times(1);
broadcasts_[broadcast_id]->RequestOwnAddress();
}
TEST_F(StateMachineTest, Mute) {
EXPECT_CALL(*(sm_callbacks_.get()), OnStateMachineCreateStatus(_, true))
.Times(1);
auto broadcast_id = InstantiateStateMachine();
ASSERT_TRUE(pending_broadcasts_.empty());
ASSERT_FALSE(broadcasts_.empty());
ASSERT_FALSE(broadcasts_[broadcast_id]->IsMuted());
broadcasts_[broadcast_id]->SetMuted(true);
ASSERT_TRUE(broadcasts_[broadcast_id]->IsMuted());
broadcasts_[broadcast_id]->SetMuted(false);
ASSERT_FALSE(broadcasts_[broadcast_id]->IsMuted());
}
static BasicAudioAnnouncementData prepareAnnouncement(
const BroadcastCodecWrapper& codec_config,
std::map<uint8_t, std::vector<uint8_t>> metadata) {
BasicAudioAnnouncementData announcement;
announcement.presentation_delay = 0x004E20;
auto const& codec_id = codec_config.GetLeAudioCodecId();
announcement.subgroup_configs = {{
.codec_config =
{
.codec_id = codec_id.coding_format,
.vendor_company_id = codec_id.vendor_company_id,
.vendor_codec_id = codec_id.vendor_codec_id,
.codec_specific_params =
codec_config.GetSubgroupCodecSpecData().Values(),
},
.metadata = std::move(metadata),
.bis_configs = {},
}};
for (uint8_t i = 0; i < codec_config.GetNumChannels(); ++i) {
announcement.subgroup_configs[0].bis_configs.push_back(
{.codec_specific_params =
codec_config.GetBisCodecSpecData(i + 1).Values(),
.bis_index = static_cast<uint8_t>(i + 1)});
}
return announcement;
}
TEST_F(StateMachineTest, UpdateAnnouncement) {
EXPECT_CALL(*(sm_callbacks_.get()), OnStateMachineCreateStatus(_, true))
.Times(1);
auto broadcast_id = InstantiateStateMachine();
std::map<uint8_t, std::vector<uint8_t>> metadata = {};
BroadcastCodecWrapper codec_config(
{.coding_format = le_audio::types::kLeAudioCodingFormatLC3,
.vendor_company_id = le_audio::types::kLeAudioVendorCompanyIdUndefined,
.vendor_codec_id = le_audio::types::kLeAudioVendorCodecIdUndefined},
{.num_channels = LeAudioCodecConfiguration::kChannelNumberMono,
.sample_rate = LeAudioCodecConfiguration::kSampleRate16000,
.bits_per_sample = LeAudioCodecConfiguration::kBitsPerSample16,
.data_interval_us = LeAudioCodecConfiguration::kInterval10000Us},
32000, 40);
auto announcement = prepareAnnouncement(codec_config, metadata);
auto adv_sid = broadcasts_[broadcast_id]->GetAdvertisingSid();
std::vector<uint8_t> data;
EXPECT_CALL(*mock_ble_advertising_manager_,
SetPeriodicAdvertisingData(adv_sid, _, _))
.Times(2)
.WillRepeatedly(SaveArg<1>(&data));
broadcasts_[broadcast_id]->UpdateBroadcastAnnouncement(
std::move(announcement));
uint8_t first_len = data.size();
ASSERT_NE(first_len, 0); // Non-zero length
ASSERT_EQ(data[1], BTM_BLE_AD_TYPE_SERVICE_DATA_TYPE);
ASSERT_EQ(data[2], (kBasicAudioAnnouncementServiceUuid & 0x00FF));
ASSERT_EQ(data[3], ((kBasicAudioAnnouncementServiceUuid >> 8) & 0x00FF));
// The rest of the packet data is already covered by the announcement tests
// Verify that changes in the announcement makes a difference
metadata = {{0x01, {0x03}}};
announcement = prepareAnnouncement(codec_config, metadata);
broadcasts_[broadcast_id]->UpdateBroadcastAnnouncement(
std::move(announcement));
uint8_t second_len = data.size();
// These should differ by the difference in metadata
ASSERT_EQ(first_len + types::LeAudioLtvMap(metadata).RawPacketSize(),
second_len);
}
TEST_F(StateMachineTest, ProcessMessageStartWhenConfigured) {
EXPECT_CALL(*(sm_callbacks_.get()), OnStateMachineCreateStatus(_, true))
.Times(1);
auto sound_context = le_audio::types::LeAudioContextType::MEDIA;
uint8_t num_channels = 2;
auto broadcast_id = InstantiateStateMachine(sound_context);
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::CONFIGURED);
uint8_t num_bises = 0;
EXPECT_CALL(*mock_iso_manager_, CreateBig)
.WillOnce([this, &num_bises](uint8_t big_id, big_create_params p) {
auto bit =
std::find_if(broadcasts_.begin(), broadcasts_.end(),
[big_id](auto const& entry) {
return entry.second->GetAdvertisingSid() == big_id;
});
if (bit == broadcasts_.end()) return;
num_bises = p.num_bis;
big_create_cmpl_evt evt;
evt.big_id = big_id;
// For test convenience lets encode big_id into conn_hdl's
// MSB
static uint8_t conn_lsb = 1;
uint16_t conn_msb = ((uint16_t)big_id) << 8;
for (auto i = 0; i < p.num_bis; ++i) {
evt.conn_handles.push_back(conn_msb | conn_lsb++);
}
bit->second->HandleHciEvent(HCI_BLE_CREATE_BIG_CPL_EVT, &evt);
});
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath).Times(num_channels);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath).Times(0);
EXPECT_CALL(*(sm_callbacks_.get()),
OnStateMachineEvent(broadcast_id,
BroadcastStateMachine::State::STREAMING, _))
.Times(1);
broadcasts_[broadcast_id]->ProcessMessage(
BroadcastStateMachine::Message::START);
// Verify the right number of BISes in the BIG being created
ASSERT_EQ(num_bises, num_channels);
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::STREAMING);
}
TEST_F(StateMachineTest, ProcessMessageStopWhenConfigured) {
EXPECT_CALL(*(sm_callbacks_.get()), OnStateMachineCreateStatus(_, true))
.Times(1);
auto broadcast_id =
InstantiateStateMachine(le_audio::types::LeAudioContextType::MEDIA);
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::CONFIGURED);
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath).Times(0);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath).Times(0);
EXPECT_CALL(*(sm_callbacks_.get()),
OnStateMachineEvent(broadcast_id,
BroadcastStateMachine::State::STOPPING, _))
.Times(1);
EXPECT_CALL(*(sm_callbacks_.get()),
OnStateMachineEvent(broadcast_id,
BroadcastStateMachine::State::STOPPED, _))
.Times(1);
broadcasts_[broadcast_id]->ProcessMessage(
BroadcastStateMachine::Message::STOP);
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::STOPPED);
}
TEST_F(StateMachineTest, ProcessMessageSuspendWhenConfigured) {
EXPECT_CALL(*(sm_callbacks_.get()), OnStateMachineCreateStatus(_, true))
.Times(1);
auto broadcast_id =
InstantiateStateMachine(le_audio::types::LeAudioContextType::MEDIA);
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::CONFIGURED);
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath).Times(0);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath).Times(0);
EXPECT_CALL(*(sm_callbacks_.get()), OnStateMachineEvent(broadcast_id, _, _))
.Times(0);
broadcasts_[broadcast_id]->ProcessMessage(
BroadcastStateMachine::Message::SUSPEND);
// There shall be no change in state
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::CONFIGURED);
}
TEST_F(StateMachineTest, ProcessMessageStartWhenStreaming) {
auto broadcast_id =
InstantiateStateMachine(le_audio::types::LeAudioContextType::MEDIA);
broadcasts_[broadcast_id]->ProcessMessage(
BroadcastStateMachine::Message::START);
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::STREAMING);
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath).Times(0);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath).Times(0);
EXPECT_CALL(*(sm_callbacks_.get()), OnStateMachineEvent(broadcast_id, _, _))
.Times(0);
broadcasts_[broadcast_id]->ProcessMessage(
BroadcastStateMachine::Message::START);
// There shall be no change in state
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::STREAMING);
}
TEST_F(StateMachineTest, ProcessMessageStopWhenStreaming) {
auto broadcast_id =
InstantiateStateMachine(le_audio::types::LeAudioContextType::MEDIA);
broadcasts_[broadcast_id]->ProcessMessage(
BroadcastStateMachine::Message::START);
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::STREAMING);
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath).Times(0);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath).Times(2);
EXPECT_CALL(*(sm_callbacks_.get()),
OnStateMachineEvent(broadcast_id,
BroadcastStateMachine::State::STOPPING, _))
.Times(1);
EXPECT_CALL(*(sm_callbacks_.get()),
OnStateMachineEvent(broadcast_id,
BroadcastStateMachine::State::STOPPED, _))
.Times(1);
broadcasts_[broadcast_id]->ProcessMessage(
BroadcastStateMachine::Message::STOP);
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::STOPPED);
}
TEST_F(StateMachineTest, ProcessMessageSuspendWhenStreaming) {
auto broadcast_id =
InstantiateStateMachine(le_audio::types::LeAudioContextType::MEDIA);
broadcasts_[broadcast_id]->ProcessMessage(
BroadcastStateMachine::Message::START);
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::STREAMING);
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath).Times(0);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath).Times(2);
EXPECT_CALL(*(sm_callbacks_.get()),
OnStateMachineEvent(broadcast_id,
BroadcastStateMachine::State::CONFIGURED, _))
.Times(1);
broadcasts_[broadcast_id]->ProcessMessage(
BroadcastStateMachine::Message::SUSPEND);
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::CONFIGURED);
}
TEST_F(StateMachineTest, ProcessMessageStartWhenStopped) {
auto broadcast_id =
InstantiateStateMachine(le_audio::types::LeAudioContextType::MEDIA);
broadcasts_[broadcast_id]->ProcessMessage(
BroadcastStateMachine::Message::STOP);
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::STOPPED);
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath).Times(2);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath).Times(0);
EXPECT_CALL(*(sm_callbacks_.get()),
OnStateMachineEvent(broadcast_id,
BroadcastStateMachine::State::CONFIGURING, _))
.Times(1);
EXPECT_CALL(*(sm_callbacks_.get()),
OnStateMachineEvent(broadcast_id,
BroadcastStateMachine::State::STREAMING, _))
.Times(1);
broadcasts_[broadcast_id]->ProcessMessage(
BroadcastStateMachine::Message::START);
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::STREAMING);
}
TEST_F(StateMachineTest, ProcessMessageStopWhenStopped) {
auto broadcast_id =
InstantiateStateMachine(le_audio::types::LeAudioContextType::MEDIA);
broadcasts_[broadcast_id]->ProcessMessage(
BroadcastStateMachine::Message::STOP);
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::STOPPED);
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath).Times(0);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath).Times(0);
EXPECT_CALL(*(sm_callbacks_.get()), OnStateMachineEvent(broadcast_id, _, _))
.Times(0);
broadcasts_[broadcast_id]->ProcessMessage(
BroadcastStateMachine::Message::STOP);
// There shall be no change in state
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::STOPPED);
}
TEST_F(StateMachineTest, ProcessMessageSuspendWhenStopped) {
auto broadcast_id =
InstantiateStateMachine(le_audio::types::LeAudioContextType::MEDIA);
broadcasts_[broadcast_id]->ProcessMessage(
BroadcastStateMachine::Message::STOP);
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::STOPPED);
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath).Times(0);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath).Times(0);
EXPECT_CALL(*(sm_callbacks_.get()), OnStateMachineEvent(broadcast_id, _, _))
.Times(0);
broadcasts_[broadcast_id]->ProcessMessage(
BroadcastStateMachine::Message::SUSPEND);
// There shall be no change in state
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::STOPPED);
}
TEST_F(StateMachineTest, OnSetupIsoDataPathError) {
EXPECT_CALL(*(sm_callbacks_.get()), OnStateMachineCreateStatus(_, true))
.Times(1);
auto broadcast_id =
InstantiateStateMachine(le_audio::types::LeAudioContextType::MEDIA);
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::CONFIGURED);
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath)
.WillOnce([this](uint16_t conn_handle, iso_data_path_params p) {
// Get the big_id encoded in conn_handle's MSB
uint8_t big_id = conn_handle >> 8;
auto bit =
std::find_if(broadcasts_.begin(), broadcasts_.end(),
[big_id](auto const& entry) {
return entry.second->GetAdvertisingSid() == big_id;
});
if (bit == broadcasts_.end()) return;
bit->second->OnSetupIsoDataPath(0, conn_handle);
})
.WillOnce([this](uint16_t conn_handle, iso_data_path_params p) {
// Get the big_id encoded in conn_handle's MSB
uint8_t big_id = conn_handle >> 8;
auto bit =
std::find_if(broadcasts_.begin(), broadcasts_.end(),
[big_id](auto const& entry) {
return entry.second->GetAdvertisingSid() == big_id;
});
if (bit == broadcasts_.end()) return;
bit->second->OnSetupIsoDataPath(1, conn_handle);
})
.RetiresOnSaturation();
broadcasts_[broadcast_id]->ProcessMessage(
BroadcastStateMachine::Message::START);
// On datapath setup failure we should go back to configured with BIG being
// destroyed. Maybe it will work out next time for the new BIG.
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::CONFIGURED);
// And still be able to start again
ON_CALL(*mock_iso_manager_, SetupIsoDataPath)
.WillByDefault([this](uint16_t conn_handle, iso_data_path_params p) {
// Get the big_id encoded in conn_handle's MSB
uint8_t big_id = conn_handle >> 8;
auto bit =
std::find_if(broadcasts_.begin(), broadcasts_.end(),
[big_id](auto const& entry) {
return entry.second->GetAdvertisingSid() == big_id;
});
if (bit == broadcasts_.end()) return;
bit->second->OnSetupIsoDataPath(0, conn_handle);
});
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath).Times(2);
broadcasts_[broadcast_id]->ProcessMessage(
BroadcastStateMachine::Message::START);
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::STREAMING);
}
TEST_F(StateMachineTest, OnRemoveIsoDataPathError) {
auto broadcast_id =
InstantiateStateMachine(le_audio::types::LeAudioContextType::MEDIA);
broadcasts_[broadcast_id]->ProcessMessage(
BroadcastStateMachine::Message::START);
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::STREAMING);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath)
.WillOnce([this](uint16_t conn_handle, uint8_t iso_direction) {
// Get the big_id encoded in conn_handle's MSB
uint8_t big_id = conn_handle >> 8;
auto bit =
std::find_if(broadcasts_.begin(), broadcasts_.end(),
[big_id](auto const& entry) {
return entry.second->GetAdvertisingSid() == big_id;
});
if (bit == broadcasts_.end()) return;
bit->second->OnRemoveIsoDataPath(0, conn_handle);
})
.WillOnce([this](uint16_t conn_handle, uint8_t iso_direction) {
// Get the big_id encoded in conn_handle's MSB
uint8_t big_id = conn_handle >> 8;
auto bit =
std::find_if(broadcasts_.begin(), broadcasts_.end(),
[big_id](auto const& entry) {
return entry.second->GetAdvertisingSid() == big_id;
});
if (bit == broadcasts_.end()) return;
bit->second->OnRemoveIsoDataPath(1, conn_handle);
})
.RetiresOnSaturation();
broadcasts_[broadcast_id]->ProcessMessage(
BroadcastStateMachine::Message::SUSPEND);
// On datapath teardown failure we should stay in CONFIGURED with BIG being
// destroyed.
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::CONFIGURED);
// And still be able to start again
broadcasts_[broadcast_id]->ProcessMessage(
BroadcastStateMachine::Message::START);
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::STREAMING);
}
TEST_F(StateMachineTest, GetConfig) {
EXPECT_CALL(*(sm_callbacks_.get()), OnStateMachineCreateStatus(_, true))
.Times(1);
auto sound_context = le_audio::types::LeAudioContextType::MEDIA;
uint8_t num_channels = 2;
auto broadcast_id = InstantiateStateMachine(sound_context);
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::CONFIGURED);
std::optional<BigConfig> const& big_cfg =
broadcasts_[broadcast_id]->GetBigConfig();
ASSERT_FALSE(big_cfg.has_value());
broadcasts_[broadcast_id]->ProcessMessage(
BroadcastStateMachine::Message::START);
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::STREAMING);
ASSERT_TRUE(big_cfg.has_value());
ASSERT_EQ(big_cfg->status, 0);
// This is an implementation specific thing
ASSERT_EQ(big_cfg->big_id, broadcasts_[broadcast_id]->GetAdvertisingSid());
ASSERT_EQ(big_cfg->connection_handles.size(), num_channels);
}
TEST_F(StateMachineTest, GetBroadcastId) {
EXPECT_CALL(*(sm_callbacks_.get()), OnStateMachineCreateStatus(_, true))
.Times(1);
auto broadcast_id = InstantiateStateMachine();
ASSERT_NE(bluetooth::le_audio::kBroadcastIdInvalid, broadcast_id);
ASSERT_EQ(broadcasts_[broadcast_id]->GetState(),
BroadcastStateMachine::State::CONFIGURED);
}
TEST_F(StateMachineTest, IsPublicBroadcast) {
EXPECT_CALL(*(sm_callbacks_.get()), OnStateMachineCreateStatus(_, true))
.Times(1);
auto broadcast_id = InstantiateStateMachine();
ASSERT_EQ(broadcasts_[broadcast_id]->IsPublicBroadcast(), true);
}
TEST_F(StateMachineTest, GetBroadcastName) {
EXPECT_CALL(*(sm_callbacks_.get()), OnStateMachineCreateStatus(_, true))
.Times(1);
auto broadcast_id = InstantiateStateMachine();
ASSERT_EQ(broadcasts_[broadcast_id]->GetBroadcastName(), test_broadcast_name);
}
TEST_F(StateMachineTest, GetBroadcastAnnouncement) {
EXPECT_CALL(*(sm_callbacks_.get()), OnStateMachineCreateStatus(_, true))
.Times(1);
auto broadcast_id = InstantiateStateMachine();
std::map<uint8_t, std::vector<uint8_t>> metadata = {};
BroadcastCodecWrapper codec_config(
{.coding_format = le_audio::types::kLeAudioCodingFormatLC3,
.vendor_company_id = le_audio::types::kLeAudioVendorCompanyIdUndefined,
.vendor_codec_id = le_audio::types::kLeAudioVendorCodecIdUndefined},
{.num_channels = LeAudioCodecConfiguration::kChannelNumberMono,
.sample_rate = LeAudioCodecConfiguration::kSampleRate16000,
.bits_per_sample = LeAudioCodecConfiguration::kBitsPerSample16,
.data_interval_us = LeAudioCodecConfiguration::kInterval10000Us},
32000, 40);
auto announcement = prepareAnnouncement(codec_config, metadata);
broadcasts_[broadcast_id]->UpdateBroadcastAnnouncement(announcement);
ASSERT_EQ(announcement,
broadcasts_[broadcast_id]->GetBroadcastAnnouncement());
}
TEST_F(StateMachineTest, GetPublicBroadcastAnnouncement) {
EXPECT_CALL(*(sm_callbacks_.get()), OnStateMachineCreateStatus(_, true))
.Times(1);
auto broadcast_id = InstantiateStateMachine();
bool is_public_metadata_valid;
types::LeAudioLtvMap public_ltv = types::LeAudioLtvMap::Parse(
default_public_metadata.data(), default_public_metadata.size(),
is_public_metadata_valid);
bluetooth::le_audio::PublicBroadcastAnnouncementData pb_announcement = {
.features = test_public_broadcast_features,
.metadata = public_ltv.Values()};
broadcasts_[broadcast_id]->UpdatePublicBroadcastAnnouncement(
broadcast_id, test_broadcast_name, pb_announcement);
ASSERT_EQ(pb_announcement,
broadcasts_[broadcast_id]->GetPublicBroadcastAnnouncement());
}
TEST_F(StateMachineTest, AnnouncementTest) {
AdvertiseParameters adv_params;
std::vector<uint8_t> a_data;
std::vector<uint8_t> p_data;
EXPECT_CALL(*mock_ble_advertising_manager_, StartAdvertisingSet)
.WillOnce([this, &p_data, &a_data, &adv_params](
uint8_t client_id, int reg_id,
BleAdvertiserInterface::IdTxPowerStatusCallback register_cb,
AdvertiseParameters params,
std::vector<uint8_t> advertise_data,
std::vector<uint8_t> scan_response_data,
PeriodicAdvertisingParameters periodic_params,
std::vector<uint8_t> periodic_data, uint16_t duration,
uint8_t maxExtAdvEvents,
BleAdvertiserInterface::IdStatusCallback timeout_cb) {
uint8_t advertiser_id = 1;
uint8_t tx_power = 0;
uint8_t status = 0;
// Since we are not using these buffers in this callback it is safe to
// move them.
a_data = std::move(advertise_data);
p_data = std::move(periodic_data);
adv_params = params;
this->adv_callbacks_->OnAdvertisingSetStarted(
BroadcastStateMachine::kLeAudioBroadcastRegId, advertiser_id,
tx_power, status);
});
EXPECT_CALL(*(sm_callbacks_.get()), OnStateMachineCreateStatus(_, true))
.Times(1);
auto broadcast_id = InstantiateStateMachine();
ASSERT_NE(broadcast_id, BroadcastStateMachine::kAdvSidUndefined);
// Check ext. advertising data for Broadcast Announcement UUID
ASSERT_NE(a_data[0], 0); // size
ASSERT_EQ(a_data[1], 0x16); // BTM_BLE_AD_TYPE_SERVICE_DATA_TYPE
ASSERT_EQ(a_data[2], (kBroadcastAudioAnnouncementServiceUuid & 0x00FF));
ASSERT_EQ(a_data[3],
((kBroadcastAudioAnnouncementServiceUuid >> 8) & 0x00FF));
ASSERT_EQ(a_data[4], (broadcast_id & 0x0000FF));
ASSERT_EQ(a_data[5], ((broadcast_id >> 8) & 0x0000FF));
ASSERT_EQ(a_data[6], ((broadcast_id >> 16) & 0x0000FF));
ASSERT_NE(a_data[7], 0); // size
ASSERT_EQ(a_data[8], 0x16); // BTM_BLE_AD_TYPE_SERVICE_DATA_TYPE
ASSERT_EQ(a_data[9], (kPublicBroadcastAnnouncementServiceUuid & 0x00FF));
ASSERT_EQ(a_data[10],
((kPublicBroadcastAnnouncementServiceUuid >> 8) & 0x00FF));
// Check periodic data for Basic Announcement UUID
ASSERT_NE(p_data[0], 0); // size
ASSERT_EQ(p_data[1], 0x16); // BTM_BLE_AD_TYPE_SERVICE_DATA_TYPE
ASSERT_EQ(p_data[2], (kBasicAudioAnnouncementServiceUuid & 0x00FF));
ASSERT_EQ(p_data[3], ((kBasicAudioAnnouncementServiceUuid >> 8) & 0x00FF));
// Check advertising parameters
ASSERT_EQ(adv_params.own_address_type, BLE_ADDR_RANDOM);
}
} // namespace
} // namespace broadcaster
} // namespace le_audio