/* * 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 #include #include #include #include "bta_gatt_api_mock.h" #include "bta_gatt_queue_mock.h" #include "bta_vc_api.h" #include "btm_api_mock.h" #include "gatt/database_builder.h" #include "hardware/bt_gatt_types.h" #include "types.h" void btif_storage_add_volume_control(const RawAddress& addr, bool auto_conn) {} namespace bluetooth { namespace vc { namespace internal { namespace { using base::Bind; using base::Unretained; using bluetooth::vc::ConnectionState; using bluetooth::vc::VolumeControlCallbacks; using testing::_; using testing::DoAll; using testing::DoDefault; using testing::Invoke; using testing::Mock; using testing::NotNull; using testing::Return; using testing::SaveArg; using testing::SetArgPointee; using testing::WithArg; RawAddress GetTestAddress(int index) { CHECK_LT(index, UINT8_MAX); RawAddress result = { {0xC0, 0xDE, 0xC0, 0xDE, 0x00, static_cast(index)}}; return result; } class MockVolumeControlCallbacks : public VolumeControlCallbacks { public: MockVolumeControlCallbacks() = default; ~MockVolumeControlCallbacks() override = default; MOCK_METHOD((void), OnConnectionState, (ConnectionState state, const RawAddress& address), (override)); MOCK_METHOD((void), OnVolumeStateChanged, (const RawAddress& address, uint8_t volume, bool mute), (override)); MOCK_METHOD((void), OnGroupVolumeStateChanged, (int group_id, uint8_t volume, bool mute), (override)); private: DISALLOW_COPY_AND_ASSIGN(MockVolumeControlCallbacks); }; class VolumeControlTest : public ::testing::Test { private: void set_sample_database(uint16_t conn_id, bool vcs, bool vcs_broken, bool aics, bool aics_broken, bool vocs, bool vocs_broken) { gatt::DatabaseBuilder builder; builder.AddService(0x0001, 0x0003, Uuid::From16Bit(0x1800), true); builder.AddCharacteristic(0x0002, 0x0003, Uuid::From16Bit(0x2a00), GATT_CHAR_PROP_BIT_READ); /* 0x0004-0x000f RFU */ if (vcs) { /* VCS */ builder.AddService(0x0010, 0x0026, kVolumeControlUuid, true); if (aics) { /* TODO Place holder */ } if (vocs) { /* TODO Place holder */ } /* 0x0015-0x001f RFU */ builder.AddCharacteristic( 0x0020, 0x0021, kVolumeControlStateUuid, GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY); builder.AddDescriptor(0x0022, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); if (!vcs_broken) { builder.AddCharacteristic(0x0023, 0x0024, kVolumeControlPointUuid, GATT_CHAR_PROP_BIT_WRITE); } builder.AddCharacteristic(0x0025, 0x0026, kVolumeFlagsUuid, GATT_CHAR_PROP_BIT_READ); /* 0x0027-0x002f RFU */ if (aics) { /* TODO Place holder for AICS */ } if (vocs) { /* TODO Place holder for VOCS */ } } /* 0x008c-0x008f RFU */ /* GATTS */ builder.AddService(0x0090, 0x0093, Uuid::From16Bit(UUID_SERVCLASS_GATT_SERVER), true); builder.AddCharacteristic(0x0091, 0x0092, Uuid::From16Bit(GATT_UUID_GATT_SRV_CHGD), GATT_CHAR_PROP_BIT_NOTIFY); builder.AddDescriptor(0x0093, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); services_map[conn_id] = builder.Build().Services(); ON_CALL(gatt_queue, ReadCharacteristic(conn_id, _, _, _)) .WillByDefault(Invoke([&](uint16_t conn_id, uint16_t handle, GATT_READ_OP_CB cb, void* cb_data) -> void { std::vector value; switch (handle) { case 0x0003: /* device name */ value.resize(20); break; case 0x0021: /* volume state */ value.resize(3); break; case 0x0026: /* volume flags */ value.resize(1); break; default: ASSERT_TRUE(false); return; } cb(conn_id, GATT_SUCCESS, handle, value.size(), value.data(), cb_data); })); } protected: void SetUp(void) override { bluetooth::manager::SetMockBtmInterface(&btm_interface); gatt::SetMockBtaGattInterface(&gatt_interface); gatt::SetMockBtaGattQueue(&gatt_queue); callbacks.reset(new MockVolumeControlCallbacks()); // 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& 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& services = services_map[conn_id]; for (auto const& service : services) { if (service.handle <= handle && service.end_handle >= handle) { return &service; } } return nullptr; })); // default action for GetServices function call ON_CALL(gatt_interface, GetServices(_)) .WillByDefault(WithArg<0>( Invoke([&](uint16_t conn_id) -> std::list* { 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 value, tGATT_WRITE_TYPE write_type, GATT_WRITE_OP_CB cb, void* cb_data) -> void { if (cb) cb(conn_id, GATT_SUCCESS, handle, cb_data); })); } void TearDown(void) override { services_map.clear(); callbacks.reset(); gatt::SetMockBtaGattQueue(nullptr); gatt::SetMockBtaGattInterface(nullptr); bluetooth::manager::SetMockBtmInterface(nullptr); } void TestAppRegister(void) { BtaAppRegisterCallback app_register_callback; EXPECT_CALL(gatt_interface, AppRegister(_, _, _)) .WillOnce(DoAll(SaveArg<0>(&gatt_callback), SaveArg<1>(&app_register_callback))); VolumeControl::Initialize(callbacks.get()); ASSERT_TRUE(gatt_callback); ASSERT_TRUE(app_register_callback); app_register_callback.Run(gatt_if, GATT_SUCCESS); ASSERT_TRUE(VolumeControl::IsVolumeControlRunning()); } void TestAppUnregister(void) { EXPECT_CALL(gatt_interface, AppDeregister(gatt_if)); VolumeControl::CleanUp(); ASSERT_FALSE(VolumeControl::IsVolumeControlRunning()); gatt_callback = nullptr; } void TestConnect(const RawAddress& address) { // by default indicate link as encrypted ON_CALL(btm_interface, GetSecurityFlagsByTransport(address, NotNull(), _)) .WillByDefault( DoAll(SetArgPointee<1>(BTM_SEC_FLAG_ENCRYPTED), Return(true))); EXPECT_CALL(gatt_interface, Open(gatt_if, address, true, _)); VolumeControl::Get()->Connect(address); } void TestDisconnect(const RawAddress& address, uint16_t conn_id) { if (conn_id) { EXPECT_CALL(gatt_interface, Close(conn_id)); } else { EXPECT_CALL(gatt_interface, CancelOpen(gatt_if, address, _)); } VolumeControl::Get()->Disconnect(address); } void TestAddFromStorage(const RawAddress& address, bool auto_connect) { // by default indicate link as encrypted ON_CALL(btm_interface, GetSecurityFlagsByTransport(address, NotNull(), _)) .WillByDefault( DoAll(SetArgPointee<1>(BTM_SEC_FLAG_ENCRYPTED), Return(true))); if (auto_connect) { EXPECT_CALL(gatt_interface, Open(gatt_if, address, false, _)); } else { EXPECT_CALL(gatt_interface, Open(gatt_if, address, _, _)).Times(0); } VolumeControl::Get()->AddFromStorage(address, auto_connect); } void TestSubscribeNotifications(const RawAddress& address, uint16_t conn_id, std::map& handle_pairs) { SetSampleDatabase(conn_id); TestAppRegister(); TestConnect(address); GetConnectedEvent(address, conn_id); EXPECT_CALL(gatt_queue, WriteDescriptor(_, _, _, _, _, _)) .WillRepeatedly(DoDefault()); EXPECT_CALL(gatt_interface, RegisterForNotifications(_, _, _)) .WillRepeatedly(DoDefault()); std::vector notify_value({0x01, 0x00}); for (auto const& handles : handle_pairs) { EXPECT_CALL(gatt_queue, WriteDescriptor(conn_id, handles.second, notify_value, GATT_WRITE, _, _)) .WillOnce(DoDefault()); EXPECT_CALL(gatt_interface, RegisterForNotifications(gatt_if, address, handles.first)) .WillOnce(DoDefault()); } GetSearchCompleteEvent(conn_id); TestAppUnregister(); } void TestReadCharacteristic(const RawAddress& address, uint16_t conn_id, std::vector handles) { SetSampleDatabase(conn_id); TestAppRegister(); TestConnect(address); GetConnectedEvent(address, conn_id); EXPECT_CALL(gatt_queue, ReadCharacteristic(conn_id, _, _, _)) .WillRepeatedly(DoDefault()); for (auto const& handle : handles) { EXPECT_CALL(gatt_queue, ReadCharacteristic(conn_id, handle, _, _)) .WillOnce(DoDefault()); } GetSearchCompleteEvent(conn_id); TestAppUnregister(); } void GetConnectedEvent(const RawAddress& address, uint16_t conn_id) { tBTA_GATTC_OPEN event_data = { .status = GATT_SUCCESS, .conn_id = conn_id, .client_if = gatt_if, .remote_bda = address, .transport = GATT_TRANSPORT_LE, .mtu = 240, }; gatt_callback(BTA_GATTC_OPEN_EVT, (tBTA_GATTC*)&event_data); } void GetDisconnectedEvent(const RawAddress& address, uint16_t conn_id) { tBTA_GATTC_CLOSE event_data = { .status = GATT_SUCCESS, .conn_id = conn_id, .client_if = gatt_if, .remote_bda = address, .reason = GATT_CONN_TERMINATE_PEER_USER, }; gatt_callback(BTA_GATTC_CLOSE_EVT, (tBTA_GATTC*)&event_data); } void GetSearchCompleteEvent(uint16_t conn_id) { tBTA_GATTC_SEARCH_CMPL event_data = { .status = GATT_SUCCESS, .conn_id = conn_id, }; gatt_callback(BTA_GATTC_SEARCH_CMPL_EVT, (tBTA_GATTC*)&event_data); } void SetEncryptionResult(const RawAddress& address, bool success) { ON_CALL(btm_interface, GetSecurityFlagsByTransport(address, NotNull(), _)) .WillByDefault(DoAll(SetArgPointee<1>(0), Return(true))); EXPECT_CALL(btm_interface, SetEncryption(address, _, NotNull(), _, BTM_BLE_SEC_ENCRYPT)) .WillOnce(Invoke( [&success](const RawAddress& bd_addr, tBT_TRANSPORT transport, tBTM_SEC_CALLBACK* p_callback, void* p_ref_data, tBTM_BLE_SEC_ACT sec_act) -> tBTM_STATUS { p_callback(&bd_addr, transport, p_ref_data, success ? BTM_SUCCESS : BTM_FAILED_ON_SECURITY); return BTM_SUCCESS; })); } void SetSampleDatabaseVCS(uint16_t conn_id) { set_sample_database(conn_id, true, false, false, false, false, false); } void SetSampleDatabaseNoVCS(uint16_t conn_id) { set_sample_database(conn_id, false, false, true, false, true, false); } void SetSampleDatabaseVCSBroken(uint16_t conn_id) { set_sample_database(conn_id, true, true, true, false, true, false); } void SetSampleDatabase(uint16_t conn_id) { set_sample_database(conn_id, true, false, true, false, true, false); } std::unique_ptr callbacks; bluetooth::manager::MockBtmInterface btm_interface; gatt::MockBtaGattInterface gatt_interface; gatt::MockBtaGattQueue gatt_queue; tBTA_GATTC_CBACK* gatt_callback; const uint8_t gatt_if = 0xff; std::map> services_map; }; TEST_F(VolumeControlTest, test_get_uninitialized) { ASSERT_DEATH(VolumeControl::Get(), ""); } TEST_F(VolumeControlTest, test_initialize) { VolumeControl::Initialize(callbacks.get()); ASSERT_TRUE(VolumeControl::IsVolumeControlRunning()); VolumeControl::CleanUp(); } TEST_F(VolumeControlTest, test_initialize_twice) { VolumeControl::Initialize(callbacks.get()); VolumeControl* volume_control_p = VolumeControl::Get(); VolumeControl::Initialize(callbacks.get()); ASSERT_EQ(volume_control_p, VolumeControl::Get()); VolumeControl::CleanUp(); } TEST_F(VolumeControlTest, test_cleanup_initialized) { VolumeControl::Initialize(callbacks.get()); VolumeControl::CleanUp(); ASSERT_FALSE(VolumeControl::IsVolumeControlRunning()); } TEST_F(VolumeControlTest, test_cleanup_uninitialized) { VolumeControl::CleanUp(); ASSERT_FALSE(VolumeControl::IsVolumeControlRunning()); } TEST_F(VolumeControlTest, test_app_registration) { TestAppRegister(); TestAppUnregister(); } TEST_F(VolumeControlTest, test_connect) { TestAppRegister(); TestConnect(GetTestAddress(0)); TestAppUnregister(); } TEST_F(VolumeControlTest, test_add_from_storage) { TestAppRegister(); TestAddFromStorage(GetTestAddress(0), true); TestAddFromStorage(GetTestAddress(1), false); TestAppUnregister(); } TEST_F(VolumeControlTest, test_disconnect_non_connected) { const RawAddress test_address = GetTestAddress(0); TestAppRegister(); TestConnect(test_address); EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address)); TestDisconnect(test_address, 0); TestAppUnregister(); } TEST_F(VolumeControlTest, test_disconnect_connected) { const RawAddress test_address = GetTestAddress(0); TestAppRegister(); TestConnect(test_address); GetConnectedEvent(test_address, 1); EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address)); TestDisconnect(test_address, 1); TestAppUnregister(); } TEST_F(VolumeControlTest, test_disconnected) { const RawAddress test_address = GetTestAddress(0); TestAppRegister(); TestConnect(test_address); GetConnectedEvent(test_address, 1); EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address)); GetDisconnectedEvent(test_address, 1); TestAppUnregister(); } TEST_F(VolumeControlTest, test_disconnected_while_autoconnect) { const RawAddress test_address = GetTestAddress(0); TestAppRegister(); TestAddFromStorage(test_address, true); GetConnectedEvent(test_address, 1); // autoconnect - don't indicate disconnection EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address)) .Times(0); GetDisconnectedEvent(test_address, 1); TestAppUnregister(); } TEST_F(VolumeControlTest, test_reconnect_after_encryption_failed) { const RawAddress test_address = GetTestAddress(0); TestAppRegister(); TestAddFromStorage(test_address, true); SetEncryptionResult(test_address, false); // autoconnect - don't indicate disconnection EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address)) .Times(0); GetConnectedEvent(test_address, 1); Mock::VerifyAndClearExpectations(&btm_interface); SetEncryptionResult(test_address, true); GetConnectedEvent(test_address, 1); TestAppUnregister(); } TEST_F(VolumeControlTest, test_discovery_vcs_found) { const RawAddress test_address = GetTestAddress(0); SetSampleDatabaseVCS(1); TestAppRegister(); TestConnect(test_address); EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::CONNECTED, test_address)); GetConnectedEvent(test_address, 1); GetSearchCompleteEvent(1); Mock::VerifyAndClearExpectations(callbacks.get()); TestAppUnregister(); } TEST_F(VolumeControlTest, test_discovery_vcs_not_found) { const RawAddress test_address = GetTestAddress(0); SetSampleDatabaseNoVCS(1); TestAppRegister(); TestConnect(test_address); EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address)); GetConnectedEvent(test_address, 1); GetSearchCompleteEvent(1); Mock::VerifyAndClearExpectations(callbacks.get()); TestAppUnregister(); } TEST_F(VolumeControlTest, test_discovery_vcs_broken) { const RawAddress test_address = GetTestAddress(0); SetSampleDatabaseVCSBroken(1); TestAppRegister(); TestConnect(test_address); EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address)); GetConnectedEvent(test_address, 1); GetSearchCompleteEvent(1); Mock::VerifyAndClearExpectations(callbacks.get()); TestAppUnregister(); } TEST_F(VolumeControlTest, test_subscribe_vcs_volume_state) { std::map handles({{0x0021, 0x0022}}); TestSubscribeNotifications(GetTestAddress(0), 1, handles); } TEST_F(VolumeControlTest, test_read_vcs_volume_state) { const RawAddress test_address = GetTestAddress(0); EXPECT_CALL(*callbacks, OnVolumeStateChanged(test_address, _, _)); std::vector handles({0x0021}); TestReadCharacteristic(test_address, 1, handles); } TEST_F(VolumeControlTest, test_read_vcs_volume_flags) { std::vector handles({0x0026}); TestReadCharacteristic(GetTestAddress(0), 1, handles); } class VolumeControlCallbackTest : public VolumeControlTest { protected: const RawAddress test_address = GetTestAddress(0); uint16_t conn_id = 22; void SetUp(void) override { VolumeControlTest::SetUp(); SetSampleDatabase(conn_id); TestAppRegister(); TestConnect(test_address); GetConnectedEvent(test_address, conn_id); GetSearchCompleteEvent(conn_id); } void TearDown(void) override { TestAppUnregister(); VolumeControlTest::TearDown(); } void GetNotificationEvent(uint16_t handle, std::vector& value) { tBTA_GATTC_NOTIFY event_data = { .conn_id = conn_id, .bda = test_address, .handle = handle, .len = (uint8_t)value.size(), .is_notify = true, }; std::copy(value.begin(), value.end(), event_data.value); gatt_callback(BTA_GATTC_NOTIF_EVT, (tBTA_GATTC*)&event_data); } }; TEST_F(VolumeControlCallbackTest, test_volume_state_changed) { std::vector value({0x03, 0x01, 0x02}); EXPECT_CALL(*callbacks, OnVolumeStateChanged(test_address, 0x03, true)); GetNotificationEvent(0x0021, value); } TEST_F(VolumeControlCallbackTest, test_volume_state_changed_malformed) { EXPECT_CALL(*callbacks, OnVolumeStateChanged(test_address, _, _)).Times(0); std::vector too_short({0x03, 0x01}); GetNotificationEvent(0x0021, too_short); std::vector too_long({0x03, 0x01, 0x02, 0x03}); GetNotificationEvent(0x0021, too_long); } class VolumeControlValueSetTest : public VolumeControlTest { protected: const RawAddress test_address = GetTestAddress(0); uint16_t conn_id = 22; void SetUp(void) override { VolumeControlTest::SetUp(); SetSampleDatabase(conn_id); TestAppRegister(); TestConnect(test_address); GetConnectedEvent(test_address, conn_id); GetSearchCompleteEvent(conn_id); } void TearDown(void) override { TestAppUnregister(); VolumeControlTest::TearDown(); } }; TEST_F(VolumeControlValueSetTest, test_set_volume) { std::vector expected_data({0x04, 0x00, 0x10}); EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, expected_data, GATT_WRITE, _, _)); VolumeControl::Get()->SetVolume(test_address, 0x10); } } // namespace } // namespace internal } // namespace vc } // namespace bluetooth