blob: f98a7da8befe7d84ca4bc579a9f187d23d6154b7 [file]
// Copyright 2026 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 <lib/fit/defer.h>
#include "src/devices/usb/drivers/usb-peripheral/usb-peripheral-test-harness.h"
namespace usb_peripheral::test {
namespace {
TEST_F(UsbPeripheralFunctionTest, ConfigureAndRouteFidlCalls) {
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
zx::result fake_function_result = BindFakeFunction();
ASSERT_OK(fake_function_result);
auto [fake_function, fake_function_endpoint] = std::move(fake_function_result.value());
fidl::WireResult alloc_res = function_client->AllocResources(1, {}, {});
ASSERT_TRUE(alloc_res.ok()) << alloc_res.status_string();
ASSERT_TRUE(alloc_res->is_ok()) << zx_status_get_string(alloc_res->error_value());
uint8_t interface_num = alloc_res->value()->interface_nums[0];
// Valid descriptors for ValidateFunction (we pass UMS's descriptors)
usb_interface_descriptor_t intf_desc = {
.b_length = sizeof(usb_interface_descriptor_t),
.b_descriptor_type = USB_DT_INTERFACE,
.b_interface_number = interface_num,
.b_alternate_setting = 0,
.b_num_endpoints = 0,
.b_interface_class = 8,
.b_interface_sub_class = 6,
.b_interface_protocol = 80,
.i_interface = 0,
};
std::vector<uint8_t> descriptors(sizeof(intf_desc));
memcpy(descriptors.data(), &intf_desc, sizeof(intf_desc));
fidl::WireResult configure_res = function_client->Configure(
fidl::VectorView<uint8_t>::FromExternal(descriptors.data(), descriptors.size()),
std::move(fake_function_endpoint));
ASSERT_TRUE(configure_res.ok()) << configure_res.status_string();
ASSERT_TRUE(configure_res->is_ok());
// Controller starts when all functions are registered.
ExpectControllerStarted(true);
ExpectState(UsbPeripheral::DeviceState::kPeripheralReady);
ASSERT_OK(dci()->SetConnected(true).status());
ExpectState(UsbPeripheral::DeviceState::kHostConnected);
fidl::Arena arena;
std::vector<uint8_t> unused;
// Test SetConfigured via standard endpoint request
fdescriptor::wire::UsbSetup setup;
setup.bm_request_type = USB_DIR_OUT | USB_RECIP_DEVICE | USB_TYPE_STANDARD;
setup.b_request = USB_REQ_SET_CONFIGURATION;
setup.w_value = 1; // Configuration 1
setup.w_index = interface_num;
setup.w_length = 0;
fidl::WireUnownedResult config_res =
dci().buffer(arena)->Control(setup, fidl::VectorView<uint8_t>::FromExternal(unused));
EXPECT_TRUE(config_res.ok()) << config_res.FormatDescription();
ASSERT_OK(config_res.value());
fake_function->WaitUntilCalled();
EXPECT_TRUE(fake_function->set_configured_called());
EXPECT_TRUE(fake_function->configured());
// Test Control via provided endpoint request.
setup.bm_request_type = USB_DIR_IN;
setup.b_request = 0xAA;
setup.w_value = 0x01;
setup.w_index = 0x02;
setup.w_length = 3;
fidl::WireUnownedResult control_res =
dci().buffer(arena)->Control(setup, fidl::VectorView<uint8_t>::FromExternal(unused));
EXPECT_TRUE(control_res.ok()) << control_res.FormatDescription();
ASSERT_OK(control_res.value());
fake_function->WaitUntilCalled();
EXPECT_TRUE(fake_function->control_called());
EXPECT_EQ(0xAA, fake_function->control_req());
// Test SetInterface via standard endpoint request
setup.bm_request_type = USB_DIR_OUT | USB_RECIP_INTERFACE | USB_TYPE_STANDARD;
setup.b_request = USB_REQ_SET_INTERFACE;
setup.w_value = 1; // Alt setting 1
setup.w_index = interface_num;
setup.w_length = 0;
fidl::WireUnownedResult intf_res =
dci().buffer(arena)->Control(setup, fidl::VectorView<uint8_t>::FromExternal(unused));
ASSERT_TRUE(intf_res->is_ok()) << intf_res.FormatDescription();
ASSERT_OK(intf_res.value());
fake_function->WaitUntilCalled();
EXPECT_TRUE(fake_function->set_interface_called());
EXPECT_EQ(interface_num, fake_function->interface());
EXPECT_EQ(1, fake_function->alt_setting());
}
// Test that a repeated SetConfiguration request for the same configuration ID
// forces the function driver to transition through an unconfigured (false)
// state. In compliance with the USB 2.0 specification (section 9.1.1.5), this
// unconfigured transition ensures that all endpoints and interface state are
// reset to default values.
TEST_F(UsbPeripheralFunctionTest, RepeatedSetConfigurationResetsFunction) {
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
zx::result fake_function_result = BindFakeFunction();
ASSERT_OK(fake_function_result);
auto [fake_function, fake_function_endpoint] = std::move(fake_function_result.value());
fidl::WireResult alloc_res = function_client->AllocResources(1, {}, {});
ASSERT_TRUE(alloc_res.ok()) << alloc_res.status_string();
ASSERT_TRUE(alloc_res->is_ok()) << zx_status_get_string(alloc_res->error_value());
uint8_t interface_num = alloc_res->value()->interface_nums[0];
// Valid descriptors for ValidateFunction (we pass UMS's descriptors)
usb_interface_descriptor_t intf_desc = {
.b_length = sizeof(usb_interface_descriptor_t),
.b_descriptor_type = USB_DT_INTERFACE,
.b_interface_number = interface_num,
.b_alternate_setting = 0,
.b_num_endpoints = 0,
.b_interface_class = 8,
.b_interface_sub_class = 6,
.b_interface_protocol = 80,
.i_interface = 0,
};
std::vector<uint8_t> descriptors(sizeof(intf_desc));
memcpy(descriptors.data(), &intf_desc, sizeof(intf_desc));
fidl::WireResult configure_res = function_client->Configure(
fidl::VectorView<uint8_t>::FromExternal(descriptors.data(), descriptors.size()),
std::move(fake_function_endpoint));
ASSERT_TRUE(configure_res.ok()) << configure_res.status_string();
ASSERT_TRUE(configure_res->is_ok());
// Controller starts when all functions are registered.
ExpectControllerStarted(true);
ExpectState(UsbPeripheral::DeviceState::kPeripheralReady);
ASSERT_OK(dci()->SetConnected(true).status());
ExpectState(UsbPeripheral::DeviceState::kHostConnected);
fidl::Arena arena;
std::vector<uint8_t> unused;
// Test SetConfigured via standard endpoint request
fdescriptor::wire::UsbSetup setup;
setup.bm_request_type = USB_DIR_OUT | USB_RECIP_DEVICE | USB_TYPE_STANDARD;
setup.b_request = USB_REQ_SET_CONFIGURATION;
setup.w_value = 1; // Configuration 1
setup.w_index = interface_num;
setup.w_length = 0;
fidl::WireUnownedResult config_res =
dci().buffer(arena)->Control(setup, fidl::VectorView<uint8_t>::FromExternal(unused));
EXPECT_TRUE(config_res.ok()) << config_res.FormatDescription();
ASSERT_OK(config_res.value());
EXPECT_TRUE(fake_function->set_configured_called());
EXPECT_TRUE(fake_function->configured());
ASSERT_EQ(fake_function->configured_history().size(), 1u);
EXPECT_TRUE(fake_function->configured_history()[0]);
// Test repeated SetConfiguration request causes unconfigure/reconfigure transition.
fake_function->clear_set_configured_called();
fidl::WireUnownedResult config_res2 =
dci().buffer(arena)->Control(setup, fidl::VectorView<uint8_t>::FromExternal(unused));
EXPECT_TRUE(config_res2.ok()) << config_res2.FormatDescription();
ASSERT_OK(config_res2.value());
EXPECT_TRUE(fake_function->set_configured_called());
EXPECT_TRUE(fake_function->configured());
ASSERT_EQ(fake_function->configured_history().size(), 3u);
EXPECT_FALSE(fake_function->configured_history()[1]);
EXPECT_TRUE(fake_function->configured_history()[2]);
}
TEST_F(UsbPeripheralFunctionTest, ClearFunctionsWaitsForTeardown) {
zx::result peripheral_client_result = ConnectPeripheral();
ASSERT_OK(peripheral_client_result);
auto peripheral_client = std::move(peripheral_client_result.value());
zx::result endpoints = fidl::CreateEndpoints<fperipheral::Events>();
ASSERT_OK(endpoints);
FakeEvents fake_events;
fake_events.Bind(std::move(endpoints->server));
auto set_listener_res = peripheral_client->SetStateChangeListener(std::move(endpoints->client));
ASSERT_TRUE(set_listener_res.ok()) << set_listener_res.FormatDescription();
// Add a function so there is something to clear.
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
zx::result fake_function_result = BindFakeFunction();
ASSERT_OK(fake_function_result);
auto [fake_function, fake_function_endpoint] = std::move(fake_function_result.value());
fidl::WireResult alloc_res = function_client->AllocResources(1, {}, {});
ASSERT_TRUE(alloc_res.ok()) << alloc_res.FormatDescription();
ASSERT_OK(alloc_res.value());
uint8_t interface_num = alloc_res->value()->interface_nums[0];
usb_interface_descriptor_t intf_desc = {
.b_length = sizeof(usb_interface_descriptor_t),
.b_descriptor_type = USB_DT_INTERFACE,
.b_interface_number = interface_num,
.b_alternate_setting = 0,
.b_num_endpoints = 0,
.b_interface_class = 8,
.b_interface_sub_class = 6,
.b_interface_protocol = 80,
.i_interface = 0,
};
std::vector<uint8_t> descriptors(sizeof(intf_desc));
memcpy(descriptors.data(), &intf_desc, sizeof(intf_desc));
fidl::WireResult configure_res = function_client->Configure(
fidl::VectorView<uint8_t>::FromExternal(descriptors.data(), descriptors.size()),
std::move(fake_function_endpoint));
ASSERT_TRUE(configure_res.ok()) << configure_res.FormatDescription();
ASSERT_OK(configure_res.value());
// Clear functions and wait for event.
auto clear_res = peripheral_client->ClearFunctions();
ASSERT_TRUE(clear_res.ok()) << clear_res.FormatDescription();
// Wait for async teardown to complete.
this->dut().runtime().RunUntilIdle();
// DCI should be stopped.
ExpectControllerStarted(false);
// 2. State should be back to kNoConfiguration.
ExpectState(UsbPeripheral::DeviceState::kNoConfiguration);
fake_events.WaitUntilCleared(this->dut().runtime());
fake_events.Unbind();
}
TEST_F(UsbPeripheralFunctionTest, ConfigureFailsIfInterfaceNotAllocated) {
ExpectControllerStarted(false);
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
zx::result fake_function_result = BindFakeFunction();
ASSERT_OK(fake_function_result);
auto [fake_function, fake_function_endpoint] = std::move(fake_function_result.value());
// We use an interface number that hasn't been allocated (0, by default in descriptors).
usb_interface_descriptor_t intf_desc = {
.b_length = sizeof(usb_interface_descriptor_t),
.b_descriptor_type = USB_DT_INTERFACE,
.b_interface_number = 0,
.b_alternate_setting = 0,
.b_num_endpoints = 0,
.b_interface_class = 8,
.b_interface_sub_class = 6,
.b_interface_protocol = 80,
.i_interface = 0,
};
std::vector<uint8_t> descriptors(sizeof(intf_desc));
memcpy(descriptors.data(), &intf_desc, sizeof(intf_desc));
fidl::WireResult configure_res = function_client->Configure(
fidl::VectorView<uint8_t>::FromExternal(descriptors.data(), descriptors.size()),
std::move(fake_function_endpoint));
ASSERT_TRUE(configure_res.ok()) << configure_res.FormatDescription();
EXPECT_STATUS(configure_res.value(), ZX_ERR_INVALID_ARGS);
}
TEST_F(UsbPeripheralFunctionTest, ConfigureFailsIfAlreadyBound) {
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
zx::result fake_function_result = BindFakeFunction();
ASSERT_OK(fake_function_result);
auto [fake_function, fake_function_endpoint] = std::move(fake_function_result.value());
fidl::WireResult alloc_res = function_client->AllocResources(1, {}, {});
ASSERT_TRUE(alloc_res.ok()) << alloc_res.status_string();
ASSERT_TRUE(alloc_res->is_ok()) << zx_status_get_string(alloc_res->error_value());
uint8_t interface_num = alloc_res->value()->interface_nums[0];
usb_interface_descriptor_t intf_desc = {
.b_length = sizeof(usb_interface_descriptor_t),
.b_descriptor_type = USB_DT_INTERFACE,
.b_interface_number = interface_num,
.b_alternate_setting = 0,
.b_num_endpoints = 0,
.b_interface_class = 8,
.b_interface_sub_class = 6,
.b_interface_protocol = 80,
.i_interface = 0,
};
std::vector<uint8_t> descriptors(sizeof(intf_desc));
memcpy(descriptors.data(), &intf_desc, sizeof(intf_desc));
// First call should succeed
fidl::WireResult configure_res = function_client->Configure(
fidl::VectorView<uint8_t>::FromExternal(descriptors.data(), descriptors.size()),
std::move(fake_function_endpoint));
ASSERT_TRUE(configure_res.ok()) << configure_res.FormatDescription();
ASSERT_OK(configure_res.value());
// Second call with a new endpoint should fail with ZX_ERR_ALREADY_BOUND.
// The driver is already in kPeripheralReady because the first function was configured.
ExpectState(UsbPeripheral::DeviceState::kPeripheralReady);
zx::result endpoints = fidl::CreateEndpoints<ffunction::UsbFunctionInterface>();
ASSERT_OK(endpoints);
auto second_fake = std::make_shared<FakeUsbFunction>();
second_fake->Bind(dut().runtime().StartBackgroundDispatcher(), std::move(endpoints->server));
auto second_fake_endpoint = std::move(endpoints->client);
ExpectState(UsbPeripheral::DeviceState::kPeripheralReady);
fidl::WireResult second_configure_res = function_client->Configure(
fidl::VectorView<uint8_t>::FromExternal(descriptors.data(), descriptors.size()),
std::move(second_fake_endpoint));
ASSERT_TRUE(second_configure_res.ok()) << second_configure_res.FormatDescription();
EXPECT_STATUS(second_configure_res.value(), ZX_ERR_ALREADY_BOUND);
}
TEST_F(UsbPeripheralFunctionTest, ConnectToEndpointFailsIfEpNotAllocated) {
ExpectControllerStarted(false);
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
auto ep_endpoints = fidl::Endpoints<fendpoint::Endpoint>::Create();
// Try to connect to endpoint 1 (which hasn't been allocated).
fidl::WireResult connect_res =
function_client->ConnectToEndpoint(1, std::move(ep_endpoints.server));
ASSERT_TRUE(connect_res.ok()) << connect_res.FormatDescription();
EXPECT_STATUS(connect_res.value(), ZX_ERR_NOT_FOUND);
}
TEST_F(UsbPeripheralFunctionTest, ConnectToEndpointSuccessAndSequencing) {
ExpectControllerStarted(false);
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
zx::result fake_function_result = BindFakeFunction();
ASSERT_OK(fake_function_result);
auto [fake_function, fake_function_endpoint] = std::move(fake_function_result.value());
fidl::Arena arena;
auto endpoints = fidl::VectorView<ffunction::wire::EndpointResource>(arena, 1);
endpoints[0].direction = fdescriptor::wire::EndpointDirection::kIn;
endpoints[0].ep_info = BulkEpInfo(arena);
endpoints[0].max_packet_size = 512;
auto ep_endpoints = fidl::Endpoints<fendpoint::Endpoint>::Create();
endpoints[0].endpoint = std::move(ep_endpoints.server);
fidl::WireResult alloc_res =
function_client->AllocResources(1, endpoints, fidl::VectorView<fidl::StringView>());
ASSERT_TRUE(alloc_res.ok()) << alloc_res.status_string();
ASSERT_TRUE(alloc_res->is_ok());
uint8_t ep_addr = alloc_res->value()->endpoint_addrs[0];
uint8_t interface_num = alloc_res->value()->interface_nums[0];
usb_interface_descriptor_t intf_desc = {
.b_length = sizeof(usb_interface_descriptor_t),
.b_descriptor_type = USB_DT_INTERFACE,
.b_interface_number = interface_num,
.b_alternate_setting = 0,
.b_num_endpoints = 1,
.b_interface_class = 8,
.b_interface_sub_class = 6,
.b_interface_protocol = 80,
};
usb_endpoint_descriptor_t ep_desc = {
.b_length = sizeof(usb_endpoint_descriptor_t),
.b_descriptor_type = USB_DT_ENDPOINT,
.b_endpoint_address = ep_addr,
.bm_attributes = static_cast<uint8_t>(fdescriptor::EndpointType::kBulk),
.w_max_packet_size = 512,
};
std::vector<uint8_t> descriptors(sizeof(intf_desc) + sizeof(ep_desc));
memcpy(descriptors.data(), &intf_desc, sizeof(intf_desc));
memcpy(descriptors.data() + sizeof(intf_desc), &ep_desc, sizeof(ep_desc));
fidl::WireResult configure_res = function_client->Configure(
fidl::VectorView<uint8_t>::FromExternal(descriptors.data(), descriptors.size()),
std::move(fake_function_endpoint));
ASSERT_TRUE(configure_res.ok()) << configure_res.status_string();
ASSERT_TRUE(configure_res->is_ok());
auto ep_endpoints2 = fidl::Endpoints<fendpoint::Endpoint>::Create();
// Try to connect to the allocated endpoint.
fidl::WireResult connect_res =
function_client->ConnectToEndpoint(ep_addr, std::move(ep_endpoints2.server));
ASSERT_TRUE(connect_res.ok()) << connect_res.FormatDescription();
ASSERT_OK(connect_res.value());
// Verify [FUNC-1.2]: SetConfigured must not have been called yet.
EXPECT_FALSE(fake_function->set_configured_called());
}
TEST_F(UsbPeripheralFunctionTest, ConnectToEndpointServerEndCloseOnTeardown) {
ExpectControllerStarted(false);
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
fidl::Arena arena;
zx::result<uint8_t> ep_addr_result = ConfigureDefaultFunction(function_client, arena);
ASSERT_OK(ep_addr_result);
uint8_t ep_addr = ep_addr_result.value();
auto ep_endpoints = fidl::Endpoints<fendpoint::Endpoint>::Create();
fidl::WireResult connect_res =
function_client->ConnectToEndpoint(ep_addr, std::move(ep_endpoints.server));
ASSERT_TRUE(connect_res.ok()) << connect_res.FormatDescription();
ASSERT_OK(connect_res.value());
// Verify the client channel is still connected (peer not closed).
zx_signals_t observed = 0;
EXPECT_STATUS(ep_endpoints.client.channel().wait_one(ZX_CHANNEL_PEER_CLOSED,
zx::time::infinite_past(), &observed),
ZX_ERR_TIMED_OUT);
// Stop the controller (which triggers teardown).
zx::result peripheral_client_result = ConnectPeripheral();
ASSERT_OK(peripheral_client_result);
fidl::WireSyncClient<fperipheral::Device> peripheral_client =
std::move(peripheral_client_result.value());
FakeEvents fake_events;
auto event_endpoints = fidl::CreateEndpoints<fperipheral::Events>();
ASSERT_OK(event_endpoints);
fake_events.Bind(std::move(event_endpoints->server));
auto set_listener_res =
peripheral_client->SetStateChangeListener(std::move(event_endpoints->client));
ASSERT_TRUE(set_listener_res.ok()) << set_listener_res.FormatDescription();
fidl::WireResult clear_res = peripheral_client->ClearFunctions();
ASSERT_TRUE(clear_res.ok()) << clear_res.status_string();
fake_events.WaitUntilCleared(dut().runtime());
// Now the endpoint channel peer MUST be closed.
EXPECT_OK(ep_endpoints.client.channel().wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time::infinite(),
&observed));
EXPECT_TRUE(observed & ZX_CHANNEL_PEER_CLOSED);
}
TEST_F(UsbPeripheralFunctionTest, ConfigureFailsIfDescriptorsMalformed) {
ExpectControllerStarted(false);
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
zx::result fake_function_result = BindFakeFunction();
ASSERT_OK(fake_function_result);
auto [fake_function, fake_function_endpoint] = std::move(fake_function_result.value());
fidl::WireResult alloc_res = function_client->AllocResources(1, {}, {});
ASSERT_TRUE(alloc_res.ok()) << alloc_res.status_string();
ASSERT_TRUE(alloc_res->is_ok());
// Pass malformed descriptors (length byte = 5, but sizeof interface is 9).
std::vector<uint8_t> malformed_descriptors(sizeof(usb_interface_descriptor_t));
auto* intf = reinterpret_cast<usb_interface_descriptor_t*>(malformed_descriptors.data());
intf->b_length = 5; // Invalid! Must be 9.
intf->b_descriptor_type = USB_DT_INTERFACE;
fidl::WireResult configure_res =
function_client->Configure(fidl::VectorView<uint8_t>::FromExternal(
malformed_descriptors.data(), malformed_descriptors.size()),
std::move(fake_function_endpoint));
ASSERT_TRUE(configure_res.ok()) << configure_res.FormatDescription();
EXPECT_STATUS(configure_res.value(), ZX_ERR_INVALID_ARGS);
}
TEST_F(UsbPeripheralFunctionTest, DeconfigureClosesEndpointChannels) {
ExpectControllerStarted(false);
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
fidl::Arena arena;
zx::result<uint8_t> ep_addr_result = ConfigureDefaultFunction(function_client, arena);
ASSERT_OK(ep_addr_result);
uint8_t ep_addr = ep_addr_result.value();
auto ep_endpoints = fidl::Endpoints<fendpoint::Endpoint>::Create();
fidl::WireResult connect_res =
function_client->ConnectToEndpoint(ep_addr, std::move(ep_endpoints.server));
ASSERT_TRUE(connect_res.ok()) << connect_res.FormatDescription();
ASSERT_OK(connect_res.value());
// Verify the client channel is still connected (peer not closed).
zx_signals_t observed = 0;
EXPECT_STATUS(ep_endpoints.client.channel().wait_one(ZX_CHANNEL_PEER_CLOSED,
zx::time::infinite_past(), &observed),
ZX_ERR_TIMED_OUT);
// Deconfigure
fidl::WireResult deconfig_res = function_client->Deconfigure();
ASSERT_TRUE(deconfig_res.ok()) << deconfig_res.FormatDescription();
ASSERT_OK(deconfig_res.value());
// Verify the client channel is closed (because deconfigure stops the controller, disabling
// endpoints).
EXPECT_OK(ep_endpoints.client.channel().wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time::infinite(),
&observed));
EXPECT_TRUE(observed & ZX_CHANNEL_PEER_CLOSED);
}
TEST_F(UsbPeripheralFunctionTest, ConfigureEndpointFailsIfDescriptorMissing) {
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
fidl::Arena arena;
zx::result<uint8_t> configure_result = ConfigureDefaultFunction(function_client, arena);
ASSERT_OK(configure_result);
uint8_t ep_addr = configure_result.value();
// Create configuration WITHOUT descriptor.
auto config_builder = ffunction::wire::EndpointConfiguration::Builder(arena);
// Do not call config_builder.descriptor(...)
auto res = function_client->ConfigureEndpoint(ep_addr, config_builder.Build());
ASSERT_TRUE(res.ok()) << res.FormatDescription();
EXPECT_STATUS(res.value(), ZX_ERR_INVALID_ARGS);
}
TEST_F(UsbPeripheralFunctionTest, DeconfigureTrivialSuccessIfNotConfigured) {
ExpectControllerStarted(false);
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
fidl::WireResult deconfig_res = function_client->Deconfigure();
ASSERT_TRUE(deconfig_res.ok()) << deconfig_res.FormatDescription();
ASSERT_TRUE(deconfig_res->is_ok()) << zx_status_get_string(deconfig_res->error_value());
}
TEST_F(UsbPeripheralFunctionTest, AllocResourcesFailsIfInvalidDirection) {
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
fidl::Arena arena;
auto endpoints = fidl::VectorView<ffunction::wire::EndpointResource>(arena, 1);
endpoints[0].direction = static_cast<fdescriptor::wire::EndpointDirection>(99);
auto ep_endpoints = fidl::Endpoints<fendpoint::Endpoint>::Create();
endpoints[0].endpoint = std::move(ep_endpoints.server);
fidl::WireResult alloc_res = function_client->AllocResources(1, endpoints, {});
if (alloc_res.ok()) {
ASSERT_TRUE(alloc_res->is_error());
EXPECT_STATUS(alloc_res->error_value(), ZX_ERR_INVALID_ARGS);
} else {
// If FIDL serialization failed, it's also acceptable validation.
EXPECT_STATUS(alloc_res.status(), ZX_ERR_INVALID_ARGS);
}
}
TEST_F(UsbPeripheralFunctionTest, DeconfigureAllowsReconfigure) {
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
zx::result fake_function_result = BindFakeFunction();
ASSERT_OK(fake_function_result);
auto [fake_function, fake_function_endpoint] = std::move(fake_function_result.value());
fidl::WireResult alloc_res = function_client->AllocResources(1, {}, {});
ASSERT_TRUE(alloc_res.ok()) << alloc_res.status_string();
ASSERT_TRUE(alloc_res->is_ok()) << zx_status_get_string(alloc_res->error_value());
uint8_t interface_num = alloc_res->value()->interface_nums[0];
usb_interface_descriptor_t intf_desc = {
.b_length = sizeof(usb_interface_descriptor_t),
.b_descriptor_type = USB_DT_INTERFACE,
.b_interface_number = interface_num,
.b_alternate_setting = 0,
.b_num_endpoints = 0,
.b_interface_class = 8,
.b_interface_sub_class = 6,
.b_interface_protocol = 80,
.i_interface = 0,
};
std::vector<uint8_t> descriptors(sizeof(intf_desc));
memcpy(descriptors.data(), &intf_desc, sizeof(intf_desc));
// First Configure
{
fidl::WireResult result = function_client->Configure(
fidl::VectorView<uint8_t>::FromExternal(descriptors.data(), descriptors.size()),
std::move(fake_function_endpoint));
ASSERT_TRUE(result.ok()) << result.FormatDescription();
ASSERT_OK(result.value());
}
ExpectState(UsbPeripheral::DeviceState::kPeripheralReady);
// Deconfigure
{
fidl::WireResult result = function_client->Deconfigure();
ASSERT_TRUE(result.ok()) << result.FormatDescription();
ASSERT_OK(result.value());
}
fake_function->WaitUntilUnbound();
ExpectState(UsbPeripheral::DeviceState::kWaitForFunctionBind);
ExpectControllerStarted(false);
// Now Configure should succeed again with a new endpoint
fake_function_result = BindFakeFunction();
ASSERT_OK(fake_function_result);
auto [new_fake_function, new_fake_function_endpoint] = std::move(fake_function_result.value());
{
fidl::WireResult result = function_client->Configure(
fidl::VectorView<uint8_t>::FromExternal(descriptors.data(), descriptors.size()),
std::move(new_fake_function_endpoint));
ASSERT_TRUE(result.ok()) << result.FormatDescription();
ASSERT_OK(result.value());
}
ExpectState(UsbPeripheral::DeviceState::kPeripheralReady);
ExpectControllerStarted(true);
// Verify new fake function is called.
{
ASSERT_OK(dci()->SetConnected(true).status());
fdescriptor::wire::UsbSetup setup;
setup.bm_request_type = USB_DIR_OUT | USB_RECIP_DEVICE | USB_TYPE_STANDARD;
setup.b_request = USB_REQ_SET_CONFIGURATION;
setup.w_value = 1; // Configuration 1
setup.w_index = interface_num;
setup.w_length = 0;
fidl::Arena arena;
std::vector<uint8_t> unused;
fidl::WireResult config_res =
dci()->Control(setup, fidl::VectorView<uint8_t>::FromExternal(unused));
EXPECT_TRUE(config_res.ok()) << config_res.FormatDescription();
ASSERT_OK(config_res.value());
}
new_fake_function->WaitUntilCalled();
EXPECT_TRUE(new_fake_function->set_configured_called());
EXPECT_TRUE(new_fake_function->configured());
}
TEST_F(UsbPeripheralFunctionTest, ControllerStoppedOnFunctionClose) {
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
zx::result fake_function_result = BindFakeFunction();
ASSERT_OK(fake_function_result);
auto [fake_function, fake_function_endpoint] = std::move(fake_function_result.value());
fidl::WireResult alloc_res = function_client->AllocResources(1, {}, {});
ASSERT_TRUE(alloc_res.ok()) << alloc_res.status_string();
ASSERT_TRUE(alloc_res->is_ok()) << zx_status_get_string(alloc_res->error_value());
uint8_t interface_num = alloc_res->value()->interface_nums[0];
usb_interface_descriptor_t intf_desc = {
.b_length = sizeof(usb_interface_descriptor_t),
.b_descriptor_type = USB_DT_INTERFACE,
.b_interface_number = interface_num,
.b_alternate_setting = 0,
.b_num_endpoints = 0,
.b_interface_class = 8,
.b_interface_sub_class = 6,
.b_interface_protocol = 80,
.i_interface = 0,
};
std::vector<uint8_t> descriptors(sizeof(intf_desc));
memcpy(descriptors.data(), &intf_desc, sizeof(intf_desc));
fidl::WireResult configure_res = function_client->Configure(
fidl::VectorView<uint8_t>::FromExternal(descriptors.data(), descriptors.size()),
std::move(fake_function_endpoint));
ASSERT_TRUE(configure_res.ok()) << configure_res.FormatDescription();
ASSERT_OK(configure_res.value());
// Controller starts when all functions are registered.
ExpectControllerStarted(true);
// Set the stop completion before unbinding.
libsync::Completion stop_completion;
dut().RunInEnvironmentTypeContext(
[&](UsbPeripheralTestEnvironment& env) { env.dci().set_stop_completion(&stop_completion); });
// Close the fake function endpoint.
fake_function->Unbind();
// Wait for the controller to stop.
ASSERT_OK(stop_completion.Wait(zx::sec(5)));
dut().RunInEnvironmentTypeContext([](UsbPeripheralTestEnvironment& env) {
env.dci().set_stop_completion(nullptr);
EXPECT_FALSE(env.dci().controller_started());
});
}
TEST_F(UsbPeripheralFunctionTest, AllocResources) {
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
fidl::Endpoints<fendpoint::Endpoint> ep_endpoints1 =
fidl::Endpoints<fendpoint::Endpoint>::Create();
fidl::Endpoints<fendpoint::Endpoint> ep_endpoints2 =
fidl::Endpoints<fendpoint::Endpoint>::Create();
zx_info_handle_basic_t info1, info2;
ASSERT_OK(ep_endpoints1.server.channel().get_info(ZX_INFO_HANDLE_BASIC, &info1, sizeof(info1),
nullptr, nullptr));
ASSERT_OK(ep_endpoints2.server.channel().get_info(ZX_INFO_HANDLE_BASIC, &info2, sizeof(info2),
nullptr, nullptr));
fidl::Arena arena;
auto endpoints = fidl::VectorView<ffunction::wire::EndpointResource>(arena, 2);
endpoints[0].direction = fdescriptor::wire::EndpointDirection::kIn;
endpoints[0].ep_info = BulkEpInfo(arena);
endpoints[0].max_packet_size = 512;
endpoints[0].endpoint = std::move(ep_endpoints1.server);
endpoints[1].direction = fdescriptor::wire::EndpointDirection::kOut;
endpoints[1].ep_info = BulkEpInfo(arena);
endpoints[1].max_packet_size = 512;
endpoints[1].endpoint = std::move(ep_endpoints2.server);
auto strings = fidl::VectorView<fidl::StringView>(arena, 2);
strings[0] = fidl::StringView(arena, "string1");
strings[1] = fidl::StringView(arena, "string2");
fidl::WireResult res = function_client->AllocResources(1, endpoints, strings);
ASSERT_TRUE(res.ok()) << res.FormatDescription();
ASSERT_TRUE(res->is_ok()) << zx_status_get_string(res->error_value());
auto* response = res->value();
ASSERT_EQ(response->interface_nums.size(), 1u);
ASSERT_EQ(response->endpoint_addrs.size(), 2u);
ASSERT_EQ(response->string_indices.size(), 2u);
uint8_t ep1_addr = response->endpoint_addrs[0];
uint8_t ep2_addr = response->endpoint_addrs[1];
// Verify endpoints connected to DCI.
dut().RunInEnvironmentTypeContext([&](UsbPeripheralTestEnvironment& env) {
auto dci_ep1 = env.dci().TakeEndpoint(ep1_addr);
auto dci_ep2 = env.dci().TakeEndpoint(ep2_addr);
ASSERT_TRUE(dci_ep1.is_valid());
ASSERT_TRUE(dci_ep2.is_valid());
zx_info_handle_basic_t dci_info1, dci_info2;
ASSERT_OK(dci_ep1.channel().get_info(ZX_INFO_HANDLE_BASIC, &dci_info1, sizeof(dci_info1),
nullptr, nullptr));
ASSERT_OK(dci_ep2.channel().get_info(ZX_INFO_HANDLE_BASIC, &dci_info2, sizeof(dci_info2),
nullptr, nullptr));
EXPECT_EQ(info1.koid, dci_info1.koid);
EXPECT_EQ(info2.koid, dci_info2.koid);
});
zx::result fake_function_result = BindFakeFunction();
ASSERT_OK(fake_function_result);
auto [fake_function, fake_function_endpoint] = std::move(fake_function_result.value());
struct {
usb_interface_descriptor_t intf;
usb_endpoint_descriptor_t ep1;
usb_endpoint_descriptor_t ep2;
} __PACKED combined_descriptors = {
.intf =
{
.b_length = sizeof(usb_interface_descriptor_t),
.b_descriptor_type = USB_DT_INTERFACE,
.b_interface_number = response->interface_nums[0],
.b_num_endpoints = 2,
.b_interface_class = 8,
.b_interface_sub_class = 6,
.b_interface_protocol = 80,
.i_interface = response->string_indices[0],
},
.ep1 =
{
.b_length = sizeof(usb_endpoint_descriptor_t),
.b_descriptor_type = USB_DT_ENDPOINT,
.b_endpoint_address = ep1_addr,
.bm_attributes = static_cast<uint8_t>(fdescriptor::EndpointType::kBulk),
.w_max_packet_size = 512,
},
.ep2 =
{
.b_length = sizeof(usb_endpoint_descriptor_t),
.b_descriptor_type = USB_DT_ENDPOINT,
.b_endpoint_address = ep2_addr,
.bm_attributes = static_cast<uint8_t>(fdescriptor::EndpointType::kBulk),
.w_max_packet_size = 512,
},
};
std::vector<uint8_t> descriptors_vec(sizeof(combined_descriptors));
memcpy(descriptors_vec.data(), &combined_descriptors, sizeof(combined_descriptors));
fidl::WireResult configure_res = function_client->Configure(
fidl::VectorView<uint8_t>::FromExternal(descriptors_vec.data(), descriptors_vec.size()),
std::move(fake_function_endpoint));
ASSERT_TRUE(configure_res.ok()) << configure_res.status_string();
ASSERT_TRUE(configure_res->is_ok());
}
TEST_F(UsbPeripheralFunctionTest, ResourceCleanupOnClose) {
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
auto function_client = std::move(function_client_result.value());
fidl::Arena arena;
auto endpoints = fidl::VectorView<ffunction::wire::EndpointResource>(arena, 1);
endpoints[0].direction = fdescriptor::wire::EndpointDirection::kIn;
endpoints[0].ep_info = BulkEpInfo(arena);
endpoints[0].max_packet_size = 512;
auto ep_endpoints = fidl::Endpoints<fendpoint::Endpoint>::Create();
endpoints[0].endpoint = std::move(ep_endpoints.server);
auto strings = fidl::VectorView<fidl::StringView>(arena, 1);
strings[0] = fidl::StringView(arena, "cleanup_test_string");
fidl::WireResult res = function_client->AllocResources(1, endpoints, strings);
ASSERT_TRUE(res.ok()) << res.FormatDescription();
ASSERT_TRUE(res->is_ok()) << zx_status_get_string(res->error_value());
// Verify resources are allocated.
UsbPeripheral::ResourceAllocations allocations;
dut().RunInDriverContext(
[&](UsbPeripheral& peripheral) { allocations = peripheral.GetResourceAllocations(0); });
ASSERT_EQ(allocations.interface_nums.size(), 1u);
ASSERT_EQ(allocations.endpoint_addrs.size(), 1u);
ASSERT_EQ(allocations.string_indices.size(), 1u);
// Close the FIDL connection.
function_client = {};
// Verify resources are cleared.
dut().runtime().RunUntil([&]() {
dut().RunInDriverContext(
[&](UsbPeripheral& peripheral) { allocations = peripheral.GetResourceAllocations(0); });
return allocations.interface_nums.empty() && allocations.endpoint_addrs.empty() &&
allocations.string_indices.empty();
});
}
TEST_F(UsbPeripheralFunctionTest, AllocResourcesRollback) {
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
auto function_client = std::move(function_client_result.value());
fidl::Arena arena;
// 1. Initial success allocation to have a baseline of "used" resources.
{
auto endpoints = fidl::VectorView<ffunction::wire::EndpointResource>(arena, 1);
endpoints[0].direction = fdescriptor::wire::EndpointDirection::kIn;
endpoints[0].ep_info = BulkEpInfo(arena);
endpoints[0].max_packet_size = 512;
auto ep_endpoints = fidl::Endpoints<fendpoint::Endpoint>::Create();
endpoints[0].endpoint = std::move(ep_endpoints.server);
auto strings = fidl::VectorView<fidl::StringView>(arena, 1);
strings[0] = fidl::StringView(arena, "initial_string");
fidl::WireResult res = function_client->AllocResources(1, endpoints, strings);
ASSERT_TRUE(res.ok()) << res.FormatDescription();
ASSERT_TRUE(res.value().is_ok()) << zx_status_get_string(res.value().error_value());
}
UsbPeripheral::ResourceAllocations initial;
dut().RunInDriverContext(
[&](UsbPeripheral& peripheral) { initial = peripheral.GetResourceAllocations(0); });
ASSERT_EQ(initial.interface_nums.size(), 1u);
ASSERT_EQ(initial.endpoint_addrs.size(), 1u);
ASSERT_EQ(initial.string_indices.size(), 1u);
// 2. Perform a request that should succeed for strings and endpoints, but
// fails for interfaces. We already have 1 interface. Requesting
// UsbPeripheral::MAX_INTERFACES more should fail.
{
auto endpoints = fidl::VectorView<ffunction::wire::EndpointResource>(arena, 1);
endpoints[0].direction = fdescriptor::wire::EndpointDirection::kOut;
endpoints[0].ep_info = BulkEpInfo(arena);
endpoints[0].max_packet_size = 512;
auto ep_endpoints = fidl::Endpoints<fendpoint::Endpoint>::Create();
endpoints[0].endpoint = std::move(ep_endpoints.server);
auto strings = fidl::VectorView<fidl::StringView>(arena, 1);
strings[0] = fidl::StringView(arena, "should_rollback");
fidl::WireResult res =
function_client->AllocResources(UsbPeripheral::kMaxInterfaces, endpoints, strings);
ASSERT_TRUE(res.ok()) << res.FormatDescription();
EXPECT_STATUS(res.value().error_value(), ZX_ERR_NO_RESOURCES);
}
// Verify only initial resources remain.
UsbPeripheral::ResourceAllocations allocations;
dut().RunInDriverContext(
[&](UsbPeripheral& peripheral) { allocations = peripheral.GetResourceAllocations(0); });
EXPECT_EQ(allocations.interface_nums, initial.interface_nums);
EXPECT_EQ(allocations.endpoint_addrs, initial.endpoint_addrs);
EXPECT_EQ(allocations.string_indices, initial.string_indices);
// 3. Perform a request that should succeed for interfaces and endpoints, but
// fails for strings. Global strings (3) + Initial function strings taken.
// Requesting enough to exceed UsbPeripheral::MAX_STRINGS should fail.
{
auto endpoints = fidl::VectorView<ffunction::wire::EndpointResource>(arena, 1);
endpoints[0].direction = fdescriptor::wire::EndpointDirection::kOut;
endpoints[0].ep_info = BulkEpInfo(arena);
endpoints[0].max_packet_size = 512;
auto ep_endpoints = fidl::Endpoints<fendpoint::Endpoint>::Create();
endpoints[0].endpoint = std::move(ep_endpoints.server);
std::vector<fidl::StringView> strings_vec(UsbPeripheral::kMaxStrings,
fidl::StringView(arena, "too_many"));
fidl::WireResult res = function_client->AllocResources(
1, endpoints,
fidl::VectorView<fidl::StringView>::FromExternal(strings_vec.data(), strings_vec.size()));
ASSERT_TRUE(res.ok()) << res.FormatDescription();
EXPECT_STATUS(res.value().error_value(), ZX_ERR_NO_RESOURCES);
}
// Verify only initial resources remain.
dut().RunInDriverContext(
[&](UsbPeripheral& peripheral) { allocations = peripheral.GetResourceAllocations(0); });
EXPECT_EQ(allocations.interface_nums, initial.interface_nums);
EXPECT_EQ(allocations.endpoint_addrs, initial.endpoint_addrs);
EXPECT_EQ(allocations.string_indices, initial.string_indices);
// 4. Perform a request that should succeed for strings and interfaces, but
// fails for endpoints. Initial function IN endpoint (1) taken. Total IN
// endpoints available: UsbPeripheral::IN_EP_END -
// UsbPeripheral::IN_EP_START + 1.
{
size_t total_in_eps = UsbPeripheral::kInEpEnd - UsbPeripheral::kInEpStart + 1;
auto endpoints = fidl::VectorView<ffunction::wire::EndpointResource>(arena, total_in_eps);
for (size_t i = 0; i < total_in_eps; i++) {
endpoints[i].direction = fdescriptor::wire::EndpointDirection::kIn;
endpoints[i].ep_info = BulkEpInfo(arena);
endpoints[i].max_packet_size = 512;
auto ep_endpoints = fidl::Endpoints<fendpoint::Endpoint>::Create();
endpoints[i].endpoint = std::move(ep_endpoints.server);
}
auto strings = fidl::VectorView<fidl::StringView>(arena, 1);
strings[0] = fidl::StringView(arena, "should_rollback");
fidl::WireResult res = function_client->AllocResources(1, endpoints, strings);
ASSERT_TRUE(res.ok()) << res.FormatDescription();
EXPECT_STATUS(res.value().error_value(), ZX_ERR_NO_RESOURCES);
}
// Verify only initial resources remain.
dut().RunInDriverContext(
[&](UsbPeripheral& peripheral) { allocations = peripheral.GetResourceAllocations(0); });
EXPECT_EQ(allocations.interface_nums, initial.interface_nums);
EXPECT_EQ(allocations.endpoint_addrs, initial.endpoint_addrs);
EXPECT_EQ(allocations.string_indices, initial.string_indices);
}
TEST_F(UsbPeripheralFunctionTest, EndpointSetStall) {
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
fidl::Arena arena;
zx::result<uint8_t> configure_result = ConfigureDefaultFunction(function_client, arena);
ASSERT_OK(configure_result);
uint8_t ep_addr = configure_result.value();
// Test setting a stall on an allocated endpoint.
auto res = function_client->EndpointSetStall(ep_addr);
ASSERT_TRUE(res.ok()) << res.FormatDescription();
ASSERT_OK(res.value());
dut().RunInEnvironmentTypeContext([ep_addr](UsbPeripheralTestEnvironment& env) {
auto stalls = env.dci().set_stalls();
EXPECT_EQ(stalls.size(), 1u);
EXPECT_EQ(stalls[0], ep_addr);
});
// Test double call to SetStall is forwarded and succeeds.
auto res_double = function_client->EndpointSetStall(ep_addr);
ASSERT_TRUE(res_double.ok()) << res_double.FormatDescription();
ASSERT_OK(res_double.value());
dut().RunInEnvironmentTypeContext([ep_addr](UsbPeripheralTestEnvironment& env) {
auto stalls = env.dci().set_stalls();
EXPECT_EQ(stalls.size(), 2u);
EXPECT_EQ(stalls[1], ep_addr);
});
// Test an unknown/failing endpoint stall by toggling `fail_stall_` in our mock.
dut().RunInEnvironmentTypeContext(
[](UsbPeripheralTestEnvironment& env) { env.dci().fail_stall_.store(true); });
auto res2 = function_client->EndpointSetStall(ep_addr);
ASSERT_TRUE(res2.ok()) << res2.FormatDescription();
EXPECT_STATUS(res2.value(), ZX_ERR_IO_NOT_PRESENT);
// Test setting a stall on an unallocated endpoint.
uint8_t unallocated_ep_addr = (ep_addr == 0x81) ? 0x82 : 0x81;
auto res3 = function_client->EndpointSetStall(unallocated_ep_addr);
ASSERT_TRUE(res3.ok()) << res3.FormatDescription();
EXPECT_STATUS(res3.value(), ZX_ERR_NOT_FOUND);
}
TEST_F(UsbPeripheralFunctionTest, EndpointClearStall) {
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
fidl::Arena arena;
zx::result<uint8_t> configure_result = ConfigureDefaultFunction(function_client, arena);
ASSERT_OK(configure_result);
uint8_t ep_addr = configure_result.value();
// Test clearing a stall on an allocated endpoint.
auto res = function_client->EndpointClearStall(ep_addr);
ASSERT_TRUE(res.ok()) << res.FormatDescription();
ASSERT_OK(res.value());
dut().RunInEnvironmentTypeContext([ep_addr](UsbPeripheralTestEnvironment& env) {
auto stalls = env.dci().clear_stalls();
EXPECT_EQ(stalls.size(), 1u);
EXPECT_EQ(stalls[0], ep_addr);
});
// Test double call to ClearStall is forwarded and succeeds.
auto res_double = function_client->EndpointClearStall(ep_addr);
ASSERT_TRUE(res_double.ok()) << res_double.FormatDescription();
ASSERT_OK(res_double.value());
dut().RunInEnvironmentTypeContext([ep_addr](UsbPeripheralTestEnvironment& env) {
auto stalls = env.dci().clear_stalls();
EXPECT_EQ(stalls.size(), 2u);
EXPECT_EQ(stalls[1], ep_addr);
});
// Test an unknown/failing endpoint stall by toggling `fail_stall_` in our mock.
dut().RunInEnvironmentTypeContext(
[](UsbPeripheralTestEnvironment& env) { env.dci().fail_stall_.store(true); });
auto res2 = function_client->EndpointClearStall(ep_addr);
ASSERT_TRUE(res2.ok()) << res2.FormatDescription();
EXPECT_STATUS(res2.value(), ZX_ERR_IO_NOT_PRESENT);
// Test clearing a stall on an unallocated endpoint.
uint8_t unallocated_ep_addr = (ep_addr == 0x81) ? 0x82 : 0x81;
auto res3 = function_client->EndpointClearStall(unallocated_ep_addr);
ASSERT_TRUE(res3.ok()) << res3.FormatDescription();
EXPECT_STATUS(res3.value(), ZX_ERR_NOT_FOUND);
}
TEST_P(UsbPeripheralFunctionConfigureEndpointTest, ConfigureEndpoint) {
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
fidl::Arena arena;
zx::result<uint8_t> configure_result = ConfigureDefaultFunction(function_client, arena);
ASSERT_OK(configure_result);
uint8_t ep_addr = configure_result.value();
ffunction::wire::EndpointDescriptor desc = {
.bm_attributes = 1,
.w_max_packet_size = 2,
.b_interval = 3,
};
auto config_builder = ffunction::wire::EndpointConfiguration::Builder(arena);
config_builder.descriptor(desc);
bool with_ss_companion = GetParam();
ffunction::wire::SuperSpeedEndpointCompanionDescriptor ss_desc;
if (with_ss_companion) {
ss_desc = {
.b_max_burst = 5,
.bm_attributes = 4,
.w_bytes_per_interval = 6,
};
config_builder.super_speed_companion(ss_desc);
}
ffunction::wire::EndpointConfiguration config = config_builder.Build();
auto res = function_client->ConfigureEndpoint(ep_addr, config);
ASSERT_TRUE(res.ok()) << res.FormatDescription();
ASSERT_OK(res.value());
dut().RunInEnvironmentTypeContext(
[ep_addr, &desc, with_ss_companion, &ss_desc](UsbPeripheralTestEnvironment& env) {
auto configured_eps = env.dci().configured_endpoints();
auto configured_ss = env.dci().configured_endpoints_ss_companion();
EXPECT_EQ(configured_eps.size(), 1u);
EXPECT_EQ(configured_eps[0].b_endpoint_address, ep_addr);
EXPECT_EQ(configured_eps[0].w_max_packet_size, desc.w_max_packet_size);
EXPECT_EQ(configured_eps[0].bm_attributes, desc.bm_attributes);
EXPECT_EQ(configured_eps[0].b_interval, desc.b_interval);
if (with_ss_companion) {
EXPECT_EQ(configured_ss[0].b_max_burst, ss_desc.b_max_burst);
EXPECT_EQ(configured_ss[0].bm_attributes, ss_desc.bm_attributes);
EXPECT_EQ(configured_ss[0].w_bytes_per_interval, ss_desc.w_bytes_per_interval);
} else {
EXPECT_EQ(configured_ss[0].b_max_burst, 0);
EXPECT_EQ(configured_ss[0].bm_attributes, 0);
EXPECT_EQ(configured_ss[0].w_bytes_per_interval, 0u);
}
});
// Test unknown endpoint configuration.
uint8_t unallocated_ep_addr = (ep_addr == 0x81) ? 0x82 : 0x81;
auto res2 = function_client->ConfigureEndpoint(unallocated_ep_addr, config);
ASSERT_TRUE(res2.ok()) << res2.FormatDescription();
EXPECT_STATUS(res2.value(), ZX_ERR_NOT_FOUND);
// Test failing configuration from DCI.
dut().RunInEnvironmentTypeContext(
[](UsbPeripheralTestEnvironment& env) { env.dci().fail_configure_.store(true); });
auto res3 = function_client->ConfigureEndpoint(ep_addr, config);
ASSERT_TRUE(res3.ok()) << res3.FormatDescription();
EXPECT_STATUS(res3.value(), ZX_ERR_IO_NOT_PRESENT);
}
INSTANTIATE_TEST_SUITE_P(UsbPeripheralFunctionConfigureEndpointTest,
UsbPeripheralFunctionConfigureEndpointTest, testing::Bool());
TEST_F(UsbPeripheralFunctionTest, DisableEndpoint) {
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
fidl::Arena arena;
zx::result<uint8_t> configure_result = ConfigureDefaultFunction(function_client, arena);
ASSERT_OK(configure_result);
uint8_t ep_addr = configure_result.value();
// Test disabling an allocated endpoint.
auto res = function_client->DisableEndpoint(ep_addr);
ASSERT_TRUE(res.ok()) << res.FormatDescription();
ASSERT_TRUE(res->is_ok()) << zx_status_get_string(res->error_value());
dut().RunInEnvironmentTypeContext([ep_addr](UsbPeripheralTestEnvironment& env) {
auto disabled_eps = env.dci().disabled_endpoints();
EXPECT_EQ(disabled_eps.size(), 1u);
EXPECT_EQ(disabled_eps[0], ep_addr);
});
// Test double call to DisableEndpoint is forwarded and succeeds.
auto res_double = function_client->DisableEndpoint(ep_addr);
ASSERT_TRUE(res_double.ok()) << res_double.FormatDescription();
ASSERT_TRUE(res_double->is_ok()) << zx_status_get_string(res_double->error_value());
dut().RunInEnvironmentTypeContext([ep_addr](UsbPeripheralTestEnvironment& env) {
auto disabled_eps = env.dci().disabled_endpoints();
EXPECT_EQ(disabled_eps.size(), 2u);
EXPECT_EQ(disabled_eps[1], ep_addr);
});
// Test unknown endpoint disable
uint8_t unallocated_ep_addr = (ep_addr == 0x81) ? 0x82 : 0x81;
auto res2 = function_client->DisableEndpoint(unallocated_ep_addr);
ASSERT_TRUE(res2.ok()) << res2.FormatDescription();
EXPECT_STATUS(res2.value(), ZX_ERR_NOT_FOUND);
// Test failing disable from DCI
dut().RunInEnvironmentTypeContext(
[](UsbPeripheralTestEnvironment& env) { env.dci().fail_disable_.store(true); });
auto res3 = function_client->DisableEndpoint(ep_addr);
ASSERT_TRUE(res3.ok()) << res3.FormatDescription();
EXPECT_STATUS(res3.value(), ZX_ERR_IO_NOT_PRESENT);
}
TEST_F(UsbPeripheralFunctionTest, StateTransitionErrors) {
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
fidl::Arena arena;
auto endpoints = fidl::VectorView<ffunction::wire::EndpointResource>(arena, 1);
endpoints[0].direction = fdescriptor::wire::EndpointDirection::kIn;
endpoints[0].ep_info = BulkEpInfo(arena);
endpoints[0].max_packet_size = 512;
auto ep_endpoints = fidl::Endpoints<fendpoint::Endpoint>::Create();
endpoints[0].endpoint = std::move(ep_endpoints.server);
fidl::WireResult alloc_res =
function_client->AllocResources(1, endpoints, fidl::VectorView<fidl::StringView>());
ASSERT_TRUE(alloc_res.ok());
ASSERT_OK(alloc_res.value());
uint8_t ep_addr = alloc_res->value()->endpoint_addrs[0];
// 1. Test endpoint methods before Configure -> should return ZX_ERR_BAD_STATE.
{
auto res = function_client->EndpointSetStall(ep_addr);
ASSERT_TRUE(res.ok());
EXPECT_STATUS(res.value(), ZX_ERR_BAD_STATE);
}
{
auto res = function_client->EndpointClearStall(ep_addr);
ASSERT_TRUE(res.ok());
EXPECT_STATUS(res.value(), ZX_ERR_BAD_STATE);
}
{
ffunction::wire::EndpointDescriptor desc = {
.bm_attributes = 1,
.w_max_packet_size = 2,
.b_interval = 3,
};
auto config_builder = ffunction::wire::EndpointConfiguration::Builder(arena);
config_builder.descriptor(desc);
auto res = function_client->ConfigureEndpoint(ep_addr, config_builder.Build());
ASSERT_TRUE(res.ok());
EXPECT_STATUS(res.value(), ZX_ERR_BAD_STATE);
}
{
auto res = function_client->DisableEndpoint(ep_addr);
ASSERT_TRUE(res.ok());
EXPECT_STATUS(res.value(), ZX_ERR_BAD_STATE);
}
// 2. Configure the function.
zx::result fake_function_result = BindFakeFunction();
ASSERT_OK(fake_function_result);
auto [fake_function, fake_function_endpoint] = std::move(fake_function_result.value());
usb_interface_descriptor_t intf_desc = {
.b_length = sizeof(usb_interface_descriptor_t),
.b_descriptor_type = USB_DT_INTERFACE,
.b_interface_number = alloc_res->value()->interface_nums[0],
.b_alternate_setting = 0,
.b_num_endpoints = 1,
.b_interface_class = 8,
.b_interface_sub_class = 6,
.b_interface_protocol = 80,
};
usb_endpoint_descriptor_t ep_desc = {
.b_length = sizeof(usb_endpoint_descriptor_t),
.b_descriptor_type = USB_DT_ENDPOINT,
.b_endpoint_address = ep_addr,
.bm_attributes = static_cast<uint8_t>(fdescriptor::EndpointType::kBulk),
.w_max_packet_size = 512,
};
std::vector<uint8_t> descriptors(sizeof(intf_desc) + sizeof(ep_desc));
memcpy(descriptors.data(), &intf_desc, sizeof(intf_desc));
memcpy(descriptors.data() + sizeof(intf_desc), &ep_desc, sizeof(ep_desc));
fidl::WireResult configure_res = function_client->Configure(
fidl::VectorView<uint8_t>::FromExternal(descriptors.data(), descriptors.size()),
std::move(fake_function_endpoint));
ASSERT_TRUE(configure_res.ok());
ASSERT_OK(configure_res.value());
// 3. Test AllocResources after Configure -> should return ZX_ERR_BAD_STATE.
{
auto endpoints2 = fidl::VectorView<ffunction::wire::EndpointResource>(arena, 0);
fidl::WireResult alloc_res2 =
function_client->AllocResources(1, endpoints2, fidl::VectorView<fidl::StringView>());
ASSERT_TRUE(alloc_res2.ok());
EXPECT_STATUS(alloc_res2.value(), ZX_ERR_BAD_STATE);
}
}
TEST_F(UsbPeripheralFunctionTest, ConfigureEndpointDuringSetConfigured) {
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
zx::result fake_function_result = BindFakeFunction();
ASSERT_OK(fake_function_result);
auto [fake_function, fake_function_endpoint] = std::move(fake_function_result.value());
fidl::Arena arena;
auto endpoints = fidl::VectorView<ffunction::wire::EndpointResource>(arena, 1);
endpoints[0].direction = fdescriptor::wire::EndpointDirection::kIn;
endpoints[0].ep_info = BulkEpInfo(arena);
endpoints[0].max_packet_size = 512;
auto ep_endpoints = fidl::Endpoints<fendpoint::Endpoint>::Create();
endpoints[0].endpoint = std::move(ep_endpoints.server);
fidl::WireResult alloc_res =
function_client->AllocResources(1, endpoints, fidl::VectorView<fidl::StringView>());
ASSERT_TRUE(alloc_res.ok());
ASSERT_OK(alloc_res.value());
uint8_t ep_addr = alloc_res->value()->endpoint_addrs[0];
uint8_t interface_num = alloc_res->value()->interface_nums[0];
std::vector<uint8_t> descriptors;
usb_interface_descriptor_t intf_desc = {
.b_length = sizeof(usb_interface_descriptor_t),
.b_descriptor_type = USB_DT_INTERFACE,
.b_interface_number = interface_num,
.b_alternate_setting = 0,
.b_num_endpoints = 0,
};
descriptors.resize(sizeof(intf_desc));
memcpy(descriptors.data(), &intf_desc, sizeof(intf_desc));
fidl::WireResult configure_res = function_client->Configure(
fidl::VectorView<uint8_t>::FromExternal(descriptors.data(), descriptors.size()),
std::move(fake_function_endpoint));
ASSERT_TRUE(configure_res.ok()) << configure_res.FormatDescription();
ASSERT_OK(configure_res.value());
ExpectControllerStarted(true);
ASSERT_OK(dci()->SetConnected(true).status());
libsync::Completion configure_endpoint_completed;
// Set the callback that simulates the condition of calling back into the
// function before responding to set configured.
auto callback_cleanup = fit::defer([&]() { fake_function->set_on_set_configured(nullptr); });
fake_function->set_on_set_configured([&]() {
fidl::Arena arena;
ffunction::wire::EndpointDescriptor desc;
desc.bm_attributes = 2;
desc.w_max_packet_size = 512;
desc.b_interval = 0;
auto config_builder = ffunction::wire::EndpointConfiguration::Builder(arena);
config_builder.descriptor(desc);
auto res = function_client->ConfigureEndpoint(ep_addr, config_builder.Build());
ASSERT_TRUE(res.ok()) << res.FormatDescription();
ASSERT_OK(res.value());
configure_endpoint_completed.Signal();
});
// Trigger SetConfigured by sending standard endpoint request SetConfiguration = 1
fdescriptor::wire::UsbSetup setup;
setup.bm_request_type = USB_DIR_OUT | USB_RECIP_DEVICE | USB_TYPE_STANDARD;
setup.b_request = USB_REQ_SET_CONFIGURATION;
setup.w_value = 1;
setup.w_index = 0;
setup.w_length = 0;
std::vector<uint8_t> unused;
fidl::WireUnownedResult config_res =
dci().buffer(arena)->Control(setup, fidl::VectorView<uint8_t>::FromExternal(unused));
ASSERT_TRUE(config_res.ok()) << config_res.FormatDescription();
ASSERT_OK(config_res.value());
fake_function->WaitUntilCalled();
EXPECT_TRUE(fake_function->set_configured_called());
EXPECT_TRUE(fake_function->configured());
ASSERT_OK(configure_endpoint_completed.Wait(zx::sec(5)));
dut().RunInEnvironmentTypeContext([ep_addr](UsbPeripheralTestEnvironment& env) {
auto configured_eps = env.dci().configured_endpoints();
EXPECT_EQ(configured_eps.size(), 1u);
EXPECT_EQ(configured_eps[0].b_endpoint_address, ep_addr);
});
}
// ============================================================================
// NEW CONTRACT TESTS (Missing Matrix Items)
// ============================================================================
// [FUNC-1.4] ConnectToEndpoint validates ep not bound (fails ALREADY_BOUND)
TEST_F(UsbPeripheralFunctionTest, ConnectToEndpointFailsIfAlreadyBound) {
ExpectControllerStarted(false);
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
fidl::Arena arena;
zx::result<uint8_t> ep_addr_result = ConfigureDefaultFunction(function_client, arena);
ASSERT_OK(ep_addr_result);
uint8_t ep_addr = ep_addr_result.value();
// First connection.
auto ep_endpoints1 = fidl::Endpoints<fendpoint::Endpoint>::Create();
fidl::WireResult connect_res1 =
function_client->ConnectToEndpoint(ep_addr, std::move(ep_endpoints1.server));
ASSERT_TRUE(connect_res1.ok()) << connect_res1.FormatDescription();
ASSERT_OK(connect_res1.value());
// Second connection to same endpoint should fail.
// Tell fake DCI to return ALREADY_BOUND for next connections.
dut().RunInEnvironmentTypeContext(
[](UsbPeripheralTestEnvironment& env) { env.dci().fail_already_bound_.store(true); });
auto ep_endpoints2 = fidl::Endpoints<fendpoint::Endpoint>::Create();
fidl::WireResult connect_res2 =
function_client->ConnectToEndpoint(ep_addr, std::move(ep_endpoints2.server));
ASSERT_TRUE(connect_res2.ok()) << connect_res2.FormatDescription();
EXPECT_STATUS(connect_res2.value(), ZX_ERR_ALREADY_BOUND);
}
// [FUNC-2.2] Configure validates first descriptor is Interface/IAD (fails INVALID_ARGS)
TEST_F(UsbPeripheralFunctionTest, ConfigureFailsIfFirstDescriptorNotInterfaceOrIad) {
ExpectControllerStarted(false);
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
zx::result fake_function_result = BindFakeFunction();
ASSERT_OK(fake_function_result);
auto [fake_function, fake_function_endpoint] = std::move(fake_function_result.value());
fidl::WireResult alloc_res = function_client->AllocResources(1, {}, {});
ASSERT_TRUE(alloc_res.ok()) << alloc_res.status_string();
ASSERT_TRUE(alloc_res->is_ok());
// Pass endpoint descriptor first instead of Interface.
usb_endpoint_descriptor_t ep_desc = {
.b_length = sizeof(usb_endpoint_descriptor_t),
.b_descriptor_type = USB_DT_ENDPOINT,
.b_endpoint_address = 0x81,
.bm_attributes = static_cast<uint8_t>(fdescriptor::EndpointType::kBulk),
.w_max_packet_size = 512,
};
std::vector<uint8_t> descriptors(sizeof(ep_desc));
memcpy(descriptors.data(), &ep_desc, sizeof(ep_desc));
fidl::WireResult configure_res = function_client->Configure(
fidl::VectorView<uint8_t>::FromExternal(descriptors.data(), descriptors.size()),
std::move(fake_function_endpoint));
ASSERT_TRUE(configure_res.ok()) << configure_res.FormatDescription();
EXPECT_STATUS(configure_res.value(), ZX_ERR_INVALID_ARGS);
}
class UsbPeripheralAllocationTest : public UsbPeripheralHarness<false> {
public:
usb_peripheral_config::Config GetDriverConfig() override {
usb_peripheral_config::Config config;
config.functions() = {"test"};
return config;
}
};
TEST_F(UsbPeripheralAllocationTest, BestFit) {
dut().RunInEnvironmentTypeContext([](UsbPeripheralTestEnvironment& env) {
std::vector<FakeDevice::EndpointCaps> caps = {
{0x81, 64, {FakeDevice::EpType::kBulk, FakeDevice::EpType::kInterrupt}},
{0x82, 512, {FakeDevice::EpType::kBulk, FakeDevice::EpType::kInterrupt}},
{0x01, 512, {FakeDevice::EpType::kBulk, FakeDevice::EpType::kInterrupt}},
};
env.dci().SetHardwareInfo(std::move(caps), false);
});
StartDriverWithConfig(GetDriverConfig());
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
auto function_client = std::move(function_client_result.value());
fidl::Arena arena;
auto endpoints = fidl::VectorView<ffunction::wire::EndpointResource>(arena, 3);
auto ep_endpoints1 = fidl::Endpoints<fendpoint::Endpoint>::Create();
endpoints[0].direction = fdescriptor::wire::EndpointDirection::kIn;
endpoints[0].ep_info = InterruptEpInfo(arena);
endpoints[0].max_packet_size = 16;
endpoints[0].endpoint = std::move(ep_endpoints1.server);
auto ep_endpoints2 = fidl::Endpoints<fendpoint::Endpoint>::Create();
endpoints[1].direction = fdescriptor::wire::EndpointDirection::kIn;
endpoints[1].ep_info = BulkEpInfo(arena);
endpoints[1].max_packet_size = 512;
endpoints[1].endpoint = std::move(ep_endpoints2.server);
auto ep_endpoints3 = fidl::Endpoints<fendpoint::Endpoint>::Create();
endpoints[2].direction = fdescriptor::wire::EndpointDirection::kOut;
endpoints[2].ep_info = BulkEpInfo(arena);
endpoints[2].max_packet_size = 512;
endpoints[2].endpoint = std::move(ep_endpoints3.server);
fidl::WireResult res = function_client->AllocResources(1, endpoints, {});
ASSERT_TRUE(res.ok()) << res.FormatDescription();
ASSERT_TRUE(res->is_ok()) << zx_status_get_string(res->error_value());
auto* response = res->value();
ASSERT_EQ(response->endpoint_addrs.size(), 3u);
EXPECT_EQ(response->endpoint_addrs[0], 0x81);
EXPECT_EQ(response->endpoint_addrs[1], 0x82);
EXPECT_EQ(response->endpoint_addrs[2], 0x01);
// Verify inspect
this->dut().RunInDriverContext([](UsbPeripheral& peripheral) {
auto hierarchy = usb_inspect::ReadHierarchyFromInspector(peripheral.inspector());
auto* node = hierarchy.GetByPath({"usb-peripheral", "hardware_info"});
ASSERT_NE(node, nullptr);
EXPECT_THAT(*node,
NodeMatches(PropertyList(Contains(BoolIs("supports_dynamic_ep_sizing", false)))));
auto* ep_81 = hierarchy.GetByPath({"usb-peripheral", "hardware_info", "ep_0x81"});
ASSERT_NE(ep_81, nullptr);
EXPECT_THAT(*ep_81, NodeMatches(PropertyList(Contains(UintIs("max_packet_size_limit", 64)))));
EXPECT_THAT(
*ep_81,
NodeMatches(PropertyList(Contains(StringIs("supported_types", "bulk, interrupt")))));
auto* ep_82 = hierarchy.GetByPath({"usb-peripheral", "hardware_info", "ep_0x82"});
ASSERT_NE(ep_82, nullptr);
EXPECT_THAT(*ep_82, NodeMatches(PropertyList(Contains(UintIs("max_packet_size_limit", 512)))));
EXPECT_THAT(
*ep_82,
NodeMatches(PropertyList(Contains(StringIs("supported_types", "bulk, interrupt")))));
auto* ep_01 = hierarchy.GetByPath({"usb-peripheral", "hardware_info", "ep_0x01"});
ASSERT_NE(ep_01, nullptr);
EXPECT_THAT(*ep_01, NodeMatches(PropertyList(Contains(UintIs("max_packet_size_limit", 512)))));
EXPECT_THAT(
*ep_01,
NodeMatches(PropertyList(Contains(StringIs("supported_types", "bulk, interrupt")))));
});
}
TEST_F(UsbPeripheralAllocationTest, Dynamic) {
dut().RunInEnvironmentTypeContext(
[](UsbPeripheralTestEnvironment& env) { env.dci().SetHardwareInfo({}, true); });
StartDriverWithConfig(GetDriverConfig());
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
auto function_client = std::move(function_client_result.value());
fidl::Arena arena;
auto endpoints = fidl::VectorView<ffunction::wire::EndpointResource>(arena, 2);
auto ep_endpoints1 = fidl::Endpoints<fendpoint::Endpoint>::Create();
endpoints[0].direction = fdescriptor::wire::EndpointDirection::kIn;
endpoints[0].ep_info = BulkEpInfo(arena);
endpoints[0].max_packet_size = 512;
endpoints[0].endpoint = std::move(ep_endpoints1.server);
auto ep_endpoints2 = fidl::Endpoints<fendpoint::Endpoint>::Create();
endpoints[1].direction = fdescriptor::wire::EndpointDirection::kOut;
endpoints[1].ep_info = BulkEpInfo(arena);
endpoints[1].max_packet_size = 512;
endpoints[1].endpoint = std::move(ep_endpoints2.server);
fidl::WireResult res = function_client->AllocResources(1, endpoints, {});
ASSERT_TRUE(res.ok()) << res.FormatDescription();
ASSERT_TRUE(res->is_ok()) << zx_status_get_string(res->error_value());
auto* response = res->value();
ASSERT_EQ(response->endpoint_addrs.size(), 2u);
EXPECT_EQ(response->endpoint_addrs[0], 0x81);
EXPECT_EQ(response->endpoint_addrs[1], 0x01);
bool alloc_called = false;
dut().RunInEnvironmentTypeContext(
[&](UsbPeripheralTestEnvironment& env) { alloc_called = env.dci().alloc_called(); });
EXPECT_TRUE(alloc_called);
}
TEST_F(UsbPeripheralAllocationTest, AllocationRollback) {
dut().RunInEnvironmentTypeContext([](UsbPeripheralTestEnvironment& env) {
env.dci().SetHardwareInfo({}, true);
// Limit allocations to 1. The second one will fail.
env.dci().set_max_allocs(1);
env.dci().clear_freed_endpoints();
});
StartDriverWithConfig(GetDriverConfig());
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
auto function_client = std::move(function_client_result.value());
fidl::Arena arena;
auto endpoints = fidl::VectorView<ffunction::wire::EndpointResource>(arena, 2);
auto ep_endpoints1 = fidl::Endpoints<fendpoint::Endpoint>::Create();
endpoints[0].direction = fdescriptor::wire::EndpointDirection::kIn;
endpoints[0].ep_info = BulkEpInfo(arena);
endpoints[0].max_packet_size = 512;
endpoints[0].endpoint = std::move(ep_endpoints1.server);
auto ep_endpoints2 = fidl::Endpoints<fendpoint::Endpoint>::Create();
endpoints[1].direction = fdescriptor::wire::EndpointDirection::kOut;
endpoints[1].ep_info = BulkEpInfo(arena);
endpoints[1].max_packet_size = 512;
endpoints[1].endpoint = std::move(ep_endpoints2.server);
// This should fail because we only allow 1 allocation, but we requested 2.
fidl::WireResult res = function_client->AllocResources(1, endpoints, {});
ASSERT_TRUE(res.ok()) << res.FormatDescription();
ASSERT_TRUE(res->is_error());
EXPECT_EQ(res->error_value(), ZX_ERR_NO_RESOURCES);
// Verify that the first endpoint (0x81) was freed.
std::vector<uint8_t> freed_endpoints;
dut().RunInEnvironmentTypeContext(
[&](UsbPeripheralTestEnvironment& env) { freed_endpoints = env.dci().freed_endpoints(); });
ASSERT_EQ(freed_endpoints.size(), 1u);
EXPECT_EQ(freed_endpoints[0], 0x81);
}
// Stall EP0 if Control returns error
TEST_F(UsbPeripheralFunctionTest, ControlStallsEp0OnError) {
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
zx::result fake_function_result = BindFakeFunction();
ASSERT_OK(fake_function_result);
auto [fake_function, fake_function_endpoint] = std::move(fake_function_result.value());
fidl::WireResult alloc_res = function_client->AllocResources(1, {}, {});
ASSERT_TRUE(alloc_res.ok()) << alloc_res.status_string();
ASSERT_TRUE(alloc_res->is_ok());
uint8_t interface_num = alloc_res->value()->interface_nums[0];
usb_interface_descriptor_t intf_desc = {
.b_length = sizeof(usb_interface_descriptor_t),
.b_descriptor_type = USB_DT_INTERFACE,
.b_interface_number = interface_num,
.b_alternate_setting = 0,
.b_num_endpoints = 0,
.b_interface_class = 8,
.b_interface_sub_class = 6,
.b_interface_protocol = 80,
};
std::vector<uint8_t> descriptors(sizeof(intf_desc));
memcpy(descriptors.data(), &intf_desc, sizeof(intf_desc));
fidl::WireResult configure_res = function_client->Configure(
fidl::VectorView<uint8_t>::FromExternal(descriptors.data(), descriptors.size()),
std::move(fake_function_endpoint));
ASSERT_TRUE(configure_res.ok());
ASSERT_OK(configure_res.value());
ASSERT_OK(dci()->SetConnected(true).status());
ExpectState(UsbPeripheral::DeviceState::kHostConnected);
fidl::Arena arena;
std::vector<uint8_t> unused;
// Configure first.
fdescriptor::wire::UsbSetup setup;
setup.bm_request_type = USB_DIR_OUT | USB_RECIP_DEVICE | USB_TYPE_STANDARD;
setup.b_request = USB_REQ_SET_CONFIGURATION;
setup.w_value = 1; // Configuration 1
setup.w_index = 0;
setup.w_length = 0;
fidl::WireUnownedResult config_res =
dci().buffer(arena)->Control(setup, fidl::VectorView<uint8_t>::FromExternal(unused));
EXPECT_TRUE(config_res.ok()) << config_res.FormatDescription();
ASSERT_OK(config_res.value());
fake_function->WaitUntilCalled();
// Set control status to return error.
fake_function->set_control_status(ZX_ERR_NOT_SUPPORTED);
// Test Control via provided endpoint request (vendor request, directed to interface).
setup.bm_request_type = USB_DIR_IN | USB_RECIP_INTERFACE | USB_TYPE_VENDOR;
setup.b_request = 0xAA;
setup.w_value = 0x01;
setup.w_index = interface_num; // Use interface_num to route to this function.
setup.w_length = 3;
fidl::WireUnownedResult control_res =
dci().buffer(arena)->Control(setup, fidl::VectorView<uint8_t>::FromExternal(unused));
// The control call should return error status.
ASSERT_TRUE(control_res.ok()) << control_res.FormatDescription();
ASSERT_TRUE(control_res->is_error());
EXPECT_STATUS(control_res->error_value(), ZX_ERR_NOT_SUPPORTED);
}
// [INTF-2.2] Stall EP0 if SetConfigured returns error
TEST_F(UsbPeripheralFunctionTest, SetConfiguredStallsEp0OnError) {
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
zx::result fake_function_result = BindFakeFunction();
ASSERT_OK(fake_function_result);
auto [fake_function, fake_function_endpoint] = std::move(fake_function_result.value());
fidl::WireResult alloc_res = function_client->AllocResources(1, {}, {});
ASSERT_TRUE(alloc_res.ok()) << alloc_res.status_string();
ASSERT_TRUE(alloc_res->is_ok());
uint8_t interface_num = alloc_res->value()->interface_nums[0];
usb_interface_descriptor_t intf_desc = {
.b_length = sizeof(usb_interface_descriptor_t),
.b_descriptor_type = USB_DT_INTERFACE,
.b_interface_number = interface_num,
.b_alternate_setting = 0,
.b_num_endpoints = 0,
.b_interface_class = 8,
.b_interface_sub_class = 6,
.b_interface_protocol = 80,
};
std::vector<uint8_t> descriptors(sizeof(intf_desc));
memcpy(descriptors.data(), &intf_desc, sizeof(intf_desc));
fidl::WireResult configure_res = function_client->Configure(
fidl::VectorView<uint8_t>::FromExternal(descriptors.data(), descriptors.size()),
std::move(fake_function_endpoint));
ASSERT_TRUE(configure_res.ok());
ASSERT_OK(configure_res.value());
ASSERT_OK(dci()->SetConnected(true).status());
ExpectState(UsbPeripheral::DeviceState::kHostConnected);
// Set SetConfigured status to return error.
fake_function->set_set_configured_status(ZX_ERR_BAD_STATE);
fidl::Arena arena;
std::vector<uint8_t> unused;
fdescriptor::wire::UsbSetup setup;
setup.bm_request_type = USB_DIR_OUT | USB_RECIP_DEVICE | USB_TYPE_STANDARD;
setup.b_request = USB_REQ_SET_CONFIGURATION;
setup.w_value = 1;
setup.w_index = 0;
setup.w_length = 0;
fidl::WireUnownedResult control_res =
dci().buffer(arena)->Control(setup, fidl::VectorView<uint8_t>::FromExternal(unused));
// The control call should return error status.
ASSERT_TRUE(control_res.ok()) << control_res.FormatDescription();
ASSERT_TRUE(control_res->is_error());
EXPECT_STATUS(control_res->error_value(), ZX_ERR_BAD_STATE);
}
// [INTF-3.2] Stall EP0 if SetInterface returns error
TEST_F(UsbPeripheralFunctionTest, SetInterfaceStallsEp0OnError) {
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
zx::result fake_function_result = BindFakeFunction();
ASSERT_OK(fake_function_result);
auto [fake_function, fake_function_endpoint] = std::move(fake_function_result.value());
fidl::WireResult alloc_res = function_client->AllocResources(1, {}, {});
ASSERT_TRUE(alloc_res.ok()) << alloc_res.status_string();
ASSERT_TRUE(alloc_res->is_ok());
uint8_t interface_num = alloc_res->value()->interface_nums[0];
usb_interface_descriptor_t intf_desc = {
.b_length = sizeof(usb_interface_descriptor_t),
.b_descriptor_type = USB_DT_INTERFACE,
.b_interface_number = interface_num,
.b_alternate_setting = 0,
.b_num_endpoints = 0,
.b_interface_class = 8,
.b_interface_sub_class = 6,
.b_interface_protocol = 80,
};
std::vector<uint8_t> descriptors(sizeof(intf_desc));
memcpy(descriptors.data(), &intf_desc, sizeof(intf_desc));
fidl::WireResult configure_res = function_client->Configure(
fidl::VectorView<uint8_t>::FromExternal(descriptors.data(), descriptors.size()),
std::move(fake_function_endpoint));
ASSERT_TRUE(configure_res.ok());
ASSERT_OK(configure_res.value());
ASSERT_OK(dci()->SetConnected(true).status());
ExpectState(UsbPeripheral::DeviceState::kHostConnected);
fidl::Arena arena;
std::vector<uint8_t> unused;
// Configure first.
fdescriptor::wire::UsbSetup setup;
setup.bm_request_type = USB_DIR_OUT | USB_RECIP_DEVICE | USB_TYPE_STANDARD;
setup.b_request = USB_REQ_SET_CONFIGURATION;
setup.w_value = 1; // Configuration 1
setup.w_index = 0;
setup.w_length = 0;
fidl::WireUnownedResult config_res =
dci().buffer(arena)->Control(setup, fidl::VectorView<uint8_t>::FromExternal(unused));
EXPECT_TRUE(config_res.ok()) << config_res.FormatDescription();
ASSERT_OK(config_res.value());
fake_function->WaitUntilCalled();
// Set SetInterface status to return error.
fake_function->set_set_interface_status(ZX_ERR_NOT_SUPPORTED);
setup.bm_request_type = USB_DIR_OUT | USB_RECIP_INTERFACE | USB_TYPE_STANDARD;
setup.b_request = USB_REQ_SET_INTERFACE;
setup.w_value = 1; // Alt setting 1
setup.w_index = interface_num;
setup.w_length = 0;
fidl::WireUnownedResult control_res =
dci().buffer(arena)->Control(setup, fidl::VectorView<uint8_t>::FromExternal(unused));
// The control call should return error status.
ASSERT_TRUE(control_res.ok()) << control_res.FormatDescription();
ASSERT_TRUE(control_res->is_error());
EXPECT_STATUS(control_res->error_value(), ZX_ERR_NOT_SUPPORTED);
}
TEST_F(UsbPeripheralFunctionTest, DISABLED_RejectConfigureWhileStopping) {
// Connect peripheral so we can call ClearFunctions().
zx::result peripheral_client_result = ConnectPeripheral();
ASSERT_OK(peripheral_client_result);
auto peripheral_client = std::move(peripheral_client_result.value());
// Connect function and allocate resources.
zx::result function_client_result = ConnectFunction();
ASSERT_OK(function_client_result);
fidl::WireSyncClient<ffunction::UsbFunction> function_client =
std::move(function_client_result.value());
zx::result fake_function_result = BindFakeFunction();
ASSERT_OK(fake_function_result);
auto [fake_function, fake_function_endpoint] = std::move(fake_function_result.value());
// Intercept SetConfigured(false) to stall teardown in kStopping state.
libsync::Completion unconfigure_received;
std::optional<FakeUsbFunction::SetConfiguredCompleterAsync> saved_completer;
auto completer_cleanup = fit::defer([&]() {
if (saved_completer.has_value()) {
saved_completer->ReplySuccess();
saved_completer.reset();
}
});
auto callback_cleanup =
fit::defer([&]() { fake_function->set_on_set_configured_async(nullptr); });
fake_function->set_on_set_configured_async(
[&](bool configured, FakeUsbFunction::SetConfiguredCompleterAsync completer) {
if (!configured) {
saved_completer = std::move(completer);
unconfigure_received.Signal();
} else {
completer.ReplySuccess();
}
});
fidl::WireResult alloc_res = function_client->AllocResources(1, {}, {});
ASSERT_TRUE(alloc_res.ok()) << alloc_res.status_string();
ASSERT_TRUE(alloc_res->is_ok()) << zx_status_get_string(alloc_res->error_value());
uint8_t interface_num = alloc_res->value()->interface_nums[0];
usb_interface_descriptor_t intf_desc = {
.b_length = sizeof(usb_interface_descriptor_t),
.b_descriptor_type = USB_DT_INTERFACE,
.b_interface_number = interface_num,
.b_alternate_setting = 0,
.b_num_endpoints = 0,
.b_interface_class = 8,
.b_interface_sub_class = 6,
.b_interface_protocol = 80,
.i_interface = 0,
};
std::vector<uint8_t> descriptors(sizeof(intf_desc));
memcpy(descriptors.data(), &intf_desc, sizeof(intf_desc));
// Configure first to set up the topology.
{
fidl::WireResult result = function_client->Configure(
fidl::VectorView<uint8_t>::FromExternal(descriptors.data(), descriptors.size()),
std::move(fake_function_endpoint));
ASSERT_TRUE(result.ok()) << result.FormatDescription();
ASSERT_TRUE(result->is_ok());
}
ExpectState(UsbPeripheral::DeviceState::kPeripheralReady);
// Clear functions asynchronously on a background thread. This is necessary because
// ClearFunctions() is a synchronous FIDL call that blocks waiting for SetConfigured(false)
// to complete, which is held open by saved_completer. Calling it on a background thread
// keeps the main test thread free to issue a concurrent Configure() call and verify it is
// rejected with ZX_ERR_BAD_STATE while in kStopping, before completing saved_completer.
auto clear_promise = std::async(std::launch::async, [&]() {
auto clear_res = peripheral_client->ClearFunctions();
EXPECT_TRUE(clear_res.ok()) << clear_res.FormatDescription();
});
// Wait until FakeUsbFunction receives SetConfigured(false).
ASSERT_OK(unconfigure_received.Wait(zx::sec(5)));
// The state is now kStopping (since unconfiguring is asynchronous).
WaitUntilState(UsbPeripheral::DeviceState::kStopping);
// While in kStopping, attempt to call Configure.
// Note: we need new endpoints for a configure call.
zx::result endpoints = fidl::CreateEndpoints<ffunction::UsbFunctionInterface>();
ASSERT_OK(endpoints);
auto second_fake = std::make_shared<FakeUsbFunction>();
second_fake->Bind(dut().runtime().StartBackgroundDispatcher(), std::move(endpoints->server));
fidl::WireResult second_configure_res = function_client->Configure(
fidl::VectorView<uint8_t>::FromExternal(descriptors.data(), descriptors.size()),
std::move(endpoints->client));
ASSERT_TRUE(second_configure_res.ok()) << second_configure_res.FormatDescription();
// Assert that configuration is rejected with ZX_ERR_BAD_STATE.
EXPECT_TRUE(second_configure_res->is_error());
EXPECT_EQ(second_configure_res->error_value(), ZX_ERR_BAD_STATE);
// Complete the SetConfigured(false) call to resume/finish teardown.
ASSERT_TRUE(saved_completer.has_value());
completer_cleanup.call();
// Wait for the ClearFunctions call to complete on the background thread.
clear_promise.get();
dut().runtime().RunUntilIdle();
}
} // namespace
} // namespace usb_peripheral::test