| // Copyright 2019 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 "hid.h" |
| |
| #include <fidl/fuchsia.hardware.hidbus/cpp/wire_test_base.h> |
| #include <lib/driver/runtime/testing/cpp/sync_helpers.h> |
| #include <lib/fdf/cpp/dispatcher.h> |
| #include <lib/hid/ambient-light.h> |
| #include <lib/hid/boot.h> |
| #include <lib/hid/paradise.h> |
| #include <unistd.h> |
| |
| #include <thread> |
| #include <vector> |
| |
| #include <fbl/ref_ptr.h> |
| #include <zxtest/zxtest.h> |
| |
| #include "src/devices/testing/mock-ddk/mock-device.h" |
| |
| namespace hid_driver { |
| |
| namespace fhidbus = fuchsia_hardware_hidbus; |
| |
| class FakeHidbus : public fidl::testing::WireTestBase<fhidbus::Hidbus> { |
| public: |
| FakeHidbus() : loop_(&kAsyncLoopConfigNeverAttachToThread) { |
| loop_.StartThread("hid-test-fake-hidbus-loop"); |
| } |
| ~FakeHidbus() { |
| libsync::Completion wait; |
| async::PostTask(loop_.dispatcher(), [this, &wait]() { |
| binding_.RemoveAll(); |
| wait.Signal(); |
| }); |
| wait.Wait(); |
| } |
| |
| void NotImplemented_(const std::string& name, fidl::CompleterBase& completer) override { |
| ASSERT_TRUE(false); |
| } |
| |
| const fhidbus::HidInfo& Query() { return info_; } |
| void Query(QueryCompleter::Sync& completer) override { |
| fidl::Arena<> arena; |
| completer.ReplySuccess(fidl::ToWire(arena, info_)); |
| } |
| void Start(StartCompleter::Sync& completer) override { |
| if (start_status_ != ZX_OK) { |
| completer.ReplyError(start_status_); |
| return; |
| } |
| completer.ReplySuccess(); |
| } |
| void Stop(StopCompleter::Sync& completer) override {} |
| void SetDescriptor(fhidbus::wire::HidbusSetDescriptorRequest* request, |
| SetDescriptorCompleter::Sync& completer) override { |
| completer.ReplyError(ZX_ERR_NOT_SUPPORTED); |
| } |
| void GetDescriptor(fhidbus::wire::HidbusGetDescriptorRequest* request, |
| GetDescriptorCompleter::Sync& completer) override { |
| completer.ReplySuccess( |
| fidl::VectorView<uint8_t>::FromExternal(report_desc_.data(), report_desc_.size())); |
| } |
| void GetReport(fhidbus::wire::HidbusGetReportRequest* request, |
| GetReportCompleter::Sync& completer) override { |
| if (request->rpt_id != last_set_report_id_) { |
| completer.ReplyError(ZX_ERR_INTERNAL); |
| return; |
| } |
| if (request->len < last_set_report_.size()) { |
| completer.ReplyError(ZX_ERR_BUFFER_TOO_SMALL); |
| } |
| |
| completer.ReplySuccess( |
| fidl::VectorView<uint8_t>::FromExternal(last_set_report_.data(), last_set_report_.size())); |
| } |
| void SetReport(fhidbus::wire::HidbusSetReportRequest* request, |
| SetReportCompleter::Sync& completer) override { |
| last_set_report_id_ = request->rpt_id; |
| last_set_report_ = |
| std::vector<uint8_t>(request->data.data(), request->data.data() + request->data.count()); |
| completer.ReplySuccess(); |
| } |
| void GetIdle(fhidbus::wire::HidbusGetIdleRequest* request, |
| GetIdleCompleter::Sync& completer) override { |
| completer.ReplySuccess(0); |
| } |
| void SetIdle(fhidbus::wire::HidbusSetIdleRequest* request, |
| SetIdleCompleter::Sync& completer) override { |
| completer.ReplySuccess(); |
| } |
| void GetProtocol(GetProtocolCompleter::Sync& completer) override { |
| completer.ReplySuccess(hid_protocol_); |
| } |
| void SetProtocol(fhidbus::wire::HidbusSetProtocolRequest* request, |
| SetProtocolCompleter::Sync& completer) override { |
| hid_protocol_ = request->protocol; |
| completer.ReplySuccess(); |
| } |
| |
| void SetProtocol(fhidbus::wire::HidProtocol proto) { hid_protocol_ = proto; } |
| void SetHidInfo(fhidbus::wire::HidInfo info) { info_ = fidl::ToNatural(info); } |
| void SetStartStatus(zx_status_t status) { start_status_ = status; } |
| void SendReport(uint8_t* report_data, size_t report_size) { |
| SendReportWithTime(report_data, report_size, zx_clock_get_monotonic()); |
| } |
| void SendReportWithTime(uint8_t* report_data, size_t report_size, zx_time_t time) { |
| binding_.ForEachBinding([&](const auto& binding) { |
| fidl::Arena arena; |
| auto result = fidl::WireSendEvent(binding)->OnReportReceived( |
| fhidbus::wire::Report::Builder(arena) |
| .buf(fidl::VectorView<uint8_t>::FromExternal(report_data, report_size)) |
| .timestamp(time) |
| .Build()); |
| ASSERT_TRUE(result.ok()); |
| }); |
| } |
| void SetDescriptor(const uint8_t* desc, size_t desc_len) { |
| report_desc_ = std::vector<uint8_t>(desc, desc + desc_len); |
| } |
| |
| fidl::ClientEnd<fhidbus::Hidbus> GetClient() { |
| auto endpoints = fidl::CreateEndpoints<fhidbus::Hidbus>(); |
| EXPECT_OK(endpoints); |
| libsync::Completion wait; |
| EXPECT_OK(async::PostTask(loop_.dispatcher(), [this, &endpoints, &wait]() { |
| binding_.AddBinding(loop_.dispatcher(), std::move(endpoints->server), this, |
| fidl::kIgnoreBindingClosure); |
| wait.Signal(); |
| })); |
| wait.Wait(); |
| return std::move(endpoints->client); |
| } |
| |
| protected: |
| std::vector<uint8_t> report_desc_; |
| |
| std::vector<uint8_t> last_set_report_; |
| uint8_t last_set_report_id_; |
| |
| fhidbus::wire::HidProtocol hid_protocol_ = fhidbus::wire::HidProtocol::kReport; |
| fhidbus::HidInfo info_; |
| zx_status_t start_status_ = ZX_OK; |
| |
| private: |
| async::Loop loop_; |
| fidl::ServerBindingGroup<fhidbus::Hidbus> binding_; |
| }; |
| |
| class HidDeviceTest : public zxtest::Test { |
| public: |
| HidDeviceTest() = default; |
| void SetUp() override { |
| // TODO(https://fxbug.dev/42075363): Migrate test to use dispatcher integration. |
| fake_root_ = MockDevice::FakeRootParent(); |
| device_ = new HidDevice(fake_root_.get(), fake_hidbus_.GetClient()); |
| |
| // Each test is responsible for calling Bind(). |
| } |
| |
| void TearDown() override { |
| auto child = fake_root_->GetLatestChild(); |
| child->UnbindOp(); |
| EXPECT_EQ(ZX_OK, child->WaitUntilUnbindReplyCalled()); |
| EXPECT_TRUE(child->UnbindReplyCalled()); |
| } |
| |
| void SetupBootMouseDevice() { |
| size_t desc_size; |
| const uint8_t* boot_mouse_desc = get_boot_mouse_report_desc(&desc_size); |
| fake_hidbus_.SetDescriptor(boot_mouse_desc, desc_size); |
| |
| fidl::Arena<> arena; |
| fake_hidbus_.SetHidInfo(fhidbus::wire::HidInfo::Builder(arena) |
| .boot_protocol(fhidbus::wire::HidBootProtocol::kPointer) |
| .vendor_id(0xabc) |
| .product_id(123) |
| .version(5) |
| .dev_num(0) |
| .polling_rate(0) |
| .Build()); |
| } |
| |
| fbl::RefPtr<HidInstance> SetupInstanceDriver() { |
| auto endpoints = fidl::Endpoints<fuchsia_hardware_input::Device>::Create(); |
| |
| auto instance = device_->CreateInstance(fdf::Dispatcher::GetCurrent()->async_dispatcher(), |
| std::move(endpoints.server)); |
| EXPECT_OK(instance); |
| |
| sync_client_ = fidl::WireSyncClient(std::move(endpoints.client)); |
| |
| RunSyncClientTask([&]() { |
| auto result = sync_client_->GetReportsEvent(); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_TRUE(result->is_ok()); |
| report_event_ = std::move(result.value()->event); |
| }); |
| |
| return instance.is_ok() ? *instance : nullptr; |
| } |
| |
| void ReadOneReport(uint8_t* report_data, size_t report_size, size_t* returned_size) { |
| RunSyncClientTask([&]() { |
| ASSERT_OK(report_event_.wait_one(DEV_STATE_READABLE, zx::time::infinite(), nullptr)); |
| |
| auto result = sync_client_->ReadReport(); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_TRUE(result->is_ok()); |
| ASSERT_TRUE(result.value()->report.has_buf()); |
| ASSERT_TRUE(result.value()->report.buf().count() <= report_size); |
| |
| for (size_t i = 0; i < result.value()->report.buf().count(); i++) { |
| report_data[i] = result.value()->report.buf()[i]; |
| } |
| *returned_size = result.value()->report.buf().count(); |
| }); |
| } |
| |
| protected: |
| // Because this test is using a fidl::WireSyncClient, we need to run any ops on the client on |
| // their own thread because the testing thread is shared with the fidl::Server<finput::Device>. |
| static void RunSyncClientTask(fit::closure task) { |
| async::Loop loop{&kAsyncLoopConfigNeverAttachToThread}; |
| loop.StartThread(); |
| zx::result result = fdf::RunOnDispatcherSync(loop.dispatcher(), std::move(task)); |
| ASSERT_EQ(ZX_OK, result.status_value()); |
| } |
| |
| fidl::WireSyncClient<fuchsia_hardware_input::Device> sync_client_; |
| zx::event report_event_; |
| |
| HidDevice* device_; |
| |
| std::shared_ptr<MockDevice> fake_root_; |
| FakeHidbus fake_hidbus_; |
| }; |
| |
| TEST_F(HidDeviceTest, LifeTimeTest) { |
| SetupBootMouseDevice(); |
| ASSERT_OK(device_->Bind()); |
| } |
| |
| TEST_F(HidDeviceTest, TestQuery) { |
| // Ids were chosen arbitrarily. |
| constexpr uint16_t kVendorId = 0xacbd; |
| constexpr uint16_t kProductId = 0xdcba; |
| constexpr uint16_t kVersion = 0x1234; |
| |
| SetupBootMouseDevice(); |
| fidl::Arena<> arena; |
| fake_hidbus_.SetHidInfo(fhidbus::wire::HidInfo::Builder(arena) |
| .boot_protocol(fhidbus::wire::HidBootProtocol::kPointer) |
| .vendor_id(kVendorId) |
| .product_id(kProductId) |
| .version(kVersion) |
| .dev_num(0) |
| .polling_rate(0) |
| .Build()); |
| |
| ASSERT_OK(device_->Bind()); |
| |
| SetupInstanceDriver(); |
| |
| RunSyncClientTask([&]() { |
| auto result = sync_client_->Query(); |
| ASSERT_OK(result.status()); |
| |
| ASSERT_EQ(kVendorId, result.value()->info.vendor_id()); |
| ASSERT_EQ(kProductId, result.value()->info.product_id()); |
| ASSERT_EQ(kVersion, result.value()->info.version()); |
| }); |
| } |
| |
| TEST_F(HidDeviceTest, BootMouseSendReport) { |
| SetupBootMouseDevice(); |
| uint8_t mouse_report[] = {0xDE, 0xAD, 0xBE}; |
| ASSERT_OK(device_->Bind()); |
| |
| SetupInstanceDriver(); |
| |
| fake_hidbus_.SendReport(mouse_report, sizeof(mouse_report)); |
| |
| uint8_t returned_report[3] = {}; |
| size_t actual; |
| ReadOneReport(returned_report, sizeof(returned_report), &actual); |
| |
| ASSERT_EQ(actual, sizeof(returned_report)); |
| for (size_t i = 0; i < actual; i++) { |
| ASSERT_EQ(returned_report[i], mouse_report[i]); |
| } |
| } |
| |
| TEST_F(HidDeviceTest, BootMouseSendReportWithTime) { |
| SetupBootMouseDevice(); |
| uint8_t mouse_report[] = {0xDE, 0xAD, 0xBE}; |
| ASSERT_OK(device_->Bind()); |
| |
| // Regsiter a device listener |
| std::pair<sync_completion_t, zx_time_t> callback_data; |
| callback_data.second = 0xabcd; |
| |
| hid_report_listener_protocol_ops_t listener_ops; |
| listener_ops.receive_report = [](void* ctx, const uint8_t* report_list, size_t report_count, |
| zx_time_t report_time) { |
| auto callback_data = static_cast<std::pair<sync_completion_t, zx_time_t>*>(ctx); |
| ASSERT_EQ(callback_data->second, report_time); |
| sync_completion_signal(&callback_data->first); |
| }; |
| hid_report_listener_protocol_t listener = {&listener_ops, &callback_data}; |
| device_->HidDeviceRegisterListener(&listener); |
| |
| fake_hidbus_.SendReportWithTime(mouse_report, sizeof(mouse_report), callback_data.second); |
| RunSyncClientTask([&callback_data]() { |
| sync_completion_wait_deadline(&callback_data.first, zx::time::infinite().get()); |
| }); |
| ASSERT_NO_FATAL_FAILURE(); |
| } |
| |
| TEST_F(HidDeviceTest, BootMouseSendReportInPieces) { |
| SetupBootMouseDevice(); |
| uint8_t mouse_report[] = {0xDE, 0xAD, 0xBE}; |
| ASSERT_OK(device_->Bind()); |
| |
| SetupInstanceDriver(); |
| |
| fake_hidbus_.SendReport(&mouse_report[0], sizeof(uint8_t)); |
| fake_hidbus_.SendReport(&mouse_report[1], sizeof(uint8_t)); |
| fake_hidbus_.SendReport(&mouse_report[2], sizeof(uint8_t)); |
| |
| uint8_t returned_report[3] = {}; |
| size_t actual; |
| ReadOneReport(returned_report, sizeof(returned_report), &actual); |
| |
| ASSERT_EQ(actual, sizeof(returned_report)); |
| for (size_t i = 0; i < actual; i++) { |
| ASSERT_EQ(returned_report[i], mouse_report[i]); |
| } |
| } |
| |
| TEST_F(HidDeviceTest, BootMouseSendMultipleReports) { |
| SetupBootMouseDevice(); |
| uint8_t double_mouse_report[] = {0xDE, 0xAD, 0xBE, 0x12, 0x34, 0x56}; |
| ASSERT_OK(device_->Bind()); |
| |
| SetupInstanceDriver(); |
| |
| fake_hidbus_.SendReport(double_mouse_report, sizeof(double_mouse_report)); |
| |
| uint8_t returned_report[3] = {}; |
| size_t actual; |
| |
| // Read the first report. |
| ReadOneReport(returned_report, sizeof(returned_report), &actual); |
| ASSERT_EQ(actual, sizeof(returned_report)); |
| for (size_t i = 0; i < actual; i++) { |
| ASSERT_EQ(returned_report[i], double_mouse_report[i]); |
| } |
| |
| // Read the second report. |
| ReadOneReport(returned_report, sizeof(returned_report), &actual); |
| ASSERT_EQ(actual, sizeof(returned_report)); |
| for (size_t i = 0; i < actual; i++) { |
| ASSERT_EQ(returned_report[i], double_mouse_report[i + 3]); |
| } |
| } |
| |
| TEST(HidDeviceTest, FailToRegister) { |
| FakeHidbus fake_hidbus; |
| auto fake_root = MockDevice::FakeRootParent(); |
| |
| fidl::Arena<> arena; |
| fake_hidbus.SetHidInfo(fhidbus::wire::HidInfo::Builder(arena) |
| .boot_protocol(fhidbus::wire::HidBootProtocol::kOther) |
| .vendor_id(0) |
| .product_id(0) |
| .version(0) |
| .dev_num(0) |
| .polling_rate(0) |
| .Build()); |
| fake_hidbus.SetStartStatus(ZX_ERR_INTERNAL); |
| HidDevice device(fake_root.get(), fake_hidbus.GetClient()); |
| |
| ASSERT_EQ(device.Bind(), ZX_ERR_INTERNAL); |
| } |
| |
| TEST_F(HidDeviceTest, ReadReportSingleReport) { |
| SetupBootMouseDevice(); |
| ASSERT_OK(device_->Bind()); |
| |
| uint8_t mouse_report[] = {0xDE, 0xAD, 0xBE}; |
| |
| SetupInstanceDriver(); |
| |
| // Send the reports. |
| zx_time_t time = 0xabcd; |
| fake_hidbus_.SendReportWithTime(mouse_report, sizeof(mouse_report), time); |
| |
| RunSyncClientTask([&]() { |
| auto result = sync_client_->ReadReport(); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_TRUE(result->is_ok()); |
| ASSERT_EQ(time, result.value()->report.timestamp()); |
| ASSERT_EQ(sizeof(mouse_report), result.value()->report.buf().count()); |
| for (size_t i = 0; i < result.value()->report.buf().count(); i++) { |
| EXPECT_EQ(mouse_report[i], result.value()->report.buf()[i]); |
| } |
| }); |
| |
| RunSyncClientTask([&]() { |
| auto result = sync_client_->ReadReport(); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_TRUE(result->is_error()); |
| ASSERT_EQ(result->error_value(), ZX_ERR_SHOULD_WAIT); |
| }); |
| } |
| |
| TEST_F(HidDeviceTest, ReadReportDoubleReport) { |
| SetupBootMouseDevice(); |
| ASSERT_OK(device_->Bind()); |
| |
| uint8_t double_mouse_report[] = {0xDE, 0xAD, 0xBE, 0x12, 0x34, 0x56}; |
| |
| SetupInstanceDriver(); |
| |
| // Send the reports. |
| zx_time_t time = 0xabcd; |
| fake_hidbus_.SendReportWithTime(double_mouse_report, sizeof(double_mouse_report), time); |
| |
| RunSyncClientTask([&]() { |
| auto result = sync_client_->ReadReport(); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_TRUE(result->is_ok()); |
| ASSERT_EQ(time, result.value()->report.timestamp()); |
| ASSERT_EQ(sizeof(hid_boot_mouse_report_t), result.value()->report.buf().count()); |
| for (size_t i = 0; i < result.value()->report.buf().count(); i++) { |
| EXPECT_EQ(double_mouse_report[i], result.value()->report.buf()[i]); |
| } |
| }); |
| |
| RunSyncClientTask([&]() { |
| auto result = sync_client_->ReadReport(); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_TRUE(result->is_ok()); |
| ASSERT_EQ(time, result.value()->report.timestamp()); |
| ASSERT_EQ(sizeof(hid_boot_mouse_report_t), result.value()->report.buf().count()); |
| for (size_t i = 0; i < result.value()->report.buf().count(); i++) { |
| EXPECT_EQ(double_mouse_report[i + sizeof(hid_boot_mouse_report_t)], |
| result.value()->report.buf()[i]); |
| } |
| }); |
| |
| RunSyncClientTask([&]() { |
| auto result = sync_client_->ReadReport(); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_TRUE(result->is_error()); |
| ASSERT_EQ(result->error_value(), ZX_ERR_SHOULD_WAIT); |
| }); |
| } |
| |
| TEST_F(HidDeviceTest, ReadReportsSingleReport) { |
| SetupBootMouseDevice(); |
| ASSERT_OK(device_->Bind()); |
| |
| uint8_t mouse_report[] = {0xDE, 0xAD, 0xBE}; |
| |
| SetupInstanceDriver(); |
| |
| // Send the reports. |
| fake_hidbus_.SendReport(mouse_report, sizeof(mouse_report)); |
| |
| RunSyncClientTask([&]() { |
| auto result = sync_client_->ReadReports(); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_TRUE(result->is_ok()); |
| ASSERT_EQ(sizeof(mouse_report), result.value()->data.count()); |
| for (size_t i = 0; i < result.value()->data.count(); i++) { |
| EXPECT_EQ(mouse_report[i], result.value()->data[i]); |
| } |
| }); |
| } |
| |
| TEST_F(HidDeviceTest, ReadReportsDoubleReport) { |
| SetupBootMouseDevice(); |
| ASSERT_OK(device_->Bind()); |
| |
| uint8_t double_mouse_report[] = {0xDE, 0xAD, 0xBE, 0x12, 0x34, 0x56}; |
| |
| SetupInstanceDriver(); |
| |
| // Send the reports. |
| fake_hidbus_.SendReport(double_mouse_report, sizeof(double_mouse_report)); |
| |
| RunSyncClientTask([&]() { |
| auto result = sync_client_->ReadReports(); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_TRUE(result->is_ok()); |
| ASSERT_EQ(sizeof(double_mouse_report), result.value()->data.count()); |
| for (size_t i = 0; i < result.value()->data.count(); i++) { |
| EXPECT_EQ(double_mouse_report[i], result.value()->data[i]); |
| } |
| }); |
| } |
| |
| TEST_F(HidDeviceTest, ReadReportsBlockingWait) { |
| SetupBootMouseDevice(); |
| ASSERT_OK(device_->Bind()); |
| |
| SetupInstanceDriver(); |
| |
| // Send the reports, but delayed. |
| uint8_t mouse_report[] = {0xDE, 0xAD, 0xBE}; |
| std::thread report_thread([&]() { |
| sleep(1); |
| fake_hidbus_.SendReport(mouse_report, sizeof(mouse_report)); |
| }); |
| |
| // Get the report. |
| RunSyncClientTask([&]() { |
| ASSERT_OK(report_event_.wait_one(DEV_STATE_READABLE, zx::time::infinite(), nullptr)); |
| |
| auto result = sync_client_->ReadReports(); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_TRUE(result->is_ok()); |
| ASSERT_EQ(sizeof(mouse_report), result.value()->data.count()); |
| for (size_t i = 0; i < result.value()->data.count(); i++) { |
| EXPECT_EQ(mouse_report[i], result.value()->data[i]); |
| } |
| |
| report_thread.join(); |
| }); |
| } |
| |
| // Test that only whole reports get sent through. |
| TEST_F(HidDeviceTest, ReadReportsOneAndAHalfReports) { |
| SetupBootMouseDevice(); |
| ASSERT_OK(device_->Bind()); |
| |
| SetupInstanceDriver(); |
| |
| // Send the report. |
| uint8_t mouse_report[] = {0xDE, 0xAD, 0xBE}; |
| fake_hidbus_.SendReport(mouse_report, sizeof(mouse_report)); |
| |
| // Send a half of a report. |
| uint8_t half_report[] = {0xDE, 0xAD}; |
| fake_hidbus_.SendReport(half_report, sizeof(half_report)); |
| |
| RunSyncClientTask([&]() { |
| auto result = sync_client_->ReadReports(); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_TRUE(result->is_ok()); |
| ASSERT_EQ(sizeof(mouse_report), result.value()->data.count()); |
| for (size_t i = 0; i < result.value()->data.count(); i++) { |
| EXPECT_EQ(mouse_report[i], result.value()->data[i]); |
| } |
| }); |
| } |
| |
| // This tests that we can set the boot mode for a non-boot device, and that the device will |
| // have it's report descriptor set to the boot mode descriptor. For this, we will take an |
| // arbitrary descriptor and claim that it can be set to a boot-mode keyboard. We then |
| // test that the report descriptor we get back is for the boot keyboard. |
| // (The descriptor doesn't matter, as long as a device claims its a boot device it should |
| // support this transformation in hardware). |
| TEST_F(HidDeviceTest, SettingBootModeMouse) { |
| size_t desc_size; |
| const uint8_t* desc = get_paradise_touchpad_v1_report_desc(&desc_size); |
| fake_hidbus_.SetDescriptor(desc, desc_size); |
| |
| fidl::Arena<> arena; |
| fake_hidbus_.SetHidInfo(fhidbus::wire::HidInfo::Builder(arena) |
| .boot_protocol(fhidbus::wire::HidBootProtocol::kPointer) |
| .vendor_id(0) |
| .product_id(0) |
| .version(0) |
| .dev_num(0) |
| .polling_rate(0) |
| .Build()); |
| |
| // Set the device to boot protocol. |
| fake_hidbus_.SetProtocol(fhidbus::wire::HidProtocol::kBoot); |
| |
| ASSERT_OK(device_->Bind()); |
| |
| size_t boot_mouse_desc_size; |
| const uint8_t* boot_mouse_desc = get_boot_mouse_report_desc(&boot_mouse_desc_size); |
| ASSERT_EQ(boot_mouse_desc_size, device_->GetReportDescLen()); |
| const uint8_t* received_desc = device_->GetReportDesc(); |
| for (size_t i = 0; i < boot_mouse_desc_size; i++) { |
| ASSERT_EQ(boot_mouse_desc[i], received_desc[i]); |
| } |
| } |
| |
| // This tests that we can set the boot mode for a non-boot device, and that the device will |
| // have it's report descriptor set to the boot mode descriptor. For this, we will take an |
| // arbitrary descriptor and claim that it can be set to a boot-mode keyboard. We then |
| // test that the report descriptor we get back is for the boot keyboard. |
| // (The descriptor doesn't matter, as long as a device claims its a boot device it should |
| // support this transformation in hardware). |
| TEST_F(HidDeviceTest, SettingBootModeKbd) { |
| size_t desc_size; |
| const uint8_t* desc = get_paradise_touchpad_v1_report_desc(&desc_size); |
| fake_hidbus_.SetDescriptor(desc, desc_size); |
| |
| fidl::Arena<> arena; |
| fake_hidbus_.SetHidInfo(fhidbus::wire::HidInfo::Builder(arena) |
| .boot_protocol(fhidbus::wire::HidBootProtocol::kKbd) |
| .vendor_id(0) |
| .product_id(0) |
| .version(0) |
| .dev_num(0) |
| .polling_rate(0) |
| .Build()); |
| |
| // Set the device to boot protocol. |
| fake_hidbus_.SetProtocol(fhidbus::wire::HidProtocol::kBoot); |
| |
| ASSERT_OK(device_->Bind()); |
| |
| size_t boot_kbd_desc_size; |
| const uint8_t* boot_kbd_desc = get_boot_kbd_report_desc(&boot_kbd_desc_size); |
| ASSERT_EQ(boot_kbd_desc_size, device_->GetReportDescLen()); |
| const uint8_t* received_desc = device_->GetReportDesc(); |
| for (size_t i = 0; i < boot_kbd_desc_size; i++) { |
| ASSERT_EQ(boot_kbd_desc[i], received_desc[i]); |
| } |
| } |
| |
| TEST_F(HidDeviceTest, GetHidDeviceInfo) { |
| SetupBootMouseDevice(); |
| ASSERT_OK(device_->Bind()); |
| |
| hid_device_info_t info; |
| device_->HidDeviceGetHidDeviceInfo(&info); |
| |
| auto hidbus_info = fake_hidbus_.Query(); |
| ASSERT_EQ(hidbus_info.vendor_id(), info.vendor_id); |
| ASSERT_EQ(hidbus_info.product_id(), info.product_id); |
| ASSERT_EQ(hidbus_info.version(), info.version); |
| } |
| |
| TEST_F(HidDeviceTest, GetDescriptor) { |
| SetupBootMouseDevice(); |
| ASSERT_OK(device_->Bind()); |
| |
| size_t known_size; |
| const uint8_t* known_descriptor = get_boot_mouse_report_desc(&known_size); |
| |
| uint8_t report_descriptor[HID_MAX_DESC_LEN]; |
| size_t actual; |
| ASSERT_OK(device_->HidDeviceGetDescriptor(report_descriptor, sizeof(report_descriptor), &actual)); |
| |
| ASSERT_EQ(known_size, actual); |
| ASSERT_BYTES_EQ(known_descriptor, report_descriptor, known_size); |
| } |
| |
| TEST_F(HidDeviceTest, RegisterListenerSendReport) { |
| SetupBootMouseDevice(); |
| ASSERT_OK(device_->Bind()); |
| |
| uint8_t mouse_report[] = {0xDE, 0xAD, 0xBE}; |
| |
| struct ReportCtx { |
| sync_completion_t* completion; |
| uint8_t* known_report; |
| }; |
| |
| sync_completion_t seen_report; |
| ReportCtx ctx; |
| ctx.completion = &seen_report; |
| ctx.known_report = mouse_report; |
| |
| hid_report_listener_protocol_ops_t ops; |
| ops.receive_report = [](void* ctx, const uint8_t* report_list, size_t report_count, |
| zx_time_t time) { |
| ASSERT_EQ(sizeof(mouse_report), report_count); |
| auto report_ctx = reinterpret_cast<ReportCtx*>(ctx); |
| ASSERT_BYTES_EQ(report_ctx->known_report, report_list, report_count); |
| sync_completion_signal(report_ctx->completion); |
| }; |
| |
| hid_report_listener_protocol_t listener; |
| listener.ctx = &ctx; |
| listener.ops = &ops; |
| |
| ASSERT_OK(device_->HidDeviceRegisterListener(&listener)); |
| |
| fake_hidbus_.SendReport(mouse_report, sizeof(mouse_report)); |
| |
| RunSyncClientTask([&seen_report]() { |
| ASSERT_OK(sync_completion_wait(&seen_report, zx::time::infinite().get())); |
| }); |
| device_->HidDeviceUnregisterListener(); |
| } |
| |
| TEST_F(HidDeviceTest, GetSetReport) { |
| const uint8_t* desc; |
| size_t desc_size = get_ambient_light_report_desc(&desc); |
| fake_hidbus_.SetDescriptor(desc, desc_size); |
| |
| fidl::Arena<> arena; |
| fake_hidbus_.SetHidInfo(fhidbus::wire::HidInfo::Builder(arena) |
| .boot_protocol(fhidbus::wire::HidBootProtocol::kNone) |
| .vendor_id(0) |
| .product_id(0) |
| .version(0) |
| .dev_num(0) |
| .polling_rate(0) |
| .Build()); |
| |
| ASSERT_OK(device_->Bind()); |
| |
| ambient_light_feature_rpt_t feature_report = {}; |
| feature_report.rpt_id = AMBIENT_LIGHT_RPT_ID_FEATURE; |
| // Below value are chosen arbitrarily. |
| feature_report.state = 100; |
| feature_report.interval_ms = 50; |
| feature_report.threshold_high = 40; |
| feature_report.threshold_low = 10; |
| |
| ASSERT_OK(device_->HidDeviceSetReport(HID_REPORT_TYPE_FEATURE, AMBIENT_LIGHT_RPT_ID_FEATURE, |
| reinterpret_cast<uint8_t*>(&feature_report), |
| sizeof(feature_report))); |
| |
| ambient_light_feature_rpt_t received_report = {}; |
| size_t actual; |
| ASSERT_OK(device_->HidDeviceGetReport(HID_REPORT_TYPE_FEATURE, AMBIENT_LIGHT_RPT_ID_FEATURE, |
| reinterpret_cast<uint8_t*>(&received_report), |
| sizeof(received_report), &actual)); |
| |
| ASSERT_EQ(sizeof(received_report), actual); |
| ASSERT_BYTES_EQ(&feature_report, &received_report, actual); |
| } |
| |
| // Tests that a device with too large reports don't cause buffer overruns. |
| TEST_F(HidDeviceTest, GetReportBufferOverrun) { |
| const uint8_t desc[] = { |
| 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) |
| 0x09, 0x02, // Usage (Mouse) |
| 0xA1, 0x01, // Collection (Application) |
| 0x05, 0x09, // Usage Page (Button) |
| 0x09, 0x30, // Usage (0x30) |
| 0x97, 0x00, 0xF0, 0x00, 0x00, // Report Count (65279) |
| 0x75, 0x08, // Report Size (8) |
| 0x25, 0x01, // Logical Maximum (1) |
| 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) |
| 0xC0, // End Collection |
| |
| // 22 bytes |
| }; |
| size_t desc_size = sizeof(desc); |
| fake_hidbus_.SetDescriptor(desc, desc_size); |
| |
| fidl::Arena<> arena; |
| fake_hidbus_.SetHidInfo(fhidbus::wire::HidInfo::Builder(arena) |
| .boot_protocol(fhidbus::wire::HidBootProtocol::kNone) |
| .vendor_id(0) |
| .product_id(0) |
| .version(0) |
| .dev_num(0) |
| .polling_rate(0) |
| .Build()); |
| |
| ASSERT_OK(device_->Bind()); |
| |
| std::vector<uint8_t> report(0xFF0000); |
| size_t actual; |
| ASSERT_EQ( |
| device_->HidDeviceGetReport(HID_REPORT_TYPE_INPUT, 0, report.data(), report.size(), &actual), |
| ZX_ERR_INTERNAL); |
| } |
| |
| TEST_F(HidDeviceTest, DeviceReportReaderSingleReport) { |
| SetupBootMouseDevice(); |
| ASSERT_OK(device_->Bind()); |
| |
| uint8_t mouse_report[] = {0xDE, 0xAD, 0xBE}; |
| |
| SetupInstanceDriver(); |
| |
| fidl::WireSyncClient<fuchsia_hardware_input::DeviceReportsReader> reader; |
| { |
| auto endpoints = fidl::Endpoints<fuchsia_hardware_input::DeviceReportsReader>::Create(); |
| |
| RunSyncClientTask([&]() { |
| auto result = sync_client_->GetDeviceReportsReader(std::move(endpoints.server)); |
| ASSERT_OK(result.status()); |
| reader = fidl::WireSyncClient<fuchsia_hardware_input::DeviceReportsReader>( |
| std::move(endpoints.client)); |
| }); |
| } |
| |
| // Send the reports. |
| fake_hidbus_.SendReport(mouse_report, sizeof(mouse_report)); |
| |
| RunSyncClientTask([&]() { |
| auto response = reader->ReadReports(); |
| ASSERT_OK(response.status()); |
| ASSERT_FALSE(response->is_error()); |
| auto result = response->value(); |
| ASSERT_EQ(result->reports.count(), 1); |
| ASSERT_EQ(result->reports[0].buf().count(), sizeof(mouse_report)); |
| for (size_t i = 0; i < result->reports[0].buf().count(); i++) { |
| EXPECT_EQ(mouse_report[i], result->reports[0].buf()[i]); |
| } |
| }); |
| } |
| |
| TEST_F(HidDeviceTest, DeviceReportReaderDoubleReport) { |
| SetupBootMouseDevice(); |
| ASSERT_OK(device_->Bind()); |
| |
| uint8_t mouse_report[] = {0xDE, 0xAD, 0xBE}; |
| uint8_t mouse_report_two[] = {0xDE, 0xAD, 0xBE}; |
| |
| auto instance = SetupInstanceDriver(); |
| |
| fidl::WireSyncClient<fuchsia_hardware_input::DeviceReportsReader> reader; |
| { |
| auto endpoints = fidl::Endpoints<fuchsia_hardware_input::DeviceReportsReader>::Create(); |
| |
| RunSyncClientTask([&]() { |
| auto result = sync_client_->GetDeviceReportsReader(std::move(endpoints.server)); |
| ASSERT_OK(result.status()); |
| reader = fidl::WireSyncClient<fuchsia_hardware_input::DeviceReportsReader>( |
| std::move(endpoints.client)); |
| }); |
| } |
| |
| // Send the reports. |
| // Note: testing_write_to_fifo_called_ ensures that we wait for both reports to be written as |
| // ReadReports only waits for one. |
| // TODO(b/341791565): Refactor tests so that testing_write_to_fifo_called_ is not needed. |
| fake_hidbus_.SendReport(mouse_report, sizeof(mouse_report)); |
| RunSyncClientTask([&instance]() { |
| instance->testing_write_to_fifo_called_.Wait(); |
| instance->testing_write_to_fifo_called_.Reset(); |
| }); |
| fake_hidbus_.SendReport(mouse_report_two, sizeof(mouse_report_two)); |
| RunSyncClientTask([&instance]() { instance->testing_write_to_fifo_called_.Wait(); }); |
| |
| RunSyncClientTask([&]() { |
| auto response = reader->ReadReports(); |
| ASSERT_OK(response.status()); |
| ASSERT_FALSE(response->is_error()); |
| auto result = response->value(); |
| ASSERT_EQ(result->reports.count(), 2); |
| ASSERT_EQ(result->reports[0].buf().count(), sizeof(mouse_report)); |
| for (size_t i = 0; i < result->reports[0].buf().count(); i++) { |
| EXPECT_EQ(mouse_report[i], result->reports[0].buf()[i]); |
| } |
| ASSERT_EQ(result->reports[1].buf().count(), sizeof(mouse_report)); |
| for (size_t i = 0; i < result->reports[1].buf().count(); i++) { |
| EXPECT_EQ(mouse_report[i], result->reports[1].buf()[i]); |
| } |
| }); |
| } |
| |
| TEST_F(HidDeviceTest, DeviceReportReaderTwoClients) { |
| SetupBootMouseDevice(); |
| ASSERT_OK(device_->Bind()); |
| |
| uint8_t mouse_report[] = {0xDE, 0xAD, 0xBE}; |
| |
| SetupInstanceDriver(); |
| |
| fidl::WireSyncClient<fuchsia_hardware_input::DeviceReportsReader> reader1; |
| fidl::WireSyncClient<fuchsia_hardware_input::DeviceReportsReader> reader2; |
| { |
| auto endpoints1 = fidl::Endpoints<fuchsia_hardware_input::DeviceReportsReader>::Create(); |
| |
| RunSyncClientTask([&]() { |
| auto result1 = sync_client_->GetDeviceReportsReader(std::move(endpoints1.server)); |
| ASSERT_OK(result1.status()); |
| reader1 = fidl::WireSyncClient<fuchsia_hardware_input::DeviceReportsReader>( |
| std::move(endpoints1.client)); |
| }); |
| |
| auto endpoints2 = fidl::Endpoints<fuchsia_hardware_input::DeviceReportsReader>::Create(); |
| |
| RunSyncClientTask([&]() { |
| auto result2 = sync_client_->GetDeviceReportsReader(std::move(endpoints2.server)); |
| ASSERT_OK(result2.status()); |
| reader2 = fidl::WireSyncClient<fuchsia_hardware_input::DeviceReportsReader>( |
| std::move(endpoints2.client)); |
| }); |
| } |
| |
| // Send the report. |
| fake_hidbus_.SendReport(mouse_report, sizeof(mouse_report)); |
| |
| RunSyncClientTask([&]() { |
| auto response = reader1->ReadReports(); |
| ASSERT_OK(response.status()); |
| ASSERT_FALSE(response->is_error()); |
| auto result = response->value(); |
| ASSERT_EQ(result->reports.count(), 1); |
| ASSERT_EQ(result->reports[0].buf().count(), sizeof(mouse_report)); |
| for (size_t i = 0; i < result->reports[0].buf().count(); i++) { |
| EXPECT_EQ(mouse_report[i], result->reports[0].buf()[i]); |
| } |
| }); |
| |
| RunSyncClientTask([&]() { |
| auto response = reader2->ReadReports(); |
| ASSERT_OK(response.status()); |
| ASSERT_FALSE(response->is_error()); |
| auto result = response->value(); |
| ASSERT_EQ(result->reports.count(), 1); |
| ASSERT_EQ(result->reports[0].buf().count(), sizeof(mouse_report)); |
| for (size_t i = 0; i < result->reports[0].buf().count(); i++) { |
| EXPECT_EQ(mouse_report[i], result->reports[0].buf()[i]); |
| } |
| }); |
| } |
| |
| // Test that only whole reports get sent through. |
| TEST_F(HidDeviceTest, DeviceReportReaderOneAndAHalfReports) { |
| SetupBootMouseDevice(); |
| ASSERT_OK(device_->Bind()); |
| |
| SetupInstanceDriver(); |
| |
| fidl::WireSyncClient<fuchsia_hardware_input::DeviceReportsReader> reader; |
| { |
| auto endpoints = fidl::Endpoints<fuchsia_hardware_input::DeviceReportsReader>::Create(); |
| |
| RunSyncClientTask([&]() { |
| auto result = sync_client_->GetDeviceReportsReader(std::move(endpoints.server)); |
| ASSERT_OK(result.status()); |
| reader = fidl::WireSyncClient<fuchsia_hardware_input::DeviceReportsReader>( |
| std::move(endpoints.client)); |
| }); |
| } |
| |
| // Send the report. |
| uint8_t mouse_report[] = {0xDE, 0xAD, 0xBE}; |
| fake_hidbus_.SendReport(mouse_report, sizeof(mouse_report)); |
| |
| // Send a half of a report. |
| uint8_t half_report[] = {0xDE, 0xAD}; |
| fake_hidbus_.SendReport(half_report, sizeof(half_report)); |
| |
| RunSyncClientTask([&]() { |
| auto response = reader->ReadReports(); |
| ASSERT_OK(response.status()); |
| ASSERT_FALSE(response->is_error()); |
| auto result = response->value(); |
| ASSERT_EQ(result->reports.count(), 1); |
| ASSERT_EQ(sizeof(mouse_report), result->reports[0].buf().count()); |
| for (size_t i = 0; i < result->reports[0].buf().count(); i++) { |
| EXPECT_EQ(mouse_report[i], result->reports[0].buf()[i]); |
| } |
| }); |
| } |
| |
| TEST_F(HidDeviceTest, DeviceReportReaderHangingGet) { |
| SetupBootMouseDevice(); |
| ASSERT_OK(device_->Bind()); |
| |
| uint8_t mouse_report[] = {0xDE, 0xAD, 0xBE}; |
| |
| SetupInstanceDriver(); |
| |
| fidl::WireSyncClient<fuchsia_hardware_input::DeviceReportsReader> reader; |
| { |
| auto endpoints = fidl::Endpoints<fuchsia_hardware_input::DeviceReportsReader>::Create(); |
| |
| RunSyncClientTask([&]() { |
| auto result = sync_client_->GetDeviceReportsReader(std::move(endpoints.server)); |
| ASSERT_OK(result.status()); |
| reader = fidl::WireSyncClient<fuchsia_hardware_input::DeviceReportsReader>( |
| std::move(endpoints.client)); |
| }); |
| } |
| |
| // Send the reports, but delayed. |
| std::thread report_thread([&]() { |
| sleep(1); |
| fake_hidbus_.SendReport(mouse_report, sizeof(mouse_report)); |
| }); |
| |
| RunSyncClientTask([&]() { |
| auto response = reader->ReadReports(); |
| ASSERT_OK(response.status()); |
| ASSERT_FALSE(response->is_error()); |
| auto result = response->value(); |
| ASSERT_EQ(result->reports.count(), 1); |
| ASSERT_EQ(result->reports[0].buf().count(), sizeof(mouse_report)); |
| for (size_t i = 0; i < result->reports[0].buf().count(); i++) { |
| EXPECT_EQ(mouse_report[i], result->reports[0].buf()[i]); |
| } |
| |
| report_thread.join(); |
| }); |
| } |
| |
| } // namespace hid_driver |