| // 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 "adb-function.h" |
| |
| #include <fidl/fuchsia.hardware.adb/cpp/fidl.h> |
| #include <fuchsia/hardware/usb/function/cpp/banjo-mock.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/async/default.h> |
| #include <lib/driver/outgoing/cpp/outgoing_directory.h> |
| #include <lib/sync/completion.h> |
| |
| #include <map> |
| #include <vector> |
| |
| #include <usb/usb-request.h> |
| #include <zxtest/zxtest.h> |
| |
| #include "src/devices/testing/mock-ddk/mock-device.h" |
| #include "src/devices/usb/lib/usb-endpoint/testing/fake-usb-endpoint-server.h" |
| |
| bool operator==(const usb_request_complete_callback_t& lhs, |
| const usb_request_complete_callback_t& rhs) { |
| // Comparison of these struct is not useful. Return true always. |
| return true; |
| } |
| |
| bool operator==(const usb_ss_ep_comp_descriptor_t& lhs, const usb_ss_ep_comp_descriptor_t& rhs) { |
| // Comparison of these struct is not useful. Return true always. |
| return true; |
| } |
| |
| bool operator==(const usb_endpoint_descriptor_t& lhs, const usb_endpoint_descriptor_t& rhs) { |
| // Comparison of these struct is not useful. Return true always. |
| return true; |
| } |
| |
| bool operator==(const usb_request_t& lhs, const usb_request_t& rhs) { |
| // Only comparing endpoint address. Use ExpectCallWithMatcher for more specific |
| // comparisons. |
| return lhs.header.ep_address == rhs.header.ep_address; |
| } |
| |
| bool operator==(const usb_function_interface_protocol_t& lhs, |
| const usb_function_interface_protocol_t& rhs) { |
| // Comparison of these struct is not useful. Return true always. |
| return true; |
| } |
| |
| namespace usb_adb_function { |
| |
| typedef struct { |
| usb_request_t* usb_request; |
| const usb_request_complete_callback_t* complete_cb; |
| } mock_usb_request_t; |
| |
| class MockUsbFunction : public ddk::MockUsbFunction { |
| public: |
| zx_status_t UsbFunctionCancelAll(uint8_t ep_address) override { |
| while (!usb_request_queues[ep_address].empty()) { |
| const mock_usb_request_t r = usb_request_queues[ep_address].back(); |
| r.complete_cb->callback(r.complete_cb->ctx, r.usb_request); |
| usb_request_queues[ep_address].pop_back(); |
| } |
| return ddk::MockUsbFunction::UsbFunctionCancelAll(ep_address); |
| } |
| |
| zx_status_t UsbFunctionSetInterface(const usb_function_interface_protocol_t* interface) override { |
| // Overriding method to store the interface passed. |
| function = *interface; |
| return ddk::MockUsbFunction::UsbFunctionSetInterface(interface); |
| } |
| |
| zx_status_t UsbFunctionConfigEp(const usb_endpoint_descriptor_t* ep_desc, |
| const usb_ss_ep_comp_descriptor_t* ss_comp_desc) override { |
| // Overriding method to handle valid cases where nullptr is passed. The generated mock tries to |
| // dereference it without checking. |
| usb_endpoint_descriptor_t ep{}; |
| usb_ss_ep_comp_descriptor_t ss{}; |
| const usb_endpoint_descriptor_t* arg1 = ep_desc ? ep_desc : &ep; |
| const usb_ss_ep_comp_descriptor_t* arg2 = ss_comp_desc ? ss_comp_desc : &ss; |
| return ddk::MockUsbFunction::UsbFunctionConfigEp(arg1, arg2); |
| } |
| |
| void UsbFunctionRequestQueue(usb_request_t* usb_request, |
| const usb_request_complete_callback_t* complete_cb) override { |
| // Override to store requests. |
| const uint8_t ep = usb_request->header.ep_address; |
| auto queue = usb_request_queues.find(ep); |
| if (queue == usb_request_queues.end()) { |
| usb_request_queues[ep] = {}; |
| } |
| usb_request_queues[ep].push_back({usb_request, complete_cb}); |
| mock_request_queue_.Call(*usb_request, *complete_cb); |
| } |
| |
| usb_function_interface_protocol_t function; |
| // Store request queues for each endpoint. |
| std::map<uint8_t, std::vector<mock_usb_request_t>> usb_request_queues; |
| }; |
| |
| struct IncomingNamespace { |
| component::OutgoingDirectory outgoing{async_get_default_dispatcher()}; |
| fake_usb_endpoint::FakeUsbFidlProvider<fuchsia_hardware_usb_function::UsbFunction> fake_dev{ |
| async_get_default_dispatcher()}; |
| fidl::ServerBindingGroup<fuchsia_hardware_usb_function::UsbFunction> usb_function_bindings_; |
| }; |
| |
| class UsbAdbTest : public zxtest::Test { |
| private: |
| class FakeAdbDaemon; |
| |
| public: |
| static constexpr uint32_t kBulkOutEp = 1; |
| static constexpr uint32_t kBulkInEp = 2; |
| static constexpr uint32_t kBulkTxRxCount = 2; |
| static constexpr uint32_t kVmoDataSize = 10; |
| |
| std::unique_ptr<FakeAdbDaemon> CreateFakeAdbDaemon() { |
| auto endpoints = fidl::CreateEndpoints<fadb::UsbAdbImpl>(); |
| EXPECT_TRUE(endpoints.is_ok()); |
| |
| { |
| auto adb_endpoints = fidl::CreateEndpoints<fadb::Device>(); |
| EXPECT_TRUE(endpoints.is_ok()); |
| std::optional<fidl::ServerBinding<fadb::Device>> binding; |
| EXPECT_OK(fdf::RunOnDispatcherSync( |
| adb_dispatcher_->async_dispatcher(), [&adb_endpoints, &binding, this]() { |
| binding.emplace(fdf::Dispatcher::GetCurrent()->async_dispatcher(), |
| std::move(adb_endpoints->server), |
| dev_->GetDeviceContext<UsbAdbDevice>(), fidl::kIgnoreBindingClosure); |
| })); |
| EXPECT_OK(fidl::WireCall(adb_endpoints->client)->Start(std::move(endpoints->server))); |
| EXPECT_OK(fdf::RunOnDispatcherSync(adb_dispatcher_->async_dispatcher(), |
| [&binding]() { binding.reset(); })); |
| } |
| |
| return std::make_unique<FakeAdbDaemon>(std::move(endpoints->client)); |
| } |
| |
| void Configure() { |
| // Call set_configured of usb adb to bring the interface online. |
| mock_usb_.ExpectConfigEp(ZX_OK, {}, {}); |
| mock_usb_.ExpectConfigEp(ZX_OK, {}, {}); |
| mock_usb_.function.ops->set_configured(mock_usb_.function.ctx, true, USB_SPEED_FULL); |
| configured_ = true; |
| } |
| |
| void SendTestData(std::unique_ptr<FakeAdbDaemon>& fake_adb, size_t size); |
| |
| private: |
| void SetUp() override { |
| ASSERT_EQ(ZX_OK, incoming_loop_.StartThread("incoming-ns-thread")); |
| |
| parent_->AddProtocol(ZX_PROTOCOL_USB_FUNCTION, mock_usb_.GetProto()->ops, |
| mock_usb_.GetProto()->ctx); |
| auto endpoints = fidl::Endpoints<fuchsia_io::Directory>::Create(); |
| incoming_.SyncCall([server = std::move(endpoints.server)](IncomingNamespace* infra) mutable { |
| ASSERT_OK( |
| infra->outgoing.template AddService<fuchsia_hardware_usb_function::UsbFunctionService>( |
| fuchsia_hardware_usb_function::UsbFunctionService::InstanceHandler({ |
| .device = infra->usb_function_bindings_.CreateHandler( |
| &infra->fake_dev, async_get_default_dispatcher(), |
| fidl::kIgnoreBindingClosure), |
| }))); |
| |
| ASSERT_OK(infra->outgoing.Serve(std::move(server))); |
| }); |
| parent_->AddFidlService(fuchsia_hardware_usb_function::UsbFunctionService::Name, |
| std::move(endpoints.client)); |
| |
| // Expect calls from UsbAdbDevice initialization |
| mock_usb_.ExpectAllocInterface(ZX_OK, 1); |
| mock_usb_.ExpectAllocEp(ZX_OK, USB_DIR_OUT, kBulkOutEp); |
| mock_usb_.ExpectAllocEp(ZX_OK, USB_DIR_IN, kBulkInEp); |
| mock_usb_.ExpectSetInterface(ZX_OK, {}); |
| incoming_.SyncCall([](IncomingNamespace* infra) { |
| infra->fake_dev.ExpectConnectToEndpoint(kBulkOutEp); |
| infra->fake_dev.ExpectConnectToEndpoint(kBulkInEp); |
| }); |
| UsbAdbDevice* dev; |
| ASSERT_OK(fdf::RunOnDispatcherSync(adb_dispatcher_->async_dispatcher(), [&]() { |
| auto adb = std::make_unique<UsbAdbDevice>(parent_.get(), kBulkTxRxCount, kBulkTxRxCount, |
| kVmoDataSize); |
| dev = adb.get(); |
| stop_sync_ = &dev->test_stop_sync_; |
| ASSERT_OK(dev->Init()); |
| |
| // The DDK now owns this reference. |
| [[maybe_unused]] auto released = adb.release(); |
| })); |
| |
| dev_ = parent_->GetLatestChild(); |
| ASSERT_EQ(dev, dev_->GetDeviceContext<UsbAdbDevice>()); |
| } |
| |
| void TearDown() override { |
| mock_usb_.ExpectDisableEp(ZX_OK, kBulkOutEp); |
| mock_usb_.ExpectDisableEp(ZX_OK, kBulkInEp); |
| ASSERT_OK(fdf::RunOnDispatcherSync(adb_dispatcher_->async_dispatcher(), |
| [this]() { dev_->SuspendNewOp(0, false, 0); })); |
| if (configured_) { |
| incoming_.SyncCall([&](IncomingNamespace* infra) { |
| for (size_t i = 0; i < kBulkTxRxCount; i++) { |
| infra->fake_dev.fake_endpoint(kBulkOutEp).RequestComplete(ZX_ERR_CANCELED, 0); |
| } |
| }); |
| } |
| dev_->WaitUntilSuspendReplyCalled(); |
| ASSERT_OK(fdf::RunOnDispatcherSync(adb_dispatcher_->async_dispatcher(), |
| [this]() { dev_->ReleaseOp(); })); |
| parent_ = nullptr; |
| mock_usb_.VerifyAndClear(); |
| } |
| |
| std::shared_ptr<MockDevice> parent_ = MockDevice::FakeRootParent(); |
| async::Loop incoming_loop_{&kAsyncLoopConfigNoAttachToCurrentThread}; |
| fdf::UnownedSynchronizedDispatcher adb_dispatcher_ = |
| mock_ddk::GetDriverRuntime()->StartBackgroundDispatcher(); |
| zx_device_t* dev_; |
| bool configured_ = false; |
| |
| protected: |
| async_patterns::TestDispatcherBound<IncomingNamespace> incoming_{incoming_loop_.dispatcher(), |
| std::in_place}; |
| MockUsbFunction mock_usb_; |
| libsync::Completion* stop_sync_; |
| }; |
| |
| // Fake Adb protocol service. |
| class UsbAdbTest::FakeAdbDaemon { |
| private: |
| class EventHandler : public fidl::WireAsyncEventHandler<fadb::UsbAdbImpl> { |
| public: |
| explicit EventHandler(FakeAdbDaemon* dev) : dev_(dev) {} |
| |
| private: |
| void OnStatusChanged(fidl::WireEvent<fadb::UsbAdbImpl::OnStatusChanged>* event) override { |
| dev_->status_ = event->status; |
| } |
| |
| FakeAdbDaemon* dev_; |
| }; |
| EventHandler event_handler_{this}; |
| fadb::StatusFlags status_; |
| |
| public: |
| explicit FakeAdbDaemon(fidl::ClientEnd<fadb::UsbAdbImpl> client) |
| : client_(std::move(client), loop_.dispatcher(), &event_handler_) {} |
| |
| void CheckStatus(fadb::StatusFlags expected_status) { |
| loop_.RunUntilIdle(); |
| EXPECT_EQ(status_, expected_status); |
| } |
| |
| async::Loop loop_{&kAsyncLoopConfigNoAttachToCurrentThread}; |
| fidl::WireClient<fadb::UsbAdbImpl> client_; |
| }; |
| |
| void UsbAdbTest::SendTestData(std::unique_ptr<FakeAdbDaemon>& fake_adb, size_t size) { |
| uint8_t test_data[size]; |
| incoming_.SyncCall([&](IncomingNamespace* infra) { |
| for (uint32_t i = 0; i < sizeof(test_data) / kVmoDataSize; i++) { |
| infra->fake_dev.fake_endpoint(kBulkInEp).RequestComplete(ZX_OK, kVmoDataSize); |
| } |
| if (sizeof(test_data) % kVmoDataSize) { |
| infra->fake_dev.fake_endpoint(kBulkInEp).RequestComplete(ZX_OK, |
| sizeof(test_data) % kVmoDataSize); |
| } |
| }); |
| |
| auto result = fake_adb->client_.sync()->QueueTx( |
| fidl::VectorView<uint8_t>::FromExternal(test_data, sizeof(test_data))); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_TRUE(result->is_ok()); |
| |
| incoming_.SyncCall([](IncomingNamespace* infra) { |
| EXPECT_EQ(infra->fake_dev.fake_endpoint(kBulkInEp).pending_request_count(), 0); |
| }); |
| } |
| |
| TEST_F(UsbAdbTest, SetUpTearDown) { ASSERT_NO_FATAL_FAILURE(); } |
| |
| TEST_F(UsbAdbTest, StartStop) { |
| mock_usb_.ExpectDisableEp(ZX_OK, kBulkOutEp); |
| mock_usb_.ExpectDisableEp(ZX_OK, kBulkInEp); |
| auto fake_adb = CreateFakeAdbDaemon(); |
| |
| // Calls during Stop(). |
| mock_usb_.ExpectDisableEp(ZX_OK, kBulkOutEp); |
| mock_usb_.ExpectDisableEp(ZX_OK, kBulkInEp); |
| // Close fake_adb so that Stop() will be invoked. |
| fake_adb.reset(); |
| stop_sync_->Wait(); |
| } |
| |
| TEST_F(UsbAdbTest, SendAdbMessage) { |
| mock_usb_.ExpectDisableEp(ZX_OK, kBulkOutEp); |
| mock_usb_.ExpectDisableEp(ZX_OK, kBulkInEp); |
| auto fake_adb = CreateFakeAdbDaemon(); |
| fake_adb->CheckStatus(fadb::StatusFlags(0)); |
| |
| Configure(); |
| fake_adb->CheckStatus(fadb::StatusFlags::kOnline); |
| |
| // Sending data that fits within a single VMO request |
| SendTestData(fake_adb, kVmoDataSize - 2); |
| // Sending data that is exactly fills up a single VMO request |
| SendTestData(fake_adb, kVmoDataSize); |
| // Sending data that exceeds a single VMO request |
| SendTestData(fake_adb, kVmoDataSize + 2); |
| // Sending data that exceeds kBulkTxRxCount VMO requests (the last packet should be stored in |
| // queue) |
| SendTestData(fake_adb, kVmoDataSize * kBulkTxRxCount + 2); |
| // Sending data that exceeds kBulkTxRxCount + 1 VMO requests (probably unneeded test, but added |
| // for good measure.) |
| SendTestData(fake_adb, kVmoDataSize * (kBulkTxRxCount + 1) + 2); |
| |
| // Calls during Stop(). |
| mock_usb_.ExpectDisableEp(ZX_OK, kBulkOutEp); |
| mock_usb_.ExpectDisableEp(ZX_OK, kBulkInEp); |
| // Close fake_adb so that Stop() will be invoked. |
| fake_adb.reset(); |
| stop_sync_->Wait(); |
| } |
| |
| TEST_F(UsbAdbTest, RecvAdbMessage) { |
| // Call set_configured of usb adb. |
| mock_usb_.ExpectDisableEp(ZX_OK, kBulkOutEp); |
| mock_usb_.ExpectDisableEp(ZX_OK, kBulkInEp); |
| Configure(); |
| auto fake_adb = CreateFakeAdbDaemon(); |
| fake_adb->CheckStatus(fadb::StatusFlags::kOnline); |
| |
| // Queue a receive request before the data is available. The request will not get an immediate |
| // reply. Data fits within a single VMO request. |
| constexpr uint32_t kReceiveSize = kVmoDataSize - 2; |
| libsync::Completion wait; |
| fake_adb->client_->Receive().ThenExactlyOnce( |
| [&wait, &kReceiveSize](fidl::WireUnownedResult<fadb::UsbAdbImpl::Receive>& response) -> void { |
| ASSERT_OK(response.status()); |
| ASSERT_FALSE(response.value().is_error()); |
| ASSERT_EQ(response.value().value()->data.count(), kReceiveSize); |
| wait.Signal(); |
| }); |
| // Invoke request completion on bulk out endpoint. |
| incoming_.SyncCall([&](IncomingNamespace* infra) { |
| infra->fake_dev.fake_endpoint(kBulkOutEp).RequestComplete(ZX_OK, kReceiveSize); |
| }); |
| do { |
| fake_adb->loop_.RunUntilIdle(); |
| } while (wait.Wait(zx::msec(1)) != ZX_OK); |
| |
| // Calls during Stop(). |
| mock_usb_.ExpectDisableEp(ZX_OK, kBulkOutEp); |
| mock_usb_.ExpectDisableEp(ZX_OK, kBulkInEp); |
| // Close fake_adb so that Stop() will be invoked. |
| fake_adb.reset(); |
| stop_sync_->Wait(); |
| } |
| |
| } // namespace usb_adb_function |