#include "device/fido/hid/fido_hid_device.h"
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include "base/check_op.h"
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_device.h"
#include "device/fido/fido_parsing_utils.h"
#include "device/fido/fido_test_data.h"
#include "device/fido/fido_types.h"
#include "device/fido/hid/fake_hid_impl_for_testing.h"
#include "device/fido/hid/fido_hid_message.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/device/public/cpp/hid/hid_device_filter.h"
#include "services/device/public/mojom/hid.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace device {
using ::testing::_;
namespace {
constexpr uint8_t kU2fMockResponseMessage[] = {
0x83, 0x00, 0x0b, 0x4d, 0x4f, 0x43, 0x4b,
0x5f, 0x44, 0x41, 0x54, 0x41, 0x90, 0x00,
};
constexpr uint8_t kU2fWinkResponseMessage[] = {0x08, 0x00};
constexpr uint8_t kU2fMockResponseData[] = {0x4d, 0x4f, 0x43, 0x4b, 0x5f, 0x44,
0x41, 0x54, 0x41, 0x90, 0x00};
constexpr uint8_t kHidUnknownCommandError[] = {0xBF, 0x00, 0x01, 0x01};
constexpr uint8_t kMockKeepAliveResponseSuffix[] = {0xbb, 0x00, 0x01, 0x01};
constexpr uint8_t kInitResponsePrefix[] = {
0xff, 0xff, 0xff, 0xff, 0x86, 0x00, 0x11,
};
constexpr uint8_t kMockU2fRequest[] = {0x00, 0x04, 0x00, 0x00,
0x00, 0x00, 0x00};
constexpr uint8_t kMockCancelResponse[] = {
0x90,
0, 1,
0x2d,
};
constexpr uint8_t kMockU2fChannelBusyResponse[] = {
0xBF,
0, 1,
0x06,
};
constexpr std::array<uint8_t, 4> kChannelId = {0x01, 0x02, 0x03, 0x04};
std::vector<uint8_t> CreateMockInitResponse(
base::span<const uint8_t> nonce,
base::span<const uint8_t> channel_id,
base::span<const uint8_t> payload = base::span<const uint8_t>()) {
auto init_response = fido_parsing_utils::Materialize(kInitResponsePrefix);
fido_parsing_utils::Append(&init_response, nonce);
fido_parsing_utils::Append(&init_response, channel_id);
fido_parsing_utils::Append(&init_response, payload);
init_response.resize(64);
return init_response;
}
std::vector<uint8_t> GetKeepAliveHidMessage(
base::span<const uint8_t> channel_id) {
auto response = fido_parsing_utils::Materialize(channel_id);
fido_parsing_utils::Append(&response, kMockKeepAliveResponseSuffix);
response.resize(64);
return response;
}
std::vector<uint8_t> CreateMockResponseWithChannelId(
base::span<const uint8_t> channel_id,
base::span<const uint8_t> response_buffer) {
auto response = fido_parsing_utils::Materialize(channel_id);
fido_parsing_utils::Append(&response, response_buffer);
response.resize(64);
return response;
}
std::vector<uint8_t> GetMockDeviceRequest() {
return fido_parsing_utils::Materialize(kMockU2fRequest);
}
device::mojom::HidDeviceInfoPtr TestHidDevice() {
auto c_info = device::mojom::HidCollectionInfo::New();
c_info->usage = device::mojom::HidUsageAndPage::New(1, 0xf1d0);
auto hid_device = device::mojom::HidDeviceInfo::New();
hid_device->guid = "A";
hid_device->vendor_id = 0x1234;
hid_device->product_id = 0x5678;
hid_device->product_name = "Test Fido device";
hid_device->serial_number = "123FIDO";
hid_device->bus_type = device::mojom::HidBusType::kHIDBusTypeUSB;
hid_device->collections.push_back(std::move(c_info));
hid_device->max_input_report_size = 64;
hid_device->max_output_report_size = 64;
return hid_device;
}
std::unique_ptr<MockFidoHidConnection>
CreateHidConnectionWithHidInitExpectations(
const std::array<uint8_t, 4>& channel_id,
FakeFidoHidManager* fake_hid_manager,
::testing::Sequence sequence) {
auto hid_device = TestHidDevice();
mojo::PendingRemote<device::mojom::HidConnection> connection_client;
auto mock_connection = std::make_unique<MockFidoHidConnection>(
hid_device.Clone(), connection_client.InitWithNewPipeAndPassReceiver(),
channel_id);
mock_connection->ExpectWriteHidInit(sequence);
EXPECT_CALL(*mock_connection, ReadPtr(_))
.InSequence(sequence)
.WillOnce([mock_connection = mock_connection.get()](
device::mojom::HidConnection::ReadCallback* cb) {
std::move(*cb).Run(
true, 0,
CreateMockInitResponse(mock_connection->nonce(),
mock_connection->connection_channel_id()));
});
fake_hid_manager->AddDeviceAndSetConnection(std::move(hid_device),
std::move(connection_client));
return mock_connection;
}
void SetupReadExpectation(MockFidoHidConnection* mock_connection,
FidoHidDeviceCommand command_type,
base::span<const uint8_t> payload,
::testing::Sequence sequence) {
auto channel_id_vector = mock_connection->connection_channel_id();
uint32_t channel_id = channel_id_vector[0] << 24 |
channel_id_vector[1] << 16 | channel_id_vector[2] << 8 |
channel_id_vector[3];
auto message = FidoHidMessage::Create(channel_id, command_type,
kHidMaxPacketSize, payload);
while (message->NumPackets() != 0) {
EXPECT_CALL(*mock_connection, ReadPtr(_))
.InSequence(sequence)
.WillOnce([packet = message->PopNextPacket()](
device::mojom::HidConnection::ReadCallback* cb) {
std::move(*cb).Run(true, 0, std::move(packet));
});
}
}
class FidoDeviceEnumerateFuture
: public base::test::TestFuture<std::vector<mojom::HidDeviceInfoPtr>> {
public:
explicit FidoDeviceEnumerateFuture(device::mojom::HidManager* hid_manager)
: hid_manager_(hid_manager) {}
FidoDeviceEnumerateFuture(const FidoDeviceEnumerateFuture&) = delete;
FidoDeviceEnumerateFuture& operator=(const FidoDeviceEnumerateFuture&) =
delete;
~FidoDeviceEnumerateFuture() = default;
std::vector<std::unique_ptr<FidoHidDevice>> TakeReturnedDevicesFiltered() {
std::vector<std::unique_ptr<FidoHidDevice>> filtered_results;
auto results = Take();
for (auto& device_info : results) {
HidDeviceFilter filter;
filter.SetUsagePage(0xf1d0);
if (filter.Matches(*device_info)) {
filtered_results.push_back(std::make_unique<FidoHidDevice>(
std::move(device_info), hid_manager_));
}
}
return filtered_results;
}
std::unique_ptr<FidoHidDevice> TakeSingleDevice() {
std::vector<std::unique_ptr<FidoHidDevice>> filtered_results =
TakeReturnedDevicesFiltered();
CHECK_EQ(filtered_results.size(), 1u);
return std::move(filtered_results.front());
}
private:
raw_ptr<device::mojom::HidManager> hid_manager_;
};
using TestDeviceFuture =
base::test::TestFuture<std::optional<std::vector<uint8_t>>>;
}
class FidoHidDeviceTest : public ::testing::Test {
public:
void SetUp() override {
fake_hid_manager_ = std::make_unique<FakeFidoHidManager>();
fake_hid_manager_->AddReceiver(hid_manager_.BindNewPipeAndPassReceiver());
}
protected:
std::unique_ptr<FidoHidDevice> GetMockDevice() {
FidoDeviceEnumerateFuture receiver(hid_manager_.get());
auto hid_device = TestHidDevice();
fake_hid_manager_->AddDevice(std::move(hid_device));
hid_manager_->GetDevices(receiver.GetCallback());
EXPECT_TRUE(receiver.Wait());
std::vector<std::unique_ptr<FidoHidDevice>> u2f_devices =
receiver.TakeReturnedDevicesFiltered();
CHECK_EQ(static_cast<size_t>(1), u2f_devices.size());
return std::move(u2f_devices.front());
}
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
mojo::Remote<device::mojom::HidManager> hid_manager_;
std::unique_ptr<FakeFidoHidManager> fake_hid_manager_;
};
TEST_F(FidoHidDeviceTest, DisplayName) {
CHECK_EQ(GetMockDevice()->GetDisplayName(), "usb-1234:5678");
}
TEST_F(FidoHidDeviceTest, TestDeviceError) {
std::unique_ptr<FidoHidDevice> device = GetMockDevice();
FakeFidoHidConnection::mock_connection_error_ = true;
TestDeviceFuture receiver_0;
device->DeviceTransact(GetMockDeviceRequest(), receiver_0.GetCallback());
EXPECT_TRUE(receiver_0.Wait());
EXPECT_FALSE(receiver_0.Get());
EXPECT_EQ(FidoDevice::State::kDeviceError, device->state_);
TestDeviceFuture future_1;
device->pending_transactions_.emplace_back(FidoHidDeviceCommand::kMsg,
GetMockDeviceRequest(),
future_1.GetCallback(), 0);
TestDeviceFuture future_2;
device->pending_transactions_.emplace_back(FidoHidDeviceCommand::kMsg,
GetMockDeviceRequest(),
future_2.GetCallback(), 0);
TestDeviceFuture future_3;
device->DeviceTransact(GetMockDeviceRequest(), future_3.GetCallback());
FakeFidoHidConnection::mock_connection_error_ = false;
EXPECT_EQ(FidoDevice::State::kDeviceError, device->state_);
EXPECT_FALSE(future_1.Get());
EXPECT_FALSE(future_2.Get());
EXPECT_FALSE(future_3.Get());
}
TEST_F(FidoHidDeviceTest, TestRetryChannelAllocation) {
constexpr uint8_t kIncorrectNonce[] = {0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00};
auto hid_device = TestHidDevice();
mojo::PendingRemote<device::mojom::HidConnection> connection_client;
MockFidoHidConnection mock_connection(
hid_device.Clone(), connection_client.InitWithNewPipeAndPassReceiver(),
kChannelId);
mock_connection.ExpectWriteHidInit();
mock_connection.ExpectHidWriteWithCommand(FidoHidDeviceCommand::kMsg);
EXPECT_CALL(mock_connection, ReadPtr(_))
.WillOnce([kIncorrectNonce, &mock_connection](auto* cb) {
std::move(*cb).Run(
true, 0,
CreateMockInitResponse(kIncorrectNonce,
mock_connection.connection_channel_id()));
})
.WillOnce(
[&mock_connection](device::mojom::HidConnection::ReadCallback* cb) {
std::move(*cb).Run(true, 0,
CreateMockInitResponse(
mock_connection.nonce(),
mock_connection.connection_channel_id()));
})
.WillOnce(
[&mock_connection](device::mojom::HidConnection::ReadCallback* cb) {
std::move(*cb).Run(true, 0,
CreateMockResponseWithChannelId(
mock_connection.connection_channel_id(),
kU2fMockResponseMessage));
});
fake_hid_manager_->AddDeviceAndSetConnection(std::move(hid_device),
std::move(connection_client));
FidoDeviceEnumerateFuture receiver(hid_manager_.get());
hid_manager_->GetDevices(receiver.GetCallback());
EXPECT_TRUE(receiver.Wait());
std::vector<std::unique_ptr<FidoHidDevice>> u2f_devices =
receiver.TakeReturnedDevicesFiltered();
ASSERT_EQ(1u, u2f_devices.size());
auto& device = u2f_devices.front();
TestDeviceFuture future;
device->DeviceTransact(GetMockDeviceRequest(), future.GetCallback());
EXPECT_TRUE(future.Wait());
const auto& value = future.Get();
ASSERT_TRUE(value);
EXPECT_THAT(*value, testing::ElementsAreArray(kU2fMockResponseData));
}
TEST_F(FidoHidDeviceTest, TestKeepAliveMessage) {
::testing::Sequence sequence;
auto mock_connection = CreateHidConnectionWithHidInitExpectations(
kChannelId, fake_hid_manager_.get(), sequence);
mock_connection->ExpectHidWriteWithCommand(FidoHidDeviceCommand::kCbor);
EXPECT_CALL(*mock_connection, ReadPtr(_))
.InSequence(sequence)
.WillOnce([&](device::mojom::HidConnection::ReadCallback* cb) {
std::move(*cb).Run(
true, 0,
GetKeepAliveHidMessage(mock_connection->connection_channel_id()));
})
.WillOnce([&](device::mojom::HidConnection::ReadCallback* cb) {
auto almost_time_out = kDeviceTimeout - base::Microseconds(1);
task_environment_.FastForwardBy(almost_time_out);
std::move(*cb).Run(true, 0,
CreateMockResponseWithChannelId(
mock_connection->connection_channel_id(),
kU2fMockResponseMessage));
});
FidoDeviceEnumerateFuture enumerate_future(hid_manager_.get());
hid_manager_->GetDevices(enumerate_future.GetCallback());
EXPECT_TRUE(enumerate_future.Wait());
std::vector<std::unique_ptr<FidoHidDevice>> u2f_devices =
enumerate_future.TakeReturnedDevicesFiltered();
ASSERT_EQ(1u, u2f_devices.size());
auto& device = u2f_devices.front();
device->set_supported_protocol(ProtocolVersion::kCtap2);
TestDeviceFuture test_device_future;
device->DeviceTransact(GetMockDeviceRequest(),
test_device_future.GetCallback());
EXPECT_TRUE(test_device_future.Wait());
const auto& value = test_device_future.Get();
ASSERT_TRUE(value);
EXPECT_THAT(*value, testing::ElementsAreArray(kU2fMockResponseData));
}
std::array<uint8_t, 4> InvertChannelID(
const std::array<uint8_t, 4> channel_id) {
std::array<uint8_t, 4> ret;
UNSAFE_TODO(memcpy(ret.data(), channel_id.data(), ret.size()));
for (size_t i = 0; i < ret.size(); i++) {
ret[i] ^= 0xff;
}
return ret;
}
TEST_F(FidoHidDeviceTest, TestMessageOnOtherChannel) {
::testing::Sequence sequence;
auto mock_connection = CreateHidConnectionWithHidInitExpectations(
kChannelId, fake_hid_manager_.get(), sequence);
mock_connection->ExpectHidWriteWithCommand(FidoHidDeviceCommand::kMsg);
EXPECT_CALL(*mock_connection, ReadPtr(_))
.InSequence(sequence)
.WillOnce([&](device::mojom::HidConnection::ReadCallback* cb) {
std::move(*cb).Run(
true, 0,
CreateMockResponseWithChannelId(
InvertChannelID(mock_connection->connection_channel_id()),
kHidUnknownCommandError));
})
.WillOnce([&](device::mojom::HidConnection::ReadCallback* cb) {
std::move(*cb).Run(true, 0,
CreateMockResponseWithChannelId(
mock_connection->connection_channel_id(),
kU2fMockResponseMessage));
});
FidoDeviceEnumerateFuture enumerate_future(hid_manager_.get());
hid_manager_->GetDevices(enumerate_future.GetCallback());
EXPECT_TRUE(enumerate_future.Wait());
std::vector<std::unique_ptr<FidoHidDevice>> u2f_devices =
enumerate_future.TakeReturnedDevicesFiltered();
ASSERT_EQ(1u, u2f_devices.size());
auto& device = u2f_devices.front();
TestDeviceFuture test_device_future;
device->DeviceTransact(GetMockDeviceRequest(),
test_device_future.GetCallback());
EXPECT_TRUE(test_device_future.Wait());
const auto& value = test_device_future.Get();
ASSERT_TRUE(value);
EXPECT_THAT(*value, testing::ElementsAreArray(kU2fMockResponseData));
}
TEST_F(FidoHidDeviceTest, TestContinuedMessageOnOtherChannel) {
::testing::Sequence sequence;
auto mock_connection = CreateHidConnectionWithHidInitExpectations(
kChannelId, fake_hid_manager_.get(), sequence);
mock_connection->ExpectHidWriteWithCommand(FidoHidDeviceCommand::kMsg);
constexpr uint8_t kOtherChannelMsgPrefix[64] = {
0x83,
0x00,
0x40,
0,
};
constexpr uint8_t kOtherChannelMsgSuffix[64] = {
0x00,
};
EXPECT_CALL(*mock_connection, ReadPtr(_))
.InSequence(sequence)
.WillOnce([&](device::mojom::HidConnection::ReadCallback* cb) {
std::move(*cb).Run(
true, 0,
CreateMockResponseWithChannelId(
InvertChannelID(mock_connection->connection_channel_id()),
kOtherChannelMsgPrefix));
})
.WillOnce([&](device::mojom::HidConnection::ReadCallback* cb) {
std::move(*cb).Run(
true, 0,
CreateMockResponseWithChannelId(
InvertChannelID(mock_connection->connection_channel_id()),
kOtherChannelMsgSuffix));
})
.WillOnce([&](device::mojom::HidConnection::ReadCallback* cb) {
std::move(*cb).Run(true, 0,
CreateMockResponseWithChannelId(
mock_connection->connection_channel_id(),
kU2fMockResponseMessage));
});
FidoDeviceEnumerateFuture enumerate_future(hid_manager_.get());
hid_manager_->GetDevices(enumerate_future.GetCallback());
EXPECT_TRUE(enumerate_future.Wait());
std::vector<std::unique_ptr<FidoHidDevice>> u2f_devices =
enumerate_future.TakeReturnedDevicesFiltered();
ASSERT_EQ(1u, u2f_devices.size());
auto& device = u2f_devices.front();
TestDeviceFuture test_device_future;
device->DeviceTransact(GetMockDeviceRequest(),
test_device_future.GetCallback());
EXPECT_TRUE(test_device_future.Wait());
const auto& value = test_device_future.Get();
ASSERT_TRUE(value);
EXPECT_THAT(*value, testing::ElementsAreArray(kU2fMockResponseData));
}
TEST_F(FidoHidDeviceTest, TestDeviceTimeoutAfterKeepAliveMessage) {
::testing::Sequence sequence;
auto mock_connection = CreateHidConnectionWithHidInitExpectations(
kChannelId, fake_hid_manager_.get(), sequence);
mock_connection->ExpectHidWriteWithCommand(FidoHidDeviceCommand::kCbor);
EXPECT_CALL(*mock_connection, ReadPtr(_))
.InSequence(sequence)
.WillOnce([&](device::mojom::HidConnection::ReadCallback* cb) {
std::move(*cb).Run(
true, 0,
GetKeepAliveHidMessage(mock_connection->connection_channel_id()));
})
.WillOnce([&](device::mojom::HidConnection::ReadCallback* cb) {
task_environment_.FastForwardBy(kDeviceTimeout);
std::move(*cb).Run(true, 0,
CreateMockResponseWithChannelId(
mock_connection->connection_channel_id(),
kU2fMockResponseMessage));
});
FidoDeviceEnumerateFuture enumerate_future(hid_manager_.get());
hid_manager_->GetDevices(enumerate_future.GetCallback());
EXPECT_TRUE(enumerate_future.Wait());
std::vector<std::unique_ptr<FidoHidDevice>> u2f_devices =
enumerate_future.TakeReturnedDevicesFiltered();
ASSERT_EQ(1u, u2f_devices.size());
auto& device = u2f_devices.front();
device->set_supported_protocol(ProtocolVersion::kCtap2);
TestDeviceFuture test_device_future;
device->DeviceTransact(GetMockDeviceRequest(),
test_device_future.GetCallback());
EXPECT_TRUE(test_device_future.Wait());
const auto& value = test_device_future.Get();
EXPECT_FALSE(value);
EXPECT_EQ(FidoDevice::State::kDeviceError, device->state_for_testing());
}
TEST_F(FidoHidDeviceTest, TestCancel) {
::testing::Sequence sequence;
auto mock_connection = CreateHidConnectionWithHidInitExpectations(
kChannelId, fake_hid_manager_.get(), sequence);
mock_connection->ExpectHidWriteWithCommand(FidoHidDeviceCommand::kCbor);
mock_connection->ExpectHidWriteWithCommand(FidoHidDeviceCommand::kCancel);
EXPECT_CALL(*mock_connection, ReadPtr(_))
.InSequence(sequence)
.WillOnce([&](device::mojom::HidConnection::ReadCallback* cb) {
auto delay = base::Seconds(2);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(std::move(*cb), true, 0,
CreateMockResponseWithChannelId(
mock_connection->connection_channel_id(),
kU2fMockResponseMessage)),
delay);
});
FidoDeviceEnumerateFuture enumerate_future(hid_manager_.get());
hid_manager_->GetDevices(enumerate_future.GetCallback());
EXPECT_TRUE(enumerate_future.Wait());
std::vector<std::unique_ptr<FidoHidDevice>> u2f_devices =
enumerate_future.TakeReturnedDevicesFiltered();
ASSERT_EQ(1u, u2f_devices.size());
auto& device = u2f_devices.front();
device->set_supported_protocol(ProtocolVersion::kCtap2);
TestDeviceFuture test_device_future;
auto token = device->DeviceTransact(GetMockDeviceRequest(),
test_device_future.GetCallback());
auto delay_before_cancel = base::Seconds(1);
auto cancel_callback = base::BindOnce(
&FidoHidDevice::Cancel, device->weak_factory_.GetWeakPtr(), token);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, std::move(cancel_callback), delay_before_cancel);
task_environment_.FastForwardUntilNoTasksRemain();
}
TEST_F(FidoHidDeviceTest, TestCancelWhileWriting) {
::testing::Sequence sequence;
auto mock_connection = CreateHidConnectionWithHidInitExpectations(
kChannelId, fake_hid_manager_.get(), sequence);
FidoDevice::CancelToken token = FidoDevice::kInvalidCancelToken;
FidoDevice* device = nullptr;
EXPECT_CALL(*mock_connection, WritePtr(_, _, _))
.InSequence(sequence)
.WillOnce(
[&token, &device](auto&&, const std::vector<uint8_t>& buffer,
device::mojom::HidConnection::WriteCallback* cb) {
device->Cancel(token);
std::move(*cb).Run(true);
});
EXPECT_CALL(*mock_connection, WritePtr(_, _, _))
.InSequence(sequence)
.WillOnce([](auto&&, const std::vector<uint8_t>& buffer,
device::mojom::HidConnection::WriteCallback* cb) {
std::move(*cb).Run(true);
});
EXPECT_CALL(*mock_connection, WritePtr(_, _, _))
.InSequence(sequence)
.WillOnce([](auto&&, const std::vector<uint8_t>& buffer,
device::mojom::HidConnection::WriteCallback* cb) {
CHECK_LE(5u, buffer.size());
CHECK_EQ(static_cast<uint8_t>(FidoHidDeviceCommand::kCancel) | 0x80,
buffer[4]);
std::move(*cb).Run(true);
});
EXPECT_CALL(*mock_connection, ReadPtr(_))
.InSequence(sequence)
.WillOnce(
[&mock_connection](device::mojom::HidConnection::ReadCallback* cb) {
std::move(*cb).Run(true, 0,
CreateMockResponseWithChannelId(
mock_connection->connection_channel_id(),
kMockCancelResponse));
});
FidoDeviceEnumerateFuture enumerate_future(hid_manager_.get());
hid_manager_->GetDevices(enumerate_future.GetCallback());
EXPECT_TRUE(enumerate_future.Wait());
std::vector<std::unique_ptr<FidoHidDevice>> u2f_devices =
enumerate_future.TakeReturnedDevicesFiltered();
ASSERT_EQ(1u, u2f_devices.size());
device = u2f_devices.front().get();
device->set_supported_protocol(ProtocolVersion::kCtap2);
TestDeviceFuture test_device_future;
std::vector<uint8_t> dummy_request(100);
token = device->DeviceTransact(std::move(dummy_request),
test_device_future.GetCallback());
EXPECT_TRUE(test_device_future.Wait());
ASSERT_TRUE(test_device_future.Get());
ASSERT_EQ(1u, test_device_future.Get()->size());
ASSERT_EQ(0x2d ,
test_device_future.Get().value()[0]);
}
TEST_F(FidoHidDeviceTest, TestCancelAfterWriting) {
::testing::Sequence sequence;
auto mock_connection = CreateHidConnectionWithHidInitExpectations(
kChannelId, fake_hid_manager_.get(), sequence);
FidoDevice::CancelToken token = FidoDevice::kInvalidCancelToken;
FidoDevice* device = nullptr;
device::mojom::HidConnection::ReadCallback read_callback;
EXPECT_CALL(*mock_connection, WritePtr(_, _, _))
.InSequence(sequence)
.WillOnce([](auto&&, const std::vector<uint8_t>& buffer,
device::mojom::HidConnection::WriteCallback* cb) {
std::move(*cb).Run(true);
});
EXPECT_CALL(*mock_connection, ReadPtr(_))
.InSequence(sequence)
.WillOnce([&read_callback, &device,
&token](device::mojom::HidConnection::ReadCallback* cb) {
read_callback = std::move(*cb);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
[](FidoDevice* device, FidoDevice::CancelToken token) {
device->Cancel(token);
},
device, token));
});
EXPECT_CALL(*mock_connection, WritePtr(_, _, _))
.InSequence(sequence)
.WillOnce([&mock_connection, &read_callback](
auto&&, const std::vector<uint8_t>& buffer,
device::mojom::HidConnection::WriteCallback* cb) {
CHECK_LE(5u, buffer.size());
CHECK_EQ(static_cast<uint8_t>(FidoHidDeviceCommand::kCancel) | 0x80,
buffer[4]);
std::move(*cb).Run(true);
std::move(read_callback)
.Run(true, 0,
CreateMockResponseWithChannelId(
mock_connection->connection_channel_id(),
kMockCancelResponse));
});
FidoDeviceEnumerateFuture enumerate_future(hid_manager_.get());
hid_manager_->GetDevices(enumerate_future.GetCallback());
EXPECT_TRUE(enumerate_future.Wait());
std::vector<std::unique_ptr<FidoHidDevice>> u2f_devices =
enumerate_future.TakeReturnedDevicesFiltered();
ASSERT_EQ(1u, u2f_devices.size());
device = u2f_devices.front().get();
device->set_supported_protocol(ProtocolVersion::kCtap2);
TestDeviceFuture test_device_future;
std::vector<uint8_t> dummy_request(1);
token = device->DeviceTransact(std::move(dummy_request),
test_device_future.GetCallback());
EXPECT_TRUE(test_device_future.Wait());
ASSERT_TRUE(test_device_future.Get());
ASSERT_EQ(1u, test_device_future.Get()->size());
ASSERT_EQ(0x2d ,
test_device_future.Get().value()[0]);
}
TEST_F(FidoHidDeviceTest, TestCancelAfterReading) {
::testing::Sequence sequence;
auto mock_connection = CreateHidConnectionWithHidInitExpectations(
kChannelId, fake_hid_manager_.get(), sequence);
FidoDevice::CancelToken token = FidoDevice::kInvalidCancelToken;
FidoDevice* device = nullptr;
device::mojom::HidConnection::ReadCallback read_callback;
EXPECT_CALL(*mock_connection, WritePtr(_, _, _))
.InSequence(sequence)
.WillOnce([](auto&&, const std::vector<uint8_t>& buffer,
device::mojom::HidConnection::WriteCallback* cb) {
std::move(*cb).Run(true);
});
EXPECT_CALL(*mock_connection, ReadPtr(_))
.InSequence(sequence)
.WillOnce(
[&mock_connection](device::mojom::HidConnection::ReadCallback* cb) {
std::vector<uint8_t> frame = {0x90, 0, 64};
frame.resize(64, 0);
std::move(*cb).Run(true, 0,
CreateMockResponseWithChannelId(
mock_connection->connection_channel_id(),
std::move(frame)));
});
EXPECT_CALL(*mock_connection, ReadPtr(_))
.InSequence(sequence)
.WillOnce([&device, &token, &mock_connection](
device::mojom::HidConnection::ReadCallback* cb) {
device->Cancel(token);
std::vector<uint8_t> frame;
frame.resize(64, 0);
std::move(*cb).Run(
true, 0,
CreateMockResponseWithChannelId(
mock_connection->connection_channel_id(), std::move(frame)));
});
FidoDeviceEnumerateFuture receiver(hid_manager_.get());
hid_manager_->GetDevices(receiver.GetCallback());
EXPECT_TRUE(receiver.Wait());
std::vector<std::unique_ptr<FidoHidDevice>> u2f_devices =
receiver.TakeReturnedDevicesFiltered();
ASSERT_EQ(1u, u2f_devices.size());
device = u2f_devices.front().get();
device->set_supported_protocol(ProtocolVersion::kCtap2);
TestDeviceFuture test_device_future;
std::vector<uint8_t> dummy_request(1);
token = device->DeviceTransact(std::move(dummy_request),
test_device_future.GetCallback());
EXPECT_TRUE(test_device_future.Wait());
ASSERT_TRUE(test_device_future.Get());
ASSERT_EQ(64u, test_device_future.Get()->size());
}
TEST_F(FidoHidDeviceTest, TestGetInfoFailsOnDeviceError) {
constexpr uint8_t kHidUnknownTransportError[] = {0x7F, 0x00, 0x01, 0x7F};
::testing::Sequence sequence;
auto mock_connection = CreateHidConnectionWithHidInitExpectations(
kChannelId, fake_hid_manager_.get(), sequence);
mock_connection->ExpectHidWriteWithCommand(FidoHidDeviceCommand::kCbor);
EXPECT_CALL(*mock_connection, ReadPtr(_))
.InSequence(sequence)
.WillOnce([&](device::mojom::HidConnection::ReadCallback* cb) {
auto delay = base::Seconds(2);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(std::move(*cb), true, 0,
CreateMockResponseWithChannelId(
mock_connection->connection_channel_id(),
kHidUnknownTransportError)),
delay);
});
FidoDeviceEnumerateFuture enumerate_future(hid_manager_.get());
hid_manager_->GetDevices(enumerate_future.GetCallback());
EXPECT_TRUE(enumerate_future.Wait());
std::vector<std::unique_ptr<FidoHidDevice>> u2f_devices =
enumerate_future.TakeReturnedDevicesFiltered();
ASSERT_EQ(1u, u2f_devices.size());
auto& device = u2f_devices.front();
base::test::TestFuture<void> get_info_future;
device->DiscoverSupportedProtocolAndDeviceInfo(get_info_future.GetCallback());
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_FALSE(get_info_future.IsReady());
EXPECT_EQ(FidoDevice::State::kDeviceError, device->state_for_testing());
}
TEST_F(FidoHidDeviceTest, TestDeviceMessageError) {
::testing::Sequence sequence;
auto mock_connection = CreateHidConnectionWithHidInitExpectations(
kChannelId, fake_hid_manager_.get(), sequence);
mock_connection->ExpectHidWriteWithCommand(FidoHidDeviceCommand::kCbor);
EXPECT_CALL(*mock_connection, ReadPtr(_))
.InSequence(sequence)
.WillOnce([&](device::mojom::HidConnection::ReadCallback* cb) {
auto delay = base::Seconds(2);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(std::move(*cb), true, 0,
CreateMockResponseWithChannelId(
mock_connection->connection_channel_id(),
kHidUnknownCommandError)),
delay);
});
FidoDeviceEnumerateFuture enumerate_future(hid_manager_.get());
hid_manager_->GetDevices(enumerate_future.GetCallback());
EXPECT_TRUE(enumerate_future.Wait());
std::vector<std::unique_ptr<FidoHidDevice>> u2f_devices =
enumerate_future.TakeReturnedDevicesFiltered();
ASSERT_EQ(1u, u2f_devices.size());
auto& device = u2f_devices.front();
base::test::TestFuture<void> get_info_future;
device->DiscoverSupportedProtocolAndDeviceInfo(get_info_future.GetCallback());
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_TRUE(get_info_future.IsReady());
}
TEST_F(FidoHidDeviceTest, TestWinkNotSupported) {
constexpr uint8_t kWinkNotSupportedPayload[] = {0x00, 0x00, 0x00, 0x00, 0x00};
auto hid_device = TestHidDevice();
mojo::PendingRemote<device::mojom::HidConnection> connection_client;
MockFidoHidConnection mock_connection(
hid_device.Clone(), connection_client.InitWithNewPipeAndPassReceiver(),
kChannelId);
mock_connection.ExpectWriteHidInit();
mock_connection.ExpectHidWriteWithCommand(FidoHidDeviceCommand::kCbor);
EXPECT_CALL(mock_connection, ReadPtr(_))
.WillOnce([&](device::mojom::HidConnection::ReadCallback* cb) {
std::move(*cb).Run(
true, 0,
CreateMockInitResponse(mock_connection.nonce(),
mock_connection.connection_channel_id(),
kWinkNotSupportedPayload));
})
.WillOnce([&](device::mojom::HidConnection::ReadCallback* cb) {
std::move(*cb).Run(true, 0,
CreateMockResponseWithChannelId(
mock_connection.connection_channel_id(),
kHidUnknownCommandError));
});
fake_hid_manager_->AddDeviceAndSetConnection(std::move(hid_device),
std::move(connection_client));
FidoDeviceEnumerateFuture enumerate_future(hid_manager_.get());
hid_manager_->GetDevices(enumerate_future.GetCallback());
EXPECT_TRUE(enumerate_future.Wait());
std::vector<std::unique_ptr<FidoHidDevice>> u2f_devices =
enumerate_future.TakeReturnedDevicesFiltered();
ASSERT_EQ(1u, u2f_devices.size());
auto& device = u2f_devices.front();
base::test::TestFuture<void> future;
device->DiscoverSupportedProtocolAndDeviceInfo(future.GetCallback());
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_TRUE(future.IsReady());
future.Clear();
device->TryWink(future.GetCallback());
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_TRUE(future.IsReady());
}
TEST_F(FidoHidDeviceTest, TestCtap2DeviceShouldNotBlink) {
constexpr uint8_t kWinkSupportedPayload[] = {0x00, 0x00, 0x00, 0x00, 0x01};
auto hid_device = TestHidDevice();
mojo::PendingRemote<device::mojom::HidConnection> connection_client;
MockFidoHidConnection mock_connection(
hid_device.Clone(), connection_client.InitWithNewPipeAndPassReceiver(),
kChannelId);
mock_connection.ExpectWriteHidInit();
mock_connection.ExpectHidWriteWithCommand(FidoHidDeviceCommand::kCbor);
::testing::Sequence sequence;
EXPECT_CALL(mock_connection, ReadPtr(_))
.InSequence(sequence)
.WillOnce([&](device::mojom::HidConnection::ReadCallback* cb) {
std::move(*cb).Run(
true, 0,
CreateMockInitResponse(mock_connection.nonce(),
mock_connection.connection_channel_id(),
kWinkSupportedPayload));
});
SetupReadExpectation(&mock_connection, FidoHidDeviceCommand::kCbor,
test_data::kTestAuthenticatorGetInfoResponse, sequence);
fake_hid_manager_->AddDeviceAndSetConnection(std::move(hid_device),
std::move(connection_client));
FidoDeviceEnumerateFuture enumerate_future(hid_manager_.get());
hid_manager_->GetDevices(enumerate_future.GetCallback());
EXPECT_TRUE(enumerate_future.Wait());
std::vector<std::unique_ptr<FidoHidDevice>> u2f_devices =
enumerate_future.TakeReturnedDevicesFiltered();
ASSERT_EQ(1u, u2f_devices.size());
auto& device = u2f_devices.front();
base::test::TestFuture<void> future;
device->DiscoverSupportedProtocolAndDeviceInfo(future.GetCallback());
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_TRUE(future.IsReady());
future.Clear();
device->TryWink(future.GetCallback());
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_TRUE(future.IsReady());
}
TEST_F(FidoHidDeviceTest, TestSuccessfulWink) {
constexpr uint8_t kWinkSupportedPayload[] = {0x00, 0x00, 0x00, 0x00, 0x01};
auto hid_device = TestHidDevice();
mojo::PendingRemote<device::mojom::HidConnection> connection_client;
MockFidoHidConnection mock_connection(
hid_device.Clone(), connection_client.InitWithNewPipeAndPassReceiver(),
kChannelId);
mock_connection.ExpectWriteHidInit();
mock_connection.ExpectHidWriteWithCommand(FidoHidDeviceCommand::kCbor);
mock_connection.ExpectHidWriteWithCommand(FidoHidDeviceCommand::kWink);
EXPECT_CALL(mock_connection, ReadPtr(_))
.WillOnce([&](device::mojom::HidConnection::ReadCallback* cb) {
std::move(*cb).Run(
true, 0,
CreateMockInitResponse(mock_connection.nonce(),
mock_connection.connection_channel_id(),
kWinkSupportedPayload));
})
.WillOnce([&](device::mojom::HidConnection::ReadCallback* cb) {
std::move(*cb).Run(true, 0,
CreateMockResponseWithChannelId(
mock_connection.connection_channel_id(),
kHidUnknownCommandError));
})
.WillOnce([&](device::mojom::HidConnection::ReadCallback* cb) {
std::move(*cb).Run(true, 0,
CreateMockResponseWithChannelId(
mock_connection.connection_channel_id(),
kU2fWinkResponseMessage));
});
fake_hid_manager_->AddDeviceAndSetConnection(std::move(hid_device),
std::move(connection_client));
FidoDeviceEnumerateFuture enumerate_future(hid_manager_.get());
hid_manager_->GetDevices(enumerate_future.GetCallback());
EXPECT_TRUE(enumerate_future.Wait());
std::vector<std::unique_ptr<FidoHidDevice>> u2f_devices =
enumerate_future.TakeReturnedDevicesFiltered();
ASSERT_EQ(1u, u2f_devices.size());
auto& device = u2f_devices.front();
base::test::TestFuture<void> future;
device->DiscoverSupportedProtocolAndDeviceInfo(future.GetCallback());
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_TRUE(future.IsReady());
future.Clear();
device->TryWink(future.GetCallback());
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_TRUE(future.IsReady());
}
TEST_F(FidoHidDeviceTest, RetryAfterU2fChannelBusy) {
testing::Sequence sequence;
std::unique_ptr<MockFidoHidConnection> mock_connection =
CreateHidConnectionWithHidInitExpectations(
kChannelId, fake_hid_manager_.get(), sequence);
mock_connection->ExpectHidWriteWithCommand(sequence,
FidoHidDeviceCommand::kMsg);
mock_connection->ExpectReadAndReplyWith(
sequence,
CreateMockResponseWithChannelId(mock_connection->connection_channel_id(),
kMockU2fChannelBusyResponse));
mock_connection->ExpectHidWriteWithCommand(sequence,
FidoHidDeviceCommand::kMsg);
mock_connection->ExpectReadAndReplyWith(
sequence,
CreateMockResponseWithChannelId(mock_connection->connection_channel_id(),
kU2fMockResponseMessage));
FidoDeviceEnumerateFuture enumerate_future(hid_manager_.get());
hid_manager_->GetDevices(enumerate_future.GetCallback());
EXPECT_TRUE(enumerate_future.Wait());
std::unique_ptr<FidoHidDevice> device = enumerate_future.TakeSingleDevice();
TestDeviceFuture test_device_future;
device->DeviceTransact(GetMockDeviceRequest(),
test_device_future.GetCallback());
EXPECT_TRUE(test_device_future.Wait());
const auto& value = test_device_future.Get();
ASSERT_TRUE(value);
EXPECT_THAT(*value, testing::ElementsAreArray(kU2fMockResponseData));
}
}