blob: 74daaf3cba5e9c7600faafaebc7daf51d7e9df7c [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 <base/bind_helpers.h>
#include <base/functional/bind.h>
#include <base/strings/string_number_conversions.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <osi/include/alarm.h>
#include <sys/socket.h>
#include <variant>
#include "bta/le_audio/le_audio_types.h"
#include "bta_csis_api.h"
#include "bta_gatt_api_mock.h"
#include "bta_gatt_queue_mock.h"
#include "bta_has_api.h"
#include "btif_storage_mock.h"
#include "btm_api_mock.h"
#include "gatt/database_builder.h"
#include "hardware/bt_gatt_types.h"
#include "has_types.h"
#include "mock_controller.h"
#include "mock_csis_client.h"
#include "stack/include/bt_uuid16.h"
#include "test/common/mock_functions.h"
bool gatt_profile_get_eatt_support(const RawAddress& addr) { return true; }
void osi_property_set_bool(const char* key, bool value);
namespace bluetooth {
namespace has {
namespace internal {
namespace {
using base::HexEncode;
using ::bluetooth::csis::CsisClient;
using ::bluetooth::has::ConnectionState;
using ::bluetooth::has::ErrorCode;
using ::bluetooth::has::HasClientCallbacks;
using ::bluetooth::has::PresetInfo;
using ::le_audio::has::HasClient;
using ::le_audio::has::HasCtpGroupOpCoordinator;
using ::le_audio::has::HasCtpOp;
using ::le_audio::has::HasDevice;
using ::le_audio::has::HasPreset;
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::DoAll;
using ::testing::DoDefault;
using ::testing::Invoke;
using ::testing::Mock;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::Sequence;
using ::testing::SetArgPointee;
using ::testing::WithArg;
// Disables most likely false-positives from base::SplitString()
// extern "C" const char* __asan_default_options() {
// return "detect_container_overflow=0";
// }
RawAddress GetTestAddress(int index) {
CHECK_LT(index, UINT8_MAX);
RawAddress result = {
{0xC0, 0xDE, 0xC0, 0xDE, 0x00, static_cast<uint8_t>(index)}};
return result;
}
static uint16_t GetTestConnId(const RawAddress& address) {
return address.address[RawAddress::kLength - 1];
}
class MockHasCallbacks : public HasClientCallbacks {
public:
MockHasCallbacks() = default;
MockHasCallbacks(const MockHasCallbacks&) = delete;
MockHasCallbacks& operator=(const MockHasCallbacks&) = delete;
~MockHasCallbacks() override = default;
MOCK_METHOD((void), OnConnectionState,
(ConnectionState state, const RawAddress& address), (override));
MOCK_METHOD((void), OnDeviceAvailable,
(const RawAddress& address, uint8_t features), (override));
MOCK_METHOD((void), OnFeaturesUpdate,
(const RawAddress& address, uint8_t features), (override));
MOCK_METHOD((void), OnActivePresetSelected,
((std::variant<RawAddress, int> addr_or_group_id),
uint8_t preset_index),
(override));
MOCK_METHOD((void), OnActivePresetSelectError,
((std::variant<RawAddress, int> addr_or_group_id),
ErrorCode result),
(override));
MOCK_METHOD((void), OnPresetInfo,
((std::variant<RawAddress, int> addr_or_group_id),
PresetInfoReason change_id,
std::vector<PresetInfo> preset_change_records),
(override));
MOCK_METHOD((void), OnPresetInfoError,
((std::variant<RawAddress, int> addr_or_group_id),
uint8_t preset_index, ErrorCode error_code),
(override));
MOCK_METHOD((void), OnSetPresetNameError,
((std::variant<RawAddress, int> addr_or_group_id),
uint8_t preset_index, ErrorCode error_code),
(override));
};
class HasClientTestBase : public ::testing::Test {
protected:
std::map<uint16_t, uint8_t> current_peer_active_preset_idx_;
std::map<uint16_t, uint8_t> current_peer_features_val_;
std::map<uint16_t, std::set<HasPreset, HasPreset::ComparatorDesc>>
current_peer_presets_;
struct HasDbBuilder {
bool has;
static constexpr uint16_t kGapSvcStartHdl = 0x0001;
static constexpr uint16_t kGapDeviceNameValHdl = 0x0003;
static constexpr uint16_t kGapSvcEndHdl = kGapDeviceNameValHdl;
static constexpr uint16_t kSvcStartHdl = 0x0010;
static constexpr uint16_t kFeaturesValHdl = 0x0012;
static constexpr uint16_t kPresetsCtpValHdl = 0x0015;
static constexpr uint16_t kActivePresetIndexValHdl = 0x0018;
static constexpr uint16_t kSvcEndHdl = 0x001E;
static constexpr uint16_t kGattSvcStartHdl = 0x0090;
static constexpr uint16_t kGattSvcChangedValHdl = 0x0092;
static constexpr uint16_t kGattSvcEndHdl = kGattSvcChangedValHdl + 1;
bool features;
bool features_ntf;
bool preset_cp;
bool preset_cp_ntf;
bool preset_cp_ind;
bool active_preset_idx;
bool active_preset_idx_ntf;
const gatt::Database Build() {
gatt::DatabaseBuilder bob;
/* Generic Access Service */
bob.AddService(kGapSvcStartHdl, kGapSvcEndHdl, Uuid::From16Bit(0x1800),
true);
/* Device Name Char. */
bob.AddCharacteristic(kGapDeviceNameValHdl - 1, kGapDeviceNameValHdl,
Uuid::From16Bit(0x2a00), GATT_CHAR_PROP_BIT_READ);
/* 0x0004-0x000f left empty on purpose */
if (has) {
bob.AddService(kSvcStartHdl, kSvcEndHdl,
::le_audio::has::kUuidHearingAccessService, true);
if (features) {
bob.AddCharacteristic(
kFeaturesValHdl - 1, kFeaturesValHdl,
::le_audio::has::kUuidHearingAidFeatures,
GATT_CHAR_PROP_BIT_READ |
(features_ntf ? GATT_CHAR_PROP_BIT_NOTIFY : 0));
if (features_ntf) {
bob.AddDescriptor(kFeaturesValHdl + 1,
Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
}
}
if (preset_cp) {
bob.AddCharacteristic(
kPresetsCtpValHdl - 1, kPresetsCtpValHdl,
::le_audio::has::kUuidHearingAidPresetControlPoint,
GATT_CHAR_PROP_BIT_WRITE |
(preset_cp_ntf ? GATT_CHAR_PROP_BIT_NOTIFY : 0) |
(preset_cp_ind ? GATT_CHAR_PROP_BIT_INDICATE : 0));
if (preset_cp_ntf || preset_cp_ind) {
bob.AddDescriptor(kPresetsCtpValHdl + 1,
Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
}
}
if (active_preset_idx) {
bob.AddCharacteristic(
kActivePresetIndexValHdl - 1, kActivePresetIndexValHdl,
::le_audio::has::kUuidActivePresetIndex,
GATT_CHAR_PROP_BIT_READ |
(active_preset_idx_ntf ? GATT_CHAR_PROP_BIT_NOTIFY : 0));
if (active_preset_idx_ntf)
bob.AddDescriptor(kActivePresetIndexValHdl + 1,
Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
}
}
/* GATTS */
/* 0x001F-0x0090 left empty on purpose */
bob.AddService(kGattSvcStartHdl, kGattSvcEndHdl,
Uuid::From16Bit(UUID_SERVCLASS_GATT_SERVER), true);
bob.AddCharacteristic(kGattSvcChangedValHdl - 1, kGattSvcChangedValHdl,
Uuid::From16Bit(GATT_UUID_GATT_SRV_CHGD),
GATT_CHAR_PROP_BIT_NOTIFY);
bob.AddDescriptor(kGattSvcChangedValHdl + 1,
Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
return bob.Build();
};
};
const gatt::Characteristic* FindCharacteristicByValueHandle(
const gatt::Service* svc, uint16_t handle) {
if (svc == nullptr) return nullptr;
auto it =
std::find_if(svc->characteristics.cbegin(), svc->characteristics.cend(),
[handle](const auto& characteristic) {
return characteristic.value_handle == handle;
});
return (it != svc->characteristics.cend()) ? &(*it) : nullptr;
}
void set_sample_database(
const RawAddress& address, HasDbBuilder& builder,
uint8_t features_val = 0x0,
std::optional<std::set<HasPreset, HasPreset::ComparatorDesc>> presets_op =
std::nullopt) {
uint16_t conn_id = GetTestConnId(address);
/* For some test cases these defaults are enough */
if (!presets_op)
presets_op = {{
HasPreset(6, HasPreset::kPropertyAvailable, "Universal"),
HasPreset(
55, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
"YourPreset55"),
}};
auto& presets = presets_op.value();
auto const active_preset = presets.begin();
services_map[conn_id] = builder.Build().Services();
current_peer_features_val_.insert_or_assign(conn_id, features_val);
current_peer_active_preset_idx_.insert_or_assign(conn_id,
active_preset->GetIndex());
current_peer_presets_.insert_or_assign(conn_id, std::move(presets));
ON_CALL(gatt_queue, ReadCharacteristic(conn_id, _, _, _))
.WillByDefault(Invoke([this](uint16_t conn_id, uint16_t handle,
GATT_READ_OP_CB cb, void* cb_data) {
auto* svc = gatt::FindService(services_map[conn_id], handle);
if (svc == nullptr) return;
std::vector<uint8_t> value;
tGATT_STATUS status = GATT_SUCCESS;
switch (handle) {
case HasDbBuilder::kGapDeviceNameValHdl:
value.resize(20);
break;
case HasDbBuilder::kFeaturesValHdl:
value.resize(1);
value[0] = current_peer_features_val_.at(conn_id);
break;
case HasDbBuilder::kActivePresetIndexValHdl:
value.resize(1);
value[0] = current_peer_active_preset_idx_.at(conn_id);
break;
case HasDbBuilder::kPresetsCtpValHdl:
/* passthrough */
default:
status = GATT_READ_NOT_PERMIT;
break;
}
if (cb)
cb(conn_id, status, handle, value.size(), value.data(), cb_data);
}));
/* Default action for the Control Point operation writes */
ON_CALL(gatt_queue,
WriteCharacteristic(conn_id, HasDbBuilder::kPresetsCtpValHdl, _,
GATT_WRITE, _, _))
.WillByDefault(Invoke([this, address](uint16_t conn_id, uint16_t handle,
std::vector<uint8_t> value,
tGATT_WRITE_TYPE write_type,
GATT_WRITE_OP_CB cb,
void* cb_data) {
auto pp = value.data();
auto len = value.size();
uint8_t op, index, num_of_indices;
const bool indicate = false;
if (len < 1) {
if (cb)
cb(conn_id, GATT_INVALID_ATTR_LEN, handle, value.size(),
value.data(), cb_data);
return;
}
STREAM_TO_UINT8(op, pp)
--len;
if (op >
static_cast<
std::underlying_type_t<::le_audio::has::PresetCtpOpcode>>(
::le_audio::has::PresetCtpOpcode::OP_MAX_)) {
/* Invalid Opcode */
if (cb)
cb(conn_id, (tGATT_STATUS)0x80, handle, value.size(),
value.data(), cb_data);
return;
}
switch (static_cast<::le_audio::has::PresetCtpOpcode>(op)) {
case ::le_audio::has::PresetCtpOpcode::READ_PRESETS:
if (len < 2) {
if (cb)
cb(conn_id, GATT_INVALID_ATTR_LEN, handle, value.size(),
value.data(), cb_data);
} else {
STREAM_TO_UINT8(index, pp);
STREAM_TO_UINT8(num_of_indices, pp);
len -= 2;
ASSERT_EQ(0u, len);
InjectNotifyReadPresetsResponse(conn_id, address, handle, value,
indicate, index, num_of_indices,
cb, cb_data);
}
break;
case ::le_audio::has::PresetCtpOpcode::SET_ACTIVE_PRESET: {
if (len < 1) {
if (cb)
cb(conn_id, GATT_INVALID_ATTR_LEN, handle, value.size(),
value.data(), cb_data);
break;
}
STREAM_TO_UINT8(index, pp);
--len;
ASSERT_EQ(0u, len);
auto presets = current_peer_presets_.at(conn_id);
if (presets.count(index)) {
current_peer_active_preset_idx_.insert_or_assign(conn_id,
index);
if (cb)
cb(conn_id, GATT_SUCCESS, handle, value.size(), value.data(),
cb_data);
InjectActivePresetNotification(conn_id, address, handle, value,
index, cb, cb_data);
} else {
/* Preset Operation Not Possible */
if (cb)
cb(conn_id, (tGATT_STATUS)0x83, handle, value.size(),
value.data(), cb_data);
}
} break;
case ::le_audio::has::PresetCtpOpcode::SET_ACTIVE_PRESET_SYNC: {
auto features = current_peer_features_val_.at(conn_id);
if ((features & ::bluetooth::has::
kFeatureBitPresetSynchronizationSupported) ==
0) {
/* Synchronization Not Supported */
if (cb)
cb(conn_id, (tGATT_STATUS)0x82, handle, value.size(),
value.data(), cb_data);
break;
}
if (len < 1) {
if (cb)
cb(conn_id, GATT_INVALID_ATTR_LEN, handle, value.size(),
value.data(), cb_data);
break;
}
STREAM_TO_UINT8(index, pp);
--len;
ASSERT_EQ(0u, len);
auto csis_api = CsisClient::Get();
int group_id = bluetooth::groups::kGroupUnknown;
if (csis_api != nullptr) {
group_id = csis_api->GetGroupId(
address, ::le_audio::uuid::kCapServiceUuid);
}
if (group_id != bluetooth::groups::kGroupUnknown) {
if (cb)
cb(conn_id, GATT_SUCCESS, handle, value.size(), value.data(),
cb_data);
/* Send notification from all grouped devices */
auto addresses = csis_api->GetDeviceList(group_id);
for (auto& addr : addresses) {
auto conn = GetTestConnId(addr);
InjectActivePresetNotification(conn, addr, handle, value,
index, cb, cb_data);
}
} else {
/* Preset Operation Not Possible */
if (cb)
cb(conn_id, (tGATT_STATUS)0x83, handle, value.size(),
value.data(), cb_data);
}
} break;
case ::le_audio::has::PresetCtpOpcode::SET_NEXT_PRESET: {
ASSERT_EQ(0u, len);
ASSERT_NE(0u, current_peer_active_preset_idx_.count(conn_id));
ASSERT_NE(0u, current_peer_presets_.count(conn_id));
auto current_preset = current_peer_active_preset_idx_.at(conn_id);
auto presets = current_peer_presets_.at(conn_id);
auto current = presets.find(current_preset);
if (current != presets.end()) {
++current;
if (current == presets.end()) current = presets.begin();
current_peer_active_preset_idx_.insert_or_assign(
conn_id, current->GetIndex());
InjectActivePresetNotification(conn_id, address, handle, value,
current->GetIndex(), cb,
cb_data);
} else {
/* Preset Operation Not Possible */
if (cb)
cb(conn_id, (tGATT_STATUS)0x83, handle, value.size(),
value.data(), cb_data);
}
} break;
case ::le_audio::has::PresetCtpOpcode::SET_PREV_PRESET: {
ASSERT_EQ(0u, len);
ASSERT_NE(0u, current_peer_active_preset_idx_.count(conn_id));
ASSERT_NE(0u, current_peer_presets_.count(conn_id));
auto current_preset = current_peer_active_preset_idx_.at(conn_id);
auto presets = current_peer_presets_.at(conn_id);
auto rit = presets.rbegin();
while (rit != presets.rend()) {
if (rit->GetIndex() == current_preset) {
rit++;
/* Wrap around */
if (rit == presets.rend()) {
rit = presets.rbegin();
}
break;
}
rit++;
}
if (rit != presets.rend()) {
if (cb)
cb(conn_id, GATT_SUCCESS, handle, value.size(), value.data(),
cb_data);
current_peer_active_preset_idx_.insert_or_assign(
conn_id, rit->GetIndex());
InjectActivePresetNotification(conn_id, address, handle, value,
rit->GetIndex(), cb, cb_data);
} else {
/* Preset Operation Not Possible */
if (cb)
cb(conn_id, (tGATT_STATUS)0x83, handle, value.size(),
value.data(), cb_data);
}
} break;
case ::le_audio::has::PresetCtpOpcode::SET_NEXT_PRESET_SYNC: {
ASSERT_EQ(0u, len);
auto features = current_peer_features_val_.at(conn_id);
if ((features & ::bluetooth::has::
kFeatureBitPresetSynchronizationSupported) ==
0) {
/* Synchronization Not Supported */
if (cb)
cb(conn_id, (tGATT_STATUS)0x82, handle, value.size(),
value.data(), cb_data);
break;
}
auto current_preset = current_peer_active_preset_idx_.at(conn_id);
auto presets = current_peer_presets_.at(conn_id);
auto rit = presets.begin();
while (rit != presets.end()) {
if (rit->GetIndex() == current_preset) {
rit++;
/* Wrap around */
if (rit == presets.end()) {
rit = presets.begin();
}
break;
}
rit++;
}
if (rit != presets.end()) {
auto synced_group = mock_csis_client_module_.GetGroupId(
GetTestAddress(conn_id), ::le_audio::uuid::kCapServiceUuid);
auto addresses =
mock_csis_client_module_.GetDeviceList(synced_group);
// Emulate locally synced op. - notify from all of the devices
for (auto addr : addresses) {
auto cid = GetTestConnId(addr);
if ((cid == conn_id) && (cb != nullptr))
cb(cid, GATT_SUCCESS, handle, value.size(), value.data(),
cb_data);
current_peer_active_preset_idx_.insert_or_assign(
conn_id, rit->GetIndex());
InjectActivePresetNotification(cid, addr, handle, value,
rit->GetIndex(), cb, cb_data);
}
} else {
/* Preset Operation Not Possible */
if (cb)
cb(conn_id, (tGATT_STATUS)0x83, handle, value.size(),
value.data(), cb_data);
}
} break;
case ::le_audio::has::PresetCtpOpcode::SET_PREV_PRESET_SYNC: {
ASSERT_EQ(0u, len);
auto features = current_peer_features_val_.at(conn_id);
if ((features & ::bluetooth::has::
kFeatureBitPresetSynchronizationSupported) ==
0) {
/* Synchronization Not Supported */
if (cb)
cb(conn_id, (tGATT_STATUS)0x82, handle, value.size(),
value.data(), cb_data);
break;
}
auto current_preset = current_peer_active_preset_idx_.at(conn_id);
auto presets = current_peer_presets_.at(conn_id);
auto rit = presets.rbegin();
while (rit != presets.rend()) {
if (rit->GetIndex() == current_preset) {
rit++;
/* Wrap around */
if (rit == presets.rend()) {
rit = presets.rbegin();
}
break;
}
rit++;
}
if (rit != presets.rend()) {
auto synced_group = mock_csis_client_module_.GetGroupId(
GetTestAddress(conn_id), ::le_audio::uuid::kCapServiceUuid);
auto addresses =
mock_csis_client_module_.GetDeviceList(synced_group);
// Emulate locally synced op. - notify from all of the devices
for (auto addr : addresses) {
auto cid = GetTestConnId(addr);
if ((cid == conn_id) && (cb != nullptr))
cb(cid, GATT_SUCCESS, handle, value.size(), value.data(),
cb_data);
current_peer_active_preset_idx_.insert_or_assign(
conn_id, rit->GetIndex());
InjectActivePresetNotification(cid, addr, handle, value,
rit->GetIndex(), cb, cb_data);
}
} else {
/* Preset Operation Not Possible */
if (cb)
cb(conn_id, (tGATT_STATUS)0x83, handle, value.size(),
value.data(), cb_data);
}
} break;
case ::le_audio::has::PresetCtpOpcode::WRITE_PRESET_NAME: {
STREAM_TO_UINT8(index, pp);
--len;
auto name = std::string(pp, pp + len);
len = 0;
ASSERT_NE(0u, current_peer_presets_.count(conn_id));
auto presets = current_peer_presets_.at(conn_id);
auto rit = presets.rbegin();
auto current = rit;
while (rit != presets.rend()) {
if (rit->GetIndex() == index) {
current = rit;
rit++;
break;
}
rit++;
}
auto prev_index = (rit == presets.rend()) ? 0 : rit->GetIndex();
ASSERT_NE(current, presets.rend());
if (cb)
cb(conn_id, GATT_SUCCESS, handle, value.size(), value.data(),
cb_data);
auto new_preset = HasPreset(current->GetIndex(),
current->GetProperties(), name);
presets.erase(current->GetIndex());
presets.insert(new_preset);
InjectPresetChanged(
conn_id, address, indicate, new_preset, prev_index,
::le_audio::has::PresetCtpChangeId::PRESET_GENERIC_UPDATE,
true);
} break;
default:
if (cb)
cb(conn_id, GATT_INVALID_HANDLE, handle, value.size(),
value.data(), cb_data);
break;
}
}));
}
void SetUp(void) override {
reset_mock_function_count_map();
controller::SetMockControllerInterface(&controller_interface_);
bluetooth::manager::SetMockBtmInterface(&btm_interface);
bluetooth::storage::SetMockBtifStorageInterface(&btif_storage_interface_);
gatt::SetMockBtaGattInterface(&gatt_interface);
gatt::SetMockBtaGattQueue(&gatt_queue);
callbacks.reset(new MockHasCallbacks());
encryption_result = true;
ON_CALL(btm_interface, SetEncryption(_, _, _, _, _))
.WillByDefault(
Invoke([this](const RawAddress& bd_addr, tBT_TRANSPORT transport,
tBTM_SEC_CALLBACK* p_callback, void* p_ref_data,
tBTM_BLE_SEC_ACT sec_act) -> tBTM_STATUS {
InjectEncryptionEvent(bd_addr);
return BTM_SUCCESS;
}));
MockCsisClient::SetMockInstanceForTesting(&mock_csis_client_module_);
ON_CALL(mock_csis_client_module_, Get())
.WillByDefault(Return(&mock_csis_client_module_));
ON_CALL(mock_csis_client_module_, IsCsisClientRunning())
.WillByDefault(Return(true));
/* default action for GetCharacteristic function call */
ON_CALL(gatt_interface, GetCharacteristic(_, _))
.WillByDefault(
Invoke([&](uint16_t conn_id,
uint16_t handle) -> const gatt::Characteristic* {
std::list<gatt::Service>& services = services_map[conn_id];
for (auto const& service : services) {
for (auto const& characteristic : service.characteristics) {
if (characteristic.value_handle == handle) {
return &characteristic;
}
}
}
return nullptr;
}));
/* default action for GetOwningService function call */
ON_CALL(gatt_interface, GetOwningService(_, _))
.WillByDefault(Invoke(
[&](uint16_t conn_id, uint16_t handle) -> const gatt::Service* {
std::list<gatt::Service>& services = services_map[conn_id];
for (auto const& service : services) {
if (service.handle <= handle && service.end_handle >= handle) {
return &service;
}
}
return nullptr;
}));
ON_CALL(gatt_interface, ServiceSearchRequest(_, _))
.WillByDefault(WithArg<0>(Invoke(
[&](uint16_t conn_id) { InjectSearchCompleteEvent(conn_id); })));
/* default action for GetServices function call */
ON_CALL(gatt_interface, GetServices(_))
.WillByDefault(WithArg<0>(
Invoke([&](uint16_t conn_id) -> std::list<gatt::Service>* {
return &services_map[conn_id];
})));
/* default action for RegisterForNotifications function call */
ON_CALL(gatt_interface, RegisterForNotifications(gatt_if, _, _))
.WillByDefault(Return(GATT_SUCCESS));
/* default action for DeregisterForNotifications function call */
ON_CALL(gatt_interface, DeregisterForNotifications(gatt_if, _, _))
.WillByDefault(Return(GATT_SUCCESS));
/* default action for WriteDescriptor function call */
ON_CALL(gatt_queue, WriteDescriptor(_, _, _, _, _, _))
.WillByDefault(
Invoke([](uint16_t conn_id, uint16_t handle,
std::vector<uint8_t> value, tGATT_WRITE_TYPE write_type,
GATT_WRITE_OP_CB cb, void* cb_data) -> void {
if (cb)
cb(conn_id, GATT_SUCCESS, handle, value.size(), value.data(),
cb_data);
}));
/* by default connect only direct connection requests */
ON_CALL(gatt_interface, Open(_, _, _, _))
.WillByDefault(
Invoke([&](tGATT_IF client_if, const RawAddress& remote_bda,
tBTM_BLE_CONN_TYPE connection_type, bool opportunistic) {
if (connection_type == BTM_BLE_DIRECT_CONNECTION)
InjectConnectedEvent(remote_bda, GetTestConnId(remote_bda));
}));
ON_CALL(gatt_interface, Close(_))
.WillByDefault(Invoke(
[&](uint16_t conn_id) { InjectDisconnectedEvent(conn_id); }));
}
void TearDown(void) override {
services_map.clear();
gatt::SetMockBtaGattQueue(nullptr);
gatt::SetMockBtaGattInterface(nullptr);
bluetooth::storage::SetMockBtifStorageInterface(nullptr);
bluetooth::manager::SetMockBtmInterface(nullptr);
controller::SetMockControllerInterface(nullptr);
callbacks.reset();
current_peer_active_preset_idx_.clear();
current_peer_features_val_.clear();
}
void TestAppRegister(void) {
BtaAppRegisterCallback app_register_callback;
EXPECT_CALL(gatt_interface, AppRegister(_, _, _))
.WillOnce(DoAll(SaveArg<0>(&gatt_callback),
SaveArg<1>(&app_register_callback)));
HasClient::Initialize(callbacks.get(), base::DoNothing());
ASSERT_TRUE(gatt_callback);
ASSERT_TRUE(app_register_callback);
app_register_callback.Run(gatt_if, GATT_SUCCESS);
ASSERT_TRUE(HasClient::IsHasClientRunning());
Mock::VerifyAndClearExpectations(&gatt_interface);
}
void TestAppUnregister(void) {
EXPECT_CALL(gatt_interface, AppDeregister(gatt_if));
HasClient::CleanUp();
ASSERT_FALSE(HasClient::IsHasClientRunning());
gatt_callback = nullptr;
}
void TestConnect(const RawAddress& address) {
ON_CALL(btm_interface, BTM_IsEncrypted(address, _))
.WillByDefault(DoAll(Return(encryption_result)));
EXPECT_CALL(gatt_interface,
Open(gatt_if, address, BTM_BLE_DIRECT_CONNECTION, _));
HasClient::Get()->Connect(address);
Mock::VerifyAndClearExpectations(&*callbacks);
Mock::VerifyAndClearExpectations(&gatt_queue);
Mock::VerifyAndClearExpectations(&gatt_interface);
Mock::VerifyAndClearExpectations(&btm_interface);
}
void TestDisconnect(const RawAddress& address, uint16_t conn_id) {
EXPECT_CALL(gatt_interface, CancelOpen(_, address, _)).Times(AnyNumber());
if (conn_id != GATT_INVALID_CONN_ID) {
assert(0);
EXPECT_CALL(gatt_interface, Close(conn_id));
} else {
EXPECT_CALL(gatt_interface, CancelOpen(gatt_if, address, _));
}
HasClient::Get()->Disconnect(address);
}
void TestAddFromStorage(const RawAddress& address, uint8_t features,
bool auto_connect) {
if (auto_connect) {
EXPECT_CALL(gatt_interface,
Open(gatt_if, address, BTM_BLE_BKG_CONNECT_ALLOW_LIST, _));
HasClient::Get()->AddFromStorage(address, features, auto_connect);
/* Inject connected event for autoconnect/background connection */
InjectConnectedEvent(address, GetTestConnId(address));
} else {
EXPECT_CALL(gatt_interface, Open(gatt_if, address, _, _)).Times(0);
HasClient::Get()->AddFromStorage(address, features, auto_connect);
}
Mock::VerifyAndClearExpectations(&gatt_interface);
}
void InjectConnectedEvent(const RawAddress& address, uint16_t conn_id,
tGATT_STATUS status = GATT_SUCCESS) {
tBTA_GATTC_OPEN event_data = {
.status = status,
.conn_id = conn_id,
.client_if = gatt_if,
.remote_bda = address,
.transport = GATT_TRANSPORT_LE,
.mtu = 240,
};
connected_devices[conn_id] = address;
gatt_callback(BTA_GATTC_OPEN_EVT, (tBTA_GATTC*)&event_data);
}
void InjectDisconnectedEvent(
uint16_t conn_id,
tGATT_DISCONN_REASON reason = GATT_CONN_TERMINATE_LOCAL_HOST,
bool allow_fake_conn = false) {
if (!allow_fake_conn) ASSERT_NE(connected_devices.count(conn_id), 0u);
tBTA_GATTC_CLOSE event_data = {
.conn_id = conn_id,
.status = GATT_SUCCESS,
.client_if = gatt_if,
.remote_bda = connected_devices[conn_id],
.reason = reason,
};
connected_devices.erase(conn_id);
gatt_callback(BTA_GATTC_CLOSE_EVT, (tBTA_GATTC*)&event_data);
}
void InjectSearchCompleteEvent(uint16_t conn_id) {
tBTA_GATTC_SEARCH_CMPL event_data = {
.conn_id = conn_id,
.status = GATT_SUCCESS,
};
gatt_callback(BTA_GATTC_SEARCH_CMPL_EVT, (tBTA_GATTC*)&event_data);
}
void InjectNotificationEvent(const RawAddress& test_address, uint16_t conn_id,
uint16_t handle, std::vector<uint8_t> value,
bool indicate = false) {
tBTA_GATTC_NOTIFY event_data = {
.conn_id = conn_id,
.bda = test_address,
.handle = handle,
.len = (uint8_t)value.size(),
.is_notify = !indicate,
};
ASSERT_TRUE(value.size() < GATT_MAX_ATTR_LEN);
std::copy(value.begin(), value.end(), event_data.value);
gatt_callback(BTA_GATTC_NOTIF_EVT, (tBTA_GATTC*)&event_data);
}
void InjectEncryptionEvent(const RawAddress& test_address) {
tBTA_GATTC_ENC_CMPL_CB event_data = {
.client_if = static_cast<tGATT_IF>(GetTestConnId(test_address)),
.remote_bda = test_address,
};
gatt_callback(BTA_GATTC_ENC_CMPL_CB_EVT, (tBTA_GATTC*)&event_data);
}
void SetEncryptionResult(const RawAddress& address, bool success) {
encryption_result = success;
ON_CALL(btm_interface, BTM_IsEncrypted(address, _))
.WillByDefault(DoAll(Return(encryption_result)));
ON_CALL(btm_interface, IsLinkKeyKnown(address, _))
.WillByDefault(DoAll(Return(true)));
}
void InjectNotifyReadPresetResponse(uint16_t conn_id,
RawAddress const& address,
uint16_t handle, const HasPreset& preset,
bool indicate, bool is_last) {
std::vector<uint8_t> value;
value.push_back(
static_cast<std::underlying_type_t<::le_audio::has::PresetCtpOpcode>>(
::le_audio::has::PresetCtpOpcode::READ_PRESET_RESPONSE));
value.push_back(is_last ? 0x01 : 0x00);
preset.ToCharacteristicValue(value);
InjectNotificationEvent(address, conn_id, handle, value, indicate);
}
void InjectPresetChanged(uint16_t conn_id, RawAddress const& address,
bool indicate, const HasPreset& preset,
uint8_t prev_index,
::le_audio::has::PresetCtpChangeId change_id,
bool is_last) {
std::vector<uint8_t> value;
value.push_back(
static_cast<std::underlying_type_t<::le_audio::has::PresetCtpOpcode>>(
::le_audio::has::PresetCtpOpcode::PRESET_CHANGED));
value.push_back(static_cast<uint8_t>(change_id));
value.push_back(is_last ? 0x01 : 0x00);
switch (change_id) {
case ::le_audio::has::PresetCtpChangeId::PRESET_GENERIC_UPDATE:
value.push_back(prev_index);
preset.ToCharacteristicValue(value);
break;
case ::le_audio::has::PresetCtpChangeId::PRESET_DELETED:
case ::le_audio::has::PresetCtpChangeId::PRESET_AVAILABLE:
case ::le_audio::has::PresetCtpChangeId::PRESET_UNAVAILABLE:
default:
value.push_back(preset.GetIndex());
break;
}
InjectNotificationEvent(address, conn_id, HasDbBuilder::kPresetsCtpValHdl,
value, indicate);
}
void InjectNotifyReadPresetsResponse(
uint16_t conn_id, RawAddress const& address, uint16_t handle,
std::vector<uint8_t> value, bool indicate, int index, int num_of_indices,
GATT_WRITE_OP_CB cb, void* cb_data) {
auto presets = current_peer_presets_.at(conn_id);
LOG_ASSERT(!presets.empty()) << __func__ << " Mocking error!";
/* Index is a start index, not necessary is a valid index for the
* peer device */
auto preset = presets.find(index);
while (preset == presets.end() &&
index++ <= ::le_audio::has::kMaxNumOfPresets) {
preset = presets.find(index);
}
if (preset == presets.end()) {
/* operation not possible */
if (cb)
cb(conn_id, (tGATT_STATUS)0x83, handle, value.size(), value.data(),
cb_data);
return;
}
if (cb)
cb(conn_id, GATT_SUCCESS, handle, value.size(), value.data(), cb_data);
/* Notify presets */
int num_of_notif = 1;
while (1) {
bool last =
preset == std::prev(presets.end()) || num_of_notif == num_of_indices;
InjectNotifyReadPresetResponse(conn_id, address, handle, *preset,
indicate, (last));
if (last) return;
num_of_notif++;
preset++;
}
}
void InjectActivePresetNotification(uint16_t conn_id,
RawAddress const& address,
uint16_t handle,
std::vector<uint8_t> wr_value,
uint8_t index, GATT_WRITE_OP_CB cb,
void* cb_data) {
auto presets = current_peer_presets_.at(conn_id);
LOG_ASSERT(!presets.empty()) << __func__ << " Mocking error!";
auto preset = presets.find(index);
if (preset == presets.end()) {
/* preset operation not possible */
if (cb)
cb(conn_id, (tGATT_STATUS)0x83, handle, wr_value.size(),
wr_value.data(), cb_data);
return;
}
std::vector<uint8_t> value;
value.push_back(index);
InjectNotificationEvent(
address, conn_id, HasDbBuilder::kActivePresetIndexValHdl, value, false);
}
void SetSampleDatabaseHasNoFeatures(const RawAddress& address) {
HasDbBuilder builder = {
.has = true,
.features = false,
.features_ntf = false,
.preset_cp = true,
.preset_cp_ntf = false,
.preset_cp_ind = true,
.active_preset_idx = true,
.active_preset_idx_ntf = true,
};
set_sample_database(address, builder);
}
void SetSampleDatabaseHasNoPresetChange(const RawAddress& address,
uint8_t features_value = 0x00) {
HasDbBuilder builder = {
.has = true,
.features = true,
.features_ntf = false,
.preset_cp = false,
.preset_cp_ntf = false,
.preset_cp_ind = false,
.active_preset_idx = false,
.active_preset_idx_ntf = false,
};
set_sample_database(address, builder, features_value);
}
void SetSampleDatabaseHasNoOptionalNtf(const RawAddress& address,
uint8_t features = 0x00) {
HasDbBuilder builder = {
.has = true,
.features = true,
.features_ntf = false,
.preset_cp = true,
.preset_cp_ntf = false,
.preset_cp_ind = true,
.active_preset_idx = true,
.active_preset_idx_ntf = true,
};
set_sample_database(address, builder, features);
}
void SetSampleDatabaseNoHas(const RawAddress& address,
uint8_t features = 0x00) {
HasDbBuilder builder = {
.has = false,
.features = false,
.features_ntf = false,
.preset_cp = false,
.preset_cp_ntf = false,
.preset_cp_ind = false,
.active_preset_idx = true,
.active_preset_idx_ntf = true,
};
set_sample_database(address, builder, features);
}
void SetSampleDatabaseHasBrokenNoActivePreset(const RawAddress& address,
uint8_t features = 0x00) {
HasDbBuilder builder = {
.has = true,
.features = true,
.features_ntf = false,
.preset_cp = true,
.preset_cp_ntf = true,
.preset_cp_ind = true,
.active_preset_idx = false,
.active_preset_idx_ntf = false,
};
set_sample_database(address, builder, features);
}
void SetSampleDatabaseHasBrokenNoActivePresetNtf(const RawAddress& address,
uint8_t features = 0x00) {
HasDbBuilder builder = {
.has = true,
.features = true,
.features_ntf = false,
.preset_cp = true,
.preset_cp_ntf = true,
.preset_cp_ind = true,
.active_preset_idx = true,
.active_preset_idx_ntf = false,
};
set_sample_database(address, builder, features);
}
void SetSampleDatabaseHasOnlyFeaturesNtf(const RawAddress& address,
uint8_t features = 0x00) {
HasDbBuilder builder = {
.has = true,
.features = true,
.features_ntf = true,
.preset_cp = false,
.preset_cp_ntf = false,
.preset_cp_ind = false,
.active_preset_idx = false,
.active_preset_idx_ntf = false,
};
set_sample_database(address, builder, features);
}
void SetSampleDatabaseHasOnlyFeaturesNoNtf(const RawAddress& address,
uint8_t features = 0x00) {
HasDbBuilder builder = {
.has = true,
.features = true,
.features_ntf = false,
.preset_cp = false,
.preset_cp_ntf = false,
.preset_cp_ind = false,
.active_preset_idx = false,
.active_preset_idx_ntf = false,
};
set_sample_database(address, builder, features);
}
void SetSampleDatabaseHasPresetsNtf(
const RawAddress& address,
uint8_t features = bluetooth::has::kFeatureBitHearingAidTypeMonaural,
std::optional<std::set<HasPreset, HasPreset::ComparatorDesc>> presets =
std::nullopt) {
HasDbBuilder builder = {
.has = true,
.features = true,
.features_ntf = true,
.preset_cp = true,
.preset_cp_ntf = true,
.preset_cp_ind = true,
.active_preset_idx = true,
.active_preset_idx_ntf = true,
};
set_sample_database(address, builder, features, presets);
}
void SetSampleDatabaseHasNoPresetsFlagsOnly(const RawAddress& address) {
uint8_t features = bluetooth::has::kFeatureBitHearingAidTypeMonaural;
HasDbBuilder builder = {
.has = true,
.features = true,
.features_ntf = true,
.preset_cp = false,
.preset_cp_ntf = false,
.preset_cp_ind = false,
.active_preset_idx = false,
.active_preset_idx_ntf = false,
};
set_sample_database(address, builder, features, std::nullopt);
}
std::unique_ptr<MockHasCallbacks> callbacks;
bluetooth::manager::MockBtmInterface btm_interface;
bluetooth::storage::MockBtifStorageInterface btif_storage_interface_;
controller::MockControllerInterface controller_interface_;
gatt::MockBtaGattInterface gatt_interface;
gatt::MockBtaGattQueue gatt_queue;
MockCsisClient mock_csis_client_module_;
tBTA_GATTC_CBACK* gatt_callback;
const uint8_t gatt_if = 0xfe;
std::map<uint8_t, RawAddress> connected_devices;
std::map<uint16_t, std::list<gatt::Service>> services_map;
bool encryption_result;
};
class HasClientTest : public HasClientTestBase {
void SetUp(void) override {
HasClientTestBase::SetUp();
TestAppRegister();
}
void TearDown(void) override {
TestAppUnregister();
HasClientTestBase::TearDown();
}
};
TEST_F(HasClientTestBase, test_get_uninitialized) {
ASSERT_DEATH(HasClient::Get(), "");
}
TEST_F(HasClientTestBase, test_initialize) {
HasClient::Initialize(callbacks.get(), base::DoNothing());
ASSERT_TRUE(HasClient::IsHasClientRunning());
HasClient::CleanUp();
}
TEST_F(HasClientTestBase, test_initialize_twice) {
HasClient::Initialize(callbacks.get(), base::DoNothing());
HasClient* has_p = HasClient::Get();
HasClient::Initialize(callbacks.get(), base::DoNothing());
ASSERT_EQ(has_p, HasClient::Get());
HasClient::CleanUp();
}
TEST_F(HasClientTestBase, test_cleanup_initialized) {
HasClient::Initialize(callbacks.get(), base::DoNothing());
HasClient::CleanUp();
ASSERT_FALSE(HasClient::IsHasClientRunning());
}
TEST_F(HasClientTestBase, test_cleanup_uninitialized) {
HasClient::CleanUp();
ASSERT_FALSE(HasClient::IsHasClientRunning());
}
TEST_F(HasClientTestBase, test_app_registration) {
TestAppRegister();
TestAppUnregister();
}
TEST_F(HasClientTest, test_connect) { TestConnect(GetTestAddress(1)); }
TEST_F(HasClientTest, test_add_from_storage) {
TestAddFromStorage(GetTestAddress(1), 0, true);
TestAddFromStorage(GetTestAddress(2), 0, false);
}
TEST_F(HasClientTest, test_disconnect_non_connected) {
const RawAddress test_address = GetTestAddress(1);
/* Override the default action to prevent us sendind the connected event */
EXPECT_CALL(gatt_interface,
Open(gatt_if, test_address, BTM_BLE_DIRECT_CONNECTION, _))
.WillOnce(Return());
HasClient::Get()->Connect(test_address);
TestDisconnect(test_address, GATT_INVALID_CONN_ID);
}
TEST_F(HasClientTest, test_has_connected) {
const RawAddress test_address = GetTestAddress(1);
/* Minimal possible HA device (only feature flags) */
SetSampleDatabaseHasNoPresetChange(
test_address, bluetooth::has::kFeatureBitHearingAidTypeBinaural);
EXPECT_CALL(
*callbacks,
OnDeviceAvailable(test_address,
bluetooth::has::kFeatureBitHearingAidTypeBinaural));
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address));
TestConnect(test_address);
}
TEST_F(HasClientTest, test_disconnect_connected) {
const RawAddress test_address = GetTestAddress(1);
/* Minimal possible HA device (only feature flags) */
SetSampleDatabaseHasNoPresetChange(
test_address, bluetooth::has::kFeatureBitHearingAidTypeBinaural);
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address))
.Times(1);
TestConnect(test_address);
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::DISCONNECTED, test_address))
.Times(1);
TestDisconnect(test_address, 1);
}
TEST_F(HasClientTest, test_disconnected_while_autoconnect) {
const RawAddress test_address = GetTestAddress(1);
TestAddFromStorage(test_address,
bluetooth::has::kFeatureBitHearingAidTypeBinaural, true);
/* autoconnect - don't indicate disconnection */
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::DISCONNECTED, test_address))
.Times(0);
/* Verify that the device still can connect in te background */
InjectDisconnectedEvent(1, GATT_CONN_TERMINATE_PEER_USER, true);
}
TEST_F(HasClientTest, test_encryption_failed) {
const RawAddress test_address = GetTestAddress(1);
SetSampleDatabaseHasNoPresetChange(
test_address, bluetooth::has::kFeatureBitHearingAidTypeBinaural);
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::DISCONNECTED, test_address))
.Times(1);
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address))
.Times(0);
SetEncryptionResult(test_address, false);
TestConnect(test_address);
}
TEST_F(HasClientTest, test_service_discovery_complete_before_encryption) {
const RawAddress test_address = GetTestAddress(1);
SetSampleDatabaseHasPresetsNtf(
test_address, bluetooth::has::kFeatureBitHearingAidTypeBinaural);
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::DISCONNECTED, test_address))
.Times(0);
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address))
.Times(0);
SetEncryptionResult(test_address, false);
ON_CALL(btm_interface, SetEncryption(_, _, _, _, _))
.WillByDefault(Return(BTM_SUCCESS));
TestConnect(test_address);
auto test_conn_id = GetTestConnId(test_address);
InjectSearchCompleteEvent(test_conn_id);
Mock::VerifyAndClearExpectations(callbacks.get());
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address))
.Times(1);
SetEncryptionResult(test_address, true);
InjectEncryptionEvent(test_address);
Mock::VerifyAndClearExpectations(callbacks.get());
}
TEST_F(HasClientTest, test_disconnect_when_link_key_is_gone) {
const RawAddress test_address = GetTestAddress(1);
SetSampleDatabaseHasPresetsNtf(
test_address, bluetooth::has::kFeatureBitHearingAidTypeBinaural);
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::DISCONNECTED, test_address))
.Times(0);
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address))
.Times(0);
ON_CALL(btm_interface, BTM_IsEncrypted(test_address, _))
.WillByDefault(DoAll(Return(false)));
ON_CALL(btm_interface, SetEncryption(test_address, _, _, _, _))
.WillByDefault(Return(BTM_ERR_KEY_MISSING));
auto test_conn_id = GetTestConnId(test_address);
EXPECT_CALL(gatt_interface, Close(test_conn_id)).Times(1);
InjectConnectedEvent(test_address, GetTestConnId(test_address));
Mock::VerifyAndClearExpectations(callbacks.get());
}
TEST_F(HasClientTest, test_reconnect_after_encryption_failed) {
const RawAddress test_address = GetTestAddress(1);
SetSampleDatabaseHasNoPresetChange(
test_address, bluetooth::has::kFeatureBitHearingAidTypeBinaural);
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::DISCONNECTED, test_address))
.Times(1);
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address))
.Times(0);
SetEncryptionResult(test_address, false);
TestConnect(test_address);
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address))
.Times(1);
SetEncryptionResult(test_address, true);
InjectConnectedEvent(test_address, GetTestConnId(test_address));
}
TEST_F(HasClientTest, test_reconnect_after_encryption_failed_from_storage) {
const RawAddress test_address = GetTestAddress(1);
SetSampleDatabaseHasNoPresetChange(
test_address, bluetooth::has::kFeatureBitHearingAidTypeBinaural);
SetEncryptionResult(test_address, false);
TestAddFromStorage(test_address, 0, true);
/* autoconnect - don't indicate disconnection */
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::DISCONNECTED, test_address))
.Times(0);
Mock::VerifyAndClearExpectations(&btm_interface);
/* Fake no persistent storage data */
ON_CALL(btif_storage_interface_, GetLeaudioHasPresets(_, _, _))
.WillByDefault([]() { return false; });
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address))
.Times(1);
SetEncryptionResult(test_address, true);
InjectConnectedEvent(test_address, GetTestConnId(test_address));
}
TEST_F(HasClientTest, test_load_from_storage_and_connect) {
const RawAddress test_address = GetTestAddress(1);
SetSampleDatabaseHasPresetsNtf(test_address, kFeatureBitDynamicPresets);
SetEncryptionResult(test_address, true);
std::set<HasPreset, HasPreset::ComparatorDesc> has_presets = {{
HasPreset(5, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
"YourWritablePreset5"),
HasPreset(55, HasPreset::kPropertyAvailable, "YourPreset55"),
}};
/* Load persistent storage data */
ON_CALL(btif_storage_interface_, GetLeaudioHasPresets(test_address, _, _))
.WillByDefault([&has_presets](const RawAddress& address,
std::vector<uint8_t>& presets_bin,
uint8_t& active_preset) {
/* Generate presets binary to be used instead the attribute values */
HasDevice device(address, 0);
device.has_presets = has_presets;
active_preset = 55;
if (device.SerializePresets(presets_bin)) return true;
return false;
});
EXPECT_CALL(gatt_interface, RegisterForNotifications(gatt_if, _, _))
.Times(1 // preset control point
+ 1 // active preset
+ 1); // features
EXPECT_CALL(*callbacks,
OnDeviceAvailable(test_address,
(kFeatureBitWritablePresets |
kFeatureBitPresetSynchronizationSupported |
kFeatureBitHearingAidTypeBanded)));
std::vector<PresetInfo> loaded_preset_details;
EXPECT_CALL(*callbacks,
OnPresetInfo(std::variant<RawAddress, int>(test_address),
PresetInfoReason::ALL_PRESET_INFO, _))
.WillOnce(SaveArg<2>(&loaded_preset_details));
EXPECT_CALL(*callbacks, OnActivePresetSelected(
std::variant<RawAddress, int>(test_address), 55));
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address));
/* Expect no read or write operations when loading from storage */
EXPECT_CALL(gatt_queue, ReadCharacteristic(1, _, _, _)).Times(0);
EXPECT_CALL(gatt_queue, WriteDescriptor(1, _, _, _, _, _)).Times(3);
TestAddFromStorage(test_address,
kFeatureBitWritablePresets |
kFeatureBitPresetSynchronizationSupported |
kFeatureBitHearingAidTypeBanded,
true);
for (auto const& info : loaded_preset_details) {
auto preset = has_presets.find(info.preset_index);
ASSERT_NE(preset, has_presets.end());
if (preset->GetProperties() & HasPreset::kPropertyAvailable)
ASSERT_TRUE(info.available);
if (preset->GetProperties() & HasPreset::kPropertyWritable)
ASSERT_TRUE(info.writable);
ASSERT_EQ(preset->GetName(), info.preset_name);
}
}
TEST_F(HasClientTest, test_load_from_storage) {
const RawAddress test_address = GetTestAddress(1);
SetSampleDatabaseHasPresetsNtf(test_address, kFeatureBitDynamicPresets);
SetEncryptionResult(test_address, true);
std::set<HasPreset, HasPreset::ComparatorDesc> has_presets = {{
HasPreset(5, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
"YourWritablePreset5"),
HasPreset(55, HasPreset::kPropertyAvailable, "YourPreset55"),
}};
/* Load persistent storage data */
ON_CALL(btif_storage_interface_, GetLeaudioHasPresets(test_address, _, _))
.WillByDefault([&has_presets](const RawAddress& address,
std::vector<uint8_t>& presets_bin,
uint8_t& active_preset) {
/* Generate presets binary to be used instead the attribute values */
HasDevice device(address, 0);
device.has_presets = has_presets;
active_preset = 55;
if (device.SerializePresets(presets_bin)) return true;
return false;
});
EXPECT_CALL(gatt_interface, RegisterForNotifications(gatt_if, _, _))
.Times(0); // features
EXPECT_CALL(*callbacks,
OnDeviceAvailable(test_address,
(kFeatureBitWritablePresets |
kFeatureBitPresetSynchronizationSupported |
kFeatureBitHearingAidTypeBanded)));
std::vector<PresetInfo> loaded_preset_details;
EXPECT_CALL(*callbacks,
OnPresetInfo(std::variant<RawAddress, int>(test_address),
PresetInfoReason::ALL_PRESET_INFO, _))
.Times(0);
/* Expect no read or write operations when loading from storage */
EXPECT_CALL(gatt_queue, ReadCharacteristic(1, _, _, _)).Times(0);
EXPECT_CALL(gatt_queue, WriteDescriptor(1, _, _, _, _, _)).Times(0);
TestAddFromStorage(test_address,
kFeatureBitWritablePresets |
kFeatureBitPresetSynchronizationSupported |
kFeatureBitHearingAidTypeBanded,
false);
}
TEST_F(HasClientTest, test_write_to_storage) {
const RawAddress test_address = GetTestAddress(1);
std::set<HasPreset, HasPreset::ComparatorDesc> has_presets = {{
HasPreset(1, HasPreset::kPropertyAvailable, "Universal"),
HasPreset(2, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
"Preset2"),
}};
SetSampleDatabaseHasPresetsNtf(
test_address,
bluetooth::has::kFeatureBitHearingAidTypeBanded |
bluetooth::has::kFeatureBitWritablePresets |
bluetooth::has::kFeatureBitDynamicPresets,
has_presets);
std::vector<uint8_t> serialized;
EXPECT_CALL(
btif_storage_interface_,
AddLeaudioHasDevice(test_address, _,
bluetooth::has::kFeatureBitHearingAidTypeBanded |
bluetooth::has::kFeatureBitWritablePresets |
bluetooth::has::kFeatureBitDynamicPresets,
1))
.WillOnce(SaveArg<1>(&serialized));
TestConnect(test_address);
/* Deserialize the written binary to verify the content */
HasDevice clone(test_address,
bluetooth::has::kFeatureBitHearingAidTypeBanded |
bluetooth::has::kFeatureBitWritablePresets |
bluetooth::has::kFeatureBitDynamicPresets);
ASSERT_TRUE(HasDevice::DeserializePresets(serialized.data(),
serialized.size(), clone));
auto storage_info = clone.GetAllPresetInfo();
ASSERT_EQ(storage_info.size(), has_presets.size());
for (auto const& info : storage_info) {
auto preset = has_presets.find(info.preset_index);
ASSERT_NE(preset, has_presets.end());
if (preset->GetProperties() & HasPreset::kPropertyAvailable)
ASSERT_TRUE(info.available);
if (preset->GetProperties() & HasPreset::kPropertyWritable)
ASSERT_TRUE(info.writable);
ASSERT_EQ(preset->GetName(), info.preset_name);
}
}
TEST_F(HasClientTest, test_discovery_basic_has_no_opt_ntf) {
const RawAddress test_address = GetTestAddress(1);
auto test_conn_id = GetTestConnId(test_address);
SetSampleDatabaseHasNoOptionalNtf(test_address);
std::variant<RawAddress, int> addr_or_group = test_address;
std::vector<PresetInfo> preset_details;
uint8_t active_preset_index;
uint8_t has_features;
EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, _))
.WillOnce(SaveArg<1>(&has_features));
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address));
EXPECT_CALL(*callbacks, OnPresetInfo(_, PresetInfoReason::ALL_PRESET_INFO, _))
.WillOnce(DoAll(SaveArg<0>(&addr_or_group), SaveArg<2>(&preset_details)));
EXPECT_CALL(*callbacks, OnActivePresetSelected(_, _))
.WillOnce(
DoAll(SaveArg<0>(&addr_or_group), SaveArg<1>(&active_preset_index)));
TestConnect(test_address);
/* Verify sample database content */
ASSERT_TRUE(std::holds_alternative<RawAddress>(addr_or_group));
ASSERT_EQ(std::get<RawAddress>(addr_or_group), test_address);
ASSERT_EQ(has_features, 0x00);
ASSERT_EQ(active_preset_index,
current_peer_presets_.at(test_conn_id).begin()->GetIndex());
/* Verify presets */
uint16_t conn_id = GetTestConnId(test_address);
ASSERT_NE(preset_details.size(), 0u);
ASSERT_EQ(current_peer_presets_.at(conn_id).size(), preset_details.size());
for (auto const& preset : current_peer_presets_.at(conn_id)) {
auto it =
std::find_if(preset_details.cbegin(), preset_details.cend(),
[&preset](auto const& preset_info) {
return preset_info.preset_index == preset.GetIndex();
});
ASSERT_NE(it, preset_details.cend());
ASSERT_EQ(preset.GetName(), it->preset_name);
ASSERT_EQ(preset.IsAvailable(), it->available);
ASSERT_EQ(preset.IsWritable(), it->writable);
}
/* Verify active preset is there */
ASSERT_EQ(preset_details.size(),
current_peer_presets_.at(test_conn_id).size());
ASSERT_TRUE(std::find_if(preset_details.begin(), preset_details.end(),
[active_preset_index](auto const& preset_info) {
return preset_info.preset_index ==
active_preset_index;
}) != preset_details.end());
}
TEST_F(HasClientTest, test_discovery_has_not_found) {
const RawAddress test_address = GetTestAddress(1);
SetSampleDatabaseNoHas(test_address);
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address))
.Times(0);
EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, _)).Times(0);
EXPECT_CALL(*callbacks, OnFeaturesUpdate(test_address, _)).Times(0);
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::DISCONNECTED, test_address));
TestConnect(test_address);
}
TEST_F(HasClientTest, test_discovery_has_broken_no_active_preset) {
const RawAddress test_address = GetTestAddress(1);
SetSampleDatabaseHasBrokenNoActivePreset(test_address);
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address))
.Times(0);
EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, _)).Times(0);
EXPECT_CALL(*callbacks, OnFeaturesUpdate(test_address, _)).Times(0);
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::DISCONNECTED, test_address));
TestConnect(test_address);
}
TEST_F(HasClientTest, test_discovery_has_broken_no_active_preset_ntf) {
const RawAddress test_address = GetTestAddress(1);
SetSampleDatabaseHasBrokenNoActivePresetNtf(test_address);
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address))
.Times(0);
EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, _)).Times(0);
EXPECT_CALL(*callbacks, OnFeaturesUpdate(test_address, _)).Times(0);
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::DISCONNECTED, test_address));
TestConnect(test_address);
}
TEST_F(HasClientTest, test_discovery_has_features_ntf) {
const RawAddress test_address = GetTestAddress(1);
auto test_conn_id = GetTestConnId(test_address);
uint8_t has_features;
SetSampleDatabaseHasOnlyFeaturesNtf(
test_address, bluetooth::has::kFeatureBitHearingAidTypeBanded);
EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, _))
.WillOnce(SaveArg<1>(&has_features));
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address))
.Times(1);
/* Verify subscription to features */
EXPECT_CALL(gatt_interface, RegisterForNotifications(gatt_if, _, _))
.Times(AnyNumber());
EXPECT_CALL(gatt_interface,
RegisterForNotifications(gatt_if, test_address,
HasDbBuilder::kFeaturesValHdl));
/* Verify features CCC was written */
EXPECT_CALL(gatt_queue, WriteDescriptor(test_conn_id, _, _, _, _, _))
.Times(AnyNumber());
EXPECT_CALL(gatt_queue,
WriteDescriptor(test_conn_id, HasDbBuilder::kFeaturesValHdl + 1,
std::vector<uint8_t>{0x01, 0x00}, _, _, _));
TestConnect(test_address);
/* Verify features */
ASSERT_EQ(has_features, bluetooth::has::kFeatureBitHearingAidTypeBanded);
uint8_t new_features;
/* Verify peer features change notification */
EXPECT_CALL(*callbacks, OnFeaturesUpdate(test_address, _))
.WillOnce(SaveArg<1>(&new_features));
InjectNotificationEvent(test_address, test_conn_id,
HasDbBuilder::kFeaturesValHdl,
std::vector<uint8_t>({0x00}));
ASSERT_NE(has_features, new_features);
}
TEST_F(HasClientTest, test_discovery_has_features_no_ntf) {
const RawAddress test_address = GetTestAddress(1);
auto test_conn_id = GetTestConnId(test_address);
uint8_t has_features;
SetSampleDatabaseHasOnlyFeaturesNoNtf(
test_address, bluetooth::has::kFeatureBitHearingAidTypeBanded);
EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, _))
.WillOnce(SaveArg<1>(&has_features));
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address))
.Times(1);
/* Verify no subscription to features */
EXPECT_CALL(gatt_interface, RegisterForNotifications(gatt_if, _, _))
.Times(AnyNumber());
EXPECT_CALL(gatt_interface,
RegisterForNotifications(gatt_if, test_address,
HasDbBuilder::kFeaturesValHdl))
.Times(0);
/* Verify no features CCC was written */
EXPECT_CALL(gatt_queue, WriteDescriptor(test_conn_id, _, _, _, _, _))
.Times(AnyNumber());
EXPECT_CALL(gatt_queue,
WriteDescriptor(test_conn_id, HasDbBuilder::kFeaturesValHdl + 1,
_, _, _, _))
.Times(0);
TestConnect(test_address);
/* Verify features */
ASSERT_EQ(has_features, bluetooth::has::kFeatureBitHearingAidTypeBanded);
}
TEST_F(HasClientTest, test_discovery_has_multiple_presets_ntf) {
const RawAddress test_address = GetTestAddress(1);
SetSampleDatabaseHasPresetsNtf(
test_address, bluetooth::has::kFeatureBitHearingAidTypeBanded);
std::variant<RawAddress, int> addr_or_group = test_address;
std::vector<PresetInfo> preset_details;
uint8_t active_preset_index;
uint8_t has_features;
EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, _))
.WillOnce(SaveArg<1>(&has_features));
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address));
EXPECT_CALL(*callbacks, OnPresetInfo(_, PresetInfoReason::ALL_PRESET_INFO, _))
.WillOnce(DoAll(SaveArg<0>(&addr_or_group), SaveArg<2>(&preset_details)));
EXPECT_CALL(*callbacks, OnActivePresetSelected(_, _))
.WillOnce(
DoAll(SaveArg<0>(&addr_or_group), SaveArg<1>(&active_preset_index)));
/* Verify subscription to control point */
EXPECT_CALL(gatt_interface, RegisterForNotifications(gatt_if, _, _))
.Times(AnyNumber());
EXPECT_CALL(gatt_interface,
RegisterForNotifications(gatt_if, test_address,
HasDbBuilder::kPresetsCtpValHdl));
/* Verify features CCC was written */
EXPECT_CALL(gatt_queue, WriteDescriptor(1, _, _, _, _, _)).Times(AnyNumber());
EXPECT_CALL(gatt_queue,
WriteDescriptor(1, HasDbBuilder::kPresetsCtpValHdl + 1,
std::vector<uint8_t>{0x03, 0x00}, _, _, _));
TestConnect(test_address);
/* Verify features */
ASSERT_EQ(has_features, bluetooth::has::kFeatureBitHearingAidTypeBanded);
}
TEST_F(HasClientTest, test_active_preset_change) {
const RawAddress test_address = GetTestAddress(1);
auto test_conn_id = GetTestConnId(test_address);
SetSampleDatabaseHasNoOptionalNtf(test_address);
uint8_t active_preset_index;
EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, _));
EXPECT_CALL(*callbacks,
OnPresetInfo(_, PresetInfoReason::ALL_PRESET_INFO, _));
EXPECT_CALL(*callbacks, OnActivePresetSelected(_, _))
.WillOnce(SaveArg<1>(&active_preset_index));
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address));
TestConnect(test_address);
uint8_t new_active_preset;
EXPECT_CALL(*callbacks, OnActivePresetSelected(
std::variant<RawAddress, int>(test_address), _))
.WillOnce(SaveArg<1>(&new_active_preset));
InjectNotificationEvent(test_address, test_conn_id,
HasDbBuilder::kActivePresetIndexValHdl,
std::vector<uint8_t>({0x00}));
ASSERT_NE(active_preset_index, new_active_preset);
ASSERT_EQ(new_active_preset, 0x00);
}
TEST_F(HasClientTest, test_duplicate_presets) {
const RawAddress test_address = GetTestAddress(1);
std::vector<PresetInfo> preset_details;
/* Handle duplicates gracefully */
SetSampleDatabaseHasPresetsNtf(
test_address, kFeatureBitWritablePresets,
{{HasPreset(5,
HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
"YourWritablePreset5"),
HasPreset(5,
HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
"YourWritablePreset5")}});
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address));
EXPECT_CALL(*callbacks, OnPresetInfo(_, PresetInfoReason::ALL_PRESET_INFO, _))
.WillOnce(SaveArg<2>(&preset_details));
TestConnect(test_address);
/* Verify presets - expect 1, no duplicates */
ASSERT_EQ(preset_details.size(), 1u);
auto preset = std::find_if(
preset_details.begin(), preset_details.end(),
[](auto const& preset_info) { return preset_info.preset_index == 5; });
ASSERT_TRUE(preset != preset_details.end());
ASSERT_EQ("YourWritablePreset5", preset->preset_name);
ASSERT_TRUE(preset->available);
ASSERT_TRUE(preset->writable);
}
TEST_F(HasClientTest, test_preset_set_name_invalid_index) {
const RawAddress test_address = GetTestAddress(1);
SetSampleDatabaseHasPresetsNtf(test_address);
TestConnect(test_address);
EXPECT_CALL(*callbacks,
OnSetPresetNameError(std::variant<RawAddress, int>(test_address),
0x40, ErrorCode::INVALID_PRESET_INDEX))
.Times(1);
EXPECT_CALL(gatt_queue,
WriteCharacteristic(1, HasDbBuilder::kPresetsCtpValHdl, _,
GATT_WRITE, _, _))
.Times(0);
HasClient::Get()->SetPresetName(test_address, 0x40, "new preset name");
}
TEST_F(HasClientTest, test_preset_set_name_non_writable) {
const RawAddress test_address = GetTestAddress(1);
uint16_t test_conn_id = GetTestConnId(test_address);
SetSampleDatabaseHasPresetsNtf(
test_address, kFeatureBitWritablePresets,
{{
HasPreset(5, HasPreset::kPropertyAvailable, "YourPreset5"),
HasPreset(
55, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
"YourWritablePreset55"),
}});
TestConnect(test_address);
EXPECT_CALL(*callbacks,
OnSetPresetNameError(_, _, ErrorCode::SET_NAME_NOT_ALLOWED))
.Times(1);
EXPECT_CALL(gatt_queue,
WriteCharacteristic(1, HasDbBuilder::kPresetsCtpValHdl, _,
GATT_WRITE, _, _))
.Times(0);
HasClient::Get()->SetPresetName(
test_address, current_peer_presets_.at(test_conn_id).begin()->GetIndex(),
"new preset name");
}
TEST_F(HasClientTest, test_preset_set_name_to_long) {
const RawAddress test_address = GetTestAddress(1);
uint16_t test_conn_id = GetTestConnId(test_address);
SetSampleDatabaseHasPresetsNtf(
test_address, kFeatureBitWritablePresets,
{{HasPreset(5,
HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
"YourWritablePreset")}});
TestConnect(test_address);
EXPECT_CALL(*callbacks,
OnSetPresetNameError(_, _, ErrorCode::INVALID_PRESET_NAME_LENGTH))
.Times(1);
EXPECT_CALL(gatt_queue,
WriteCharacteristic(test_conn_id, HasDbBuilder::kPresetsCtpValHdl,
_, GATT_WRITE, _, _))
.Times(0);
HasClient::Get()->SetPresetName(test_address, 5,
"this name is more than 40 characters long");
}
TEST_F(HasClientTest, test_preset_set_name) {
const RawAddress test_address = GetTestAddress(1);
uint16_t test_conn_id = GetTestConnId(test_address);
SetSampleDatabaseHasPresetsNtf(
test_address, kFeatureBitWritablePresets,
{{HasPreset(5,
HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
"YourWritablePreset5")}});
TestConnect(test_address);
std::vector<uint8_t> value;
EXPECT_CALL(*callbacks, OnSetPresetNameError(_, _, _)).Times(0);
EXPECT_CALL(gatt_queue,
WriteCharacteristic(test_conn_id, HasDbBuilder::kPresetsCtpValHdl,
_, GATT_WRITE, _, _));
std::vector<PresetInfo> updated_preset_details;
EXPECT_CALL(*callbacks,
OnPresetInfo(std::variant<RawAddress, int>(test_address),
PresetInfoReason::PRESET_INFO_UPDATE, _))
.WillOnce(SaveArg<2>(&updated_preset_details));
HasClient::Get()->SetPresetName(test_address, 5, "new preset name");
ASSERT_EQ(1u, updated_preset_details.size());
ASSERT_EQ(updated_preset_details[0].preset_name, "new preset name");
}
TEST_F(HasClientTest, test_preset_group_set_name) {
/* None of these devices support preset syncing */
const RawAddress test_address1 = GetTestAddress(1);
SetSampleDatabaseHasPresetsNtf(
test_address1, bluetooth::has::kFeatureBitHearingAidTypeBinaural |
bluetooth::has::kFeatureBitWritablePresets);
const RawAddress test_address2 = GetTestAddress(2);
SetSampleDatabaseHasPresetsNtf(
test_address2, bluetooth::has::kFeatureBitHearingAidTypeBinaural |
bluetooth::has::kFeatureBitWritablePresets);
TestConnect(test_address1);
TestConnect(test_address2);
/* Mock the csis group with two devices */
uint8_t not_synced_group = 13;
ON_CALL(mock_csis_client_module_, GetDeviceList(not_synced_group))
.WillByDefault(
Return(std::vector<RawAddress>({{test_address1, test_address2}})));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address1, ::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(not_synced_group));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address2, ::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(not_synced_group));
std::vector<PresetInfo> preset_details;
EXPECT_CALL(*callbacks, OnActivePresetSelected(
std::variant<RawAddress, int>(test_address1), 55))
.Times(0);
EXPECT_CALL(*callbacks, OnActivePresetSelected(
std::variant<RawAddress, int>(test_address2), 55))
.Times(0);
/* This should be a group callback */
EXPECT_CALL(*callbacks,
OnPresetInfo(std::variant<RawAddress, int>(not_synced_group),
PresetInfoReason::PRESET_INFO_UPDATE, _))
.Times(1)
.WillOnce(SaveArg<2>(&preset_details));
/* No locally synced opcodes support so expect both devices getting writes */
EXPECT_CALL(gatt_queue, WriteCharacteristic(GetTestConnId(test_address1),
HasDbBuilder::kPresetsCtpValHdl,
_, GATT_WRITE, _, _))
.Times(1);
EXPECT_CALL(gatt_queue, WriteCharacteristic(GetTestConnId(test_address2),
HasDbBuilder::kPresetsCtpValHdl,
_, GATT_WRITE, _, _))
.Times(1);
HasClient::Get()->SetPresetName(not_synced_group, 55, "new preset name");
ASSERT_EQ(preset_details.size(), 1u);
ASSERT_EQ(preset_details[0].preset_name, "new preset name");
ASSERT_EQ(preset_details[0].preset_index, 55);
}
TEST_F(HasClientTest, test_multiple_presets_get_name) {
const RawAddress test_address = GetTestAddress(1);
SetSampleDatabaseHasPresetsNtf(
test_address, kFeatureBitWritablePresets,
{{
HasPreset(
5, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
"YourWritablePreset5"),
HasPreset(55, HasPreset::kPropertyAvailable, "YourPreset55"),
HasPreset(99, 0, "YourPreset99"),
}});
std::vector<PresetInfo> preset_details;
EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, _));
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address));
EXPECT_CALL(*callbacks, OnPresetInfo(_, PresetInfoReason::ALL_PRESET_INFO, _))
.WillOnce(SaveArg<2>(&preset_details));
TestConnect(test_address);
/* Get each preset info individually */
for (auto const& preset : preset_details) {
std::vector<PresetInfo> new_preset_details;
EXPECT_CALL(*callbacks,
OnPresetInfo(std::variant<RawAddress, int>(test_address),
PresetInfoReason::PRESET_INFO_REQUEST_RESPONSE, _))
.Times(1)
.WillOnce(SaveArg<2>(&new_preset_details));
HasClient::Get()->GetPresetInfo(test_address, preset.preset_index);
Mock::VerifyAndClearExpectations(&*callbacks);
ASSERT_EQ(1u, new_preset_details.size());
ASSERT_EQ(preset.preset_index, new_preset_details[0].preset_index);
ASSERT_EQ(preset.preset_name, new_preset_details[0].preset_name);
ASSERT_EQ(preset.writable, new_preset_details[0].writable);
ASSERT_EQ(preset.available, new_preset_details[0].available);
}
}
TEST_F(HasClientTest, test_presets_get_name_invalid_index) {
const RawAddress test_address = GetTestAddress(1);
SetSampleDatabaseHasPresetsNtf(test_address);
TestConnect(test_address);
EXPECT_CALL(*callbacks,
OnPresetInfoError(std::variant<RawAddress, int>(test_address),
128, ErrorCode::INVALID_PRESET_INDEX));
HasClient::Get()->GetPresetInfo(test_address, 128);
EXPECT_CALL(*callbacks,
OnPresetInfoError(std::variant<RawAddress, int>(test_address), 0,
ErrorCode::INVALID_PRESET_INDEX));
HasClient::Get()->GetPresetInfo(test_address, 0);
}
TEST_F(HasClientTest, test_presets_changed_generic_update_no_add_or_delete) {
const RawAddress test_address = GetTestAddress(1);
uint16_t test_conn_id = GetTestConnId(test_address);
std::set<HasPreset, HasPreset::ComparatorDesc> presets = {{
HasPreset(1, HasPreset::kPropertyAvailable, "Universal"),
HasPreset(2, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
"Preset2"),
HasPreset(4, HasPreset::kPropertyAvailable, "Preset4"),
HasPreset(7, HasPreset::kPropertyAvailable, "Preset7"),
}};
SetSampleDatabaseHasPresetsNtf(
test_address,
bluetooth::has::kFeatureBitHearingAidTypeBanded |
bluetooth::has::kFeatureBitDynamicPresets |
bluetooth::has::kFeatureBitWritablePresets,
presets);
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address));
TestConnect(test_address);
std::vector<PresetInfo> preset_details;
EXPECT_CALL(*callbacks,
OnPresetInfo(std::variant<RawAddress, int>(test_address),
PresetInfoReason::PRESET_INFO_UPDATE, _))
.Times(1)
.WillOnce(SaveArg<2>(&preset_details));
/* Inject generic update on the first preset */
auto preset_index = 2;
auto new_test_preset = HasPreset(preset_index, 0, "props new name");
ASSERT_NE(*current_peer_presets_.at(test_conn_id).find(preset_index),
new_test_preset);
InjectPresetChanged(test_conn_id, test_address, false, new_test_preset,
1 /* prev_index */,
::le_audio::has::PresetCtpChangeId::PRESET_GENERIC_UPDATE,
true /* is_last */);
/* Verify received preset info update on the 2nd preset */
ASSERT_EQ(1u, preset_details.size());
ASSERT_EQ(new_test_preset.GetIndex(), preset_details[0].preset_index);
ASSERT_EQ(new_test_preset.IsAvailable(), preset_details[0].available);
ASSERT_EQ(new_test_preset.IsWritable(), preset_details[0].writable);
ASSERT_EQ(new_test_preset.GetName(), preset_details[0].preset_name);
}
TEST_F(HasClientTest, test_presets_changed_generic_update_add_and_delete) {
const RawAddress test_address = GetTestAddress(1);
uint16_t test_conn_id = GetTestConnId(test_address);
std::set<HasPreset, HasPreset::ComparatorDesc> presets = {{
HasPreset(1, HasPreset::kPropertyAvailable, "Universal"),
HasPreset(2, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
"Preset2"),
HasPreset(4, HasPreset::kPropertyAvailable, "Preset4"),
HasPreset(5, HasPreset::kPropertyAvailable, "Preset5"),
HasPreset(32, HasPreset::kPropertyAvailable, "Preset32"),
}};
SetSampleDatabaseHasPresetsNtf(
test_address,
bluetooth::has::kFeatureBitHearingAidTypeBanded |
bluetooth::has::kFeatureBitWritablePresets,
presets);
std::vector<PresetInfo> preset_details;
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address));
EXPECT_CALL(*callbacks,
OnPresetInfo(std::variant<RawAddress, int>(test_address),
PresetInfoReason::ALL_PRESET_INFO, _))
.Times(1)
.WillOnce(SaveArg<2>(&preset_details));
TestConnect(test_address);
/* Expect more OnPresetInfo call */
std::vector<PresetInfo> updated_preset_details;
EXPECT_CALL(*callbacks,
OnPresetInfo(std::variant<RawAddress, int>(test_address),
PresetInfoReason::PRESET_INFO_UPDATE, _))
.Times(1)
.WillOnce(SaveArg<2>(&updated_preset_details));
/* Expect more OnPresetInfo call */
std::vector<PresetInfo> deleted_preset_details;
EXPECT_CALL(*callbacks,
OnPresetInfo(std::variant<RawAddress, int>(test_address),
PresetInfoReason::PRESET_DELETED, _))
.Times(1)
.WillOnce(SaveArg<2>(&deleted_preset_details));
/* Inject generic updates */
/* First event replaces all the existing presets from 1 to 8 with preset 8
*/
auto new_test_preset1 =
HasPreset(8, HasPreset::kPropertyAvailable, "props new name9");
InjectPresetChanged(test_conn_id, test_address, false, new_test_preset1,
1 /* prev_index */,
::le_audio::has::PresetCtpChangeId::PRESET_GENERIC_UPDATE,
false /* is_last */);
/* Second event adds preset 9 to the already existing presets 1 and 8 */
auto new_test_preset2 =
HasPreset(9, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
"props new name11");
InjectPresetChanged(test_conn_id, test_address, false, new_test_preset2,
8 /* prev_index */,
::le_audio::has::PresetCtpChangeId::PRESET_GENERIC_UPDATE,
true /* is_last */);
/* Verify received preset info - expect presets 1, 32 unchanged, 8, 9
* updated, and 2, 4, 5 deleted.
*/
ASSERT_EQ(2u, updated_preset_details.size());
ASSERT_EQ(new_test_preset1.GetIndex(),
updated_preset_details[0].preset_index);
ASSERT_EQ(new_test_preset1.IsAvailable(),
updated_preset_details[0].available);
ASSERT_EQ(new_test_preset1.IsWritable(), updated_preset_details[0].writable);
ASSERT_EQ(new_test_preset1.GetName(), updated_preset_details[0].preset_name);
ASSERT_EQ(new_test_preset2.GetIndex(),
updated_preset_details[1].preset_index);
ASSERT_EQ(new_test_preset2.IsAvailable(),
updated_preset_details[1].available);
ASSERT_EQ(new_test_preset2.IsWritable(), updated_preset_details[1].writable);
ASSERT_EQ(new_test_preset2.GetName(), updated_preset_details[1].preset_name);
ASSERT_EQ(3u, deleted_preset_details.size());
ASSERT_EQ(2, deleted_preset_details[0].preset_index);
ASSERT_EQ(4, deleted_preset_details[1].preset_index);
ASSERT_EQ(5, deleted_preset_details[2].preset_index);
}
TEST_F(HasClientTest, test_presets_changed_deleted) {
const RawAddress test_address = GetTestAddress(1);
uint16_t test_conn_id = GetTestConnId(test_address);
std::set<HasPreset, HasPreset::ComparatorDesc> presets = {{
HasPreset(1, HasPreset::kPropertyAvailable, "Universal"),
HasPreset(2, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
"Preset2"),
}};
SetSampleDatabaseHasPresetsNtf(
test_address,
bluetooth::has::kFeatureBitHearingAidTypeBanded |
bluetooth::has::kFeatureBitWritablePresets |
bluetooth::has::kFeatureBitDynamicPresets,
presets);
std::vector<PresetInfo> preset_details;
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address));
EXPECT_CALL(*callbacks,
OnPresetInfo(std::variant<RawAddress, int>(test_address),
PresetInfoReason::ALL_PRESET_INFO, _))
.Times(1)
.WillOnce(SaveArg<2>(&preset_details));
TestConnect(test_address);
/* Expect second OnPresetInfo call */
std::vector<PresetInfo> deleted_preset_details;
EXPECT_CALL(*callbacks,
OnPresetInfo(std::variant<RawAddress, int>(test_address),
PresetInfoReason::PRESET_DELETED, _))
.Times(1)
.WillOnce(SaveArg<2>(&deleted_preset_details));
/* Inject preset deletion of index 2 */
auto deleted_index = preset_details[1].preset_index;
InjectPresetChanged(test_conn_id, test_address, false,
*presets.find(deleted_index), 0 /* prev_index */,
::le_audio::has::PresetCtpChangeId::PRESET_DELETED,
true /* is_last */);
ASSERT_EQ(2u, preset_details.size());
ASSERT_EQ(1u, deleted_preset_details.size());
ASSERT_EQ(preset_details[1].preset_index,
deleted_preset_details[0].preset_index);
ASSERT_EQ(preset_details[1].writable, deleted_preset_details[0].writable);
ASSERT_EQ(preset_details[1].available, deleted_preset_details[0].available);
ASSERT_EQ(preset_details[1].preset_name,
deleted_preset_details[0].preset_name);
}
TEST_F(HasClientTest, test_presets_changed_available) {
const RawAddress test_address = GetTestAddress(1);
uint16_t test_conn_id = GetTestConnId(test_address);
std::set<HasPreset, HasPreset::ComparatorDesc> presets = {{
HasPreset(1, 0, "Universal"),
HasPreset(2, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
"Preset2"),
}};
SetSampleDatabaseHasPresetsNtf(
test_address,
bluetooth::has::kFeatureBitHearingAidTypeBanded |
bluetooth::has::kFeatureBitWritablePresets |
bluetooth::has::kFeatureBitDynamicPresets,
presets);
std::vector<PresetInfo> preset_details;
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address));
EXPECT_CALL(*callbacks,
OnPresetInfo(std::variant<RawAddress, int>(test_address),
PresetInfoReason::ALL_PRESET_INFO, _))
.Times(1)
.WillOnce(SaveArg<2>(&preset_details));
TestConnect(test_address);
/* Expect second OnPresetInfo call */
std::vector<PresetInfo> changed_preset_details;
EXPECT_CALL(*callbacks,
OnPresetInfo(std::variant<RawAddress, int>(test_address),
PresetInfoReason::PRESET_AVAILABILITY_CHANGED, _))
.Times(1)
.WillOnce(SaveArg<2>(&changed_preset_details));
/* Inject preset deletion of index 2 */
auto changed_index = preset_details[0].preset_index;
InjectPresetChanged(test_conn_id, test_address, false,
*presets.find(changed_index), 0 /* prev_index */,
::le_audio::has::PresetCtpChangeId::PRESET_AVAILABLE,
true /* is_last */);
ASSERT_EQ(2u, preset_details.size());
ASSERT_EQ(1u, changed_preset_details.size());
ASSERT_EQ(preset_details[0].preset_index,
changed_preset_details[0].preset_index);
ASSERT_EQ(preset_details[0].writable, changed_preset_details[0].writable);
ASSERT_EQ(preset_details[0].preset_name,
changed_preset_details[0].preset_name);
/* This field should have changed */
ASSERT_NE(preset_details[0].available, changed_preset_details[0].available);
ASSERT_TRUE(changed_preset_details[0].available);
}
TEST_F(HasClientTest, test_presets_changed_unavailable) {
const RawAddress test_address = GetTestAddress(1);
uint16_t test_conn_id = GetTestConnId(test_address);
std::set<HasPreset, HasPreset::ComparatorDesc> presets = {{
HasPreset(1, HasPreset::kPropertyAvailable, "Universal"),
HasPreset(2, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
"Preset2"),
}};
SetSampleDatabaseHasPresetsNtf(
test_address,
bluetooth::has::kFeatureBitHearingAidTypeBanded |
bluetooth::has::kFeatureBitWritablePresets |
bluetooth::has::kFeatureBitDynamicPresets,
presets);
std::vector<PresetInfo> preset_details;
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address));
EXPECT_CALL(*callbacks,
OnPresetInfo(std::variant<RawAddress, int>(test_address),
PresetInfoReason::ALL_PRESET_INFO, _))
.Times(1)
.WillOnce(SaveArg<2>(&preset_details));
TestConnect(test_address);
/* Expect second OnPresetInfo call */
std::vector<PresetInfo> changed_preset_details;
EXPECT_CALL(*callbacks,
OnPresetInfo(std::variant<RawAddress, int>(test_address),
PresetInfoReason::PRESET_AVAILABILITY_CHANGED, _))
.Times(1)
.WillOnce(SaveArg<2>(&changed_preset_details));
/* Inject preset deletion of index 2 */
auto changed_index = preset_details[0].preset_index;
InjectPresetChanged(test_conn_id, test_address, false,
*presets.find(changed_index), 0 /* prev_index */,
::le_audio::has::PresetCtpChangeId::PRESET_UNAVAILABLE,
true /* is_last */);
ASSERT_EQ(2u, preset_details.size());
ASSERT_EQ(1u, changed_preset_details.size());
ASSERT_EQ(preset_details[0].preset_index,
changed_preset_details[0].preset_index);
ASSERT_EQ(preset_details[0].writable, changed_preset_details[0].writable);
ASSERT_EQ(preset_details[0].preset_name,
changed_preset_details[0].preset_name);
/* This field should have changed */
ASSERT_NE(preset_details[0].available, changed_preset_details[0].available);
ASSERT_FALSE(changed_preset_details[0].available);
}
TEST_F(HasClientTest, test_select_preset_valid) {
const RawAddress test_address = GetTestAddress(1);
SetSampleDatabaseHasPresetsNtf(test_address);
uint8_t active_preset_index = 0;
std::vector<PresetInfo> preset_details;
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address));
EXPECT_CALL(*callbacks,
OnPresetInfo(std::variant<RawAddress, int>(test_address),
PresetInfoReason::ALL_PRESET_INFO, _))
.Times(1)
.WillOnce(SaveArg<2>(&preset_details));
EXPECT_CALL(*callbacks, OnActivePresetSelected(_, _))
.WillOnce(SaveArg<1>(&active_preset_index));
TestConnect(test_address);
ASSERT_TRUE(preset_details.size() > 1);
ASSERT_EQ(preset_details.front().preset_index, active_preset_index);
uint8_t new_active_preset_index = 0;
EXPECT_CALL(*callbacks, OnActivePresetSelected(_, _))
.WillOnce(SaveArg<1>(&new_active_preset_index));
HasClient::Get()->SelectActivePreset(test_address,
preset_details.back().preset_index);
Mock::VerifyAndClearExpectations(&*callbacks);
ASSERT_NE(active_preset_index, new_active_preset_index);
ASSERT_EQ(preset_details.back().preset_index, new_active_preset_index);
}
TEST_F(HasClientTest, test_select_group_preset_invalid_group) {
const RawAddress test_address1 = GetTestAddress(1);
SetSampleDatabaseHasPresetsNtf(test_address1);
const RawAddress test_address2 = GetTestAddress(2);
SetSampleDatabaseHasPresetsNtf(test_address2);
TestConnect(test_address1);
TestConnect(test_address2);
/* Mock the csis group with no devices */
uint8_t unlucky_group = 13;
ON_CALL(mock_csis_client_module_, GetDeviceList(unlucky_group))
.WillByDefault(Return(std::vector<RawAddress>()));
EXPECT_CALL(*callbacks, OnActivePresetSelectError(
std::variant<RawAddress, int>(unlucky_group),
ErrorCode::OPERATION_NOT_POSSIBLE))
.Times(1);
HasClient::Get()->SelectActivePreset(unlucky_group, 6);
}
TEST_F(HasClientTest, test_select_group_preset_valid_no_preset_sync_supported) {
/* None of these devices support preset syncing */
const RawAddress test_address1 = GetTestAddress(1);
SetSampleDatabaseHasPresetsNtf(
test_address1, bluetooth::has::kFeatureBitHearingAidTypeBinaural);
const RawAddress test_address2 = GetTestAddress(2);
SetSampleDatabaseHasPresetsNtf(
test_address2, bluetooth::has::kFeatureBitHearingAidTypeBinaural);
TestConnect(test_address1);
TestConnect(test_address2);
/* Mock the csis group with two devices */
uint8_t not_synced_group = 13;
ON_CALL(mock_csis_client_module_, GetDeviceList(not_synced_group))
.WillByDefault(
Return(std::vector<RawAddress>({{test_address1, test_address2}})));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address1, ::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(not_synced_group));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address2, ::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(not_synced_group));
uint8_t group_active_preset_index = 0;
EXPECT_CALL(*callbacks, OnActivePresetSelected(
std::variant<RawAddress, int>(test_address1), 55))
.Times(0);
EXPECT_CALL(*callbacks, OnActivePresetSelected(
std::variant<RawAddress, int>(test_address2), 55))
.Times(0);
EXPECT_CALL(*callbacks,
OnActivePresetSelected(
std::variant<RawAddress, int>(not_synced_group), _))
.WillOnce(SaveArg<1>(&group_active_preset_index));
/* No locally synced opcodes support so expect both devices getting writes */
EXPECT_CALL(gatt_queue, WriteCharacteristic(GetTestConnId(test_address1),
HasDbBuilder::kPresetsCtpValHdl,
_, GATT_WRITE, _, _))
.Times(1);
EXPECT_CALL(gatt_queue, WriteCharacteristic(GetTestConnId(test_address2),
HasDbBuilder::kPresetsCtpValHdl,
_, GATT_WRITE, _, _))
.Times(1);
HasClient::Get()->SelectActivePreset(not_synced_group, 55);
ASSERT_EQ(group_active_preset_index, 55);
}
TEST_F(HasClientTest, test_select_group_preset_valid_preset_sync_supported) {
/* Only one of these devices support preset syncing */
const RawAddress test_address1 = GetTestAddress(1);
uint16_t test_conn_id1 = GetTestConnId(test_address1);
SetSampleDatabaseHasPresetsNtf(
test_address1, bluetooth::has::kFeatureBitHearingAidTypeBinaural);
const RawAddress test_address2 = GetTestAddress(2);
uint16_t test_conn_id2 = GetTestConnId(test_address2);
SetSampleDatabaseHasPresetsNtf(
test_address2,
bluetooth::has::kFeatureBitHearingAidTypeBinaural |
bluetooth::has::kFeatureBitPresetSynchronizationSupported);
uint8_t active_preset_index1 = 0;
uint8_t active_preset_index2 = 0;
EXPECT_CALL(*callbacks, OnActivePresetSelected(
std::variant<RawAddress, int>(test_address1), _))
.WillOnce(SaveArg<1>(&active_preset_index1));
TestConnect(test_address1);
EXPECT_CALL(*callbacks, OnActivePresetSelected(
std::variant<RawAddress, int>(test_address2), _))
.WillOnce(SaveArg<1>(&active_preset_index2));
TestConnect(test_address2);
/* Mock the csis group with two devices */
uint8_t synced_group = 13;
ON_CALL(mock_csis_client_module_, GetDeviceList(synced_group))
.WillByDefault(
Return(std::vector<RawAddress>({{test_address1, test_address2}})));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address1, ::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(synced_group));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address2, ::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(synced_group));
EXPECT_CALL(*callbacks, OnActivePresetSelectError(
_, ErrorCode::GROUP_OPERATION_NOT_SUPPORTED))
.Times(0);
/* Expect callback from the group but not from the devices */
uint8_t group_active_preset_index = 0;
EXPECT_CALL(*callbacks, OnActivePresetSelected(
std::variant<RawAddress, int>(test_address1), _))
.Times(0);
EXPECT_CALL(*callbacks, OnActivePresetSelected(
std::variant<RawAddress, int>(test_address2), _))
.Times(0);
EXPECT_CALL(*callbacks, OnActivePresetSelected(
std::variant<RawAddress, int>(synced_group), _))
.WillOnce(SaveArg<1>(&group_active_preset_index));
/* Expect Ctp write on on this device which forwards operation to the other */
EXPECT_CALL(gatt_queue, WriteCharacteristic(test_conn_id1,
HasDbBuilder::kPresetsCtpValHdl,
_, GATT_WRITE, _, _))
.Times(0);
EXPECT_CALL(gatt_queue, WriteCharacteristic(test_conn_id2,
HasDbBuilder::kPresetsCtpValHdl,
_, GATT_WRITE, _, _))
.Times(1);
HasClient::Get()->SelectActivePreset(synced_group, 55);
ASSERT_EQ(group_active_preset_index, 55);
}
TEST_F(HasClientTest, test_select_preset_invalid) {
const RawAddress test_address = GetTestAddress(1);
uint16_t test_conn_id = GetTestConnId(test_address);
std::set<HasPreset, HasPreset::ComparatorDesc> presets = {{
HasPreset(1, HasPreset::kPropertyAvailable, "Universal"),
HasPreset(2, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
"Preset2"),
}};
SetSampleDatabaseHasPresetsNtf(
test_address,
bluetooth::has::kFeatureBitHearingAidTypeBanded |
bluetooth::has::kFeatureBitWritablePresets |
bluetooth::has::kFeatureBitDynamicPresets,
presets);
uint8_t active_preset_index = 0;
std::vector<PresetInfo> preset_details;
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address));
EXPECT_CALL(*callbacks,
OnPresetInfo(std::variant<RawAddress, int>(test_address),
PresetInfoReason::ALL_PRESET_INFO, _))
.Times(1)
.WillOnce(SaveArg<2>(&preset_details));
EXPECT_CALL(*callbacks, OnActivePresetSelected(_, _))
.WillOnce(SaveArg<1>(&active_preset_index));
TestConnect(test_address);
ASSERT_TRUE(preset_details.size() > 1);
ASSERT_EQ(preset_details.front().preset_index, active_preset_index);
/* Inject preset deletion of index 2 */
auto deleted_index = preset_details[1].preset_index;
InjectPresetChanged(test_conn_id, test_address, false,
*presets.find(deleted_index), 0 /* prev_index */,
::le_audio::has::PresetCtpChangeId::PRESET_DELETED,
true /* is_last */);
EXPECT_CALL(*callbacks, OnActivePresetSelectError(
std::variant<RawAddress, int>(test_address),
ErrorCode::INVALID_PRESET_INDEX))
.Times(1);
/* Check if preset was actually deleted - try setting it as an active one */
HasClient::Get()->SelectActivePreset(test_address,
preset_details[1].preset_index);
}
TEST_F(HasClientTest, test_select_preset_next) {
const RawAddress test_address = GetTestAddress(1);
std::set<HasPreset, HasPreset::ComparatorDesc> presets = {{
HasPreset(1, HasPreset::kPropertyAvailable, "Universal"),
HasPreset(2, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
"Preset2"),
}};
SetSampleDatabaseHasPresetsNtf(
test_address,
bluetooth::has::kFeatureBitHearingAidTypeBanded |
bluetooth::has::kFeatureBitWritablePresets |
bluetooth::has::kFeatureBitDynamicPresets,
presets);
uint8_t active_preset_index = 0;
std::vector<PresetInfo> preset_details;
EXPECT_CALL(*callbacks,
OnPresetInfo(std::variant<RawAddress, int>(test_address),
PresetInfoReason::ALL_PRESET_INFO, _))
.Times(1)
.WillOnce(SaveArg<2>(&preset_details));
EXPECT_CALL(*callbacks, OnActivePresetSelected(_, _))
.WillOnce(SaveArg<1>(&active_preset_index));
TestConnect(test_address);
ASSERT_TRUE(preset_details.size() > 1);
ASSERT_EQ(1, active_preset_index);
/* Verify active preset change */
EXPECT_CALL(*callbacks, OnActivePresetSelected(
std::variant<RawAddress, int>(test_address), 2));
HasClient::Get()->NextActivePreset(test_address);
}
TEST_F(HasClientTest, test_select_group_preset_next_no_preset_sync_supported) {
/* None of these devices support preset syncing */
const RawAddress test_address1 = GetTestAddress(1);
SetSampleDatabaseHasPresetsNtf(
test_address1, bluetooth::has::kFeatureBitHearingAidTypeBinaural);
const RawAddress test_address2 = GetTestAddress(2);
SetSampleDatabaseHasPresetsNtf(
test_address2, bluetooth::has::kFeatureBitHearingAidTypeBinaural);
TestConnect(test_address1);
TestConnect(test_address2);
/* Mock the csis group with two devices */
uint8_t not_synced_group = 13;
ON_CALL(mock_csis_client_module_, GetDeviceList(not_synced_group))
.WillByDefault(
Return(std::vector<RawAddress>({{test_address1, test_address2}})));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address1, ::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(not_synced_group));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address2, ::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(not_synced_group));
uint8_t group_active_preset_index = 0;
EXPECT_CALL(*callbacks, OnActivePresetSelected(
std::variant<RawAddress, int>(test_address1), 55))
.Times(0);
EXPECT_CALL(*callbacks, OnActivePresetSelected(
std::variant<RawAddress, int>(test_address2), 55))
.Times(0);
EXPECT_CALL(*callbacks,
OnActivePresetSelected(
std::variant<RawAddress, int>(not_synced_group), _))
.WillOnce(SaveArg<1>(&group_active_preset_index));
/* No locally synced opcodes support so expect both devices getting writes */
EXPECT_CALL(gatt_queue, WriteCharacteristic(GetTestConnId(test_address1),
HasDbBuilder::kPresetsCtpValHdl,
_, GATT_WRITE, _, _))
.Times(1);
EXPECT_CALL(gatt_queue, WriteCharacteristic(GetTestConnId(test_address2),
HasDbBuilder::kPresetsCtpValHdl,
_, GATT_WRITE, _, _))
.Times(1);
HasClient::Get()->NextActivePreset(not_synced_group);
ASSERT_EQ(group_active_preset_index, 55);
}
TEST_F(HasClientTest, test_select_group_preset_next_preset_sync_supported) {
/* Only one of these devices support preset syncing */
const RawAddress test_address1 = GetTestAddress(1);
uint16_t test_conn_id1 = GetTestConnId(test_address1);
SetSampleDatabaseHasPresetsNtf(
test_address1, bluetooth::has::kFeatureBitHearingAidTypeBinaural);
const RawAddress test_address2 = GetTestAddress(2);
uint16_t test_conn_id2 = GetTestConnId(test_address2);
SetSampleDatabaseHasPresetsNtf(
test_address2,
bluetooth::has::kFeatureBitHearingAidTypeBinaural |
bluetooth::has::kFeatureBitPresetSynchronizationSupported);
uint8_t active_preset_index1 = 0;
uint8_t active_preset_index2 = 0;
EXPECT_CALL(*callbacks, OnActivePresetSelected(
std::variant<RawAddress, int>(test_address1), _))
.WillOnce(SaveArg<1>(&active_preset_index1));
TestConnect(test_address1);
EXPECT_CALL(*callbacks, OnActivePresetSelected(
std::variant<RawAddress, int>(test_address2), _))
.WillOnce(SaveArg<1>(&active_preset_index2));
TestConnect(test_address2);
/* Mock the csis group with two devices */
uint8_t synced_group = 13;
ON_CALL(mock_csis_client_module_, GetDeviceList(synced_group))
.WillByDefault(
Return(std::vector<RawAddress>({{test_address1, test_address2}})));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address1, ::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(synced_group));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address2, ::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(synced_group));
EXPECT_CALL(*callbacks, OnActivePresetSelectError(
_, ErrorCode::GROUP_OPERATION_NOT_SUPPORTED))
.Times(0);
/* Expect callback from the group but not from the devices */
uint8_t group_active_preset_index = 0;
EXPECT_CALL(*callbacks, OnActivePresetSelected(
std::variant<RawAddress, int>(test_address1), _))
.Times(0);
EXPECT_CALL(*callbacks, OnActivePresetSelected(
std::variant<RawAddress, int>(test_address2), _))
.Times(0);
EXPECT_CALL(*callbacks, OnActivePresetSelected(
std::variant<RawAddress, int>(synced_group), _))
.WillOnce(SaveArg<1>(&group_active_preset_index));
/* Expect Ctp write on on this device which forwards operation to the other */
EXPECT_CALL(gatt_queue, WriteCharacteristic(test_conn_id1,
HasDbBuilder::kPresetsCtpValHdl,
_, GATT_WRITE, _, _))
.Times(0);
EXPECT_CALL(gatt_queue, WriteCharacteristic(test_conn_id2,
HasDbBuilder::kPresetsCtpValHdl,
_, GATT_WRITE, _, _))
.Times(1);
HasClient::Get()->NextActivePreset(synced_group);
ASSERT_EQ(group_active_preset_index, 55);
}
TEST_F(HasClientTest, test_select_preset_prev) {
const RawAddress test_address = GetTestAddress(1);
std::set<HasPreset, HasPreset::ComparatorDesc> presets = {{
HasPreset(1, HasPreset::kPropertyAvailable, "Universal"),
HasPreset(2, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
"Preset2"),
}};
SetSampleDatabaseHasPresetsNtf(
test_address,
bluetooth::has::kFeatureBitHearingAidTypeBanded |
bluetooth::has::kFeatureBitWritablePresets |
bluetooth::has::kFeatureBitDynamicPresets,
presets);
uint8_t active_preset_index = 0;
std::vector<PresetInfo> preset_details;
EXPECT_CALL(*callbacks,
OnPresetInfo(std::variant<RawAddress, int>(test_address),
PresetInfoReason::ALL_PRESET_INFO, _))
.Times(1)
.WillOnce(SaveArg<2>(&preset_details));
ON_CALL(*callbacks, OnActivePresetSelected(_, _))
.WillByDefault(SaveArg<1>(&active_preset_index));
TestConnect(test_address);
HasClient::Get()->SelectActivePreset(test_address, 2);
ASSERT_TRUE(preset_details.size() > 1);
ASSERT_EQ(2, active_preset_index);
/* Verify active preset change */
EXPECT_CALL(*callbacks, OnActivePresetSelected(
std::variant<RawAddress, int>(test_address), 1));
HasClient::Get()->PreviousActivePreset(test_address);
}
TEST_F(HasClientTest, test_select_group_preset_prev_no_preset_sync_supported) {
/* None of these devices support preset syncing */
const RawAddress test_address1 = GetTestAddress(1);
SetSampleDatabaseHasPresetsNtf(
test_address1, bluetooth::has::kFeatureBitHearingAidTypeBinaural);
const RawAddress test_address2 = GetTestAddress(2);
SetSampleDatabaseHasPresetsNtf(
test_address2, bluetooth::has::kFeatureBitHearingAidTypeBinaural);
TestConnect(test_address1);
TestConnect(test_address2);
/* Mock the csis group with two devices */
uint8_t not_synced_group = 13;
ON_CALL(mock_csis_client_module_, GetDeviceList(not_synced_group))
.WillByDefault(
Return(std::vector<RawAddress>({{test_address1, test_address2}})));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address1, ::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(not_synced_group));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address2, ::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(not_synced_group));
uint8_t group_active_preset_index = 0;
EXPECT_CALL(*callbacks, OnActivePresetSelected(
std::variant<RawAddress, int>(test_address1), 55))
.Times(0);
EXPECT_CALL(*callbacks, OnActivePresetSelected(
std::variant<RawAddress, int>(test_address2), 55))
.Times(0);
EXPECT_CALL(*callbacks,
OnActivePresetSelected(
std::variant<RawAddress, int>(not_synced_group), _))
.WillOnce(SaveArg<1>(&group_active_preset_index));
/* No locally synced opcodes support so expect both devices getting writes */
EXPECT_CALL(gatt_queue, WriteCharacteristic(GetTestConnId(test_address1),
HasDbBuilder::kPresetsCtpValHdl,
_, GATT_WRITE, _, _))
.Times(1);
EXPECT_CALL(gatt_queue, WriteCharacteristic(GetTestConnId(test_address2),
HasDbBuilder::kPresetsCtpValHdl,
_, GATT_WRITE, _, _))
.Times(1);
HasClient::Get()->PreviousActivePreset(not_synced_group);
ASSERT_EQ(group_active_preset_index, 55);
}
TEST_F(HasClientTest, test_select_group_preset_prev_preset_sync_supported) {
/* Only one of these devices support preset syncing */
const RawAddress test_address1 = GetTestAddress(1);
uint16_t test_conn_id1 = GetTestConnId(test_address1);
SetSampleDatabaseHasPresetsNtf(
test_address1, bluetooth::has::kFeatureBitHearingAidTypeBinaural);
const RawAddress test_address2 = GetTestAddress(2);
uint16_t test_conn_id2 = GetTestConnId(test_address2);
SetSampleDatabaseHasPresetsNtf(
test_address2,
bluetooth::has::kFeatureBitHearingAidTypeBinaural |
bluetooth::has::kFeatureBitPresetSynchronizationSupported);
uint8_t active_preset_index1 = 0;
uint8_t active_preset_index2 = 0;
EXPECT_CALL(*callbacks, OnActivePresetSelected(
std::variant<RawAddress, int>(test_address1), _))
.WillOnce(SaveArg<1>(&active_preset_index1));
TestConnect(test_address1);
EXPECT_CALL(*callbacks, OnActivePresetSelected(
std::variant<RawAddress, int>(test_address2), _))
.WillOnce(SaveArg<1>(&active_preset_index2));
TestConnect(test_address2);
/* Mock the csis group with two devices */
uint8_t synced_group = 13;
ON_CALL(mock_csis_client_module_, GetDeviceList(synced_group))
.WillByDefault(
Return(std::vector<RawAddress>({{test_address1, test_address2}})));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address1, ::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(synced_group));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address2, ::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(synced_group));
EXPECT_CALL(*callbacks, OnActivePresetSelectError(
_, ErrorCode::GROUP_OPERATION_NOT_SUPPORTED))
.Times(0);
/* Expect callback from the group but not from the devices */
uint8_t group_active_preset_index = 0;
EXPECT_CALL(*callbacks, OnActivePresetSelected(
std::variant<RawAddress, int>(test_address1), _))
.Times(0);
EXPECT_CALL(*callbacks, OnActivePresetSelected(
std::variant<RawAddress, int>(test_address2), _))
.Times(0);
EXPECT_CALL(*callbacks, OnActivePresetSelected(
std::variant<RawAddress, int>(synced_group), _))
.WillOnce(SaveArg<1>(&group_active_preset_index));
/* Expect Ctp write on on this device which forwards operation to the other */
EXPECT_CALL(gatt_queue, WriteCharacteristic(test_conn_id1,
HasDbBuilder::kPresetsCtpValHdl,
_, GATT_WRITE, _, _))
.Times(0);
EXPECT_CALL(gatt_queue, WriteCharacteristic(test_conn_id2,
HasDbBuilder::kPresetsCtpValHdl,
_, GATT_WRITE, _, _))
.Times(1);
HasClient::Get()->PreviousActivePreset(synced_group);
ASSERT_EQ(group_active_preset_index, 55);
}
TEST_F(HasClientTest, test_select_has_no_presets) {
const RawAddress test_address = GetTestAddress(1);
SetSampleDatabaseHasNoPresetsFlagsOnly(test_address);
EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, _)).Times(1);
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address))
.Times(1);
TestConnect(test_address);
/* Test this not so useful service */
EXPECT_CALL(*callbacks,
OnActivePresetSelectError(_, ErrorCode::OPERATION_NOT_SUPPORTED))
.Times(3);
HasClient::Get()->SelectActivePreset(test_address, 0x01);
HasClient::Get()->NextActivePreset(test_address);
HasClient::Get()->PreviousActivePreset(test_address);
}
static int GetSocketBufferSize(int sockfd) {
int socket_buffer_size;
socklen_t optlen = sizeof(socket_buffer_size);
getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, (void*)&socket_buffer_size,
&optlen);
return socket_buffer_size;
}
bool SimpleJsonValidator(int fd, int* dumpsys_byte_cnt) {
std::ostringstream ss;
char buf{0};
bool within_double_quotes{false};
int left_bracket{0}, right_bracket{0};
int left_sq_bracket{0}, right_sq_bracket{0};
while (read(fd, &buf, 1) != -1) {
switch (buf) {
(*dumpsys_byte_cnt)++;
case '"':
within_double_quotes = !within_double_quotes;
break;
case '{':
if (!within_double_quotes) {
left_bracket++;
}
break;
case '}':
if (!within_double_quotes) {
right_bracket++;
}
break;
case '[':
if (!within_double_quotes) {
left_sq_bracket++;
}
break;
case ']':
if (!within_double_quotes) {
right_sq_bracket++;
}
break;
default:
break;
}
ss << buf;
}
LOG(ERROR) << __func__ << ": " << ss.str();
return (left_bracket == right_bracket) &&
(left_sq_bracket == right_sq_bracket);
}
TEST_F(HasClientTest, test_dumpsys) {
const RawAddress test_address = GetTestAddress(1);
std::set<HasPreset, HasPreset::ComparatorDesc> presets = {{
HasPreset(1, HasPreset::kPropertyAvailable, "Universal"),
HasPreset(2, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
"Preset2"),
}};
SetSampleDatabaseHasPresetsNtf(
test_address,
bluetooth::has::kFeatureBitHearingAidTypeBanded |
bluetooth::has::kFeatureBitWritablePresets |
bluetooth::has::kFeatureBitDynamicPresets,
presets);
uint8_t active_preset_index = 0;
std::vector<PresetInfo> preset_details;
EXPECT_CALL(*callbacks,
OnPresetInfo(std::variant<RawAddress, int>(test_address),
PresetInfoReason::ALL_PRESET_INFO, _))
.Times(1)
.WillOnce(SaveArg<2>(&preset_details));
ON_CALL(*callbacks, OnActivePresetSelected(_, _))
.WillByDefault(SaveArg<1>(&active_preset_index));
TestConnect(test_address);
int sv[2];
ASSERT_EQ(0, socketpair(AF_LOCAL, SOCK_STREAM | SOCK_NONBLOCK, 0, sv));
int socket_buffer_size = GetSocketBufferSize(sv[0]);
HasClient::Get()->DebugDump(sv[0]);
int dumpsys_byte_cnt = 0;
ASSERT_TRUE(dumpsys_byte_cnt < socket_buffer_size);
ASSERT_TRUE(SimpleJsonValidator(sv[1], &dumpsys_byte_cnt));
}
TEST_F(HasClientTest, test_connect_database_out_of_sync) {
osi_property_set_bool("persist.bluetooth.has.always_use_preset_cache", false);
const RawAddress test_address = GetTestAddress(1);
std::set<HasPreset, HasPreset::ComparatorDesc> has_presets = {{
HasPreset(1, HasPreset::kPropertyAvailable, "Universal"),
HasPreset(2, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
"Preset2"),
}};
SetSampleDatabaseHasPresetsNtf(
test_address,
bluetooth::has::kFeatureBitHearingAidTypeBanded |
bluetooth::has::kFeatureBitWritablePresets |
bluetooth::has::kFeatureBitDynamicPresets,
has_presets);
EXPECT_CALL(*callbacks, OnDeviceAvailable(
test_address,
bluetooth::has::kFeatureBitHearingAidTypeBanded |
bluetooth::has::kFeatureBitWritablePresets |
bluetooth::has::kFeatureBitDynamicPresets));
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address));
TestConnect(test_address);
ON_CALL(gatt_queue, WriteCharacteristic(_, _, _, _, _, _))
.WillByDefault(
Invoke([this](uint16_t conn_id, uint16_t handle,
std::vector<uint8_t> value, tGATT_WRITE_TYPE write_type,
GATT_WRITE_OP_CB cb, void* cb_data) {
auto* svc = gatt::FindService(services_map[conn_id], handle);
if (svc == nullptr) return;
tGATT_STATUS status = GATT_DATABASE_OUT_OF_SYNC;
if (cb)
cb(conn_id, status, handle, value.size(), value.data(), cb_data);
}));
ON_CALL(gatt_interface, ServiceSearchRequest(_, _)).WillByDefault(Return());
EXPECT_CALL(gatt_interface, ServiceSearchRequest(_, _));
HasClient::Get()->GetPresetInfo(test_address, 1);
}
class HasTypesTest : public ::testing::Test {
protected:
void SetUp(void) override { reset_mock_function_count_map(); }
void TearDown(void) override {}
}; // namespace
TEST_F(HasTypesTest, test_has_preset_serialize) {
HasPreset preset(0x01,
HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
"My Writable Preset01");
auto sp_sz = preset.SerializedSize();
std::vector<uint8_t> serialized(sp_sz);
ASSERT_EQ(1 + // preset index
1 + // properties
1 + // name length
preset.GetName().length(),
sp_sz);
/* Serialize should move the received buffer pointer by the size of data
*/
ASSERT_EQ(preset.Serialize(serialized.data(), serialized.size()),
serialized.data() + serialized.size());
/* Deserialize */
HasPreset clone;
ASSERT_EQ(HasPreset::Deserialize(serialized.data(), serialized.size(), clone),
serialized.data() + serialized.size());
/* Verify */
ASSERT_EQ(preset.GetIndex(), clone.GetIndex());
ASSERT_EQ(preset.GetProperties(), clone.GetProperties());
ASSERT_EQ(preset.GetName(), clone.GetName());
}
TEST_F(HasTypesTest, test_has_preset_serialize_output_buffer_to_small) {
HasPreset preset(0x01,
HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
"My Writable Preset01");
/* On failure, the offset should still point on .data() */
std::vector<uint8_t> serialized(preset.SerializedSize() - 1);
ASSERT_EQ(preset.Serialize(serialized.data(), serialized.size()),
serialized.data());
ASSERT_EQ(preset.Serialize(serialized.data(), 0), serialized.data());
ASSERT_EQ(preset.Serialize(serialized.data(), 1), serialized.data());
ASSERT_EQ(preset.Serialize(serialized.data(), 10), serialized.data());
}
TEST_F(HasTypesTest, test_has_preset_serialize_name_to_long) {
HasPreset preset(0x01,
HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
"This name is more than 40 characters long");
/* On failure, the offset should still point on .data() */
std::vector<uint8_t> serialized(preset.SerializedSize());
EXPECT_EQ(preset.Serialize(serialized.data(), serialized.size()),
serialized.data());
}
TEST_F(HasTypesTest, test_has_preset_deserialize_input_buffer_to_small) {
HasPreset preset(0x01,
HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
"My Writable Preset01");
std::vector<uint8_t> serialized(preset.SerializedSize());
/* Serialize should move the received buffer pointer by the size of data
*/
ASSERT_EQ(preset.Serialize(serialized.data(), serialized.size()),
serialized.data() + serialized.size());
/* Deserialize */
HasPreset clone;
ASSERT_EQ(HasPreset::Deserialize(serialized.data(), 0, clone),
serialized.data());
ASSERT_EQ(HasPreset::Deserialize(serialized.data(), 1, clone),
serialized.data());
ASSERT_EQ(HasPreset::Deserialize(serialized.data(), 11, clone),
serialized.data());
ASSERT_EQ(
HasPreset::Deserialize(serialized.data(), serialized.size() - 1, clone),
serialized.data());
}
TEST_F(HasTypesTest, test_has_presets_serialize) {
HasPreset preset(0x01,
HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
"My Writable Preset01");
HasPreset preset2(0x02, 0, "Nonwritable Unavailable Preset");
HasDevice has_device(GetTestAddress(1));
has_device.has_presets.insert(preset);
has_device.has_presets.insert(preset2);
auto out_buf_sz = has_device.SerializedPresetsSize();
ASSERT_EQ(out_buf_sz, preset.SerializedSize() + preset2.SerializedSize() + 2);
/* Serialize should append to the vector */
std::vector<uint8_t> serialized;
ASSERT_TRUE(has_device.SerializePresets(serialized));
ASSERT_EQ(out_buf_sz, serialized.size());
/* Deserialize */
HasDevice clone(GetTestAddress(1));
ASSERT_TRUE(HasDevice::DeserializePresets(serialized.data(),
serialized.size(), clone));
/* Verify */
ASSERT_EQ(clone.has_presets.size(), has_device.has_presets.size());
ASSERT_NE(0u, clone.has_presets.count(0x01));
ASSERT_NE(0u, clone.has_presets.count(0x02));
ASSERT_EQ(clone.has_presets.find(0x01)->GetIndex(),
has_device.has_presets.find(0x01)->GetIndex());
ASSERT_EQ(clone.has_presets.find(0x01)->GetProperties(),
has_device.has_presets.find(0x01)->GetProperties());
ASSERT_EQ(clone.has_presets.find(0x01)->GetName(),
has_device.has_presets.find(0x01)->GetName());
ASSERT_EQ(clone.has_presets.find(0x02)->GetIndex(),
has_device.has_presets.find(0x02)->GetIndex());
ASSERT_EQ(clone.has_presets.find(0x02)->GetProperties(),
has_device.has_presets.find(0x02)->GetProperties());
ASSERT_EQ(clone.has_presets.find(0x02)->GetName(),
has_device.has_presets.find(0x02)->GetName());
}
TEST_F(HasTypesTest, test_group_op_coordinator_init) {
HasCtpGroupOpCoordinator::Initialize([](void*) {
/* Do nothing */
});
ASSERT_EQ(0u, HasCtpGroupOpCoordinator::ref_cnt);
auto address1 = GetTestAddress(1);
auto address2 = GetTestAddress(2);
HasCtpGroupOpCoordinator wrapper(
{address1, address2},
HasCtpOp(0x01, ::le_audio::has::PresetCtpOpcode::READ_PRESETS, 6));
ASSERT_EQ(2u, wrapper.ref_cnt);
HasCtpGroupOpCoordinator::Cleanup();
ASSERT_EQ(0u, wrapper.ref_cnt);
ASSERT_EQ(1, get_func_call_count("alarm_free"));
ASSERT_EQ(1, get_func_call_count("alarm_new"));
}
TEST_F(HasTypesTest, test_group_op_coordinator_copy) {
HasCtpGroupOpCoordinator::Initialize([](void*) {
/* Do nothing */
});
ASSERT_EQ(0u, HasCtpGroupOpCoordinator::ref_cnt);
auto address1 = GetTestAddress(1);
auto address2 = GetTestAddress(2);
HasCtpGroupOpCoordinator wrapper(
{address1, address2},
HasCtpOp(0x01, ::le_audio::has::PresetCtpOpcode::READ_PRESETS, 6));
HasCtpGroupOpCoordinator wrapper2(
{address1},
HasCtpOp(0x01, ::le_audio::has::PresetCtpOpcode::READ_PRESETS, 6));
ASSERT_EQ(3u, wrapper.ref_cnt);
HasCtpGroupOpCoordinator wrapper3 = wrapper2;
auto* wrapper4 =
new HasCtpGroupOpCoordinator(HasCtpGroupOpCoordinator(wrapper2));
ASSERT_EQ(5u, wrapper.ref_cnt);
delete wrapper4;
ASSERT_EQ(4u, wrapper.ref_cnt);
HasCtpGroupOpCoordinator::Cleanup();
ASSERT_EQ(0u, wrapper.ref_cnt);
ASSERT_EQ(1, get_func_call_count("alarm_free"));
ASSERT_EQ(1, get_func_call_count("alarm_new"));
}
TEST_F(HasTypesTest, test_group_op_coordinator_completion) {
HasCtpGroupOpCoordinator::Initialize([](void*) {
/* Do nothing */
LOG(INFO) << __func__ << " callback call";
});
ASSERT_EQ(0u, HasCtpGroupOpCoordinator::ref_cnt);
auto address1 = GetTestAddress(1);
auto address2 = GetTestAddress(2);
auto address3 = GetTestAddress(3);
HasCtpGroupOpCoordinator wrapper(
{address1, address3},
HasCtpOp(0x01, ::le_audio::has::PresetCtpOpcode::READ_PRESETS, 6));
HasCtpGroupOpCoordinator wrapper2(
{address2},
HasCtpOp(0x01, ::le_audio::has::PresetCtpOpcode::READ_PRESETS, 6));
ASSERT_EQ(3u, wrapper.ref_cnt);
ASSERT_FALSE(wrapper.IsFullyCompleted());
wrapper.SetCompleted(address1);
ASSERT_EQ(2u, wrapper.ref_cnt);
wrapper.SetCompleted(address3);
ASSERT_EQ(1u, wrapper.ref_cnt);
ASSERT_FALSE(wrapper.IsFullyCompleted());
ASSERT_EQ(0, get_func_call_count("alarm_free"));
/* Non existing address completion */
wrapper.SetCompleted(address2);
ASSERT_EQ(0, get_func_call_count("alarm_free"));
ASSERT_EQ(1u, wrapper.ref_cnt);
/* Last device address completion */
wrapper2.SetCompleted(address2);
ASSERT_TRUE(wrapper.IsFullyCompleted());
ASSERT_EQ(0u, wrapper.ref_cnt);
const int alarm_free_count = get_func_call_count("alarm_free");
ASSERT_EQ(1, alarm_free_count);
HasCtpGroupOpCoordinator::Cleanup();
ASSERT_EQ(alarm_free_count, get_func_call_count("alarm_free"));
ASSERT_EQ(1, get_func_call_count("alarm_new"));
}
} // namespace
} // namespace internal
} // namespace has
} // namespace bluetooth