blob: 4632da30f0966bfb4f6954530d90abb8fd516696 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "device/fido/fido_ble_device.h"
#include "base/optional.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_task_environment.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/test/bluetooth_test.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/bluetooth/test/mock_bluetooth_device.h"
#include "device/fido/fido_ble_uuids.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_parsing_utils.h"
#include "device/fido/mock_fido_ble_connection.h"
#include "device/fido/test_callback_receiver.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace device {
namespace {
using ::testing::_;
using ::testing::Invoke;
using ::testing::Test;
using TestDeviceCallbackReceiver =
test::ValueCallbackReceiver<base::Optional<std::vector<uint8_t>>>;
using NiceMockBluetoothAdapter = ::testing::NiceMock<MockBluetoothAdapter>;
using NiceMockBluetoothDevice = ::testing::NiceMock<MockBluetoothDevice>;
constexpr uint16_t kControlPointLength = 20;
constexpr uint8_t kTestData[] = {'T', 'E', 'S', 'T'};
std::vector<std::vector<uint8_t>> ToSerializedFragments(
FidoBleDeviceCommand command,
std::vector<uint8_t> payload,
size_t max_fragment_size) {
FidoBleFrame frame(command, std::move(payload));
auto fragments = frame.ToFragments(max_fragment_size);
const size_t num_fragments = 1 /* init_fragment */ + fragments.second.size();
std::vector<std::vector<uint8_t>> serialized_fragments(num_fragments);
fragments.first.Serialize(&serialized_fragments[0]);
for (size_t i = 1; i < num_fragments; ++i) {
fragments.second.front().Serialize(&serialized_fragments[i]);
fragments.second.pop();
}
return serialized_fragments;
}
void SetAdvertisingDataFlags(BluetoothDevice* device,
base::Optional<uint8_t> flags) {
device->UpdateAdvertisementData(
0 /* rssi */, std::move(flags), BluetoothDevice::UUIDList(),
base::nullopt /* tx_power */, BluetoothDevice::ServiceDataMap(),
BluetoothDevice::ManufacturerDataMap());
}
void SetServiceData(BluetoothDevice* device,
BluetoothDevice::ServiceDataMap service_data) {
device->UpdateAdvertisementData(
0 /* rssi */, base::nullopt /* flags */, BluetoothDevice::UUIDList(),
base::nullopt /* tx_power */, std::move(service_data),
BluetoothDevice::ManufacturerDataMap());
}
} // namespace
class FidoBleDeviceTest : public Test {
public:
FidoBleDeviceTest() {
auto connection = std::make_unique<MockFidoBleConnection>(
BluetoothTestBase::kTestDeviceAddress1);
connection_ = connection.get();
device_ = std::make_unique<FidoBleDevice>(std::move(connection));
connection_->connection_status_callback() =
device_->GetConnectionStatusCallbackForTesting();
connection_->read_callback() = device_->GetReadCallbackForTesting();
}
FidoBleDevice* device() { return device_.get(); }
MockFidoBleConnection* connection() { return connection_; }
void ConnectWithLength(uint16_t length) {
EXPECT_CALL(*connection(), Connect()).WillOnce(Invoke([this] {
connection()->connection_status_callback().Run(true);
}));
EXPECT_CALL(*connection(), ReadControlPointLengthPtr(_))
.WillOnce(Invoke([length](auto* cb) { std::move(*cb).Run(length); }));
device()->Connect();
}
protected:
base::test::ScopedTaskEnvironment scoped_task_environment_{
base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME};
private:
MockFidoBleConnection* connection_;
std::unique_ptr<FidoBleDevice> device_;
};
TEST_F(FidoBleDeviceTest, ConnectionFailureTest) {
EXPECT_CALL(*connection(), Connect()).WillOnce(Invoke([this] {
connection()->connection_status_callback().Run(false);
}));
device()->Connect();
}
TEST_F(FidoBleDeviceTest, SendPingTest_Failure_WriteFailed) {
ConnectWithLength(kControlPointLength);
EXPECT_CALL(*connection(), WriteControlPointPtr(_, _))
.WillOnce(Invoke([this](const auto& data, auto* cb) {
scoped_task_environment_.GetMainThreadTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(std::move(*cb), false));
}));
TestDeviceCallbackReceiver callback_receiver;
auto payload = fido_parsing_utils::Materialize(kTestData);
device()->SendPing(std::move(payload), callback_receiver.callback());
callback_receiver.WaitForCallback();
EXPECT_FALSE(callback_receiver.value());
}
TEST_F(FidoBleDeviceTest, SendPingTest_Failure_NoResponse) {
ConnectWithLength(kControlPointLength);
EXPECT_CALL(*connection(), WriteControlPointPtr(_, _))
.WillOnce(Invoke([this](const auto& data, auto* cb) {
scoped_task_environment_.GetMainThreadTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(std::move(*cb), true));
}));
TestDeviceCallbackReceiver callback_receiver;
const auto payload = fido_parsing_utils::Materialize(kTestData);
device()->SendPing(payload, callback_receiver.callback());
callback_receiver.WaitForCallback();
EXPECT_FALSE(callback_receiver.value().has_value());
}
TEST_F(FidoBleDeviceTest, SendPingTest_Failure_SlowResponse) {
ConnectWithLength(kControlPointLength);
EXPECT_CALL(*connection(), WriteControlPointPtr(_, _))
.WillOnce(Invoke([this](const auto& data, auto* cb) {
scoped_task_environment_.GetMainThreadTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(std::move(*cb), true));
}));
TestDeviceCallbackReceiver callback_receiver;
auto payload = fido_parsing_utils::Materialize(kTestData);
device()->SendPing(payload, callback_receiver.callback());
callback_receiver.WaitForCallback();
EXPECT_FALSE(callback_receiver.value());
// Imitate a ping response from the device after the timeout has passed.
for (auto&& fragment :
ToSerializedFragments(FidoBleDeviceCommand::kPing, std::move(payload),
kControlPointLength)) {
connection()->read_callback().Run(std::move(fragment));
}
}
TEST_F(FidoBleDeviceTest, SendPingTest) {
ConnectWithLength(kControlPointLength);
EXPECT_CALL(*connection(), WriteControlPointPtr(_, _))
.WillOnce(Invoke([this](const auto& data, auto* cb) {
scoped_task_environment_.GetMainThreadTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(std::move(*cb), true));
scoped_task_environment_.GetMainThreadTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(connection()->read_callback(), data));
}));
TestDeviceCallbackReceiver callback_receiver;
const auto payload = fido_parsing_utils::Materialize(kTestData);
device()->SendPing(payload, callback_receiver.callback());
callback_receiver.WaitForCallback();
const auto& value = callback_receiver.value();
ASSERT_TRUE(value);
EXPECT_EQ(payload, *value);
}
TEST_F(FidoBleDeviceTest, SendCancelTest) {
// BLE cancel command, follow bytes 2 bytes of zero length payload.
constexpr uint8_t kBleCancelCommand[] = {0xBE, 0x00, 0x00};
ConnectWithLength(kControlPointLength);
EXPECT_CALL(*connection(),
WriteControlPointPtr(
fido_parsing_utils::Materialize(kBleCancelCommand), _));
device()->Cancel();
scoped_task_environment_.FastForwardUntilNoTasksRemain();
}
TEST_F(FidoBleDeviceTest, StaticGetIdTest) {
std::string address = BluetoothTestBase::kTestDeviceAddress1;
EXPECT_EQ("ble:" + address, FidoBleDevice::GetId(address));
}
TEST_F(FidoBleDeviceTest, TryWinkTest) {
test::TestCallbackReceiver<> closure_receiver;
device()->TryWink(closure_receiver.callback());
closure_receiver.WaitForCallback();
}
TEST_F(FidoBleDeviceTest, GetIdTest) {
EXPECT_EQ(std::string("ble:") + BluetoothTestBase::kTestDeviceAddress1,
device()->GetId());
}
TEST_F(FidoBleDeviceTest, IsInPairingMode) {
// By default, a device is not in pairing mode.
EXPECT_FALSE(device()->IsInPairingMode());
// Initiate default connection behavior, which will attempt to obtain an
// adapter.
auto mock_adapter = base::MakeRefCounted<NiceMockBluetoothAdapter>();
BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter);
EXPECT_CALL(*connection(), Connect()).WillOnce(Invoke([this] {
connection()->FidoBleConnection::Connect();
}));
device()->Connect();
// Add a mock fido device. This should also not be considered to be in pairing
// mode.
auto mock_bluetooth_device = std::make_unique<NiceMockBluetoothDevice>(
mock_adapter.get(), /* bluetooth_class */ 0u,
BluetoothTestBase::kTestDeviceNameU2f,
BluetoothTestBase::kTestDeviceAddress1, /* paired */ true,
/* connected */ false);
EXPECT_CALL(*mock_adapter, GetDevice(BluetoothTestBase::kTestDeviceAddress1))
.WillRepeatedly(Return(mock_bluetooth_device.get()));
EXPECT_FALSE(device()->IsInPairingMode());
// Provide advertisement flags, but set neither the Limited nor General LE
// Discoverable Mode bit.
SetAdvertisingDataFlags(mock_bluetooth_device.get(), 0);
EXPECT_FALSE(device()->IsInPairingMode());
// Set the limited bit, expect to be in pairing mode.
SetAdvertisingDataFlags(mock_bluetooth_device.get(),
1 << kLeLimitedDiscoverableModeBit);
EXPECT_TRUE(device()->IsInPairingMode());
// Set the general bit, expect to be in pairing mode.
SetAdvertisingDataFlags(mock_bluetooth_device.get(),
1 << kLeGeneralDiscoverableModeBit);
EXPECT_TRUE(device()->IsInPairingMode());
// Set both bits, expect to be NOT in pairing mode.
SetAdvertisingDataFlags(
mock_bluetooth_device.get(),
1 << kLeLimitedDiscoverableModeBit | 1 << kLeGeneralDiscoverableModeBit);
EXPECT_FALSE(device()->IsInPairingMode());
// Remove flags, should not result in pairing mode.
SetAdvertisingDataFlags(mock_bluetooth_device.get(), base::nullopt);
EXPECT_FALSE(device()->IsInPairingMode());
// Update the advertised service data to include the corresponding pairing
// mode flag. This should result in the device to be considered in pairing
// mode.
SetServiceData(mock_bluetooth_device.get(),
{{BluetoothUUID(kFidoServiceUUID),
{static_cast<int>(FidoServiceDataFlags::kPairingMode)}}});
EXPECT_TRUE(device()->IsInPairingMode());
// Clear out the service data again, device should not be considered to be in
// pairing mode anymore.
SetServiceData(mock_bluetooth_device.get(), {});
EXPECT_FALSE(device()->IsInPairingMode());
}
} // namespace device