blob: 54067a651ab51bdffab9f43cba78dc82682e53d8 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "device/bluetooth/floss/floss_admin_client.h"
#include <map>
#include <utility>
#include <vector>
#include "base/compiler_specific.h"
#include "base/containers/to_vector.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "components/device_event_log/device_event_log.h"
#include "dbus/bus.h"
#include "dbus/message.h"
#include "dbus/mock_bus.h"
#include "dbus/mock_exported_object.h"
#include "dbus/mock_object_proxy.h"
#include "dbus/object_path.h"
#include "device/bluetooth/floss/floss_dbus_client.h"
#include "device/bluetooth/floss/floss_manager_client.h"
#include "device/bluetooth/floss/test_helpers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace floss {
namespace {
using testing::_;
using testing::DoAll;
const uint32_t kTestCallbackId = 1000;
constexpr size_t kUUIDSize = 16;
const uint8_t kTestUuidInBytes[][kUUIDSize] = {
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
{15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0},
};
const std::vector<device::BluetoothUUID> kTestUuidStr = {
device::BluetoothUUID("00010203-0405-0607-0809-0a0b0c0d0e0f"),
device::BluetoothUUID("0f0e0d0c-0b0a-0908-0706-050403020100"),
};
void FakeExportMethod(
const std::string& interface_name,
const std::string& method_name,
const dbus::ExportedObject::MethodCallCallback& method_call_callback,
dbus::ExportedObject::OnExportedCallback on_exported_callback) {
std::move(on_exported_callback)
.Run(interface_name, method_name, /*success=*/true);
}
} // namespace
class FlossAdminClientTest : public testing::Test,
public FlossAdminClientObserver {
public:
FlossAdminClientTest() = default;
base::Version GetCurrVersion() {
return floss::version::GetMaximalSupportedVersion();
}
void SetUp() override {
::dbus::Bus::Options options;
options.bus_type = ::dbus::Bus::BusType::SYSTEM;
bus_ = base::MakeRefCounted<::dbus::MockBus>(std::move(options));
client_ = FlossAdminClient::Create();
client_->AddObserver(this);
admin_path_ = FlossDBusClient::GenerateAdminPath(adapter_index_);
callback_path_ = dbus::ObjectPath(FlossAdminClient::kExportedCallbacksPath);
object_proxy_ = base::MakeRefCounted<::dbus::MockObjectProxy>(
bus_.get(), kAdapterInterface, admin_path_);
EXPECT_CALL(*bus_.get(), GetObjectProxy(kAdapterInterface, admin_path_))
.WillRepeatedly(::testing::Return(object_proxy_.get()));
}
void TearDown() override {
// Clean up the client first so it gets rid of all its references to the
// various buses, object proxies, etc.
client_.reset();
}
// AdminClientObserver overrides
void DevicePolicyEffectChanged(
const FlossDeviceId& device_id,
const std::optional<PolicyEffect>& effect) override {
fake_device_policy_effect_info_ = {device_id, effect};
}
void ServiceAllowlistChanged(
const std::vector<device::BluetoothUUID>& allowlist) override {
fake_service_allowlist_info_ = allowlist;
}
bool IsClientRegistered() { return client_->IsClientRegistered(); }
void TestInit() {
scoped_refptr<::dbus::MockExportedObject> exported_callback =
base::MakeRefCounted<::dbus::MockExportedObject>(bus_.get(),
callback_path_);
// Expected exported callbacks
dbus::ExportedObject::MethodCallCallback
method_handler_on_device_policy_effect_changed;
EXPECT_CALL(*exported_callback.get(),
ExportMethod(admin::kCallbackInterface,
admin::kOnDevicePolicyEffectChanged, _, _))
.WillOnce(DoAll(testing::SaveArg<2>(
&method_handler_on_device_policy_effect_changed),
&FakeExportMethod));
dbus::ExportedObject::MethodCallCallback
method_handler_on_service_allowlist_changed;
EXPECT_CALL(*exported_callback.get(),
ExportMethod(admin::kCallbackInterface,
admin::kOnServiceAllowlistChanged, _, _))
.WillOnce(DoAll(
testing::SaveArg<2>(&method_handler_on_service_allowlist_changed),
&FakeExportMethod));
EXPECT_CALL(*bus_.get(), GetExportedObject(callback_path_))
.WillRepeatedly(testing::Return(exported_callback.get()));
// Expected call to RegisterAdminCallback when client is initialized
EXPECT_CALL(*object_proxy_.get(),
CallMethodWithErrorResponse(
HasMemberOf(admin::kRegisterCallback), _, _))
.WillOnce([this](::dbus::MethodCall* method_call, int timeout_ms,
::dbus::ObjectProxy::ResponseOrErrorCallback cb) {
dbus::MessageReader msg(method_call);
// D-Bus method call should have 1 parameter.
dbus::ObjectPath param1;
ASSERT_TRUE(msg.PopObjectPath(&param1));
EXPECT_EQ(param1, this->callback_path_);
EXPECT_FALSE(msg.HasMoreData());
// Create a fake response with uint32_t return value.
auto response = ::dbus::Response::CreateEmpty();
dbus::MessageWriter writer(response.get());
writer.AppendUint32(kTestCallbackId);
std::move(cb).Run(response.get(), /*err=*/nullptr);
});
ASSERT_FALSE(IsClientRegistered());
client_->Init(bus_.get(), kAdapterInterface, adapter_index_,
GetCurrVersion(), base::DoNothing());
// Test exported callbacks are correctly parsed
ASSERT_TRUE(!!method_handler_on_device_policy_effect_changed);
ASSERT_TRUE(!!method_handler_on_service_allowlist_changed);
ASSERT_TRUE(IsClientRegistered());
// Expected call to UnregisterAdminCallback when client is destroyed
EXPECT_CALL(*object_proxy_.get(),
CallMethodWithErrorResponse(
HasMemberOf(admin::kUnregisterCallback), _, _))
.WillOnce([](::dbus::MethodCall* method_call, int timeout_ms,
::dbus::ObjectProxy::ResponseOrErrorCallback cb) {
dbus::MessageReader msg(method_call);
// D-Bus method call should have 1 parameter.
uint32_t param1;
ASSERT_TRUE(FlossDBusClient::ReadAllDBusParams(&msg, &param1));
EXPECT_EQ(kTestCallbackId, param1);
EXPECT_FALSE(msg.HasMoreData());
});
}
void TestSetServiceAllowlist() {
// Expected call to SetAllowedServices
EXPECT_CALL(*object_proxy_.get(),
CallMethodWithErrorResponse(
HasMemberOf(admin::kSetAllowedServices), _, _))
.WillOnce([](::dbus::MethodCall* method_call, int timeout_ms,
::dbus::ObjectProxy::ResponseOrErrorCallback cb) {
dbus::MessageReader reader(method_call);
dbus::MessageReader array_reader(nullptr);
base::span<const uint8_t> buf;
EXPECT_TRUE(reader.PopArray(&array_reader));
for (const auto& uuid_in_bytes : kTestUuidInBytes) {
EXPECT_TRUE(array_reader.PopArrayOfBytes(&buf));
EXPECT_EQ(buf.size(), kUUIDSize);
EXPECT_EQ(buf, uuid_in_bytes);
}
EXPECT_FALSE(reader.HasMoreData());
EXPECT_FALSE(array_reader.HasMoreData());
// Create a fake response with uint32_t return value.
auto response = ::dbus::Response::CreateEmpty();
dbus::MessageWriter writer(response.get());
writer.AppendUint32(kTestCallbackId);
std::move(cb).Run(response.get(), /*err=*/nullptr);
});
client_->SetAllowedServices(
base::BindLambdaForTesting([](DBusResult<Void> ret) {}), kTestUuidStr);
}
void TestSetSimpleSecurePairingEnabled() {
// Expected call to SetSimpleSecurePairingEnabled
EXPECT_CALL(*object_proxy_.get(),
CallMethodWithErrorResponse(
HasMemberOf(admin::kSetSimpleSecurePairingEnabled), _, _))
.WillOnce([](::dbus::MethodCall* method_call, int timeout_ms,
::dbus::ObjectProxy::ResponseOrErrorCallback cb) {
dbus::MessageReader reader(method_call);
bool enable;
EXPECT_TRUE(reader.PopBool(&enable));
EXPECT_TRUE(enable);
// Create a fake response with uint32_t return value.
auto response = ::dbus::Response::CreateEmpty();
dbus::MessageWriter writer(response.get());
writer.AppendUint32(kTestCallbackId);
std::move(cb).Run(response.get(), /*err=*/nullptr);
});
client_->SetSimpleSecurePairingEnabled(
base::BindLambdaForTesting([](DBusResult<Void> ret) {}), true);
}
int adapter_index_ = 5;
dbus::ObjectPath admin_path_;
dbus::ObjectPath callback_path_;
scoped_refptr<::dbus::MockBus> bus_;
scoped_refptr<::dbus::MockObjectProxy> object_proxy_;
std::unique_ptr<FlossAdminClient> client_;
// For observer test inspections.
std::optional<std::tuple<FlossDeviceId, std::optional<PolicyEffect>>>
fake_device_policy_effect_info_;
std::vector<device::BluetoothUUID> fake_service_allowlist_info_;
base::test::TaskEnvironment task_environment_;
base::WeakPtrFactory<FlossAdminClientTest> weak_ptr_factory_{this};
};
TEST_F(FlossAdminClientTest, TestInitExportRegisterAdmin) {
TestInit();
}
TEST_F(FlossAdminClientTest, TestSetServiceAllowlistBeforeInit) {
TestSetServiceAllowlist();
TestInit();
}
TEST_F(FlossAdminClientTest, TestSetServiceAllowlistAfterInit) {
TestInit();
TestSetServiceAllowlist();
}
TEST_F(FlossAdminClientTest, TestSetSimpleSecurePairingEnabledAfterInit) {
TestInit();
TestSetSimpleSecurePairingEnabled();
}
TEST_F(FlossAdminClientTest, TestSetMultiplePolicyAfterInit) {
TestInit();
TestSetServiceAllowlist();
TestSetSimpleSecurePairingEnabled();
}
TEST_F(FlossAdminClientTest, TestSetMultiplePolicyBeforeInit) {
TestSetServiceAllowlist();
TestSetSimpleSecurePairingEnabled();
TestInit();
}
} // namespace floss