| // 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 <lib/async-loop/default.h> | |
| #include <lib/async/default.h> | |
| #include <lib/driver/outgoing/cpp/outgoing_directory.h> | |
| #include <lib/driver/testing/cpp/driver_test.h> | |
| #include <lib/fidl/cpp/wire/status.h> | |
| #include <lib/inspect/testing/cpp/inspect.h> | |
| #include <lib/sync/completion.h> | |
| #include <lib/zx/result.h> | |
| #include <zircon/compiler.h> | |
| #include <vector> | |
| #include <gtest/gtest.h> | |
| #include <usb-inspect/usb-inspect-test-helper.h> | |
| #include <usb/usb-request.h> | |
| #include "src/devices/usb/lib/usb-endpoint/testing/fake-usb-endpoint-server.h" | |
| namespace fdescriptor = fuchsia_hardware_usb_descriptor; | |
| namespace usb_adb_function { | |
| class UsbAdbTestHelper { | |
| public: | |
| static State state(const UsbAdbDevice& device) { return device.state_; } | |
| static void set_state(UsbAdbDevice& device, State state) { device.state_ = state; } | |
| static bool has_usb_function_binding(const UsbAdbDevice& device) { | |
| return device.usb_function_binding_.has_value(); | |
| } | |
| static bool has_adb_binding(const UsbAdbDevice& device) { | |
| return device.adb_binding_.has_value(); | |
| } | |
| static bool CancelAllCompleted(const UsbAdbDevice& device) { return device.CancelAllCompleted(); } | |
| static bool AllRequestsReturned(UsbAdbDevice& device) { return device.AllRequestsReturned(); } | |
| static bool InternalPoolsFull(UsbAdbDevice& device) { | |
| return device.bulk_out_ep_.RequestsFull() && device.bulk_in_ep_.RequestsFull(); | |
| } | |
| static size_t BulkInEpPendingRequests(UsbAdbDevice& device) { | |
| return device.bulk_in_ep_.GetInFlightCount(); | |
| } | |
| static size_t TxPendingReqsCount(const UsbAdbDevice& device) { | |
| return device.tx_pending_reqs_.size(); | |
| } | |
| static size_t RxRequestsCount(const UsbAdbDevice& device) { return device.rx_requests_.size(); } | |
| static size_t PendingRepliesCount(const UsbAdbDevice& device) { | |
| return device.pending_replies_.size(); | |
| } | |
| static inspect::Inspector GetInspector(UsbAdbDevice& device) { | |
| if (device.component_inspector_.has_value()) { | |
| return device.component_inspector_->inspector(); | |
| } | |
| return {}; | |
| } | |
| static usb_inspect::ThroughputTracker* GetThroughputTracker(UsbAdbDevice& device) { | |
| if (device.throughput_tracker_.has_value()) { | |
| return &*device.throughput_tracker_; | |
| } | |
| return nullptr; | |
| } | |
| }; | |
| static constexpr uint32_t kBulkOutEp = 1; | |
| static constexpr uint32_t kBulkInEp = 2; | |
| class DelayedCancelEndpoint : public fake_usb_endpoint::FakeEndpoint { | |
| public: | |
| void CancelAll(CancelAllCompleter::Sync& completer) override { | |
| zx_status_t reply_status = ZX_OK; | |
| { | |
| std::lock_guard<std::mutex> _(lock_); | |
| reply_status = cancel_status_; | |
| if (reply_status == ZX_OK && hold_cancel_) { | |
| delayed_cancels_.push_back(completer.ToAsync()); | |
| return; | |
| } | |
| } | |
| // Requests are drained even when replying an error. This mirrors | |
| // Dwc3::EpServer::CancelAll(), which completes all active requests when the | |
| // controller is stopped or powered off. Replying an error without draining | |
| // would leave requests permanently outstanding and stall teardown. | |
| DoCancelAll(completer.ToAsync(), reply_status); | |
| } | |
| void ReleaseCancelAll() { | |
| std::vector<CancelAllCompleter::Async> completers; | |
| { | |
| std::lock_guard<std::mutex> _(lock_); | |
| completers = std::move(delayed_cancels_); | |
| delayed_cancels_.clear(); | |
| } | |
| for (auto& completer : completers) { | |
| DoCancelAll(std::move(completer)); | |
| } | |
| } | |
| void set_hold_cancel(bool hold) { | |
| std::lock_guard<std::mutex> _(lock_); | |
| hold_cancel_ = hold; | |
| } | |
| void set_cancel_status(zx_status_t status) { | |
| std::lock_guard<std::mutex> _(lock_); | |
| cancel_status_ = status; | |
| } | |
| private: | |
| void DoCancelAll(CancelAllCompleter::Async completer, zx_status_t reply_status = ZX_OK) { | |
| std::vector<fuchsia_hardware_usb_endpoint::Completion> completions; | |
| std::optional<fidl::ServerBindingRef<fuchsia_hardware_usb_endpoint::Endpoint>> local_binding; | |
| { | |
| std::lock_guard<std::mutex> _(lock_); | |
| local_binding = binding_ref_; | |
| std::vector<fuchsia_hardware_usb_request::Request> pending_requests = std::move(requests_); | |
| requests_.clear(); | |
| completions.reserve(pending_requests.size()); | |
| for (auto& request : pending_requests) { | |
| completions.emplace_back(std::move(fuchsia_hardware_usb_endpoint::Completion() | |
| .request(std::move(request)) | |
| .status(ZX_ERR_CANCELED) | |
| .transfer_size(0))); | |
| } | |
| } | |
| if (!completions.empty() && local_binding.has_value()) { | |
| EXPECT_TRUE(fidl::SendEvent(*local_binding)->OnCompletion(std::move(completions)).is_ok()); | |
| } | |
| if (reply_status == ZX_OK) { | |
| completer.Reply(fit::ok()); | |
| } else { | |
| completer.Reply(fit::error(reply_status)); | |
| } | |
| } | |
| zx_status_t cancel_status_ __TA_GUARDED(lock_) = ZX_OK; | |
| bool hold_cancel_ __TA_GUARDED(lock_) = false; | |
| std::vector<CancelAllCompleter::Async> delayed_cancels_ __TA_GUARDED(lock_); | |
| }; | |
| class AdbFakeUsb | |
| : public fake_usb_endpoint::FakeUsbFidlProvider<fuchsia_hardware_usb_function::UsbFunction, | |
| DelayedCancelEndpoint> { | |
| public: | |
| using Base = fake_usb_endpoint::FakeUsbFidlProvider<fuchsia_hardware_usb_function::UsbFunction, | |
| DelayedCancelEndpoint>; | |
| AdbFakeUsb(async_dispatcher_t* dispatcher) : Base(dispatcher), dispatcher_(dispatcher) {} | |
| void AllocResources( | |
| fidl::Request<fuchsia_hardware_usb_function::UsbFunction::AllocResources>& request, | |
| fidl::internal::NaturalCompleter< | |
| fuchsia_hardware_usb_function::UsbFunction::AllocResources>::Sync& completer) override { | |
| zx_status_t alloc_status = ZX_OK; | |
| bool omit_eps = false; | |
| { | |
| std::lock_guard<std::mutex> _(lock_); | |
| alloc_status = alloc_resources_status_; | |
| omit_eps = omit_endpoints_; | |
| } | |
| if (alloc_status != ZX_OK) { | |
| completer.Reply(fit::error(alloc_status)); | |
| return; | |
| } | |
| fuchsia_hardware_usb_function::UsbFunctionAllocResourcesResponse response; | |
| EXPECT_EQ(request.endpoints().size(), 2u); | |
| EXPECT_EQ(request.interface_count(), 1u); | |
| EXPECT_EQ(request.strings().size(), 0u); | |
| response.interface_nums() = {0}; | |
| if (omit_eps) { | |
| response.endpoint_addrs() = {kBulkOutEp}; | |
| } else { | |
| response.endpoint_addrs() = {kBulkOutEp, kBulkInEp}; | |
| } | |
| response.string_indices() = {}; | |
| size_t count = std::min(request.endpoints().size(), response.endpoint_addrs().size()); | |
| for (size_t i = 0; i < count; i++) { | |
| uint8_t addr = response.endpoint_addrs()[i]; | |
| fake_endpoint(addr).Connect(dispatcher_, std::move(request.endpoints()[i].endpoint())); | |
| } | |
| completer.Reply(fit::ok(std::move(response))); | |
| } | |
| void Configure( | |
| fidl::Request<fuchsia_hardware_usb_function::UsbFunction::Configure>& request, | |
| fidl::internal::NaturalCompleter<fuchsia_hardware_usb_function::UsbFunction::Configure>::Sync& | |
| completer) override { | |
| fit::callback<void()> cb; | |
| { | |
| std::lock_guard<std::mutex> _(lock_); | |
| if (configure_status_ != ZX_OK) { | |
| completer.Reply(fit::error(configure_status_)); | |
| return; | |
| } | |
| if (iface_client_.is_valid()) { | |
| completer.Reply(fit::error(ZX_ERR_ALREADY_BOUND)); | |
| return; | |
| } | |
| iface_client_ = std::move(request.iface()); | |
| configured_ = true; | |
| deconfigured_ = false; | |
| cb = std::move(on_configured_); | |
| } | |
| completer.Reply(fit::ok()); | |
| if (cb) { | |
| cb(); | |
| } | |
| } | |
| void Deconfigure( | |
| fidl::internal::NaturalCompleter< | |
| fuchsia_hardware_usb_function::UsbFunction::Deconfigure>::Sync& completer) override { | |
| fit::callback<void()> cb; | |
| { | |
| std::lock_guard<std::mutex> _(lock_); | |
| deconfigured_ = true; | |
| deconfigure_count_++; | |
| configured_ = false; | |
| if (hold_deconfigure_) { | |
| delayed_deconfigure_completer_ = completer.ToAsync(); | |
| return; | |
| } | |
| cb = std::move(on_deconfigured_); | |
| } | |
| completer.Reply(fit::ok()); | |
| if (cb) { | |
| cb(); | |
| } | |
| } | |
| size_t deconfigure_count() const { | |
| std::lock_guard<std::mutex> _(lock_); | |
| return deconfigure_count_; | |
| } | |
| bool is_configured() const { | |
| std::lock_guard<std::mutex> _(lock_); | |
| return configured_; | |
| } | |
| void set_hold_deconfigure(bool hold) { | |
| std::lock_guard<std::mutex> _(lock_); | |
| hold_deconfigure_ = hold; | |
| } | |
| fidl::ClientEnd<fuchsia_hardware_usb_function::UsbFunctionInterface> TakeIfaceClient() { | |
| std::lock_guard<std::mutex> _(lock_); | |
| return std::move(iface_client_); | |
| } | |
| void set_on_configured(fit::callback<void()> on_configured) { | |
| bool call_now = false; | |
| { | |
| std::lock_guard<std::mutex> _(lock_); | |
| // Only report "already configured" when a fresh iface client is actually | |
| // available to hand out. Otherwise WaitConfigured() would re-register and | |
| // be signalled immediately on every iteration, spinning until the driver | |
| // finishes teardown and rebinds. | |
| if (configured_ && iface_client_.is_valid()) { | |
| call_now = true; | |
| } else { | |
| on_configured_ = std::move(on_configured); | |
| } | |
| } | |
| if (call_now && on_configured) { | |
| on_configured(); | |
| } | |
| } | |
| void set_on_deconfigured(fit::callback<void()> on_deconfigured) { | |
| bool call_now = false; | |
| { | |
| std::lock_guard<std::mutex> _(lock_); | |
| if (deconfigured_) { | |
| call_now = true; | |
| } else { | |
| on_deconfigured_ = std::move(on_deconfigured); | |
| } | |
| } | |
| if (call_now && on_deconfigured) { | |
| on_deconfigured(); | |
| } | |
| } | |
| void DisableEndpoint( | |
| fidl::Request<fuchsia_hardware_usb_function::UsbFunction::DisableEndpoint>& request, | |
| fidl::internal::NaturalCompleter< | |
| fuchsia_hardware_usb_function::UsbFunction::DisableEndpoint>::Sync& completer) override { | |
| { | |
| std::lock_guard<std::mutex> _(lock_); | |
| disabled_endpoints_.push_back(request.endpoint_address()); | |
| } | |
| Base::DisableEndpoint(request, completer); | |
| } | |
| std::vector<uint8_t> disabled_endpoints() const { | |
| std::lock_guard<std::mutex> _(lock_); | |
| return disabled_endpoints_; | |
| } | |
| void clear_disabled_endpoints() { | |
| std::lock_guard<std::mutex> _(lock_); | |
| disabled_endpoints_.clear(); | |
| } | |
| void ConfigureEndpoint( | |
| fidl::Request<fuchsia_hardware_usb_function::UsbFunction::ConfigureEndpoint>& request, | |
| fidl::internal::NaturalCompleter< | |
| fuchsia_hardware_usb_function::UsbFunction::ConfigureEndpoint>::Sync& completer) | |
| override { | |
| bool fail = false; | |
| { | |
| std::lock_guard<std::mutex> _(lock_); | |
| fail = fail_configure_endpoint_ || | |
| (fail_configure_endpoint_addr_.has_value() && | |
| *fail_configure_endpoint_addr_ == request.endpoint_address()); | |
| } | |
| if (fail) { | |
| completer.Reply(fit::error(ZX_ERR_NO_RESOURCES)); | |
| return; | |
| } | |
| Base::ConfigureEndpoint(request, completer); | |
| } | |
| void set_fail_configure_endpoint(bool fail) { | |
| std::lock_guard<std::mutex> _(lock_); | |
| fail_configure_endpoint_ = fail; | |
| } | |
| void set_fail_configure_endpoint_addr(std::optional<uint8_t> ep_addr) { | |
| std::lock_guard<std::mutex> _(lock_); | |
| fail_configure_endpoint_addr_ = ep_addr; | |
| } | |
| void set_alloc_resources_status(zx_status_t status) { | |
| std::lock_guard<std::mutex> _(lock_); | |
| alloc_resources_status_ = status; | |
| } | |
| void set_configure_status(zx_status_t status) { | |
| std::lock_guard<std::mutex> _(lock_); | |
| configure_status_ = status; | |
| } | |
| void set_omit_endpoints(bool omit) { | |
| std::lock_guard<std::mutex> _(lock_); | |
| omit_endpoints_ = omit; | |
| } | |
| private: | |
| async_dispatcher_t* dispatcher_; | |
| fidl::ClientEnd<fuchsia_hardware_usb_function::UsbFunctionInterface> iface_client_ | |
| __TA_GUARDED(lock_); | |
| fit::callback<void()> on_configured_ __TA_GUARDED(lock_); | |
| fit::callback<void()> on_deconfigured_ __TA_GUARDED(lock_); | |
| zx_status_t alloc_resources_status_ __TA_GUARDED(lock_) = ZX_OK; | |
| zx_status_t configure_status_ __TA_GUARDED(lock_) = ZX_OK; | |
| bool omit_endpoints_ __TA_GUARDED(lock_) = false; | |
| bool fail_configure_endpoint_ __TA_GUARDED(lock_) = false; | |
| std::optional<uint8_t> fail_configure_endpoint_addr_ __TA_GUARDED(lock_); | |
| bool hold_deconfigure_ __TA_GUARDED(lock_) = false; | |
| std::optional<fidl::internal::NaturalCompleter< | |
| fuchsia_hardware_usb_function::UsbFunction::Deconfigure>::Async> | |
| delayed_deconfigure_completer_ __TA_GUARDED(lock_); | |
| bool deconfigured_ __TA_GUARDED(lock_) = false; | |
| size_t deconfigure_count_ __TA_GUARDED(lock_) = 0; | |
| bool configured_ __TA_GUARDED(lock_) = false; | |
| std::vector<uint8_t> disabled_endpoints_ __TA_GUARDED(lock_); | |
| mutable std::mutex lock_; | |
| }; | |
| class UsbAdbEnvironment : public fdf_testing::Environment { | |
| public: | |
| zx::result<> Serve(fdf::OutgoingDirectory& to_driver_vfs) override { | |
| async_dispatcher_t* dispatcher = fdf::Dispatcher::GetCurrent()->async_dispatcher(); | |
| fake_dev_.emplace(dispatcher); | |
| fake_dev_->set_alloc_resources_status(alloc_resources_status_); | |
| fake_dev_->set_configure_status(configure_status_); | |
| fake_dev_->set_omit_endpoints(omit_endpoints_); | |
| fuchsia_hardware_usb_function::UsbFunctionService::InstanceHandler handler({ | |
| .device = usb_function_bindings_.CreateHandler(&*fake_dev_, dispatcher, | |
| fidl::kIgnoreBindingClosure), | |
| }); | |
| EXPECT_TRUE( | |
| to_driver_vfs | |
| .AddService<fuchsia_hardware_usb_function::UsbFunctionService>(std::move(handler)) | |
| .is_ok()); | |
| return zx::ok(); | |
| } | |
| void set_alloc_resources_status(zx_status_t status) { alloc_resources_status_ = status; } | |
| void set_configure_status(zx_status_t status) { configure_status_ = status; } | |
| void set_omit_endpoints(bool omit) { omit_endpoints_ = omit; } | |
| void CancelAllUsbRequests() { | |
| if (!fake_dev_.has_value()) { | |
| return; | |
| } | |
| while (fake_dev_->fake_endpoint(kBulkOutEp).pending_request_count() > 0) { | |
| fake_dev_->fake_endpoint(kBulkOutEp).RequestComplete(ZX_ERR_CANCELED, 0); | |
| } | |
| while (fake_dev_->fake_endpoint(kBulkInEp).pending_request_count() > 0) { | |
| fake_dev_->fake_endpoint(kBulkInEp).RequestComplete(ZX_ERR_CANCELED, 0); | |
| } | |
| } | |
| std::optional<AdbFakeUsb> fake_dev_; | |
| fidl::ServerBindingGroup<fuchsia_hardware_usb_function::UsbFunction> usb_function_bindings_; | |
| zx_status_t alloc_resources_status_ = ZX_OK; | |
| zx_status_t configure_status_ = ZX_OK; | |
| bool omit_endpoints_ = false; | |
| }; | |
| class UsbAdbTestConfig final { | |
| public: | |
| using DriverType = UsbAdbDevice; | |
| using EnvironmentType = UsbAdbEnvironment; | |
| }; | |
| class EventHandler : public fidl::WireSyncEventHandler<fadb::UsbAdbImpl> { | |
| public: | |
| ~EventHandler() { EXPECT_TRUE(expected_statuses_.empty()); } | |
| void OnStatusChanged(fidl::WireEvent<fadb::UsbAdbImpl::OnStatusChanged>* event) override { | |
| ASSERT_FALSE(expected_statuses_.empty()); | |
| EXPECT_EQ(event->status, expected_statuses_.front()); | |
| expected_statuses_.pop(); | |
| } | |
| std::queue<fadb::StatusFlags> expected_statuses_; | |
| }; | |
| class UsbAdbTest : public testing::Test { | |
| public: | |
| void ReadInspect(const inspect::Inspector& inspector) { | |
| hierarchy_ = usb_inspect::ReadHierarchyFromInspector(inspector); | |
| } | |
| const inspect::Hierarchy& hierarchy() const { return *hierarchy_; } | |
| std::optional<inspect::Hierarchy> hierarchy_; | |
| fidl::WireSyncClient<fadb::UsbAdbImpl> NormalStartAdb() { | |
| auto [client_end, server_end] = fidl::Endpoints<fadb::UsbAdbImpl>::Create(); | |
| auto start_res = client_->StartAdb(std::move(server_end)); | |
| EXPECT_TRUE(start_res.ok()); | |
| EXPECT_TRUE(start_res->is_ok()); | |
| WaitConfigured(); | |
| EnableUsb(); | |
| return fidl::WireSyncClient<fadb::UsbAdbImpl>(std::move(client_end)); | |
| } | |
| void SetUp() override { | |
| ASSERT_EQ(driver_test_.StartDriver().status_value(), ZX_OK); | |
| driver_test_.runtime().RunUntil([&]() { | |
| bool ready = false; | |
| driver_test_.RunInEnvironmentTypeContext( | |
| [&](UsbAdbEnvironment& env) { ready = env.fake_dev_->is_configured(); }); | |
| return ready; | |
| }); | |
| auto device = driver_test_.Connect<fadb::Service::Adb>(); | |
| EXPECT_EQ(device.status_value(), ZX_OK); | |
| client_.Bind(std::move(device.value())); | |
| driver_test_.RunInDriverContext([&](UsbAdbDevice& dev) { | |
| EXPECT_EQ(dev.bulk_out_addr(), kBulkOutEp); | |
| EXPECT_EQ(dev.bulk_in_addr(), kBulkInEp); | |
| }); | |
| } | |
| void TearDown() override { | |
| client_ = {}; | |
| iface_client_ = {}; | |
| SafeStopDriver(); | |
| driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) { | |
| if (env.fake_dev_.has_value()) { | |
| env.fake_dev_->set_on_configured(nullptr); | |
| env.fake_dev_->set_on_deconfigured(nullptr); | |
| env.fake_dev_.reset(); | |
| } | |
| env.usb_function_bindings_.RemoveAll(); | |
| }); | |
| driver_test_.runtime().RunUntilIdle(); | |
| } | |
| void EnsureIfaceBound() { WaitConfigured(); } | |
| void EnableUsb() { | |
| EnsureIfaceBound(); | |
| ASSERT_TRUE(iface_client_.is_valid()); | |
| fidl::Result result = iface_client_->SetConfigured({{ | |
| .configured = true, | |
| .speed = fdescriptor::UsbSpeed::kFull, | |
| }}); | |
| EXPECT_TRUE(result.is_ok()) << result.error_value().FormatDescription(); | |
| driver_test_.runtime().RunUntil([&]() { | |
| size_t count = 0; | |
| driver_test_.RunInEnvironmentTypeContext([&count](UsbAdbEnvironment& env) { | |
| count = env.fake_dev_->fake_endpoint(kBulkOutEp).pending_request_count(); | |
| }); | |
| bool is_online = false; | |
| driver_test_.RunInDriverContext([&is_online](UsbAdbDevice& dev) { | |
| is_online = (UsbAdbTestHelper::state(dev) == State::kOnline); | |
| }); | |
| return (count == kBulkRxCount) && is_online; | |
| }); | |
| } | |
| void WaitConfigured() { | |
| if (iface_client_.is_valid()) { | |
| return; | |
| } | |
| fidl::ClientEnd<fuchsia_hardware_usb_function::UsbFunctionInterface> iface_client; | |
| for (;;) { | |
| libsync::Completion configured; | |
| driver_test_.RunInEnvironmentTypeContext( | |
| [&iface_client, &configured](UsbAdbEnvironment& env) { | |
| if (!env.fake_dev_.has_value()) { | |
| return; | |
| } | |
| iface_client = env.fake_dev_->TakeIfaceClient(); | |
| if (iface_client.is_valid()) { | |
| env.fake_dev_->set_on_configured(nullptr); | |
| return; | |
| } | |
| env.fake_dev_->set_on_configured([&configured]() { configured.Signal(); }); | |
| }); | |
| if (iface_client.is_valid()) { | |
| iface_client_.Bind(std::move(iface_client)); | |
| return; | |
| } | |
| configured.Wait(); | |
| driver_test_.RunInEnvironmentTypeContext([&](UsbAdbEnvironment& env) { | |
| if (env.fake_dev_.has_value()) { | |
| env.fake_dev_->set_on_configured(nullptr); | |
| } | |
| }); | |
| } | |
| } | |
| void SafeStopDriver() { | |
| if (driver_stopped_) { | |
| return; | |
| } | |
| driver_test_.runtime().RunUntilIdle(); | |
| CancelAllUsbRequestsOnDeconfigure(); | |
| EXPECT_TRUE(driver_test_.StopDriver().is_ok()); | |
| driver_stopped_ = true; | |
| } | |
| void WaitForState(State target_state) { | |
| driver_test_.runtime().RunUntil([&]() { | |
| bool match = false; | |
| driver_test_.RunInDriverContext( | |
| [&](UsbAdbDevice& dev) { match = (UsbAdbTestHelper::state(dev) == target_state); }); | |
| return match; | |
| }); | |
| } | |
| void ExpectHandleOneEventSafe(fidl::WireSyncClient<fadb::UsbAdbImpl>& client, | |
| EventHandler& handler) { | |
| while (true) { | |
| auto result = client.HandleOneEvent(handler); | |
| if (result.status() == ZX_ERR_PEER_CLOSED) { | |
| break; | |
| } | |
| ASSERT_EQ(result.status(), ZX_OK); | |
| if (handler.expected_statuses_.empty()) { | |
| break; | |
| } | |
| } | |
| } | |
| void SendTestData(fidl::WireSyncClient<fadb::UsbAdbImpl>& usb_impl, size_t size) { | |
| std::vector<uint8_t> test_data(size); | |
| driver_test_.RunInEnvironmentTypeContext([&test_data](UsbAdbEnvironment& env) { | |
| for (uint32_t i = 0; i < test_data.size() / kVmoDataSize; i++) { | |
| env.fake_dev_->fake_endpoint(kBulkInEp).RequestComplete(ZX_OK, kVmoDataSize); | |
| } | |
| if (test_data.size() % kVmoDataSize) { | |
| env.fake_dev_->fake_endpoint(kBulkInEp).RequestComplete(ZX_OK, | |
| test_data.size() % kVmoDataSize); | |
| } | |
| }); | |
| auto result = usb_impl->QueueTx( | |
| fidl::VectorView<uint8_t>::FromExternal(test_data.data(), test_data.size())); | |
| ASSERT_TRUE(result.ok()); | |
| ASSERT_TRUE(result->is_ok()); | |
| driver_test_.runtime().RunUntil([&] { | |
| bool empty = false; | |
| driver_test_.RunInEnvironmentTypeContext([&](UsbAdbEnvironment& env) { | |
| empty = (env.fake_dev_->fake_endpoint(kBulkInEp).pending_request_count() == 0u); | |
| }); | |
| if (!empty) { | |
| return false; | |
| } | |
| driver_test_.RunInDriverContext([&](UsbAdbDevice& dev) { | |
| empty = (UsbAdbTestHelper::BulkInEpPendingRequests(dev) == 0u); | |
| }); | |
| return empty; | |
| }); | |
| } | |
| void ExpectReceiveData(size_t size) { | |
| driver_test_.RunInEnvironmentTypeContext([size](UsbAdbEnvironment& env) { | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).RequestComplete(ZX_OK, size); | |
| }); | |
| } | |
| // Safety net that drains any fake endpoint requests still outstanding at | |
| // deconfigure time. The driver now issues its own CancelAll() at the start of | |
| // teardown, which is what actually drains requests, and Deconfigure() only runs | |
| // once all requests have already been returned. This therefore matters only via | |
| // set_on_deconfigured()'s immediate-callback path, when a previous teardown has | |
| // already set deconfigured_. | |
| void CancelAllUsbRequestsOnDeconfigure() { | |
| driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) { | |
| if (env.fake_dev_.has_value()) { | |
| env.fake_dev_->set_on_deconfigured([&]() { env.CancelAllUsbRequests(); }); | |
| } | |
| }); | |
| } | |
| fdf_testing::BackgroundDriverTest<UsbAdbTestConfig> driver_test_; | |
| fidl::WireSyncClient<fadb::Device> client_; | |
| fidl::SyncClient<fuchsia_hardware_usb_function::UsbFunctionInterface> iface_client_; | |
| bool driver_stopped_ = false; | |
| }; | |
| TEST_F(UsbAdbTest, StopBeforeUsbStartsUp) { | |
| driver_test_.runtime().RunUntil([&]() { | |
| bool awaiting = false; | |
| driver_test_.RunInDriverContext([&](UsbAdbDevice& dev) { | |
| awaiting = (UsbAdbTestHelper::state(dev) == State::kAwaitingUsbConnection); | |
| }); | |
| return awaiting; | |
| }); | |
| driver_test_.RunInDriverContext([&](UsbAdbDevice& dev) { | |
| EXPECT_EQ(UsbAdbTestHelper::state(dev), State::kAwaitingUsbConnection); | |
| }); | |
| SafeStopDriver(); | |
| } | |
| TEST_F(UsbAdbTest, StartStop) { | |
| auto [client_end, server_end] = fidl::Endpoints<fadb::UsbAdbImpl>::Create(); | |
| EXPECT_TRUE(client_->StartAdb(std::move(server_end)).ok()); | |
| auto usb_impl = fidl::WireSyncClient<fadb::UsbAdbImpl>(std::move(client_end)); | |
| EventHandler handler; | |
| EnableUsb(); | |
| handler.expected_statuses_.push(fadb::StatusFlags::kOnline); | |
| ExpectHandleOneEventSafe(usb_impl, handler); | |
| libsync::Completion stop_requested; | |
| driver_test_.RunInEnvironmentTypeContext([&stop_requested](UsbAdbEnvironment& env) { | |
| env.fake_dev_->set_on_deconfigured([&stop_requested]() { stop_requested.Signal(); }); | |
| }); | |
| std::atomic<bool> stop_finished = false; | |
| auto dev_client_end = client_.TakeClientEnd(); | |
| fidl::WireClient async_client(std::move(dev_client_end), | |
| fdf::Dispatcher::GetCurrent()->async_dispatcher()); | |
| async_client->StopAdb().ThenExactlyOnce([&stop_finished](auto& result) { | |
| EXPECT_TRUE(result.ok() || result.status() == ZX_ERR_PEER_CLOSED); | |
| stop_finished = true; | |
| }); | |
| driver_test_.RunInEnvironmentTypeContext( | |
| [&](UsbAdbEnvironment& env) { env.CancelAllUsbRequests(); }); | |
| driver_test_.runtime().RunUntil([&]() { return stop_finished.load(); }); | |
| driver_test_.RunInEnvironmentTypeContext( | |
| [&](UsbAdbEnvironment& env) { env.fake_dev_->set_on_deconfigured(nullptr); }); | |
| usb_impl = {}; | |
| async_client = {}; | |
| SafeStopDriver(); | |
| } | |
| TEST_F(UsbAdbTest, StopDriverWhileConnected) { | |
| auto usb_impl = NormalStartAdb(); | |
| EventHandler handler; | |
| handler.expected_statuses_.emplace(fadb::StatusFlags::kOnline); | |
| ExpectHandleOneEventSafe(usb_impl, handler); | |
| CancelAllUsbRequestsOnDeconfigure(); | |
| usb_impl = {}; | |
| SafeStopDriver(); | |
| } | |
| TEST_F(UsbAdbTest, UsbStackRequestsStop) { | |
| auto usb_impl = NormalStartAdb(); | |
| EventHandler handler; | |
| handler.expected_statuses_.emplace(fadb::StatusFlags::kOnline); | |
| ExpectHandleOneEventSafe(usb_impl, handler); | |
| auto iface_client_end = iface_client_.TakeClientEnd(); | |
| fidl::WireClient async_iface(std::move(iface_client_end), | |
| fdf::Dispatcher::GetCurrent()->async_dispatcher()); | |
| std::atomic<bool> set_configured_finished = false; | |
| async_iface->SetConfigured(false, fuchsia_hardware_usb_descriptor::wire::UsbSpeed::kFull) | |
| .ThenExactlyOnce([&](auto& result) { | |
| EXPECT_TRUE(result.ok() || result.status() == ZX_ERR_PEER_CLOSED); | |
| set_configured_finished = true; | |
| }); | |
| driver_test_.RunInEnvironmentTypeContext( | |
| [](UsbAdbEnvironment& env) { env.CancelAllUsbRequests(); }); | |
| handler.expected_statuses_.emplace(0); | |
| ExpectHandleOneEventSafe(usb_impl, handler); | |
| usb_impl = {}; | |
| driver_test_.runtime().RunUntil([&] { return set_configured_finished.load(); }); | |
| SafeStopDriver(); | |
| } | |
| TEST_F(UsbAdbTest, SendAdbMessage) { | |
| auto usb_impl = NormalStartAdb(); | |
| ASSERT_NO_FATAL_FAILURE(SendTestData(usb_impl, kVmoDataSize - 2)); | |
| ASSERT_NO_FATAL_FAILURE(SendTestData(usb_impl, kVmoDataSize)); | |
| ASSERT_NO_FATAL_FAILURE(SendTestData(usb_impl, kVmoDataSize + 2)); | |
| ASSERT_NO_FATAL_FAILURE(SendTestData(usb_impl, kVmoDataSize * (kBulkTxCount - 2) + 2)); | |
| ASSERT_NO_FATAL_FAILURE(SendTestData(usb_impl, kVmoDataSize * (kBulkTxCount - 1) + 2)); | |
| SafeStopDriver(); | |
| } | |
| TEST_F(UsbAdbTest, RecvAdbMessage) { | |
| constexpr uint32_t kReceiveSize = kVmoDataSize - 2; | |
| auto usb_impl = NormalStartAdb(); | |
| auto dev_client_end = usb_impl.TakeClientEnd(); | |
| fidl::WireClient async_usb(std::move(dev_client_end), | |
| fdf::Dispatcher::GetCurrent()->async_dispatcher()); | |
| std::atomic<bool> completed = false; | |
| size_t actual_size = 0; | |
| zx_status_t response_status = ZX_ERR_INTERNAL; | |
| async_usb->Receive().ThenExactlyOnce([&](auto& response) { | |
| completed = true; | |
| response_status = response.status(); | |
| if (response.ok()) { | |
| actual_size = response.value().value()->data.size(); | |
| } | |
| }); | |
| driver_test_.runtime().RunUntilIdle(); | |
| EXPECT_NO_FATAL_FAILURE(ExpectReceiveData(kReceiveSize)); | |
| driver_test_.runtime().RunUntil([&] { return completed.load(); }); | |
| EXPECT_EQ(response_status, ZX_OK); | |
| EXPECT_EQ(actual_size, kReceiveSize); | |
| SafeStopDriver(); | |
| } | |
| TEST_F(UsbAdbTest, VerifyShutdownCompletesWhenHardwareUnresponsive) { | |
| auto usb_impl = NormalStartAdb(); | |
| EventHandler handler; | |
| handler.expected_statuses_.emplace(fadb::StatusFlags::kOnline); | |
| ExpectHandleOneEventSafe(usb_impl, handler); | |
| auto iface_client_end = iface_client_.TakeClientEnd(); | |
| ASSERT_TRUE(iface_client_end.is_valid()); | |
| fidl::WireClient async_iface(std::move(iface_client_end), | |
| fdf::Dispatcher::GetCurrent()->async_dispatcher()); | |
| CancelAllUsbRequestsOnDeconfigure(); | |
| // Simulate hardware power-off or inactive controller endpoints where CancelAll | |
| // returns ZX_ERR_IO_NOT_PRESENT. This is armed *before* teardown is triggered so the | |
| // driver's CancelAll() deterministically observes the failure. Arming it afterwards | |
| // would race the background dispatcher, which usually processes SetConfigured(false) | |
| // first and therefore exercises the ZX_OK path instead. | |
| driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) { | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).set_cancel_status(ZX_ERR_IO_NOT_PRESENT); | |
| env.fake_dev_->fake_endpoint(kBulkInEp).set_cancel_status(ZX_ERR_IO_NOT_PRESENT); | |
| }); | |
| // The driver should reply ZX_OK to held SetConfigured(false) completers before Deconfigure() | |
| // and complete shutdown cleanly. | |
| std::atomic<bool> set_configured_replied = false; | |
| async_iface->SetConfigured(false, fuchsia_hardware_usb_descriptor::wire::UsbSpeed::kHigh) | |
| .ThenExactlyOnce([&](auto& result) { | |
| EXPECT_TRUE(result.ok()); | |
| EXPECT_TRUE(result->is_ok()); | |
| set_configured_replied = true; | |
| }); | |
| driver_test_.runtime().RunUntilIdle(); | |
| EXPECT_TRUE(driver_test_.StopDriver().is_ok()); | |
| driver_test_.runtime().RunUntil([&]() { return set_configured_replied.load(); }); | |
| driver_stopped_ = true; | |
| } | |
| TEST_F(UsbAdbTest, VerifyInspect) { | |
| auto usb_impl = NormalStartAdb(); | |
| // Queue some tx packets | |
| ASSERT_NO_FATAL_FAILURE(SendTestData(usb_impl, 100)); | |
| auto dev_client_end = usb_impl.TakeClientEnd(); | |
| fidl::WireClient async_usb(std::move(dev_client_end), | |
| fdf::Dispatcher::GetCurrent()->async_dispatcher()); | |
| std::atomic<bool> completed = false; | |
| size_t actual_size = 0; | |
| zx_status_t response_status = ZX_ERR_INTERNAL; | |
| async_usb->Receive().ThenExactlyOnce([&](auto& response) { | |
| completed = true; | |
| response_status = response.status(); | |
| if (response.ok()) { | |
| actual_size = response.value().value()->data.size(); | |
| } | |
| }); | |
| driver_test_.runtime().RunUntilIdle(); | |
| EXPECT_NO_FATAL_FAILURE(ExpectReceiveData(200)); | |
| driver_test_.runtime().RunUntil([&] { return completed.load(); }); | |
| EXPECT_EQ(response_status, ZX_OK); | |
| EXPECT_EQ(actual_size, 200u); | |
| // Fetch inspector from driver | |
| inspect::Inspector inspector; | |
| driver_test_.RunInDriverContext([&](UsbAdbDevice& dev) { | |
| if (auto* tracker = UsbAdbTestHelper::GetThroughputTracker(dev)) { | |
| tracker->MeasureForTesting(zx::sec(1)); | |
| } | |
| inspector = UsbAdbTestHelper::GetInspector(dev); | |
| }); | |
| ASSERT_NO_FATAL_FAILURE(ReadInspect(inspector)); | |
| // Verify adb function inspect node exists | |
| auto* root_node = this->hierarchy().GetByPath({"usb-adb-function"}); | |
| ASSERT_NE(nullptr, root_node); | |
| // Verify state string | |
| const auto* state_prop = root_node->node().get_property<inspect::StringPropertyValue>("state"); | |
| ASSERT_NE(nullptr, state_prop); | |
| EXPECT_EQ("kOnline", state_prop->value()); | |
| // Verify online boolean property | |
| const auto* online_prop = root_node->node().get_property<inspect::BoolPropertyValue>("online"); | |
| ASSERT_NE(nullptr, online_prop); | |
| EXPECT_TRUE(online_prop->value()); | |
| // Verify bulk_in (TX) stats using the shared helper | |
| auto* bulk_in = this->hierarchy().GetByPath({"usb-adb-function", "bulk_in"}); | |
| ASSERT_NE(nullptr, bulk_in); | |
| auto err_in = | |
| usb_inspect::VerifyEndpointInspect(bulk_in, 100, std::nullopt, 0, std::nullopt, 100, 0); | |
| EXPECT_TRUE(err_in.is_ok()) << err_in.error_value(); | |
| // Verify bulk_out (RX) stats using the shared helper | |
| auto* bulk_out = this->hierarchy().GetByPath({"usb-adb-function", "bulk_out"}); | |
| ASSERT_NE(nullptr, bulk_out); | |
| auto err_out = | |
| usb_inspect::VerifyEndpointInspect(bulk_out, std::nullopt, 200, std::nullopt, 0, 200, 0); | |
| EXPECT_TRUE(err_out.is_ok()) << err_out.error_value(); | |
| // Verify function-level lifecycle events are logged under usb-adb-function/event_history | |
| auto* event_history = this->hierarchy().GetByPath({"usb-adb-function", "event_history"}); | |
| ASSERT_NE(nullptr, event_history); | |
| EXPECT_GT(event_history->children().size(), 0u); | |
| async_usb = {}; | |
| } | |
| TEST_F(UsbAdbTest, QueueTxZeroLengthRejected) { | |
| // TODO(b/395982869): Move this test to a separate error-expecting test file/suite after all | |
| // this is done so we don't have to degrade the max_severity checks for other tests. | |
| auto usb_impl_sync = NormalStartAdb(); | |
| auto dev_client_end = usb_impl_sync.TakeClientEnd(); | |
| fidl::WireClient async_usb(std::move(dev_client_end), | |
| fdf::Dispatcher::GetCurrent()->async_dispatcher()); | |
| // Queue a zero-length vector, which should be rejected. | |
| std::atomic<bool> completed = false; | |
| async_usb->QueueTx(fidl::VectorView<uint8_t>()).ThenExactlyOnce([&](auto& result) { | |
| ASSERT_TRUE(result.ok()); | |
| ASSERT_TRUE(result->is_error()); | |
| EXPECT_EQ(result->error_value(), ZX_ERR_INVALID_ARGS); | |
| completed = true; | |
| }); | |
| driver_test_.runtime().RunUntil([&]() { return completed.load(); }); | |
| async_usb = {}; | |
| } | |
| TEST_F(UsbAdbTest, OfflineRxQueuedAndServicedOnline) { | |
| auto [client_end, server_end] = fidl::Endpoints<fadb::UsbAdbImpl>::Create(); | |
| EXPECT_TRUE(client_->StartAdb(std::move(server_end)).ok()); | |
| auto usb_impl = | |
| fidl::WireClient(std::move(client_end), fdf::Dispatcher::GetCurrent()->async_dispatcher()); | |
| constexpr uint32_t kReceiveSize = 128; | |
| std::atomic<bool> rx_completed = false; | |
| usb_impl->Receive().ThenExactlyOnce([&](auto& response) { | |
| ASSERT_TRUE(response.ok()); | |
| ASSERT_TRUE(response.value().is_ok()); | |
| EXPECT_EQ(response.value().value()->data.size(), kReceiveSize); | |
| rx_completed = true; | |
| }); | |
| // Wait for the driver to queue the Receive request in its software queue. | |
| driver_test_.runtime().RunUntil([&]() { | |
| size_t count = 0; | |
| driver_test_.RunInDriverContext( | |
| [&](UsbAdbDevice& dev) { count = UsbAdbTestHelper::RxRequestsCount(dev); }); | |
| return count == 1; | |
| }); | |
| // Bring USB online. | |
| WaitConfigured(); | |
| EnableUsb(); | |
| // Trigger RX complete at the hardware layer. | |
| driver_test_.RunInEnvironmentTypeContext([&](UsbAdbEnvironment& env) { | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).RequestComplete(ZX_OK, kReceiveSize); | |
| }); | |
| // Wait for the background thread to finish. | |
| driver_test_.runtime().RunUntil([&]() { return rx_completed.load(); }); | |
| usb_impl = {}; | |
| } | |
| TEST_F(UsbAdbTest, StartAdbTwiceFails) { | |
| auto usb_impl = NormalStartAdb(); | |
| // Try to start ADB again with a new channel. It should fail with ZX_ERR_ALREADY_BOUND. | |
| auto [client_end, server_end] = fidl::Endpoints<fadb::UsbAdbImpl>::Create(); | |
| auto result = client_->StartAdb(std::move(server_end)); | |
| ASSERT_TRUE(result.ok()); | |
| ASSERT_TRUE(result->is_error()); | |
| EXPECT_EQ(result->error_value(), ZX_ERR_ALREADY_BOUND); | |
| } | |
| TEST_F(UsbAdbTest, IsConfiguredReflectsDeconfigureState) { | |
| auto usb_impl = NormalStartAdb(); | |
| driver_test_.RunInEnvironmentTypeContext([&](UsbAdbEnvironment& env) { | |
| EXPECT_TRUE(env.fake_dev_->is_configured()); | |
| EXPECT_EQ(env.fake_dev_->deconfigure_count(), 0u); | |
| EXPECT_TRUE(env.fake_dev_->disabled_endpoints().empty()); | |
| }); | |
| driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) { | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).set_hold_cancel(true); | |
| env.fake_dev_->fake_endpoint(kBulkInEp).set_hold_cancel(true); | |
| }); | |
| // Trigger ADB-initiated USB reset via StopAdb(). | |
| auto dev_client_end = client_.TakeClientEnd(); | |
| fidl::WireClient async_client(std::move(dev_client_end), | |
| fdf::Dispatcher::GetCurrent()->async_dispatcher()); | |
| std::atomic<bool> stop_done = false; | |
| async_client->StopAdb().ThenExactlyOnce([&](auto& result) { | |
| EXPECT_TRUE(result.ok()); | |
| stop_done = true; | |
| }); | |
| WaitForState(State::kStoppingForReconnect); | |
| // 1. DisableEndpoint should have been called immediately upon stop request: | |
| driver_test_.RunInEnvironmentTypeContext([&](UsbAdbEnvironment& env) { | |
| auto eps = env.fake_dev_->disabled_endpoints(); | |
| EXPECT_EQ(eps.size(), 2u); | |
| }); | |
| // 2. Deconfigure() should NOT have been called yet while CancelAll is pending: | |
| driver_test_.RunInEnvironmentTypeContext( | |
| [&](UsbAdbEnvironment& env) { EXPECT_EQ(env.fake_dev_->deconfigure_count(), 0u); }); | |
| EXPECT_FALSE(stop_done.load()); | |
| // 3. Release CancelAll: | |
| driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) { | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).ReleaseCancelAll(); | |
| env.fake_dev_->fake_endpoint(kBulkInEp).ReleaseCancelAll(); | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).set_hold_cancel(false); | |
| env.fake_dev_->fake_endpoint(kBulkInEp).set_hold_cancel(false); | |
| env.CancelAllUsbRequests(); | |
| }); | |
| // 4. Now Deconfigure() should have been called and StopAdb() completed: | |
| driver_test_.runtime().RunUntil([&]() { | |
| size_t count = 0; | |
| driver_test_.RunInEnvironmentTypeContext( | |
| [&](UsbAdbEnvironment& env) { count = env.fake_dev_->deconfigure_count(); }); | |
| return count >= 1 && stop_done.load(); | |
| }); | |
| async_client = {}; | |
| } | |
| TEST_F(UsbAdbTest, OfflineTxQueuedAndFlushedOnline) { | |
| auto [client_end, server_end] = fidl::Endpoints<fadb::UsbAdbImpl>::Create(); | |
| ASSERT_TRUE(client_->StartAdb(std::move(server_end)).ok()); | |
| auto usb_impl = fidl::WireClient<fadb::UsbAdbImpl>( | |
| std::move(client_end), fdf::Dispatcher::GetCurrent()->async_dispatcher()); | |
| // Queue a TX request while offline. | |
| std::vector<uint8_t> test_data(100, 0xAA); | |
| std::atomic<bool> tx_completed = false; | |
| usb_impl->QueueTx(fidl::VectorView<uint8_t>::FromExternal(test_data.data(), test_data.size())) | |
| .ThenExactlyOnce([&](fidl::WireUnownedResult<fadb::UsbAdbImpl::QueueTx>& result) { | |
| if (result.ok() && result->is_ok()) { | |
| tx_completed = true; | |
| } | |
| }); | |
| // Wait for the driver to receive and queue the request in its software queue. | |
| driver_test_.runtime().RunUntil([&]() { | |
| size_t count = 0; | |
| driver_test_.RunInDriverContext( | |
| [&](UsbAdbDevice& dev) { count = UsbAdbTestHelper::TxPendingReqsCount(dev); }); | |
| return count == 1; | |
| }); | |
| // Verify that nothing has been queued to the hardware yet. | |
| driver_test_.RunInEnvironmentTypeContext([&](UsbAdbEnvironment& env) { | |
| EXPECT_EQ(env.fake_dev_->fake_endpoint(kBulkInEp).pending_request_count(), 0u); | |
| }); | |
| // Now bring USB online. | |
| WaitConfigured(); | |
| EnableUsb(); | |
| // The driver should now automatically dequeue the request and queue it to bulk IN endpoint. | |
| driver_test_.runtime().RunUntil([&]() { | |
| size_t pending_endpoints = 0; | |
| driver_test_.RunInEnvironmentTypeContext([&](UsbAdbEnvironment& env) { | |
| pending_endpoints = env.fake_dev_->fake_endpoint(kBulkInEp).pending_request_count(); | |
| }); | |
| return pending_endpoints == 1; | |
| }); | |
| // Complete the request at the hardware layer. | |
| driver_test_.RunInEnvironmentTypeContext([&](UsbAdbEnvironment& env) { | |
| env.fake_dev_->fake_endpoint(kBulkInEp).RequestComplete(ZX_OK, test_data.size()); | |
| }); | |
| // Wait for the FIDL callback to finish. | |
| driver_test_.runtime().RunUntil([&]() { return tx_completed.load(); }); | |
| } | |
| TEST_F(UsbAdbTest, UsbFunctionBindingReconfigure) { | |
| // Establish an initial ADB connection | |
| auto [client_end1, server_end1] = fidl::Endpoints<fadb::UsbAdbImpl>::Create(); | |
| auto result1 = client_->StartAdb(std::move(server_end1)); | |
| ASSERT_TRUE(result1.ok()); | |
| // Wait for iface_client_ to become valid | |
| ASSERT_NO_FATAL_FAILURE(EnsureIfaceBound()); | |
| auto config_result_first = iface_client_->SetConfigured({{ | |
| .configured = true, | |
| .speed = fuchsia_hardware_usb_descriptor::UsbSpeed::kHigh, | |
| }}); | |
| ASSERT_TRUE(config_result_first.is_ok()); | |
| auto deconfig_result = iface_client_->SetConfigured({{ | |
| .configured = false, | |
| .speed = fuchsia_hardware_usb_descriptor::UsbSpeed::kHigh, | |
| }}); | |
| ASSERT_TRUE(deconfig_result.is_ok()); | |
| // SetConfigured(false) must transition cleanly to kAwaitingUsbConnection without tearing down | |
| // usb_function_binding_ or calling Deconfigure(). | |
| WaitForState(State::kAwaitingUsbConnection); | |
| driver_test_.RunInEnvironmentTypeContext( | |
| [](UsbAdbEnvironment& env) { EXPECT_EQ(env.fake_dev_->deconfigure_count(), 0u); }); | |
| // Re-configuring on the same iface_client_ must succeed. | |
| auto config_result = iface_client_->SetConfigured({{ | |
| .configured = true, | |
| .speed = fuchsia_hardware_usb_descriptor::UsbSpeed::kHigh, | |
| }}); | |
| ASSERT_TRUE(config_result.is_ok()); | |
| driver_test_.runtime().RunUntilIdle(); | |
| // Verify that driver is online AND usb_function_binding_ remains bound. | |
| driver_test_.RunInDriverContext([&](UsbAdbDevice& dev) { | |
| EXPECT_EQ(UsbAdbTestHelper::state(dev), State::kOnline); | |
| EXPECT_TRUE(UsbAdbTestHelper::has_usb_function_binding(dev)); | |
| }); | |
| // Verify the channel to usb_function_binding_ is still live by making a synchronous call. | |
| auto ping_result = iface_client_->SetConfigured({{ | |
| .configured = true, | |
| .speed = fuchsia_hardware_usb_descriptor::UsbSpeed::kHigh, | |
| }}); | |
| EXPECT_TRUE(ping_result.is_ok()); | |
| ASSERT_NO_FATAL_FAILURE(SafeStopDriver()); | |
| } | |
| TEST_F(UsbAdbTest, DisconnectTransitionsToStoppingState) { | |
| auto usb_impl = NormalStartAdb(); | |
| EventHandler handler; | |
| handler.expected_statuses_.emplace(fadb::StatusFlags::kOnline); | |
| ExpectHandleOneEventSafe(usb_impl, handler); | |
| // Disable cancellation so the fake endpoints hold onto the requests during stopping. | |
| driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) { | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).set_hold_cancel(true); | |
| env.fake_dev_->fake_endpoint(kBulkInEp).set_hold_cancel(true); | |
| }); | |
| // Deconfiguring USB transitions driver state to kStoppingForReconnect and holds the | |
| // SetConfigured(false) completer until CancelAll finishes. | |
| auto iface_client_end = iface_client_.TakeClientEnd(); | |
| fidl::WireClient async_iface(std::move(iface_client_end), | |
| fdf::Dispatcher::GetCurrent()->async_dispatcher()); | |
| std::atomic<bool> unconfigured_done = false; | |
| async_iface->SetConfigured(false, fuchsia_hardware_usb_descriptor::wire::UsbSpeed::kHigh) | |
| .ThenExactlyOnce([&](auto& result) { | |
| EXPECT_TRUE(result.ok()); | |
| EXPECT_TRUE(result->is_ok()); | |
| unconfigured_done = true; | |
| }); | |
| WaitForState(State::kStoppingForReconnect); | |
| EXPECT_FALSE(unconfigured_done.load()); | |
| // Release cancellation so the driver can finish stopping cleanly. | |
| driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) { | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).ReleaseCancelAll(); | |
| env.fake_dev_->fake_endpoint(kBulkInEp).ReleaseCancelAll(); | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).set_hold_cancel(false); | |
| env.fake_dev_->fake_endpoint(kBulkInEp).set_hold_cancel(false); | |
| env.CancelAllUsbRequests(); | |
| }); | |
| driver_test_.runtime().RunUntil([&]() { return unconfigured_done.load(); }); | |
| async_iface = {}; | |
| ASSERT_NO_FATAL_FAILURE(SafeStopDriver()); | |
| } | |
| TEST_F(UsbAdbTest, TeardownWhileRequestsPending) { | |
| auto usb_impl = NormalStartAdb(); | |
| EventHandler handler; | |
| handler.expected_statuses_.emplace(fadb::StatusFlags::kOnline); | |
| ExpectHandleOneEventSafe(usb_impl, handler); | |
| // Disable cancellation so the fake endpoints hold onto the requests. | |
| driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) { | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).set_hold_cancel(true); | |
| env.fake_dev_->fake_endpoint(kBulkInEp).set_hold_cancel(true); | |
| }); | |
| // Initiate disconnect/reset while hold is active to set up pending CancelAll requests. | |
| usb_impl = {}; | |
| iface_client_ = {}; | |
| WaitForState(State::kStoppingForReconnect); | |
| // Verify the driver is waiting for CancelAll and in stopping state: | |
| driver_test_.RunInDriverContext([&](UsbAdbDevice& dev) { | |
| EXPECT_FALSE(UsbAdbTestHelper::CancelAllCompleted(dev)); | |
| EXPECT_EQ(UsbAdbTestHelper::state(dev), State::kStoppingForReconnect); | |
| }); | |
| // Release cancellation so the driver receives the CancelAll callbacks during unbind. | |
| driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) { | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).ReleaseCancelAll(); | |
| env.fake_dev_->fake_endpoint(kBulkInEp).ReleaseCancelAll(); | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).set_hold_cancel(false); | |
| env.fake_dev_->fake_endpoint(kBulkInEp).set_hold_cancel(false); | |
| env.CancelAllUsbRequests(); | |
| }); | |
| ASSERT_NO_FATAL_FAILURE(SafeStopDriver()); | |
| } | |
| TEST_F(UsbAdbTest, InspectStateTransitions) { | |
| inspect::Inspector inspector; | |
| driver_test_.RunInDriverContext( | |
| [&](UsbAdbDevice& dev) { inspector = UsbAdbTestHelper::GetInspector(dev); }); | |
| ASSERT_NO_FATAL_FAILURE(ReadInspect(inspector)); | |
| auto* root_node = this->hierarchy().GetByPath({"usb-adb-function"}); | |
| ASSERT_NE(nullptr, root_node); | |
| const auto* state_prop = root_node->node().get_property<inspect::StringPropertyValue>("state"); | |
| ASSERT_NE(nullptr, state_prop); | |
| EXPECT_EQ("kAwaitingUsbConnection", state_prop->value()); | |
| const auto* online_prop = root_node->node().get_property<inspect::BoolPropertyValue>("online"); | |
| ASSERT_NE(nullptr, online_prop); | |
| EXPECT_FALSE(online_prop->value()); | |
| auto usb_impl = NormalStartAdb(); | |
| driver_test_.RunInDriverContext( | |
| [&](UsbAdbDevice& dev) { inspector = UsbAdbTestHelper::GetInspector(dev); }); | |
| ASSERT_NO_FATAL_FAILURE(ReadInspect(inspector)); | |
| root_node = this->hierarchy().GetByPath({"usb-adb-function"}); | |
| ASSERT_NE(nullptr, root_node); | |
| state_prop = root_node->node().get_property<inspect::StringPropertyValue>("state"); | |
| ASSERT_NE(nullptr, state_prop); | |
| EXPECT_EQ("kOnline", state_prop->value()); | |
| online_prop = root_node->node().get_property<inspect::BoolPropertyValue>("online"); | |
| ASSERT_NE(nullptr, online_prop); | |
| EXPECT_TRUE(online_prop->value()); | |
| ASSERT_NO_FATAL_FAILURE(SafeStopDriver()); | |
| } | |
| TEST_F(UsbAdbTest, InspectLifecycleEvents) { | |
| auto has_function_event = [&](const std::string& expected_event) { | |
| const auto* event_history = this->hierarchy().GetByPath({"usb-adb-function", "event_history"}); | |
| if (event_history == nullptr) { | |
| return false; | |
| } | |
| for (const auto& child : event_history->children()) { | |
| const auto* time_prop = child.node().get_property<inspect::UintPropertyValue>("@time"); | |
| const auto* event_prop = child.node().get_property<inspect::StringPropertyValue>("event"); | |
| if (time_prop != nullptr && event_prop != nullptr && event_prop->value() == expected_event) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| }; | |
| auto has_endpoint_event = [&](const char* endpoint_name, const std::string& expected_event) { | |
| const auto* event_history = | |
| this->hierarchy().GetByPath({"usb-adb-function", endpoint_name, "event_history"}); | |
| if (event_history == nullptr) { | |
| return false; | |
| } | |
| for (const auto& child : event_history->children()) { | |
| const auto* time_prop = child.node().get_property<inspect::UintPropertyValue>("@time"); | |
| const auto* event_prop = child.node().get_property<inspect::StringPropertyValue>("event"); | |
| if (time_prop != nullptr && event_prop != nullptr && event_prop->value() == expected_event) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| }; | |
| auto expect_lifecycle_event = [&](const std::string& expected_event) { | |
| inspect::Inspector inspector; | |
| driver_test_.RunInDriverContext( | |
| [&](UsbAdbDevice& dev) { inspector = UsbAdbTestHelper::GetInspector(dev); }); | |
| ASSERT_NO_FATAL_FAILURE(ReadInspect(inspector)); | |
| EXPECT_TRUE(has_function_event(expected_event)) | |
| << "Missing event '" << expected_event << "' in usb-adb-function/event_history"; | |
| EXPECT_FALSE(has_endpoint_event("bulk_in", expected_event)) | |
| << "Function event '" << expected_event << "' unexpectedly duplicated in bulk_in"; | |
| EXPECT_FALSE(has_endpoint_event("bulk_out", expected_event)) | |
| << "Function event '" << expected_event << "' unexpectedly duplicated in bulk_out"; | |
| }; | |
| // 1. StartAdb logs "adb_client_connected" and configuring USB logs "SetConfigured(true)". | |
| auto usb_impl = NormalStartAdb(); | |
| EventHandler handler; | |
| handler.expected_statuses_.emplace(fadb::StatusFlags::kOnline); | |
| ExpectHandleOneEventSafe(usb_impl, handler); | |
| ASSERT_NO_FATAL_FAILURE(expect_lifecycle_event("adb_client_connected")); | |
| ASSERT_NO_FATAL_FAILURE(expect_lifecycle_event("SetConfigured(true)")); | |
| // 2. SetConfigured(false) while kOnline logs unconfigure milestones on the function node | |
| // and per-endpoint "CancelAll complete" on bulk_in and bulk_out. | |
| auto deconfig_result = iface_client_->SetConfigured({{ | |
| .configured = false, | |
| .speed = fuchsia_hardware_usb_descriptor::UsbSpeed::kHigh, | |
| }}); | |
| ASSERT_TRUE(deconfig_result.is_ok()); | |
| WaitForState(State::kAwaitingUsbConnection); | |
| ASSERT_NO_FATAL_FAILURE(expect_lifecycle_event("usb_bus_disconnected: SetConfigured(false)")); | |
| ASSERT_NO_FATAL_FAILURE(expect_lifecycle_event("SetConfigured(false) start")); | |
| ASSERT_NO_FATAL_FAILURE(expect_lifecycle_event("DisableEndpoints complete")); | |
| ASSERT_NO_FATAL_FAILURE(expect_lifecycle_event("SetConfigured(false) reply")); | |
| EXPECT_TRUE(has_endpoint_event("bulk_in", "CancelAll complete")); | |
| EXPECT_TRUE(has_endpoint_event("bulk_out", "CancelAll complete")); | |
| { | |
| inspect::Inspector inspector; | |
| driver_test_.RunInDriverContext( | |
| [&](UsbAdbDevice& dev) { inspector = UsbAdbTestHelper::GetInspector(dev); }); | |
| ASSERT_NO_FATAL_FAILURE(ReadInspect(inspector)); | |
| const auto* adb_node = hierarchy_->GetByPath({"usb-adb-function"}); | |
| ASSERT_NE(adb_node, nullptr); | |
| const auto* cfg_prop = adb_node->node().get_property<inspect::BoolPropertyValue>("configured"); | |
| ASSERT_NE(cfg_prop, nullptr); | |
| EXPECT_FALSE(cfg_prop->value()); | |
| const auto* uncfg_prop = | |
| adb_node->node().get_property<inspect::BoolPropertyValue>("unconfiguring"); | |
| ASSERT_NE(uncfg_prop, nullptr); | |
| EXPECT_FALSE(uncfg_prop->value()); | |
| const auto* true_cnt = | |
| adb_node->node().get_property<inspect::UintPropertyValue>("set_configured_true_count"); | |
| ASSERT_NE(true_cnt, nullptr); | |
| EXPECT_EQ(true_cnt->value(), 1u); | |
| const auto* false_cnt = | |
| adb_node->node().get_property<inspect::UintPropertyValue>("set_configured_false_count"); | |
| ASSERT_NE(false_cnt, nullptr); | |
| EXPECT_EQ(false_cnt->value(), 1u); | |
| for (const char* ep_name : {"bulk_in", "bulk_out"}) { | |
| const auto* ep_node = hierarchy_->GetByPath({"usb-adb-function", ep_name}); | |
| ASSERT_NE(ep_node, nullptr) << ep_name; | |
| const auto* canc = ep_node->node().get_property<inspect::BoolPropertyValue>("cancelled"); | |
| ASSERT_NE(canc, nullptr) << ep_name; | |
| EXPECT_TRUE(canc->value()); | |
| const auto* inflight = | |
| ep_node->node().get_property<inspect::UintPropertyValue>("in_flight_requests"); | |
| ASSERT_NE(inflight, nullptr) << ep_name; | |
| EXPECT_EQ(inflight->value(), 0u); | |
| const auto* total = | |
| ep_node->node().get_property<inspect::UintPropertyValue>("total_requests"); | |
| ASSERT_NE(total, nullptr) << ep_name; | |
| EXPECT_GT(total->value(), 0u); | |
| } | |
| } | |
| // 3. StopAdb while kOnline logs both "adb_client_disconnected: StopAdb" and "reset_usb_on_stop". | |
| // SetConfigured(false) in step 2 keeps both usb_function_binding_ and adb_binding_ bound, | |
| // so drain the offline status event and re-enable USB on the existing bindings. | |
| handler.expected_statuses_.emplace(fadb::StatusFlags(0)); | |
| ExpectHandleOneEventSafe(usb_impl, handler); | |
| EnableUsb(); | |
| handler.expected_statuses_.emplace(fadb::StatusFlags::kOnline); | |
| ExpectHandleOneEventSafe(usb_impl, handler); | |
| // Hold CancelAll briefly so we also exercise connecting a new ADB client while stopping for | |
| // reconnect ("adb_client_connected_during_stop"). | |
| driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) { | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).set_hold_cancel(true); | |
| env.fake_dev_->fake_endpoint(kBulkInEp).set_hold_cancel(true); | |
| }); | |
| std::atomic<bool> stop_done = false; | |
| auto dev_client_end = client_.TakeClientEnd(); | |
| fidl::WireClient async_client(std::move(dev_client_end), | |
| fdf::Dispatcher::GetCurrent()->async_dispatcher()); | |
| async_client->StopAdb().ThenExactlyOnce([&](auto& result) { | |
| EXPECT_TRUE(result.ok()); | |
| stop_done = true; | |
| }); | |
| WaitForState(State::kStoppingForReconnect); | |
| ASSERT_NO_FATAL_FAILURE(expect_lifecycle_event("adb_client_disconnected: StopAdb")); | |
| ASSERT_NO_FATAL_FAILURE(expect_lifecycle_event("reset_usb_on_stop")); | |
| auto [client_end_during_stop, server_end_during_stop] = | |
| fidl::Endpoints<fadb::UsbAdbImpl>::Create(); | |
| std::atomic<bool> start_during_stop_done = false; | |
| async_client->StartAdb(std::move(server_end_during_stop)).ThenExactlyOnce([&](auto& result) { | |
| EXPECT_TRUE(result.ok()); | |
| start_during_stop_done = true; | |
| }); | |
| driver_test_.runtime().RunUntil([&]() { return start_during_stop_done.load(); }); | |
| ASSERT_NO_FATAL_FAILURE(expect_lifecycle_event("adb_client_connected_during_stop")); | |
| driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) { | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).ReleaseCancelAll(); | |
| env.fake_dev_->fake_endpoint(kBulkInEp).ReleaseCancelAll(); | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).set_hold_cancel(false); | |
| env.fake_dev_->fake_endpoint(kBulkInEp).set_hold_cancel(false); | |
| env.CancelAllUsbRequests(); | |
| }); | |
| driver_test_.runtime().RunUntil([&]() { return stop_done.load(); }); | |
| WaitForState(State::kAwaitingUsbConnection); | |
| async_client = {}; | |
| client_.Bind(*driver_test_.Connect<fadb::Service::Adb>()); | |
| client_end_during_stop.reset(); | |
| driver_test_.runtime().RunUntil([&]() { | |
| bool bound = true; | |
| driver_test_.RunInDriverContext( | |
| [&](UsbAdbDevice& dev) { bound = UsbAdbTestHelper::has_adb_binding(dev); }); | |
| return !bound; | |
| }); | |
| // 4. RxComplete / TxComplete observing ZX_ERR_IO_NOT_PRESENT while kOnline logs | |
| // "usb_disconnect_observed: RxComplete" / "usb_disconnect_observed: TxComplete", and | |
| // closing UsbAdbImpl when configure subsequently fails logs "restarting_usb_connection_failed". | |
| iface_client_ = {}; | |
| usb_impl = NormalStartAdb(); | |
| handler.expected_statuses_.emplace(fadb::StatusFlags::kOnline); | |
| ExpectHandleOneEventSafe(usb_impl, handler); | |
| driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) { | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).RequestComplete(ZX_ERR_IO_NOT_PRESENT, 0); | |
| }); | |
| driver_test_.runtime().RunUntil([&]() { | |
| inspect::Inspector inspector; | |
| driver_test_.RunInDriverContext( | |
| [&](UsbAdbDevice& dev) { inspector = UsbAdbTestHelper::GetInspector(dev); }); | |
| ReadInspect(inspector); | |
| return has_function_event("usb_disconnect_observed: RxComplete"); | |
| }); | |
| // Reset usb_disconnect_observed_ via SetConfigured(false) -> SetConfigured(true), then queue a TX | |
| // packet and fail it with ZX_ERR_IO_NOT_PRESENT to verify "usb_disconnect_observed: TxComplete". | |
| ASSERT_TRUE(iface_client_ | |
| ->SetConfigured({{ | |
| .configured = false, | |
| .speed = fuchsia_hardware_usb_descriptor::UsbSpeed::kHigh, | |
| }}) | |
| .is_ok()); | |
| WaitForState(State::kAwaitingUsbConnection); | |
| handler.expected_statuses_.emplace(fadb::StatusFlags(0)); | |
| ExpectHandleOneEventSafe(usb_impl, handler); | |
| EnableUsb(); | |
| handler.expected_statuses_.emplace(fadb::StatusFlags::kOnline); | |
| ExpectHandleOneEventSafe(usb_impl, handler); | |
| uint8_t tx_byte = 0x55; | |
| auto async_usb = fidl::WireClient<fadb::UsbAdbImpl>( | |
| usb_impl.TakeClientEnd(), fdf::Dispatcher::GetCurrent()->async_dispatcher()); | |
| async_usb->QueueTx(fidl::VectorView<uint8_t>::FromExternal(&tx_byte, 1)) | |
| .ThenExactlyOnce([](auto&) {}); | |
| driver_test_.runtime().RunUntil([&]() { | |
| size_t pending = 0; | |
| driver_test_.RunInEnvironmentTypeContext([&](UsbAdbEnvironment& env) { | |
| pending = env.fake_dev_->fake_endpoint(kBulkInEp).pending_request_count(); | |
| }); | |
| return pending == 1u; | |
| }); | |
| driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) { | |
| env.fake_dev_->fake_endpoint(kBulkInEp).RequestComplete(ZX_ERR_IO_NOT_PRESENT, 0); | |
| }); | |
| driver_test_.runtime().RunUntil([&]() { | |
| inspect::Inspector inspector; | |
| driver_test_.RunInDriverContext( | |
| [&](UsbAdbDevice& dev) { inspector = UsbAdbTestHelper::GetInspector(dev); }); | |
| ReadInspect(inspector); | |
| return has_function_event("usb_disconnect_observed: TxComplete"); | |
| }); | |
| // Re-cycle SetConfigured(false)/(true) so usb_disconnect_observed_ is false, close | |
| // iface_client_ while bound to verify "usb_function_disconnected: unbind", inject Configure | |
| // failure, and close async_usb to verify "adb_client_disconnected: unbind" + | |
| // "restarting_usb_connection_failed". | |
| ASSERT_TRUE(iface_client_ | |
| ->SetConfigured({{ | |
| .configured = false, | |
| .speed = fuchsia_hardware_usb_descriptor::UsbSpeed::kHigh, | |
| }}) | |
| .is_ok()); | |
| WaitForState(State::kAwaitingUsbConnection); | |
| EnableUsb(); | |
| iface_client_ = {}; | |
| driver_test_.runtime().RunUntil([&]() { | |
| inspect::Inspector inspector; | |
| driver_test_.RunInDriverContext( | |
| [&](UsbAdbDevice& dev) { inspector = UsbAdbTestHelper::GetInspector(dev); }); | |
| ReadInspect(inspector); | |
| return has_function_event("usb_function_disconnected: unbind"); | |
| }); | |
| driver_test_.RunInEnvironmentTypeContext( | |
| [](UsbAdbEnvironment& env) { env.fake_dev_->set_configure_status(ZX_ERR_BAD_STATE); }); | |
| async_usb = {}; | |
| WaitForState(State::kAwaitingUsbConnection); | |
| ASSERT_NO_FATAL_FAILURE(expect_lifecycle_event("adb_client_disconnected: unbind")); | |
| ASSERT_NO_FATAL_FAILURE(expect_lifecycle_event("restarting_usb_connection_failed")); | |
| // 5. Stopping the driver logs "driver_stopped". | |
| inspect::Inspector inspector; | |
| driver_test_.RunInDriverContext( | |
| [&](UsbAdbDevice& dev) { inspector = UsbAdbTestHelper::GetInspector(dev); }); | |
| ASSERT_NO_FATAL_FAILURE(SafeStopDriver()); | |
| ASSERT_NO_FATAL_FAILURE(ReadInspect(inspector)); | |
| EXPECT_TRUE(has_function_event("driver_stopped")); | |
| } | |
| TEST_F(UsbAdbTest, StartAdbFailsWhileStopping) { | |
| driver_test_.RunInDriverContext( | |
| [](UsbAdbDevice& dev) { UsbAdbTestHelper::set_state(dev, State::kStoppingForUnbind); }); | |
| auto [client_end, server_end] = fidl::Endpoints<fadb::UsbAdbImpl>::Create(); | |
| auto result = client_->StartAdb(std::move(server_end)); | |
| EXPECT_TRUE(result.ok()); | |
| EXPECT_TRUE(result->is_error()); | |
| EXPECT_EQ(result->error_value(), ZX_ERR_BAD_STATE); | |
| driver_test_.RunInDriverContext( | |
| [](UsbAdbDevice& dev) { UsbAdbTestHelper::set_state(dev, State::kAwaitingUsbConnection); }); | |
| ASSERT_NO_FATAL_FAILURE(SafeStopDriver()); | |
| } | |
| TEST_F(UsbAdbTest, OperationsFailWhileStopping) { | |
| auto [client_end, server_end] = fidl::Endpoints<fadb::UsbAdbImpl>::Create(); | |
| auto result = client_->StartAdb(std::move(server_end)); | |
| EXPECT_TRUE(result.ok()); | |
| fidl::WireSyncClient<fadb::UsbAdbImpl> adb_client{std::move(client_end)}; | |
| driver_test_.RunInDriverContext( | |
| [](UsbAdbDevice& dev) { UsbAdbTestHelper::set_state(dev, State::kStoppingForUnbind); }); | |
| std::vector<uint8_t> data(1); | |
| auto tx_result = | |
| adb_client->QueueTx(fidl::VectorView<uint8_t>::FromExternal(data.data(), data.size())); | |
| EXPECT_TRUE(tx_result.ok()); | |
| EXPECT_TRUE(tx_result->is_error()); | |
| EXPECT_EQ(tx_result->error_value(), ZX_ERR_BAD_STATE); | |
| auto rx_result = adb_client->Receive(); | |
| EXPECT_TRUE(rx_result.ok()); | |
| EXPECT_TRUE(rx_result->is_error()); | |
| EXPECT_EQ(rx_result->error_value(), ZX_ERR_BAD_STATE); | |
| driver_test_.RunInDriverContext( | |
| [](UsbAdbDevice& dev) { UsbAdbTestHelper::set_state(dev, State::kAwaitingUsbConnection); }); | |
| ASSERT_NO_FATAL_FAILURE(SafeStopDriver()); | |
| } | |
| TEST_F(UsbAdbTest, EarlyConnectRace) { | |
| auto [client_end, server_end] = fidl::Endpoints<fadb::UsbAdbImpl>::Create(); | |
| EXPECT_TRUE(client_->StartAdb(std::move(server_end)).ok()); | |
| uint8_t test_data[] = {0x01, 0x02, 0x03}; | |
| fidl::WireClient async_usb(std::move(client_end), | |
| fdf::Dispatcher::GetCurrent()->async_dispatcher()); | |
| bool completed = false; | |
| async_usb->QueueTx(fidl::VectorView<uint8_t>::FromExternal(test_data, sizeof(test_data))) | |
| .ThenExactlyOnce([&](auto& response) { completed = true; }); | |
| driver_test_.runtime().RunUntil([&]() { | |
| size_t count = 0; | |
| driver_test_.RunInDriverContext( | |
| [&](UsbAdbDevice& dev) { count = UsbAdbTestHelper::TxPendingReqsCount(dev); }); | |
| return count == 1; | |
| }); | |
| SafeStopDriver(); | |
| } | |
| TEST(UsbAdbStartTest, StartFailsGracefullyWhenConfigureFails) { | |
| fdf_testing::BackgroundDriverTest<UsbAdbTestConfig> driver_test; | |
| driver_test.RunInEnvironmentTypeContext( | |
| [](UsbAdbEnvironment& env) { env.set_configure_status(ZX_ERR_BAD_STATE); }); | |
| // StartDriver must fail with ZX_ERR_BAD_STATE rather than crashing/panicking. | |
| zx::result<> start_result = driver_test.StartDriver(); | |
| EXPECT_TRUE(start_result.is_error()); | |
| EXPECT_EQ(start_result.status_value(), ZX_ERR_BAD_STATE); | |
| driver_test.runtime().RunUntilIdle(); | |
| } | |
| TEST_F(UsbAdbTest, ReconnectHandlesConfigureFailureGracefully) { | |
| auto usb_impl = NormalStartAdb(); | |
| EventHandler handler; | |
| handler.expected_statuses_.emplace(fadb::StatusFlags::kOnline); | |
| ExpectHandleOneEventSafe(usb_impl, handler); | |
| // Set the fake device to reject subsequent Configure calls (simulating peripheral | |
| // stopping/tearing down). | |
| driver_test_.RunInEnvironmentTypeContext( | |
| [](UsbAdbEnvironment& env) { env.fake_dev_->set_configure_status(ZX_ERR_BAD_STATE); }); | |
| // Trigger an ADB-initiated USB reset by closing the UsbAdbImpl channel while online. | |
| usb_impl = {}; | |
| WaitForState(State::kAwaitingUsbConnection); | |
| // The driver should have attempted to restart USB, encountered ZX_ERR_BAD_STATE, | |
| // transitioned cleanly to kAwaitingUsbConnection, and logged a warning without panicking. | |
| // Verify that subsequent StopAdb() completes cleanly (returning zx::ok()) rather than hanging. | |
| auto stop_res = client_->StopAdb(); | |
| ASSERT_TRUE(stop_res.ok()); | |
| EXPECT_TRUE(stop_res->is_ok()); | |
| ASSERT_NO_FATAL_FAILURE(SafeStopDriver()); | |
| } | |
| // Verifies that when endpoint configuration fails during SetConfigured(true) (e.g. Bulk OUT | |
| // fails due to resource exhaustion in the controller), EnableEndpoints() propagates the error | |
| // cleanly via the FIDL completer instead of calling ZX_PANIC. Verifies that the driver remains | |
| // in State::kAwaitingUsbConnection and can successfully recover when endpoint configuration | |
| // succeeds on a subsequent retry. | |
| TEST_F(UsbAdbTest, ConfigureEndpointFailureDoesNotPanic) { | |
| // Connect ADB client while USB is not yet configured. | |
| auto [client_end, server_end] = fidl::Endpoints<fadb::UsbAdbImpl>::Create(); | |
| ASSERT_TRUE(client_->StartAdb(std::move(server_end)).ok()); | |
| WaitConfigured(); | |
| ASSERT_TRUE(iface_client_.is_valid()); | |
| // Set the fake device to fail endpoint configuration. | |
| driver_test_.RunInEnvironmentTypeContext( | |
| [](UsbAdbEnvironment& env) { env.fake_dev_->set_fail_configure_endpoint(true); }); | |
| // SetConfigured(true) should fail gracefully with ZX_ERR_NO_RESOURCES rather than panicking. | |
| auto result = iface_client_->SetConfigured({{ | |
| .configured = true, | |
| .speed = fuchsia_hardware_usb_descriptor::UsbSpeed::kFull, | |
| }}); | |
| EXPECT_TRUE(result.is_error()); | |
| EXPECT_EQ(result.error_value().domain_error(), ZX_ERR_NO_RESOURCES); | |
| // The driver should remain in kAwaitingUsbConnection rather than transitioning to kOnline. | |
| driver_test_.RunInDriverContext([&](UsbAdbDevice& dev) { | |
| EXPECT_EQ(UsbAdbTestHelper::state(dev), State::kAwaitingUsbConnection); | |
| }); | |
| // Clear the endpoint configuration failure on the fake device and retry. | |
| driver_test_.RunInEnvironmentTypeContext( | |
| [](UsbAdbEnvironment& env) { env.fake_dev_->set_fail_configure_endpoint(false); }); | |
| auto retry_result = iface_client_->SetConfigured({{ | |
| .configured = true, | |
| .speed = fuchsia_hardware_usb_descriptor::UsbSpeed::kFull, | |
| }}); | |
| EXPECT_TRUE(retry_result.is_ok()); | |
| WaitForState(State::kOnline); | |
| ASSERT_NO_FATAL_FAILURE(SafeStopDriver()); | |
| } | |
| // Verifies that when Bulk IN endpoint configuration fails after Bulk OUT has already been | |
| // successfully configured, EnableEndpoints() rolls back the partial configuration by calling | |
| // DisableEndpoint() on the Bulk OUT endpoint. Verifies that the driver does not leave orphan | |
| // active endpoints in the controller, remains in State::kAwaitingUsbConnection, and can | |
| // recover to State::kOnline when the failure condition clears. | |
| TEST_F(UsbAdbTest, ConfigureEndpointFailureBulkInRollsBackBulkOut) { | |
| // Connect ADB client while USB is not yet configured. | |
| auto [client_end, server_end] = fidl::Endpoints<fadb::UsbAdbImpl>::Create(); | |
| ASSERT_TRUE(client_->StartAdb(std::move(server_end)).ok()); | |
| WaitConfigured(); | |
| ASSERT_TRUE(iface_client_.is_valid()); | |
| // Fail specifically on Bulk IN endpoint configuration (after Bulk OUT has succeeded). | |
| driver_test_.RunInEnvironmentTypeContext( | |
| [](UsbAdbEnvironment& env) { env.fake_dev_->set_fail_configure_endpoint_addr(kBulkInEp); }); | |
| // SetConfigured(true) should fail with ZX_ERR_NO_RESOURCES. | |
| auto result = iface_client_->SetConfigured({{ | |
| .configured = true, | |
| .speed = fuchsia_hardware_usb_descriptor::UsbSpeed::kFull, | |
| }}); | |
| EXPECT_TRUE(result.is_error()); | |
| EXPECT_EQ(result.error_value().domain_error(), ZX_ERR_NO_RESOURCES); | |
| // Bulk OUT must have been rolled back via DisableEndpoint. | |
| driver_test_.RunInEnvironmentTypeContext([&](UsbAdbEnvironment& env) { | |
| auto disabled = env.fake_dev_->disabled_endpoints(); | |
| ASSERT_EQ(disabled.size(), 1u); | |
| EXPECT_EQ(disabled[0], kBulkOutEp); | |
| env.fake_dev_->clear_disabled_endpoints(); | |
| }); | |
| // The driver should remain in kAwaitingUsbConnection rather than transitioning to kOnline. | |
| driver_test_.RunInDriverContext([&](UsbAdbDevice& dev) { | |
| EXPECT_EQ(UsbAdbTestHelper::state(dev), State::kAwaitingUsbConnection); | |
| }); | |
| // Clear the endpoint configuration failure on the fake device and retry. | |
| driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) { | |
| env.fake_dev_->set_fail_configure_endpoint_addr(std::nullopt); | |
| }); | |
| auto retry_result = iface_client_->SetConfigured({{ | |
| .configured = true, | |
| .speed = fuchsia_hardware_usb_descriptor::UsbSpeed::kFull, | |
| }}); | |
| EXPECT_TRUE(retry_result.is_ok()); | |
| WaitForState(State::kOnline); | |
| ASSERT_NO_FATAL_FAILURE(SafeStopDriver()); | |
| } | |
| // Verifies that during a USB disconnect/reconnect cycle, if endpoint configuration fails when the | |
| // host reconfigures USB, the driver handles the error gracefully without panicking. Verifies | |
| // that SetConfigured(true) returns an error, the driver maintains State::kAwaitingUsbConnection, | |
| // and full connectivity to State::kOnline is restored once endpoint configuration succeeds. | |
| TEST_F(UsbAdbTest, ReconnectHandlesConfigureEndpointFailureGracefully) { | |
| auto usb_impl = NormalStartAdb(); | |
| EventHandler handler; | |
| handler.expected_statuses_.emplace(fadb::StatusFlags::kOnline); | |
| ExpectHandleOneEventSafe(usb_impl, handler); | |
| // Trigger disconnect by deconfiguring USB. | |
| auto deconfig_result = iface_client_->SetConfigured({{ | |
| .configured = false, | |
| .speed = fuchsia_hardware_usb_descriptor::UsbSpeed::kHigh, | |
| }}); | |
| ASSERT_TRUE(deconfig_result.is_ok()); | |
| // Wait for requests to drain and driver to reach kAwaitingUsbConnection. | |
| WaitForState(State::kAwaitingUsbConnection); | |
| ASSERT_TRUE(iface_client_.is_valid()); | |
| // Set the fake USB device to fail endpoint configuration on reconnect. | |
| driver_test_.RunInEnvironmentTypeContext( | |
| [](UsbAdbEnvironment& env) { env.fake_dev_->set_fail_configure_endpoint(true); }); | |
| // Host attempts to re-enable USB. Configuration should fail gracefully. | |
| auto reconfig_result = iface_client_->SetConfigured({{ | |
| .configured = true, | |
| .speed = fuchsia_hardware_usb_descriptor::UsbSpeed::kHigh, | |
| }}); | |
| EXPECT_TRUE(reconfig_result.is_error()); | |
| EXPECT_EQ(reconfig_result.error_value().domain_error(), ZX_ERR_NO_RESOURCES); | |
| // Driver must remain in kAwaitingUsbConnection. | |
| driver_test_.RunInDriverContext([&](UsbAdbDevice& dev) { | |
| EXPECT_EQ(UsbAdbTestHelper::state(dev), State::kAwaitingUsbConnection); | |
| }); | |
| // Clear the endpoint configuration failure and re-attempt connection. | |
| driver_test_.RunInEnvironmentTypeContext( | |
| [](UsbAdbEnvironment& env) { env.fake_dev_->set_fail_configure_endpoint(false); }); | |
| auto retry_result = iface_client_->SetConfigured({{ | |
| .configured = true, | |
| .speed = fuchsia_hardware_usb_descriptor::UsbSpeed::kHigh, | |
| }}); | |
| EXPECT_TRUE(retry_result.is_ok()); | |
| WaitForState(State::kOnline); | |
| ASSERT_NO_FATAL_FAILURE(SafeStopDriver()); | |
| } | |
| // Verifies that if SetConfigured(true) arrives while the driver is stopping for reconnect | |
| // (e.g. rapid host reconnect while CancelAll is still draining), the driver records | |
| // pending_reconfigure_ and immediately re-enables endpoints and transitions to State::kOnline | |
| // once the stopping cycle completes. | |
| TEST_F(UsbAdbTest, SetConfiguredTrueDuringStoppingReconfiguresCleanly) { | |
| auto usb_impl = NormalStartAdb(); | |
| EventHandler handler; | |
| handler.expected_statuses_.emplace(fadb::StatusFlags::kOnline); | |
| ExpectHandleOneEventSafe(usb_impl, handler); | |
| // 1. Hold CancelAll so that the driver remains in State::kStoppingForReconnect. | |
| driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) { | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).set_hold_cancel(true); | |
| env.fake_dev_->fake_endpoint(kBulkInEp).set_hold_cancel(true); | |
| }); | |
| EnsureIfaceBound(); | |
| ASSERT_TRUE(iface_client_.is_valid()); | |
| auto iface_client_end = iface_client_.TakeClientEnd(); | |
| fidl::WireClient async_iface(std::move(iface_client_end), | |
| fdf::Dispatcher::GetCurrent()->async_dispatcher()); | |
| // 2. Disconnect USB: SetConfigured(false) asynchronously while CancelAll is held. | |
| std::atomic<bool> unconfigured_done = false; | |
| async_iface->SetConfigured(false, fuchsia_hardware_usb_descriptor::wire::UsbSpeed::kFull) | |
| .ThenExactlyOnce([&](auto& result) { | |
| EXPECT_TRUE(result.ok()); | |
| EXPECT_TRUE(result->is_ok()); | |
| unconfigured_done = true; | |
| }); | |
| WaitForState(State::kStoppingForReconnect); | |
| EXPECT_FALSE(unconfigured_done.load()); | |
| // 3. While still in kStoppingForReconnect, host reconnects: SetConfigured(true). | |
| std::atomic<bool> reconfigured_done = false; | |
| async_iface->SetConfigured(true, fuchsia_hardware_usb_descriptor::wire::UsbSpeed::kFull) | |
| .ThenExactlyOnce([&](auto& result) { | |
| EXPECT_TRUE(result.ok()); | |
| EXPECT_TRUE(result->is_ok()); | |
| reconfigured_done = true; | |
| }); | |
| driver_test_.runtime().RunUntil([&]() { return reconfigured_done.load(); }); | |
| // 4. Release CancelAll so the stopping process completes. | |
| driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) { | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).ReleaseCancelAll(); | |
| env.fake_dev_->fake_endpoint(kBulkInEp).ReleaseCancelAll(); | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).set_hold_cancel(false); | |
| env.fake_dev_->fake_endpoint(kBulkInEp).set_hold_cancel(false); | |
| env.CancelAllUsbRequests(); | |
| }); | |
| // 5. The driver should complete its stop and immediately transition to State::kOnline. | |
| WaitForState(State::kOnline); | |
| driver_test_.runtime().RunUntil([&]() { return unconfigured_done.load(); }); | |
| async_iface = {}; | |
| ASSERT_NO_FATAL_FAILURE(SafeStopDriver()); | |
| } | |
| // Verifies that SetConfigured(false) from usb-peripheral (e.g. on physical unplug or host reset) | |
| // holds its FIDL completer until CancelAll() finishes, does NOT call function_->Deconfigure() | |
| // or StartUsb(), and keeps both usb_function_binding_ and adb_binding_ alive across reconnect. | |
| TEST_F(UsbAdbTest, SetConfiguredFalseDoesNotDeconfigureFunctionAndKeepsAdbBound) { | |
| auto usb_impl = NormalStartAdb(); | |
| EventHandler handler; | |
| handler.expected_statuses_.emplace(fadb::StatusFlags::kOnline); | |
| ExpectHandleOneEventSafe(usb_impl, handler); | |
| driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) { | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).set_hold_cancel(true); | |
| env.fake_dev_->fake_endpoint(kBulkInEp).set_hold_cancel(true); | |
| }); | |
| auto iface_client_end = iface_client_.TakeClientEnd(); | |
| fidl::WireClient async_iface(std::move(iface_client_end), | |
| fdf::Dispatcher::GetCurrent()->async_dispatcher()); | |
| std::atomic<bool> set_configured_done = false; | |
| async_iface->SetConfigured(false, fuchsia_hardware_usb_descriptor::wire::UsbSpeed::kHigh) | |
| .ThenExactlyOnce([&](auto& result) { | |
| ASSERT_TRUE(result.ok()); | |
| EXPECT_TRUE(result->is_ok()); | |
| set_configured_done = true; | |
| }); | |
| WaitForState(State::kStoppingForReconnect); | |
| EXPECT_FALSE(set_configured_done.load()); | |
| driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) { | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).ReleaseCancelAll(); | |
| env.fake_dev_->fake_endpoint(kBulkInEp).ReleaseCancelAll(); | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).set_hold_cancel(false); | |
| env.fake_dev_->fake_endpoint(kBulkInEp).set_hold_cancel(false); | |
| env.CancelAllUsbRequests(); | |
| }); | |
| driver_test_.runtime().RunUntil([&]() { return set_configured_done.load(); }); | |
| WaitForState(State::kAwaitingUsbConnection); | |
| // Verify Deconfigure() was NEVER called and both bindings remain alive. | |
| driver_test_.RunInEnvironmentTypeContext( | |
| [](UsbAdbEnvironment& env) { EXPECT_EQ(env.fake_dev_->deconfigure_count(), 0u); }); | |
| driver_test_.RunInDriverContext([](UsbAdbDevice& dev) { | |
| EXPECT_TRUE(UsbAdbTestHelper::has_usb_function_binding(dev)); | |
| EXPECT_TRUE(UsbAdbTestHelper::has_adb_binding(dev)); | |
| }); | |
| // The existing usb_impl client must receive OnStatusChanged(0) without its channel closing. | |
| handler.expected_statuses_.emplace(fadb::StatusFlags(0)); | |
| ExpectHandleOneEventSafe(usb_impl, handler); | |
| // Re-configuring on the same async_iface brings the same usb_impl client back online. | |
| std::atomic<bool> reconfigured_done = false; | |
| async_iface->SetConfigured(true, fuchsia_hardware_usb_descriptor::wire::UsbSpeed::kHigh) | |
| .ThenExactlyOnce([&](auto& result) { | |
| ASSERT_TRUE(result.ok()); | |
| EXPECT_TRUE(result->is_ok()); | |
| reconfigured_done = true; | |
| }); | |
| driver_test_.runtime().RunUntil([&]() { return reconfigured_done.load(); }); | |
| WaitForState(State::kOnline); | |
| handler.expected_statuses_.emplace(fadb::StatusFlags::kOnline); | |
| ExpectHandleOneEventSafe(usb_impl, handler); | |
| async_iface = {}; | |
| ASSERT_NO_FATAL_FAILURE(SafeStopDriver()); | |
| } | |
| // Verifies the physical unplug race: when DWC3 cancels an active RX request with | |
| // ZX_ERR_IO_NOT_PRESENT slightly before usb-peripheral delivers SetConfigured(false), | |
| // ReceiveQueued() does not re-queue the failed request onto the disconnecting endpoint, | |
| // and if adbd calls StopAdb() before SetConfigured(false) arrives, SetConfigured(false) | |
| // coalesces cleanly without logging a warning or calling function_->Deconfigure(). | |
| TEST_F(UsbAdbTest, SetConfiguredFalseDuringStopAdbCoalescesWithoutDeconfigure) { | |
| auto usb_impl_sync = NormalStartAdb(); | |
| EventHandler handler; | |
| handler.expected_statuses_.emplace(fadb::StatusFlags::kOnline); | |
| ExpectHandleOneEventSafe(usb_impl_sync, handler); | |
| auto usb_impl = fidl::WireClient<fadb::UsbAdbImpl>( | |
| usb_impl_sync.TakeClientEnd(), fdf::Dispatcher::GetCurrent()->async_dispatcher()); | |
| // 1. Queue a Receive() call and complete one bulk OUT request with ZX_ERR_IO_NOT_PRESENT. | |
| std::atomic<bool> rx_failed = false; | |
| usb_impl->Receive().ThenExactlyOnce([&](auto& response) { | |
| ASSERT_TRUE(response.ok()); | |
| ASSERT_TRUE(response->is_error()); | |
| EXPECT_EQ(response->error_value(), ZX_ERR_CANCELED); | |
| rx_failed = true; | |
| }); | |
| driver_test_.runtime().RunUntil([&]() { | |
| size_t count = 0; | |
| driver_test_.RunInDriverContext( | |
| [&](UsbAdbDevice& dev) { count = UsbAdbTestHelper::RxRequestsCount(dev); }); | |
| return count == 1; | |
| }); | |
| driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) { | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).RequestComplete(ZX_ERR_IO_NOT_PRESENT, 0); | |
| }); | |
| driver_test_.runtime().RunUntil([&]() { return rx_failed.load(); }); | |
| // ReceiveQueued() must have returned the request to the pool rather than re-queuing it onto the | |
| // disconnecting endpoint. | |
| driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) { | |
| EXPECT_EQ(env.fake_dev_->fake_endpoint(kBulkOutEp).pending_request_count(), kBulkRxCount - 1); | |
| }); | |
| // 2. Hold CancelAll and simulate adbd calling StopAdb() 5ms before SetConfigured(false) arrives. | |
| driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) { | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).set_hold_cancel(true); | |
| env.fake_dev_->fake_endpoint(kBulkInEp).set_hold_cancel(true); | |
| }); | |
| auto dev_client_end = client_.TakeClientEnd(); | |
| fidl::WireClient async_client(std::move(dev_client_end), | |
| fdf::Dispatcher::GetCurrent()->async_dispatcher()); | |
| std::atomic<bool> stop_adb_done = false; | |
| async_client->StopAdb().ThenExactlyOnce([&](auto& result) { | |
| ASSERT_TRUE(result.ok()); | |
| EXPECT_TRUE(result->is_ok()); | |
| stop_adb_done = true; | |
| }); | |
| WaitForState(State::kStoppingForReconnect); | |
| EXPECT_FALSE(stop_adb_done.load()); | |
| // 3. Now SetConfigured(false) arrives from usb-peripheral while kStoppingForReconnect is active. | |
| auto iface_client_end = iface_client_.TakeClientEnd(); | |
| fidl::WireClient async_iface(std::move(iface_client_end), | |
| fdf::Dispatcher::GetCurrent()->async_dispatcher()); | |
| std::atomic<bool> set_configured_done = false; | |
| async_iface->SetConfigured(false, fuchsia_hardware_usb_descriptor::wire::UsbSpeed::kHigh) | |
| .ThenExactlyOnce([&](auto& result) { | |
| ASSERT_TRUE(result.ok()); | |
| EXPECT_TRUE(result->is_ok()); | |
| set_configured_done = true; | |
| }); | |
| driver_test_.runtime().RunUntilIdle(); | |
| EXPECT_FALSE(stop_adb_done.load()); | |
| EXPECT_FALSE(set_configured_done.load()); | |
| // 4. Release CancelAll and verify both completers finish without calling Deconfigure(). | |
| driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) { | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).ReleaseCancelAll(); | |
| env.fake_dev_->fake_endpoint(kBulkInEp).ReleaseCancelAll(); | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).set_hold_cancel(false); | |
| env.fake_dev_->fake_endpoint(kBulkInEp).set_hold_cancel(false); | |
| env.CancelAllUsbRequests(); | |
| }); | |
| driver_test_.runtime().RunUntil( | |
| [&]() { return stop_adb_done.load() && set_configured_done.load(); }); | |
| WaitForState(State::kAwaitingUsbConnection); | |
| driver_test_.RunInEnvironmentTypeContext( | |
| [](UsbAdbEnvironment& env) { EXPECT_EQ(env.fake_dev_->deconfigure_count(), 0u); }); | |
| usb_impl = {}; | |
| async_client = {}; | |
| async_iface = {}; | |
| ASSERT_NO_FATAL_FAILURE(SafeStopDriver()); | |
| } | |
| // Verifies that StartAdb() can be called after USB is already online (State::kOnline) AND | |
| // while a previous session's stop/reconnect is still draining (State::kStoppingForReconnect). | |
| TEST_F(UsbAdbTest, StartAdbAfterUsbAlreadyOnlineAndDuringStop) { | |
| // Part A: Bring USB online BEFORE calling StartAdb(), and verify that a Bulk OUT packet | |
| // arriving before StartAdb() is buffered in pending_replies_ and delivered once StartAdb() | |
| // and Receive() are called. | |
| WaitConfigured(); | |
| EnableUsb(); | |
| WaitForState(State::kOnline); | |
| ExpectReceiveData(32); | |
| auto [client_end1, server_end1] = fidl::Endpoints<fadb::UsbAdbImpl>::Create(); | |
| auto start_res1 = client_->StartAdb(std::move(server_end1)); | |
| ASSERT_TRUE(start_res1.ok()); | |
| ASSERT_TRUE(start_res1->is_ok()); | |
| fidl::WireSyncClient<fadb::UsbAdbImpl> usb_impl1(std::move(client_end1)); | |
| EventHandler handler1; | |
| handler1.expected_statuses_.emplace(fadb::StatusFlags::kOnline); | |
| ExpectHandleOneEventSafe(usb_impl1, handler1); | |
| auto rx1_res = usb_impl1->Receive(); | |
| ASSERT_TRUE(rx1_res.ok()); | |
| ASSERT_TRUE(rx1_res->is_ok()); | |
| EXPECT_EQ(rx1_res->value()->data.size(), 32u); | |
| // Part B: Hold CancelAll, call StopAdb(), and immediately call StartAdb() with a new channel | |
| // while the driver is still in State::kStoppingForReconnect. | |
| driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) { | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).set_hold_cancel(true); | |
| env.fake_dev_->fake_endpoint(kBulkInEp).set_hold_cancel(true); | |
| }); | |
| auto dev_client_end = client_.TakeClientEnd(); | |
| fidl::WireClient async_client(std::move(dev_client_end), | |
| fdf::Dispatcher::GetCurrent()->async_dispatcher()); | |
| std::atomic<bool> stop_done = false; | |
| async_client->StopAdb().ThenExactlyOnce([&](auto& result) { | |
| ASSERT_TRUE(result.ok()); | |
| EXPECT_TRUE(result->is_ok()); | |
| stop_done = true; | |
| }); | |
| WaitForState(State::kStoppingForReconnect); | |
| handler1.expected_statuses_.emplace(fadb::StatusFlags(0)); | |
| ExpectHandleOneEventSafe(usb_impl1, handler1); | |
| usb_impl1 = {}; | |
| // Connect new ADB session while State::kStoppingForReconnect is still draining! | |
| auto [client_end2, server_end2] = fidl::Endpoints<fadb::UsbAdbImpl>::Create(); | |
| std::atomic<bool> start2_done = false; | |
| async_client->StartAdb(std::move(server_end2)).ThenExactlyOnce([&](auto& result) { | |
| ASSERT_TRUE(result.ok()); | |
| EXPECT_TRUE(result->is_ok()); | |
| start2_done = true; | |
| }); | |
| driver_test_.runtime().RunUntil([&]() { return start2_done.load(); }); | |
| // Queue an offline Receive() on the new session while still stopping. | |
| fidl::WireClient<fadb::UsbAdbImpl> usb_impl2(std::move(client_end2), | |
| fdf::Dispatcher::GetCurrent()->async_dispatcher()); | |
| std::atomic<bool> rx2_done = false; | |
| usb_impl2->Receive().ThenExactlyOnce([&](auto& response) { | |
| ASSERT_TRUE(response.ok()); | |
| ASSERT_TRUE(response->is_ok()); | |
| EXPECT_EQ(response->value()->data.size(), 64u); | |
| rx2_done = true; | |
| }); | |
| driver_test_.runtime().RunUntilIdle(); | |
| // Release CancelAll so the stop completes and restarts USB (Deconfigure -> Configure). | |
| iface_client_ = {}; | |
| driver_test_.RunInEnvironmentTypeContext([](UsbAdbEnvironment& env) { | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).ReleaseCancelAll(); | |
| env.fake_dev_->fake_endpoint(kBulkInEp).ReleaseCancelAll(); | |
| env.fake_dev_->fake_endpoint(kBulkOutEp).set_hold_cancel(false); | |
| env.fake_dev_->fake_endpoint(kBulkInEp).set_hold_cancel(false); | |
| env.CancelAllUsbRequests(); | |
| }); | |
| driver_test_.runtime().RunUntil([&]() { return stop_done.load(); }); | |
| WaitForState(State::kAwaitingUsbConnection); | |
| // Re-enable USB and verify the new ADB session comes online and services its queued Receive(). | |
| WaitConfigured(); | |
| EnableUsb(); | |
| ExpectReceiveData(64); | |
| driver_test_.runtime().RunUntil([&]() { return rx2_done.load(); }); | |
| usb_impl2 = {}; | |
| async_client = {}; | |
| ASSERT_NO_FATAL_FAILURE(SafeStopDriver()); | |
| } | |
| } // namespace usb_adb_function |