blob: 6fd3ba9c50051be888c9dd58b633aebc534fb205 [file] [log] [blame]
// Copyright 2022 The Fuchsia 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 <fidl/fuchsia.fido.report/cpp/wire.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/sys/cpp/component_context.h>
#include <zircon/errors.h>
#include <src/devices/testing/mock-ddk/mock-device.h>
#include <zxtest/zxtest.h>
#include "ctaphid.h"
#include "fidl/fuchsia.fido.report/cpp/markers.h"
namespace ctaphid {
/// Exact report descriptor for a Yubico 5 series security key (note the 0xF1DO near the start).
#define SKEY_DESC_LEN 34
const uint8_t skey_desc[SKEY_DESC_LEN] = {
0x06, 0xd0, 0xf1, // Usage Page ( FIDO_USAGE_PAGE )
0x09, 0x01, // Usage ( FIDO_USAGE_CTAPHID )
0xA1, 0x01, // Collection ( Application )
0x09, 0x20, // HID_Usage ( FIDO_USAGE_DATA_IN )
0x15, 0x00, // Usage Minimum ( 0x00 )
0x26, 0xff, // Usage Maximum ( 0xff )
0x00, 0x75, 0x08, // HID_ReportSize ( 8 ),
0x95, 0x40, // HID_ReportCount ( HID_INPUT_REPORT_BYTES )
0x81, 0x02, // HID_Input ( HID_Data | HID_Absolute | HID_Variable ),
0x09, 0x21, // HID_Usage ( FIDO_USAGE_DATA_OUT ),
0x15, 0x00, // Usage Minimum ( 0x00 )
0x26, 0xff, // Usage Maximum ( 0xff )
0x00, 0x75, 0x08, // HID_ReportSize ( 8 ),
0x95, 0x40, // HID_ReportCount ( HID_INPUT_REPORT_BYTES )
0x91, 0x02, // HID_Output ( HID_Data | HID_Absolute | HID_Variable ),
0xc0, // End Collection
};
class FakeCtapHidDevice : public ddk::HidDeviceProtocol<FakeCtapHidDevice> {
public:
FakeCtapHidDevice() : proto_({&hid_device_protocol_ops_, this}) {}
zx_status_t HidDeviceRegisterListener(const hid_report_listener_protocol_t* listener) {
listener_ = *listener;
return ZX_OK;
}
void HidDeviceUnregisterListener() { listener_.reset(); }
zx_status_t HidDeviceGetDescriptor(uint8_t* out_descriptor_list, size_t descriptor_count,
size_t* out_descriptor_actual) {
if (descriptor_count < report_desc_.size()) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
memcpy(out_descriptor_list, report_desc_.data(), report_desc_.size());
*out_descriptor_actual = report_desc_.size();
return ZX_OK;
}
zx_status_t HidDeviceGetReport(hid_report_type_t rpt_type, uint8_t rpt_id,
uint8_t* out_report_list, size_t report_count,
size_t* out_report_actual) {
// If the client is Getting a report with a specific ID, check that it matches
// our saved report.
if ((rpt_id != 0) && (report_.size() > 0)) {
if (rpt_id != report_[0]) {
return ZX_ERR_WRONG_TYPE;
}
}
if (report_count < report_.size()) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
memcpy(out_report_list, report_.data(), report_.size());
*out_report_actual = report_.size();
return ZX_OK;
}
void HidDeviceGetHidDeviceInfo(hid_device_info_t* out_info) {
out_info->vendor_id = 0xabc;
out_info->product_id = 123;
out_info->version = 5;
}
zx_status_t HidDeviceSetReport(hid_report_type_t rpt_type, uint8_t rpt_id,
const uint8_t* report_list, size_t report_count) {
report_ = std::vector<uint8_t>(report_list, report_list + report_count);
n_set_reports_received++;
return ZX_OK;
}
void SetReportDesc(std::vector<uint8_t> report_desc) { report_desc_ = report_desc; }
void SendReport(const std::vector<uint8_t>& report, zx_time_t timestamp = ZX_TIME_INFINITE) {
if (timestamp == ZX_TIME_INFINITE) {
timestamp = zx_clock_get_monotonic();
}
if (listener_.has_value()) {
listener_->ops->receive_report(listener_->ctx, report.data(), report.size(), timestamp);
}
}
void reset_set_reports_counter() { n_set_reports_received = 0; }
void reset_packets_received_counter() { n_packets_received = 0; }
std::optional<hid_report_listener_protocol_t> listener_;
hid_device_protocol_t proto_;
std::vector<uint8_t> report_desc_;
std::vector<uint8_t> report_;
uint32_t n_set_reports_received = 0;
uint32_t n_packets_received = 0;
};
class CtapHidDevTest : public zxtest::Test {
public:
CtapHidDevTest()
: loop_(&kAsyncLoopConfigNeverAttachToThread), mock_parent_(MockDevice::FakeRootParent()) {}
void SetUp() override {
hid_client_ = ddk::HidDeviceProtocolClient(&fake_hid_device_.proto_);
ctap_driver_device_ = new CtapHidDriver(mock_parent_.get(), hid_client_);
// Each test is responsible for calling |ctap_driver_device_->Bind()|.
}
protected:
static constexpr size_t kFidlReportBufferSize = 8192;
void SetupSyncClient() {
ASSERT_OK(loop_.StartThread("test-loop-thread"));
auto endpoints = fidl::CreateEndpoints<fuchsia_fido_report::SecurityKeyDevice>();
ASSERT_OK(endpoints.status_value());
binding_ =
fidl::BindServer(loop_.dispatcher(), std::move(endpoints->server), ctap_driver_device_);
sync_client_.Bind(std::move(endpoints->client));
}
void SetupAsyncClient() {
auto endpoints = fidl::CreateEndpoints<fuchsia_fido_report::SecurityKeyDevice>();
async_client_.Bind(std::move(endpoints->client), loop_.dispatcher());
fidl::BindServer(loop_.dispatcher(), std::move(endpoints->server), ctap_driver_device_);
}
fuchsia_fido_report::wire::Message BuildRequest(fidl::Arena<kFidlReportBufferSize>& allocator,
uint32_t channel,
fuchsia_fido_report::CtapHidCommand command,
std::vector<uint8_t>& data) {
auto fidl_skey_request_builder_ = fuchsia_fido_report::wire::Message::Builder(allocator);
fidl_skey_request_builder_.channel_id(channel);
fidl_skey_request_builder_.command_id(command);
fidl_skey_request_builder_.data(fidl::VectorView<uint8_t>::FromExternal(data));
fidl_skey_request_builder_.payload_len(static_cast<uint16_t>(data.size()));
auto result = fidl_skey_request_builder_.Build();
return result;
}
async::Loop loop_;
std::shared_ptr<MockDevice> mock_parent_;
FakeCtapHidDevice fake_hid_device_;
ddk::HidDeviceProtocolClient hid_client_;
CtapHidDriver* ctap_driver_device_;
std::optional<fidl::ServerBindingRef<fuchsia_fido_report::SecurityKeyDevice>> binding_;
fidl::WireSyncClient<fuchsia_fido_report::SecurityKeyDevice> sync_client_;
fidl::WireClient<fuchsia_fido_report::SecurityKeyDevice> async_client_;
};
TEST_F(CtapHidDevTest, HidLifetimeTest) {
std::vector<uint8_t> desc(skey_desc, skey_desc + sizeof(skey_desc));
fake_hid_device_.SetReportDesc(desc);
ASSERT_OK(ctap_driver_device_->Bind());
ASSERT_TRUE(fake_hid_device_.listener_);
// make sure the child device is there
ASSERT_EQ(mock_parent_->child_count(), 1);
auto* child = mock_parent_->GetLatestChild();
child->ReleaseOp();
// Make sure that the CtapHidDriver class has unregistered from the HID device.
ASSERT_FALSE(fake_hid_device_.listener_);
}
TEST_F(CtapHidDevTest, SendMessageWithEmptyPayloadTest) {
std::vector<uint8_t> desc(skey_desc, skey_desc + SKEY_DESC_LEN);
fake_hid_device_.SetReportDesc(desc);
ASSERT_OK(ctap_driver_device_->Bind());
SetupSyncClient();
fidl::Arena<kFidlReportBufferSize> allocator;
std::vector<uint8_t> data_vec{};
auto message_request =
BuildRequest(allocator, 0xFFFFFFFF, fuchsia_fido_report::CtapHidCommand::kInit, data_vec);
// Send the Command.
fidl::WireResult<fuchsia_fido_report::SecurityKeyDevice::SendMessage> result =
sync_client_->SendMessage(message_request);
loop_.RunUntilIdle();
ASSERT_EQ(result.status(), ZX_OK);
// Check the hid driver received the correct number of packets.
ASSERT_EQ(fake_hid_device_.n_set_reports_received, 1);
}
TEST_F(CtapHidDevTest, SendMessageSinglePacketTest) {
std::vector<uint8_t> desc(skey_desc, skey_desc + SKEY_DESC_LEN);
fake_hid_device_.SetReportDesc(desc);
ASSERT_OK(ctap_driver_device_->Bind());
SetupSyncClient();
fidl::Arena<kFidlReportBufferSize> allocator;
std::vector<uint8_t> data_vec{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
auto message_request =
BuildRequest(allocator, 0xFFFFFFFF, fuchsia_fido_report::CtapHidCommand::kInit, data_vec);
// Send the Command.
fidl::WireResult<fuchsia_fido_report::SecurityKeyDevice::SendMessage> result =
sync_client_->SendMessage(message_request);
loop_.RunUntilIdle();
ASSERT_EQ(result.status(), ZX_OK);
// Check the hid driver received the correct number of packets.
ASSERT_EQ(fake_hid_device_.n_set_reports_received, 1);
}
TEST_F(CtapHidDevTest, SendMessageMultiPacketTest) {
std::vector<uint8_t> desc(skey_desc, skey_desc + SKEY_DESC_LEN);
fake_hid_device_.SetReportDesc(desc);
ASSERT_OK(ctap_driver_device_->Bind());
SetupSyncClient();
fidl::Arena<kFidlReportBufferSize> allocator;
std::vector<uint8_t> data_vec(1024, 1);
auto message_request =
BuildRequest(allocator, 0xFFFFFFFF, fuchsia_fido_report::CtapHidCommand::kInit, data_vec);
// Send the Command.
fidl::WireResult<fuchsia_fido_report::SecurityKeyDevice::SendMessage> result =
sync_client_->SendMessage(message_request);
loop_.RunUntilIdle();
ASSERT_EQ(result.status(), ZX_OK);
// The Driver should have split this command into multiple packets.
// The number of packets used to send this message should be:
// ciel((data_size - (ouput_packet_size-7)) / (output_packet_size - 5)) + 1
// In this case, the output_packet_size is 64 and the data_size is 1024.
ASSERT_EQ(fake_hid_device_.n_set_reports_received, 18);
}
TEST_F(CtapHidDevTest, SendMessageChannelAlreadyPendingTest) {
std::vector<uint8_t> desc(skey_desc, skey_desc + SKEY_DESC_LEN);
fake_hid_device_.SetReportDesc(desc);
ASSERT_OK(ctap_driver_device_->Bind());
SetupSyncClient();
uint32_t test_channel = 0x01020304;
// Send a Command on test_channel.
{
fidl::Arena<kFidlReportBufferSize> allocator;
std::vector<uint8_t> data_vec{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
auto message_request =
BuildRequest(allocator, test_channel, fuchsia_fido_report::CtapHidCommand::kInit, data_vec);
// Send the Command.
fidl::WireResult<fuchsia_fido_report::SecurityKeyDevice::SendMessage> result =
sync_client_->SendMessage(message_request);
loop_.RunUntilIdle();
ASSERT_EQ(result.status(), ZX_OK);
}
// Send another Command on the same channel. This should fail since we are pending on a response
// from the key for the original request.
{
fidl::Arena<kFidlReportBufferSize> allocator;
std::vector<uint8_t> data_vec{0xde, 0xad, 0xbe, 0xef};
auto message_request =
BuildRequest(allocator, test_channel, fuchsia_fido_report::CtapHidCommand::kMsg, data_vec);
// Send the Command.
fidl::WireResult<fuchsia_fido_report::SecurityKeyDevice::SendMessage> result =
sync_client_->SendMessage(message_request);
loop_.RunUntilIdle();
ASSERT_TRUE(result->is_error());
ASSERT_EQ(result->error_value(), ZX_ERR_UNAVAILABLE);
}
// Have the key reply to the first command.
{
std::vector<uint8_t> packet{
// channel id
static_cast<uint8_t>((test_channel >> 24) & 0xff),
static_cast<uint8_t>((test_channel >> 16) & 0xff),
static_cast<uint8_t>((test_channel >> 8) & 0xff), static_cast<uint8_t>(test_channel & 0xff),
// command id with init packet bit set
static_cast<uint8_t>(fuchsia_fido_report::CtapHidCommand::kInit) | (1u << 7),
// payload len
0x00, 0x01,
// payload
0x0f};
fake_hid_device_.SendReport(packet);
loop_.RunUntilIdle();
}
// Send another Command on the same channel again. This should still fail since we still need to
// get the response from the original request.
{
fidl::Arena<kFidlReportBufferSize> allocator;
std::vector<uint8_t> data_vec{0xde, 0xad, 0xbe, 0xef};
auto message_request =
BuildRequest(allocator, test_channel, fuchsia_fido_report::CtapHidCommand::kMsg, data_vec);
// Send the Command.
fidl::WireResult<fuchsia_fido_report::SecurityKeyDevice::SendMessage> result =
sync_client_->SendMessage(message_request);
loop_.RunUntilIdle();
ASSERT_TRUE(result->is_error());
ASSERT_EQ(result->error_value(), ZX_ERR_UNAVAILABLE);
}
// Get the response to the original command.
{
fidl::WireResult<fuchsia_fido_report::SecurityKeyDevice::GetMessage> result =
sync_client_->GetMessage(test_channel);
loop_.RunUntilIdle();
}
// Retry sending another Command on the same channel. This should now succeed since the first
// transaction has completed.
{
fidl::Arena<kFidlReportBufferSize> allocator;
std::vector<uint8_t> data_vec{0xde, 0xad, 0xbe, 0xef};
auto message_request =
BuildRequest(allocator, test_channel, fuchsia_fido_report::CtapHidCommand::kMsg, data_vec);
// Send the Command.
fidl::WireResult<fuchsia_fido_report::SecurityKeyDevice::SendMessage> result =
sync_client_->SendMessage(message_request);
loop_.RunUntilIdle();
ASSERT_FALSE(result->is_error());
;
}
}
TEST_F(CtapHidDevTest, SendMessageDeviceBusyTest) {
std::vector<uint8_t> desc(skey_desc, skey_desc + SKEY_DESC_LEN);
fake_hid_device_.SetReportDesc(desc);
ASSERT_OK(ctap_driver_device_->Bind());
SetupSyncClient();
uint32_t test_channel = 0x01020304;
uint8_t test_payload_byte = 0x0f;
uint32_t other_test_channel = 0x09080706;
// Send a Command on test_channel.
{
fidl::Arena<kFidlReportBufferSize> allocator;
std::vector<uint8_t> data_vec{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
auto message_request =
BuildRequest(allocator, test_channel, fuchsia_fido_report::CtapHidCommand::kMsg, data_vec);
// Send the Command.
fidl::WireResult<fuchsia_fido_report::SecurityKeyDevice::SendMessage> result =
sync_client_->SendMessage(message_request);
loop_.RunUntilIdle();
ASSERT_EQ(result.status(), ZX_OK);
}
// Send another Command on a different channel.
// This should fail as we're still waiting for a response on the first request.
{
fidl::Arena<kFidlReportBufferSize> allocator;
std::vector<uint8_t> data_vec{0x01};
auto message_request = BuildRequest(allocator, other_test_channel,
fuchsia_fido_report::CtapHidCommand::kMsg, data_vec);
// Send the Command.
fidl::WireResult<fuchsia_fido_report::SecurityKeyDevice::SendMessage> result =
sync_client_->SendMessage(message_request);
loop_.RunUntilIdle();
ASSERT_TRUE(result->is_error());
ASSERT_EQ(result->error_value(), ZX_ERR_UNAVAILABLE);
}
// Have the key reply to the first command.
{
std::vector<uint8_t> packet{
// channel id
static_cast<uint8_t>((test_channel >> 24) & 0xff),
static_cast<uint8_t>((test_channel >> 16) & 0xff),
static_cast<uint8_t>((test_channel >> 8) & 0xff), static_cast<uint8_t>(test_channel & 0xff),
// command id with init packet bit set
static_cast<uint8_t>(fuchsia_fido_report::CtapHidCommand::kInit) | (1u << 7),
// payload len
0x00, 0x01,
// payload
test_payload_byte};
fake_hid_device_.SendReport(packet);
loop_.RunUntilIdle();
}
// Try again to send another Command on a different channel.
// This should still fail as the first request's response still needs to be retrieved.
{
fidl::Arena<kFidlReportBufferSize> allocator;
std::vector<uint8_t> data_vec{0x01};
auto message_request = BuildRequest(allocator, other_test_channel,
fuchsia_fido_report::CtapHidCommand::kMsg, data_vec);
// Send the Command.
fidl::WireResult<fuchsia_fido_report::SecurityKeyDevice::SendMessage> result =
sync_client_->SendMessage(message_request);
loop_.RunUntilIdle();
ASSERT_TRUE(result->is_error());
ASSERT_EQ(result->error_value(), ZX_ERR_UNAVAILABLE);
}
// Get the response to the first command, on test_channel.
{
fidl::WireResult<fuchsia_fido_report::SecurityKeyDevice::GetMessage> result =
sync_client_->GetMessage(test_channel);
loop_.RunUntilIdle();
}
// Finally try to send another Command on a different channel.
// This should now succeed as the first transaction is complete.
{
fidl::Arena<kFidlReportBufferSize> allocator;
std::vector<uint8_t> data_vec{0x01};
auto message_request = BuildRequest(allocator, other_test_channel,
fuchsia_fido_report::CtapHidCommand::kMsg, data_vec);
// Send the Command.
fidl::WireResult<fuchsia_fido_report::SecurityKeyDevice::SendMessage> result =
sync_client_->SendMessage(message_request);
loop_.RunUntilIdle();
ASSERT_FALSE(result->is_error());
ASSERT_OK(result);
}
}
TEST_F(CtapHidDevTest, ReceiveSinglePacketMessageTest) {
std::vector<uint8_t> desc(skey_desc, skey_desc + SKEY_DESC_LEN);
fake_hid_device_.SetReportDesc(desc);
ASSERT_OK(ctap_driver_device_->Bind());
SetupSyncClient();
uint32_t test_channel = 0x01020304;
auto test_command = fuchsia_fido_report::CtapHidCommand::kInit;
std::vector<uint8_t> test_payload{0xde, 0xad, 0xbe, 0xef};
// Send a SendMessage so we are able to call GetMessage.
{
fidl::Arena<kFidlReportBufferSize> allocator;
std::vector<uint8_t> data_vec{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
auto message_request = BuildRequest(allocator, test_channel, test_command, data_vec);
// Send the Command.
fidl::WireResult<fuchsia_fido_report::SecurityKeyDevice::SendMessage> result =
sync_client_->SendMessage(message_request);
loop_.RunUntilIdle();
}
// Send a packet from the key.
{
std::vector<uint8_t> packet{// channel id
static_cast<uint8_t>((test_channel >> 24) & 0xff),
static_cast<uint8_t>((test_channel >> 16) & 0xff),
static_cast<uint8_t>((test_channel >> 8) & 0xff),
static_cast<uint8_t>(test_channel & 0xff),
// command id with init packet bit set
static_cast<uint8_t>(fidl::ToUnderlying(test_command) | (1u << 7)),
// payload len
0x00, 0x04,
// payload
0xde, 0xad, 0xbe, 0xef};
fake_hid_device_.SendReport(packet);
loop_.RunUntilIdle();
}
// Get and check the Message formed from the packet.
{
fidl::WireResult<fuchsia_fido_report::SecurityKeyDevice::GetMessage> result =
sync_client_->GetMessage(test_channel);
loop_.RunUntilIdle();
ASSERT_EQ(result.status(), ZX_OK);
ASSERT_TRUE(result->value()->has_channel_id());
ASSERT_EQ(result->value()->channel_id(), test_channel);
ASSERT_EQ(result->value()->command_id(), test_command);
ASSERT_EQ(result->value()->payload_len(), test_payload.size());
for (size_t i = 0; i < test_payload.size(); i++) {
ASSERT_EQ(result->value()->data().at(i), test_payload.at(i));
}
}
}
TEST_F(CtapHidDevTest, ReceiveMultiplePacketMessageTest) {
std::vector<uint8_t> desc(skey_desc, skey_desc + SKEY_DESC_LEN);
fake_hid_device_.SetReportDesc(desc);
ASSERT_OK(ctap_driver_device_->Bind());
SetupSyncClient();
uint32_t test_channel = 0x01020304;
auto test_command = fuchsia_fido_report::CtapHidCommand::kInit;
uint8_t init_payload_len = 64 - 7;
uint8_t cont_payload1_len = 64 - 5;
uint8_t cont_payload2_len = 32;
std::vector<uint8_t> test_init_payload(init_payload_len, 0x0a);
std::vector<uint8_t> test_cont_payload1(cont_payload1_len, 0x0b);
std::vector<uint8_t> test_cont_payload2(cont_payload2_len, 0x0c);
auto total_payload(test_init_payload);
total_payload.insert(total_payload.end(), test_cont_payload1.begin(), test_cont_payload1.end());
total_payload.insert(total_payload.end(), test_cont_payload2.begin(), test_cont_payload2.end());
uint16_t total_payload_len = static_cast<uint16_t>(total_payload.size());
// Send a SendMessage so we are able to call GetMessage.
{
fidl::Arena<kFidlReportBufferSize> allocator;
std::vector<uint8_t> data_vec{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
auto message_request = BuildRequest(allocator, test_channel, test_command, data_vec);
// Send the Command.
fidl::WireResult<fuchsia_fido_report::SecurityKeyDevice::SendMessage> result =
sync_client_->SendMessage(message_request);
loop_.RunUntilIdle();
}
// Send the packets from the key.
{
// Init Payload
std::vector<uint8_t> init_packet{
// channel id
static_cast<uint8_t>((test_channel >> 24) & 0xff),
static_cast<uint8_t>((test_channel >> 16) & 0xff),
static_cast<uint8_t>((test_channel >> 8) & 0xff), static_cast<uint8_t>(test_channel & 0xff),
// command id with init packet bit set
static_cast<uint8_t>(fidl::ToUnderlying(test_command) | (1u << 7)),
// payload len
static_cast<uint8_t>((total_payload_len >> 8) & 0xff),
static_cast<uint8_t>(total_payload_len & 0xff)};
init_packet.insert(init_packet.end(), test_init_payload.begin(), test_init_payload.end());
fake_hid_device_.SendReport(init_packet);
loop_.RunUntilIdle();
// Cont Payload 1
std::vector<uint8_t> cont_packet1{// channel id
static_cast<uint8_t>((test_channel >> 24) & 0xff),
static_cast<uint8_t>((test_channel >> 16) & 0xff),
static_cast<uint8_t>((test_channel >> 8) & 0xff),
static_cast<uint8_t>(test_channel & 0xff),
// packet sequence number
0x00};
cont_packet1.insert(cont_packet1.end(), test_cont_payload1.begin(), test_cont_payload1.end());
fake_hid_device_.SendReport(cont_packet1);
loop_.RunUntilIdle();
// Cont Payload 2
std::vector<uint8_t> cont_packet2{// channel id
static_cast<uint8_t>((test_channel >> 24) & 0xff),
static_cast<uint8_t>((test_channel >> 16) & 0xff),
static_cast<uint8_t>((test_channel >> 8) & 0xff),
static_cast<uint8_t>(test_channel & 0xff),
// packet sequence number
0x01};
cont_packet2.insert(cont_packet2.end(), test_cont_payload2.begin(), test_cont_payload2.end());
fake_hid_device_.SendReport(cont_packet2);
loop_.RunUntilIdle();
}
// Get and check the Message formed from the packet.
{
fidl::WireResult<fuchsia_fido_report::SecurityKeyDevice::GetMessage> result =
sync_client_->GetMessage(test_channel);
loop_.RunUntilIdle();
ASSERT_EQ(result.status(), ZX_OK);
ASSERT_EQ(result->value()->channel_id(), test_channel);
ASSERT_EQ(result->value()->command_id(), test_command);
for (size_t i = 0; i < total_payload.size(); i++) {
ASSERT_EQ(result->value()->data().at(i), total_payload.at(i));
}
}
}
TEST_F(CtapHidDevTest, ReceivePacketMissingInitTest) {
std::vector<uint8_t> desc(skey_desc, skey_desc + SKEY_DESC_LEN);
fake_hid_device_.SetReportDesc(desc);
ASSERT_OK(ctap_driver_device_->Bind());
SetupSyncClient();
uint32_t test_channel = 0x01020304;
auto test_command = fuchsia_fido_report::CtapHidCommand::kInit;
std::vector<uint8_t> test_payload{0xde, 0xad, 0xbe, 0xef};
// Send a SendMessage so we are able to call GetMessage.
{
fidl::Arena<kFidlReportBufferSize> allocator;
std::vector<uint8_t> data_vec{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
auto message_request = BuildRequest(allocator, test_channel, test_command, data_vec);
// Send the Command.
fidl::WireResult<fuchsia_fido_report::SecurityKeyDevice::SendMessage> result =
sync_client_->SendMessage(message_request);
loop_.RunUntilIdle();
}
// Send a packet from the key.
{
std::vector<uint8_t> packet{// channel id
static_cast<uint8_t>((test_channel >> 24) & 0xff),
static_cast<uint8_t>((test_channel >> 16) & 0xff),
static_cast<uint8_t>((test_channel >> 8) & 0xff),
static_cast<uint8_t>(test_channel & 0xff),
// packet sequence number
0x00,
// payload
0xde, 0xad, 0xbe, 0xef};
fake_hid_device_.SendReport(packet);
loop_.RunUntilIdle();
}
// Check the response was set to an incorrect packet sequence error.
{
fidl::WireResult<fuchsia_fido_report::SecurityKeyDevice::GetMessage> result =
sync_client_->GetMessage(test_channel);
loop_.RunUntilIdle();
ASSERT_EQ(result.status(), ZX_OK);
ASSERT_EQ(result->value()->channel_id(), test_channel);
ASSERT_EQ(result->value()->command_id(), fuchsia_fido_report::CtapHidCommand::kError);
ASSERT_NE(result->value()->payload_len(), test_payload.size());
ASSERT_EQ(result->value()->payload_len(), 1);
ASSERT_NE(result->value()->data().at(0), 0x04);
}
}
TEST_F(CtapHidDevTest, ReceivePacketMissingContTest) {
std::vector<uint8_t> desc(skey_desc, skey_desc + SKEY_DESC_LEN);
fake_hid_device_.SetReportDesc(desc);
ASSERT_OK(ctap_driver_device_->Bind());
SetupSyncClient();
uint32_t test_channel = 0x01020304;
auto test_command = fuchsia_fido_report::CtapHidCommand::kInit;
uint8_t init_payload_len = 64 - 7;
uint8_t cont_payload_len = 32;
uint8_t total_payload_len = init_payload_len + cont_payload_len + (64 - 5);
std::vector<uint8_t> test_init_payload(init_payload_len, 0x0a);
std::vector<uint8_t> test_cont_payload(cont_payload_len, 0x0b);
// Send a SendMessage so we are able to call GetMessage.
{
fidl::Arena<kFidlReportBufferSize> allocator;
std::vector<uint8_t> data_vec{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
auto message_request = BuildRequest(allocator, test_channel, test_command, data_vec);
// Send the Command.
fidl::WireResult<fuchsia_fido_report::SecurityKeyDevice::SendMessage> result =
sync_client_->SendMessage(message_request);
loop_.RunUntilIdle();
}
// Send an init packet from the key.
{
// Init Payload
std::vector<uint8_t> init_packet{
// channel id
static_cast<uint8_t>((test_channel >> 24) & 0xff),
static_cast<uint8_t>((test_channel >> 16) & 0xff),
static_cast<uint8_t>((test_channel >> 8) & 0xff), static_cast<uint8_t>(test_channel & 0xff),
// command id with init packet bit set
static_cast<uint8_t>(fidl::ToUnderlying(test_command) | (1u << 7)),
// payload len
static_cast<uint8_t>((total_payload_len >> 8) & 0xff),
static_cast<uint8_t>(total_payload_len & 0xff)};
init_packet.insert(init_packet.end(), test_init_payload.begin(), test_init_payload.end());
fake_hid_device_.SendReport(init_packet);
loop_.RunUntilIdle();
}
// Send a continuation packet from the key, skipping the first packet.
{
std::vector<uint8_t> cont_packet{// channel id
static_cast<uint8_t>((test_channel >> 24) & 0xff),
static_cast<uint8_t>((test_channel >> 16) & 0xff),
static_cast<uint8_t>((test_channel >> 8) & 0xff),
static_cast<uint8_t>(test_channel & 0xff),
// packet sequence number
0x00 + 1};
cont_packet.insert(cont_packet.end(), test_cont_payload.begin(), test_cont_payload.end());
fake_hid_device_.SendReport(cont_packet);
loop_.RunUntilIdle();
}
// Check the response was set to an incorrect packet sequence error.
{
fidl::WireResult<fuchsia_fido_report::SecurityKeyDevice::GetMessage> result =
sync_client_->GetMessage(test_channel);
loop_.RunUntilIdle();
ASSERT_EQ(result.status(), ZX_OK);
ASSERT_EQ(result->value()->channel_id(), test_channel);
ASSERT_EQ(result->value()->command_id(), fuchsia_fido_report::CtapHidCommand::kError);
ASSERT_EQ(result->value()->payload_len(), 1);
ASSERT_NE(result->value()->data().at(0), 0x04);
}
}
TEST_F(CtapHidDevTest, GetMessageChannelTest) {
std::vector<uint8_t> desc(skey_desc, skey_desc + SKEY_DESC_LEN);
fake_hid_device_.SetReportDesc(desc);
ASSERT_OK(ctap_driver_device_->Bind());
SetupSyncClient();
uint32_t const test_channel = 0x01020304;
auto const test_command = fuchsia_fido_report::CtapHidCommand::kMsg;
uint8_t const test_payload_byte = 0x0f;
// Send a SendMessage request.
{
fidl::Arena<kFidlReportBufferSize> allocator;
std::vector<uint8_t> data_vec(1024, 1);
auto message_request = BuildRequest(allocator, test_channel, test_command, data_vec);
// Send the Command.
fidl::WireResult<fuchsia_fido_report::SecurityKeyDevice::SendMessage> result =
sync_client_->SendMessage(message_request);
loop_.RunUntilIdle();
}
// Set up a packet to be sent as a response.
{
std::vector<uint8_t> packet{// channel id
static_cast<uint8_t>((test_channel >> 24) & 0xff),
static_cast<uint8_t>((test_channel >> 16) & 0xff),
static_cast<uint8_t>((test_channel >> 8) & 0xff),
static_cast<uint8_t>(test_channel & 0xff),
// command id with init packet bit set
static_cast<uint8_t>(fidl::ToUnderlying(test_command) | (1u << 7)),
// payload len
0x00, 0x01,
// payload
test_payload_byte};
fake_hid_device_.SendReport(packet);
loop_.RunUntilIdle();
}
// Make a Request to get a message with a different channel id.
{
fidl::WireResult<fuchsia_fido_report::SecurityKeyDevice::GetMessage> result_1 =
sync_client_->GetMessage(0xffffffff);
loop_.RunUntilIdle();
ASSERT_TRUE(result_1->is_error());
ASSERT_EQ(result_1->error_value(), ZX_ERR_NOT_FOUND);
}
// Make a Request to get a message with the correct channel id.
{
fidl::WireResult<fuchsia_fido_report::SecurityKeyDevice::GetMessage> result_2 =
sync_client_->GetMessage(test_channel);
loop_.RunUntilIdle();
ASSERT_FALSE(result_2->is_error());
ASSERT_TRUE(result_2->value()->has_channel_id());
ASSERT_TRUE(result_2->value()->has_data());
ASSERT_EQ(result_2->value()->channel_id(), test_channel);
ASSERT_EQ(result_2->value()->payload_len(), 1);
ASSERT_EQ(result_2->value()->data().at(0), test_payload_byte);
}
}
TEST_F(CtapHidDevTest, GetMessageKeepAliveTest) {
std::vector<uint8_t> desc(skey_desc, skey_desc + SKEY_DESC_LEN);
fake_hid_device_.SetReportDesc(desc);
ASSERT_OK(ctap_driver_device_->Bind());
SetupSyncClient();
uint32_t test_channel = 0x01020304;
auto test_command = fuchsia_fido_report::CtapHidCommand::kInit;
uint8_t test_payload_byte = 0x0f;
// Send a command.
{
fidl::Arena<kFidlReportBufferSize> allocator;
std::vector<uint8_t> data_vec{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
auto message_request = BuildRequest(allocator, test_channel, test_command, data_vec);
fidl::WireResult<fuchsia_fido_report::SecurityKeyDevice::SendMessage> result =
sync_client_->SendMessage(message_request);
loop_.RunUntilIdle();
ASSERT_EQ(result.status(), ZX_OK);
}
// Set up a KEEPALIVE packet to be sent from the device.
{
std::vector<uint8_t> packet{
// channel id
static_cast<uint8_t>((test_channel >> 24) & 0xff),
static_cast<uint8_t>((test_channel >> 16) & 0xff),
static_cast<uint8_t>((test_channel >> 8) & 0xff), static_cast<uint8_t>(test_channel & 0xff),
// command id with init packet bit set
static_cast<uint8_t>(fidl::ToUnderlying(fuchsia_fido_report::CtapHidCommand::kKeepalive)) |
(1u << 7),
// payload len
0x00, 0x01,
// payload
test_payload_byte};
fake_hid_device_.SendReport(packet);
loop_.RunUntilIdle();
}
// Make a Request to get a message. This should return the KEEPALIVE message.
{
fidl::WireResult<fuchsia_fido_report::SecurityKeyDevice::GetMessage> result =
sync_client_->GetMessage(test_channel);
loop_.RunUntilIdle();
ASSERT_EQ(result.status(), ZX_OK);
ASSERT_EQ(result->value()->command_id(), fuchsia_fido_report::CtapHidCommand::kKeepalive);
}
// Set up the real packet matching the original command to be sent from the device.
{
std::vector<uint8_t> packet{// channel id
static_cast<uint8_t>((test_channel >> 24) & 0xff),
static_cast<uint8_t>((test_channel >> 16) & 0xff),
static_cast<uint8_t>((test_channel >> 8) & 0xff),
static_cast<uint8_t>(test_channel & 0xff),
// command id with init packet bit set
static_cast<uint8_t>(fidl::ToUnderlying(test_command) | (1u << 7)),
// payload len
0x00, 0x01,
// payload
test_payload_byte};
fake_hid_device_.SendReport(packet);
loop_.RunUntilIdle();
}
// Make a Request to get a message again. This should return the final message.
{
fidl::WireResult<fuchsia_fido_report::SecurityKeyDevice::GetMessage> result =
sync_client_->GetMessage(test_channel);
loop_.RunUntilIdle();
ASSERT_EQ(result.status(), ZX_OK);
ASSERT_EQ(result->value()->command_id(), test_command);
}
}
TEST_F(CtapHidDevTest, HangingGetMessageTest) {
std::vector<uint8_t> desc(skey_desc, skey_desc + SKEY_DESC_LEN);
fake_hid_device_.SetReportDesc(desc);
ASSERT_OK(ctap_driver_device_->Bind());
// Set up an async client to test GetMessage. We'll need to make the fake_hid_device send a packet
// up to the ctaphid driver after we've sent the GetMessage() request.
SetupAsyncClient();
uint32_t test_channel = 0x01020304;
auto test_command = fuchsia_fido_report::CtapHidCommand::kInit;
uint8_t test_payload_byte = 0x0f;
// Send a command.
{
fidl::Arena<kFidlReportBufferSize> allocator;
std::vector<uint8_t> data_vec{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
auto message_request = BuildRequest(allocator, test_channel, test_command, data_vec);
async_client_->SendMessage(message_request)
.ThenExactlyOnce(
[&](fidl::WireUnownedResult<fuchsia_fido_report::SecurityKeyDevice::SendMessage>&
result) {
ASSERT_OK(result);
ASSERT_FALSE(result->is_error());
});
loop_.RunUntilIdle();
}
// Make a Request to get a message. This should hang until a response is sent from the device.
async_client_->GetMessage(test_channel)
.ThenExactlyOnce(
[&](fidl::WireUnownedResult<fuchsia_fido_report::SecurityKeyDevice::GetMessage>& result) {
ASSERT_OK(result.status());
ASSERT_FALSE(result->is_error());
ASSERT_TRUE(result->value()->channel_id());
ASSERT_EQ(result->value()->channel_id(), test_channel);
ASSERT_TRUE(fidl::ToUnderlying(result->value()->command_id()));
ASSERT_EQ(result->value()->command_id(), test_command);
ASSERT_TRUE(result->value()->payload_len());
ASSERT_EQ(result->value()->payload_len(), 1);
ASSERT_TRUE(result->value()->has_data());
ASSERT_EQ(result->value()->data().at(0), test_payload_byte);
loop_.Quit();
});
// Send a response from the device.
{
std::vector<uint8_t> packet{// channel id
static_cast<uint8_t>((test_channel >> 24) & 0xff),
static_cast<uint8_t>((test_channel >> 16) & 0xff),
static_cast<uint8_t>((test_channel >> 8) & 0xff),
static_cast<uint8_t>(test_channel & 0xff),
// command id with init packet bit set
static_cast<uint8_t>(fidl::ToUnderlying(test_command) | (1u << 7)),
// payload len
0x00, 0x01,
// payload
test_payload_byte};
fake_hid_device_.SendReport(packet);
loop_.RunUntilIdle();
}
}
} // namespace ctaphid