| // 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::Endpoints<fuchsia_fido_report::SecurityKeyDevice>::Create(); |
| 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 |