blob: b5cd43834bb3b1a2935e73ce01f376bf942d02c8 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "adb-function.h"
#include <fidl/fuchsia.hardware.adb/cpp/fidl.h>
#include <fuchsia/hardware/usb/function/cpp/banjo-mock.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/async/dispatcher.h>
#include <lib/fidl-async/cpp/bind.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"
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;
};
class UsbAdbTest : public zxtest::Test {
public:
static constexpr uint32_t kBulkOutEp = 1;
static constexpr uint32_t kBulkInEp = 2;
void SetUp() override {
parent_ = MockDevice::FakeRootParent();
parent_->AddProtocol(ZX_PROTOCOL_USB_FUNCTION, mock_usb_.GetProto()->ops,
mock_usb_.GetProto()->ctx);
// Expect calls from UsbAdbDevice initialization
mock_usb_.ExpectGetRequestSize(sizeof(usb_request_t));
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, {});
adb_loop_ = std::make_unique<async::Loop>(&kAsyncLoopConfigNeverAttachToThread);
auto adb = std::make_unique<UsbAdbDevice>(parent_.get(), adb_loop_->dispatcher());
adb_ = adb.get();
ASSERT_OK(adb_->Init());
// Mock ddk owns the reference.
[[maybe_unused]] auto released = adb.release();
auto adb_ctxt = parent_->GetLatestChild()->GetDeviceContext<UsbAdbDevice>();
ASSERT_EQ(adb_, adb_ctxt);
auto endpoints = fidl::CreateEndpoints<fuchsia_hardware_adb::Device>();
ASSERT_TRUE(endpoints.is_ok());
fidl_loop_ = std::make_unique<async::Loop>(&kAsyncLoopConfigNeverAttachToThread);
ASSERT_OK(fidl_loop_->StartThread("usb-adb-test-loop"));
fidl::BindServer(fidl_loop_->dispatcher(), std::move(endpoints->server), adb_);
client_ = std::move(endpoints->client);
}
void TearDown() override {
adb_loop_->Shutdown();
mock_usb_.ExpectDisableEp(ZX_OK, kBulkOutEp);
mock_usb_.ExpectDisableEp(ZX_OK, kBulkInEp);
mock_usb_.ExpectCancelAll(ZX_OK, kBulkOutEp);
mock_usb_.ExpectCancelAll(ZX_OK, kBulkInEp);
parent_->GetLatestChild()->SuspendNewOp(0, false, 0);
parent_->GetLatestChild()->WaitUntilSuspendReplyCalled();
fidl_loop_->Shutdown();
parent_ = nullptr;
mock_usb_.VerifyAndClear();
}
protected:
UsbAdbDevice* adb_;
MockUsbFunction mock_usb_;
std::shared_ptr<MockDevice> parent_;
std::unique_ptr<async::Loop> fidl_loop_;
std::unique_ptr<async::Loop> adb_loop_;
fidl::ClientEnd<fuchsia_hardware_adb::Device> client_;
};
// Fake Adb protocol service.
class FakeAdbDaemon : public fidl::WireAsyncEventHandler<fuchsia_hardware_adb::UsbAdbImpl> {
public:
explicit FakeAdbDaemon() : loop_(&kAsyncLoopConfigNeverAttachToThread) {
auto endpoints = fidl::CreateEndpoints<fuchsia_hardware_adb::UsbAdbImpl>();
ASSERT_TRUE(endpoints.is_ok());
client_ = fidl::WireSharedClient<fuchsia_hardware_adb::UsbAdbImpl>(std::move(endpoints->client),
loop_.dispatcher(), this);
server_ = std::move(endpoints->server);
}
fidl::ServerEnd<fuchsia_hardware_adb::UsbAdbImpl>&& GetServer() { return std::move(server_); }
void OnStatusChanged(
fidl::WireEvent<fuchsia_hardware_adb::UsbAdbImpl::OnStatusChanged>* event) override {
status_ = event->status;
}
async::Loop& loop() { return loop_; }
fuchsia_hardware_adb::StatusFlags status() { return status_; }
fidl::WireSharedClient<fuchsia_hardware_adb::UsbAdbImpl>& client() { return client_; }
private:
fidl::WireSharedClient<fuchsia_hardware_adb::UsbAdbImpl> client_;
fuchsia_hardware_adb::StatusFlags status_;
async::Loop loop_;
fidl::ServerEnd<fuchsia_hardware_adb::UsbAdbImpl> server_;
};
TEST_F(UsbAdbTest, SetUpTearDown) { ASSERT_NO_FATAL_FAILURE(); }
TEST_F(UsbAdbTest, StartStop) {
fidl::WireSyncClient<fuchsia_hardware_adb::Device> adb_client(std::move(client_));
auto fake_adb = FakeAdbDaemon();
ASSERT_NO_FATAL_FAILURE();
mock_usb_.ExpectDisableEp(ZX_OK, kBulkOutEp);
mock_usb_.ExpectDisableEp(ZX_OK, kBulkInEp);
ASSERT_OK(adb_client->Start(std::move(fake_adb.GetServer())));
// Calls during Stop().
mock_usb_.ExpectDisableEp(ZX_OK, kBulkOutEp);
mock_usb_.ExpectDisableEp(ZX_OK, kBulkInEp);
// Close the adb client channel so that Stop() will be invoked.
adb_client.TakeClientEnd().channel().reset();
adb_loop_->RunUntilIdle();
}
TEST_F(UsbAdbTest, SendAdbMessage) {
auto fake_adb = FakeAdbDaemon();
ASSERT_NO_FATAL_FAILURE();
// Start adb transactions.
mock_usb_.ExpectDisableEp(ZX_OK, kBulkOutEp);
mock_usb_.ExpectDisableEp(ZX_OK, kBulkInEp);
fidl::WireSyncClient<fuchsia_hardware_adb::Device> adb_client(std::move(client_));
ASSERT_OK(adb_client->Start(std::move(fake_adb.GetServer())));
// Call set_configured of usb adb to bring the interface online.
mock_usb_.ExpectConfigEp(ZX_OK, {}, {});
mock_usb_.ExpectConfigEp(ZX_OK, {}, {});
const usb_header_t rx_header = {.ep_address = kBulkOutEp};
for (uint32_t i = 0; i < kBulkRxCount; i++) {
mock_usb_.ExpectRequestQueue({.header = rx_header}, {});
}
mock_usb_.function.ops->set_configured(mock_usb_.function.ctx, true, USB_SPEED_FULL);
// Run loop to send event
adb_loop_->RunUntilIdle();
// Run loop to receive event
while (fake_adb.status() != fuchsia_hardware_adb::StatusFlags::kOnline) {
EXPECT_EQ(ZX_OK, fake_adb.loop().RunUntilIdle());
}
// Queue transaction and check that the request is passed down the driver stack.
uint8_t test_data[] = "test-data";
mock_usb_.mock_request_queue().ExpectCallWithMatcher(
[&](usb_request_t r, usb_request_complete_callback_t cb) -> void {
ASSERT_EQ(r.header.ep_address, kBulkInEp);
uint8_t got[32];
const ssize_t length = usb_request_copy_from(&r, got, 32, 0);
ASSERT_GT(length, 0);
ASSERT_EQ(memcmp(test_data, got, sizeof(test_data)), 0);
});
sync_completion_t completion;
ASSERT_OK(fake_adb.loop().StartThread("adb-send-thread"));
fake_adb.client()
->QueueTx(fidl::VectorView<uint8_t>::FromExternal(test_data, sizeof(test_data)))
.ThenExactlyOnce(
[&](fidl::WireUnownedResult<fuchsia_hardware_adb::UsbAdbImpl::QueueTx>& response) {
ASSERT_TRUE(response->is_ok());
sync_completion_signal(&completion);
});
adb_loop_->RunUntilIdle();
sync_completion_wait(&completion, zx::duration::infinite().get());
// Calls during Stop().
mock_usb_.ExpectDisableEp(ZX_OK, kBulkOutEp);
mock_usb_.ExpectDisableEp(ZX_OK, kBulkInEp);
// Close the adb client channel so that Stop() will be invoked.
adb_client.TakeClientEnd().channel().reset();
adb_loop_->RunUntilIdle();
}
TEST_F(UsbAdbTest, RecvAdbMessage) {
// Call set_configured of usb adb.
mock_usb_.ExpectDisableEp(ZX_OK, kBulkOutEp);
mock_usb_.ExpectDisableEp(ZX_OK, kBulkInEp);
mock_usb_.function.ops->set_configured(mock_usb_.function.ctx, true, USB_SPEED_FULL);
auto fake_adb = FakeAdbDaemon();
ASSERT_NO_FATAL_FAILURE();
// Start adb transactions. This will also result in endpoint configuration.
mock_usb_.ExpectConfigEp(ZX_OK, {}, {});
mock_usb_.ExpectConfigEp(ZX_OK, {}, {});
const usb_header_t header = {.ep_address = kBulkOutEp};
for (uint32_t i = 0; i < kBulkRxCount; i++) {
mock_usb_.ExpectRequestQueue({.header = header}, {});
}
fidl::WireSyncClient<fuchsia_hardware_adb::Device> adb_client(std::move(client_));
ASSERT_OK(adb_client->Start(std::move(fake_adb.GetServer())));
// Run loop to send event
adb_loop_->RunUntilIdle();
// Run loop to receive event
while (fake_adb.status() != fuchsia_hardware_adb::StatusFlags::kOnline) {
EXPECT_EQ(ZX_OK, fake_adb.loop().RunUntilIdle());
}
// Queue a receive request before the data is available. The request will not get an immediate
// reply. Queue a Receive request.
const uint8_t test_data[] = "test-data";
sync_completion_t completion;
ASSERT_OK(fake_adb.loop().StartThread("adb-recv-thread"));
fake_adb.client()->Receive().ThenExactlyOnce(
[&](fidl::WireUnownedResult<::fuchsia_hardware_adb::UsbAdbImpl::Receive>& response) -> void {
ASSERT_OK(response.status());
ASSERT_FALSE(response.value().is_error());
ASSERT_EQ(response.value().value()->data.count(), sizeof(test_data));
ASSERT_BYTES_EQ(response.value().value()->data.data(), test_data, sizeof(test_data));
sync_completion_signal(&completion);
});
// Invoke request completion on bulk out endpoint.
const mock_usb_request_t req = mock_usb_.usb_request_queues[kBulkOutEp].back();
mock_usb_.usb_request_queues[kBulkOutEp].pop_back();
const ssize_t length = usb_request_copy_to(req.usb_request, test_data, sizeof(test_data), 0);
ASSERT_GT(length, 0);
req.usb_request->response.status = ZX_OK;
req.usb_request->response.actual = sizeof(test_data);
mock_usb_.ExpectRequestQueue({.header = header}, {});
req.complete_cb->callback(req.complete_cb->ctx, req.usb_request);
// Process the Receive request and wait for completion.
adb_loop_->RunUntilIdle();
sync_completion_wait(&completion, zx::duration::infinite().get());
// Calls during Stop().
mock_usb_.ExpectDisableEp(ZX_OK, kBulkOutEp);
mock_usb_.ExpectDisableEp(ZX_OK, kBulkInEp);
// Close the adb client channel so that Stop() will be invoked.
adb_client.TakeClientEnd().channel().reset();
adb_loop_->RunUntilIdle();
}
} // namespace usb_adb_function