blob: a801f6e2ba2520698a5e14464fcd82ea3efcbf26 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "../usb-device.h"
#include <fuchsia/hardware/usb/device/llcpp/fidl.h>
#include <lib/fake_ddk/fake_ddk.h>
#include <lib/fit/function.h>
#include <thread>
#include <usb/request-cpp.h>
#include <utf_conversion/utf_conversion.h>
#include <zxtest/zxtest.h>
namespace usb_bus {
template <typename T, size_t N>
constexpr T MakeConstant(const char value[N]) {
T retval = 0;
for (T i = 0; i < N; i++) {
retval = static_cast<T>(retval | (static_cast<T>(value[i]) << (i * 8)));
}
static_assert(N <= sizeof(T));
return retval;
}
constexpr uint8_t kVendorId = 81;
constexpr uint8_t kProductId = 35;
constexpr uint8_t kDeviceClass = 2;
constexpr uint8_t kDeviceSubclass = 6;
constexpr uint8_t kDeviceProtocol = 250;
constexpr uint32_t kDeviceId = 42;
constexpr uint32_t kHubId = 32;
constexpr uint32_t kMaxTransferSize = 9001;
constexpr uint8_t kTransferSizeEndpoint = 5;
constexpr uint64_t kCurrentFrame = MakeConstant<uint64_t, 7>("fuchsia");
constexpr size_t kRequestSize = 256;
const char16_t* kStringDescriptors[][2] = {{u"Fuchsia", u"Fucsia"}, {u"Device", u"Dispositivo"}};
constexpr usb_speed_t kDeviceSpeed = MakeConstant<usb_speed_t, 4>("slow");
class Binder : public fake_ddk::Bind {
public:
void* get_context() { return op_ctx_; }
bool get_remove_called() { return remove_called_; }
};
class FakeHci : public ddk::UsbHciProtocol<FakeHci> {
public:
FakeHci() {
proto_.ops = &usb_hci_protocol_ops_;
proto_.ctx = this;
}
uint64_t UsbHciGetCurrentFrame() { return kCurrentFrame; }
zx_status_t UsbHciConfigureHub(uint32_t device_id, usb_speed_t speed,
const usb_hub_descriptor_t* desc, bool multi_tt) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t UsbHciHubDeviceAdded(uint32_t device_id, uint32_t port, usb_speed_t speed) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t UsbHciHubDeviceRemoved(uint32_t device_id, uint32_t port) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t UsbHciHubDeviceReset(uint32_t device_id, uint32_t port) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t UsbHciResetEndpoint(uint32_t device_id, uint8_t ep_address) {
if (device_id == kDeviceId) {
reset_endpoint_ = ep_address;
}
return ZX_OK;
}
zx_status_t UsbHciResetDevice(uint32_t hub_address, uint32_t device_id) {
if (device_id == kDeviceId) {
device_reset_ = true;
}
return ZX_OK;
}
size_t UsbHciGetMaxTransferSize(uint32_t device_id, uint8_t ep_address) {
return ((device_id == kDeviceId) && (ep_address == kTransferSizeEndpoint)) ? kMaxTransferSize
: 0;
}
zx_status_t UsbHciCancelAll(uint32_t device_id, uint8_t ep_address) {
auto requests = pending_requests();
requests.CompleteAll(ZX_ERR_CANCELED, 0);
return ZX_OK;
}
void UsbHciSetBusInterface(const usb_bus_interface_protocol_t* bus_intf) {}
size_t UsbHciGetMaxDeviceCount() { return 0; }
size_t UsbHciGetRequestSize() {
return usb::BorrowedRequest<void>::RequestSize(sizeof(usb_request_t));
}
void UsbHciRequestQueue(usb_request_t* usb_request_, const usb_request_complete_t* complete_cb_) {
usb::BorrowedRequest<void> request(usb_request_, *complete_cb_, sizeof(usb_request_t));
if ((request.request()->header.ep_address == 0) && !custom_control_) {
if ((request.request()->setup.bmRequestType ==
(USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE)) &&
(request.request()->setup.bRequest == USB_REQ_GET_DESCRIPTOR)) {
uint8_t type = static_cast<uint8_t>(request.request()->setup.wValue >> 8);
uint8_t index = static_cast<uint8_t>(request.request()->setup.wValue);
switch (type) {
case USB_DT_DEVICE: {
usb_device_descriptor_t* descriptor;
request.Mmap(reinterpret_cast<void**>(&descriptor));
descriptor->bNumConfigurations = 2;
descriptor->idVendor = kVendorId;
descriptor->idProduct = kProductId;
descriptor->bDeviceClass = kDeviceClass;
descriptor->bDeviceSubClass = kDeviceSubclass;
descriptor->bDeviceProtocol = kDeviceProtocol;
request.Complete(ZX_OK, sizeof(*descriptor));
}
return;
case USB_DT_CONFIG: {
usb_configuration_descriptor_t* descriptor;
request.Mmap(reinterpret_cast<void**>(&descriptor));
descriptor->wTotalLength = sizeof(*descriptor);
descriptor->bConfigurationValue = static_cast<uint8_t>(index + 1);
request.Complete(ZX_OK, sizeof(*descriptor));
}
return;
case USB_DT_STRING: {
if (index == 0) {
// Fetch language table
usb_langid_desc_t* languages;
request.Mmap(reinterpret_cast<void**>(&languages));
languages->bLength = 2 + (2 * 2);
languages->wLangIds[0] = MakeConstant<uint16_t, 2>("EN");
languages->wLangIds[1] = MakeConstant<uint16_t, 2>("ES");
request.Complete(ZX_OK, languages->bLength);
return;
}
index--;
uint16_t lang = request.request()->setup.wIndex;
switch (lang) {
case MakeConstant<uint16_t, 2>("EN"):
lang = 0;
break;
case MakeConstant<uint16_t, 2>("ES"):
lang = 1;
break;
}
if ((index < 2) && (lang < 2)) {
usb_string_desc_t* descriptor;
request.Mmap(reinterpret_cast<void**>(&descriptor));
descriptor->bLength = static_cast<uint8_t>(
2 + (2 * std::char_traits<char16_t>::length(kStringDescriptors[index][lang])));
memcpy(descriptor->code_points, kStringDescriptors[index][lang],
descriptor->bLength - 2);
request.Complete(ZX_OK, descriptor->bLength);
return;
}
}
}
}
if ((request.request()->setup.bmRequestType ==
(USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE)) &&
(request.request()->setup.bRequest == USB_REQ_SET_CONFIGURATION)) {
selected_configuration_ = static_cast<uint8_t>(request.request()->setup.wValue);
request.Complete(ZX_OK, 0);
return;
}
request.Complete(ZX_ERR_INVALID_ARGS, 0);
return;
}
pending_requests_.push(std::move(request));
}
zx_status_t UsbHciEnableEndpoint(uint32_t device_id, const usb_endpoint_descriptor_t* ep_desc,
const usb_ss_ep_comp_descriptor_t* ss_com_desc, bool enable) {
if (!enable_endpoint_hook_) {
return ZX_ERR_BAD_STATE;
}
return enable_endpoint_hook_(device_id, ep_desc, ss_com_desc, enable);
}
const usb_hci_protocol_t* proto() { return &proto_; }
uint8_t configuration() { return selected_configuration_; }
usb::BorrowedRequestQueue<void> pending_requests() { return std::move(pending_requests_); }
void set_custom_control_handling(bool enabled) { custom_control_ = enabled; }
void set_enable_endpoint_hook(
fit::function<zx_status_t(uint32_t device_id, const usb_endpoint_descriptor_t* ep_desc,
const usb_ss_ep_comp_descriptor_t* ss_com_desc, bool enable)>
hook) {
enable_endpoint_hook_ = std::move(hook);
}
uint8_t reset_endpoint() { return reset_endpoint_; }
bool device_reset() { return device_reset_; }
private:
bool device_reset_ = false;
bool custom_control_ = false;
uint8_t selected_configuration_ = 0;
usb_hci_protocol_t proto_;
uint8_t reset_endpoint_ = 0;
fit::function<zx_status_t(uint32_t device_id, const usb_endpoint_descriptor_t* ep_desc,
const usb_ss_ep_comp_descriptor_t* ss_com_desc, bool enable)>
enable_endpoint_hook_;
usb::BorrowedRequestQueue<void> pending_requests_;
};
class FakeTimer : public UsbWaiterInterface {
public:
zx_status_t Wait(sync_completion_t* completion, zx_duration_t duration) override {
return timeout_handler_(completion, duration);
}
void set_timeout_handler(fit::function<zx_status_t(sync_completion_t*, zx_duration_t)> handler) {
timeout_handler_ = std::move(handler);
}
private:
fit::function<zx_status_t(sync_completion_t*, zx_duration_t)> timeout_handler_;
};
class DeviceTest : public zxtest::Test {
public:
DeviceTest() {
timer_ = fbl::MakeRefCounted<FakeTimer>();
timer_->set_timeout_handler([=](sync_completion_t* completion, zx_duration_t duration) {
return sync_completion_wait(completion, duration);
});
}
auto& get_fidl() {
if (!fidl_.has_value()) {
fidl_.emplace(std::move(ddk_.FidlClient()));
}
return fidl_.value();
}
void SetUp() override {
auto device = fbl::MakeRefCounted<UsbDevice>(fake_ddk::kFakeParent,
ddk::UsbHciProtocolClient(hci_.proto()), kDeviceId,
kHubId, kDeviceSpeed, timer_);
ASSERT_OK(device->Init());
device_ = static_cast<UsbDevice*>(ddk_.get_context());
}
void TearDown() override {
device_->DdkUnbindDeprecated();
ASSERT_TRUE(ddk_.get_remove_called());
device_->DdkRelease();
}
void CancelAll() { ASSERT_OK(device_->UsbCancelAll(1)); }
size_t get_parent_request_size() { return device_->UsbGetRequestSize(); }
void RequestQueue(usb_request_t* request, const usb_request_complete_t* completion) {
device_->UsbRequestQueue(request, completion);
}
ddk::UsbProtocolClient get_usb_protocol() {
usb_protocol_t usb;
device_->DdkGetProtocol(ZX_PROTOCOL_USB, &usb);
return ddk::UsbProtocolClient(&usb);
}
ddk::UsbBusProtocolClient get_usb_bus_protocol() {
usb_bus_protocol_t usb;
device_->DdkGetProtocol(ZX_PROTOCOL_USB_BUS, &usb);
return ddk::UsbBusProtocolClient(&usb);
}
void set_custom_control_handling(bool enabled) { hci_.set_custom_control_handling(enabled); }
usb::BorrowedRequestQueue<void> get_pending_requests() { return hci_.pending_requests(); }
uint8_t get_configuration() { return hci_.configuration(); }
void set_enable_endpoint_hook(
fit::function<zx_status_t(uint32_t device_id, const usb_endpoint_descriptor_t* ep_desc,
const usb_ss_ep_comp_descriptor_t* ss_com_desc, bool enable)>
hook) {
hci_.set_enable_endpoint_hook(std::move(hook));
}
void set_timeout_handler(fit::function<zx_status_t(sync_completion_t*, zx_duration_t)> handler) {
timer_->set_timeout_handler(std::move(handler));
}
bool get_device_reset() { return hci_.device_reset(); }
uint8_t get_reset_endpoint() { return hci_.reset_endpoint(); }
private:
fbl::RefPtr<FakeTimer> timer_;
std::optional<llcpp::fuchsia::hardware::usb::device::Device::SyncClient> fidl_;
FakeHci hci_;
Binder ddk_;
// UsbDevice context pointer owned by us through FakeDDK
// This is freed by calling DdkRelease in TearDown.
UsbDevice* device_;
};
// CancelAll-specific test
TEST_F(DeviceTest, CancelAllCancelsAllRequestsThenReturns) {
using Request = usb::CallbackRequest<sizeof(max_align_t)>;
std::atomic<size_t> completed = 0;
for (size_t i = 0; i < 500; i++) {
std::optional<Request> request;
Request::Alloc(&request, 0, 1, get_parent_request_size(),
[&](Request request) { completed++; });
request->Queue(*this);
}
CancelAll();
ASSERT_EQ(completed.load(), 500);
}
// USB protocol tests
TEST_F(DeviceTest, ControlOut) {
auto usb = get_usb_protocol();
uint8_t data[5];
for (size_t i = 0; i < 5; i++) {
data[i] = static_cast<uint8_t>(i);
}
set_custom_control_handling(true);
set_timeout_handler([&](sync_completion_t* completion, zx_duration_t duration) {
EXPECT_EQ(duration, 9001);
auto requests = get_pending_requests();
auto request = requests.pop();
EXPECT_EQ(request->request()->header.length, sizeof(data));
void* mapped_data;
request->Mmap(&mapped_data);
EXPECT_EQ(0, memcmp(data, data, sizeof(data)));
EXPECT_EQ(request->request()->setup.bmRequestType, 5);
EXPECT_EQ(request->request()->setup.bRequest, 97);
EXPECT_EQ(request->request()->setup.wValue, 8);
EXPECT_EQ(request->request()->setup.wIndex, 12);
request->Complete(ZX_OK, sizeof(data));
return sync_completion_wait(completion, ZX_TIME_INFINITE);
});
ASSERT_OK(usb.ControlOut(5, 97, 8, 12, 9001, data, sizeof(data)));
}
TEST_F(DeviceTest, ControlIn) {
auto usb = get_usb_protocol();
uint8_t data[5];
for (size_t i = 0; i < 5; i++) {
data[i] = static_cast<uint8_t>(i);
}
set_custom_control_handling(true);
set_timeout_handler([&](sync_completion_t* completion, zx_duration_t duration) {
EXPECT_EQ(duration, 9001);
auto requests = get_pending_requests();
auto request = requests.pop();
EXPECT_EQ(request->request()->header.length, sizeof(data));
void* mapped_data;
request->Mmap(&mapped_data);
memcpy(mapped_data, data, sizeof(data));
EXPECT_EQ(request->request()->setup.bmRequestType, 5 | USB_DIR_IN);
EXPECT_EQ(request->request()->setup.bRequest, 97);
EXPECT_EQ(request->request()->setup.wValue, 8);
EXPECT_EQ(request->request()->setup.wIndex, 12);
request->Complete(ZX_OK, sizeof(data));
return sync_completion_wait(completion, ZX_TIME_INFINITE);
});
uint8_t buffer[5];
size_t actual;
ASSERT_OK(usb.ControlIn(5 | USB_DIR_IN, 97, 8, 12, 9001, buffer, sizeof(buffer), &actual));
ASSERT_EQ(0, memcmp(buffer, data, sizeof(buffer)));
}
TEST_F(DeviceTest, RequestQueue) {
auto usb = get_usb_protocol();
using Request = usb::CallbackRequest<sizeof(max_align_t)>;
std::optional<Request> request;
sync_completion_t completion;
usb_request_t* request_ptr;
Request::Alloc(&request, 0, 1, get_parent_request_size(), [&](Request owned_request) {
ASSERT_EQ(owned_request.request(), request_ptr);
sync_completion_signal(&completion);
});
request_ptr = request->request();
request->Queue(usb);
auto requests = get_pending_requests();
auto usb_request = requests.pop();
usb_request->Complete(ZX_OK, 0);
sync_completion_wait(&completion, ZX_TIME_INFINITE);
}
TEST_F(DeviceTest, GetSpeed) {
auto usb = get_usb_protocol();
ASSERT_EQ(usb.GetSpeed(), kDeviceSpeed);
}
TEST_F(DeviceTest, SetInterface) {
auto usb = get_usb_protocol();
set_custom_control_handling(true);
set_timeout_handler([&](sync_completion_t* completion, zx_duration_t duration) {
EXPECT_EQ(duration, ZX_TIME_INFINITE);
auto requests = get_pending_requests();
auto request = requests.pop();
EXPECT_EQ(request->request()->header.ep_address, 0);
EXPECT_EQ(request->request()->setup.bmRequestType,
USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_INTERFACE);
EXPECT_EQ(request->request()->setup.bRequest, USB_REQ_SET_INTERFACE);
EXPECT_EQ(request->request()->setup.wValue, 5);
EXPECT_EQ(request->request()->setup.wIndex, 98);
request->Complete(ZX_OK, 0);
return sync_completion_wait(completion, ZX_TIME_INFINITE);
});
usb.SetInterface(98, 5);
}
TEST_F(DeviceTest, GetConfiguration) {
auto usb = get_usb_protocol();
ASSERT_EQ(usb.GetConfiguration(), 1);
}
TEST_F(DeviceTest, SetConfiguration) {
auto usb = get_usb_protocol();
ASSERT_OK(usb.SetConfiguration(2));
ASSERT_EQ(get_configuration(), 2);
}
TEST_F(DeviceTest, EnableEndpoint) {
auto usb = get_usb_protocol();
usb_endpoint_descriptor_t epdesc;
usb_ss_ep_comp_descriptor_t ss;
set_enable_endpoint_hook([&](uint32_t device_id, const usb_endpoint_descriptor_t* ep_desc,
const usb_ss_ep_comp_descriptor_t* ss_com_desc, bool enable) {
EXPECT_EQ(device_id, kDeviceId);
EXPECT_EQ(ep_desc, &epdesc);
EXPECT_EQ(ss_com_desc, &ss);
EXPECT_TRUE(enable);
return ZX_OK;
});
ASSERT_OK(usb.EnableEndpoint(&epdesc, &ss, true));
}
TEST_F(DeviceTest, ResetEndpoint) {
auto usb = get_usb_protocol();
ASSERT_OK(usb.ResetEndpoint(97));
ASSERT_EQ(get_reset_endpoint(), 97);
}
TEST_F(DeviceTest, ResetDevice) {
auto usb = get_usb_protocol();
ASSERT_OK(usb.ResetDevice());
ASSERT_TRUE(get_device_reset());
}
TEST_F(DeviceTest, GetMaxTransferSize) {
auto usb = get_usb_protocol();
ASSERT_EQ(usb.GetMaxTransferSize(kTransferSizeEndpoint), kMaxTransferSize);
}
TEST_F(DeviceTest, GetDeviceId) {
auto usb = get_usb_protocol();
ASSERT_EQ(usb.GetDeviceId(), kDeviceId);
}
TEST_F(DeviceTest, GetDeviceDescriptor) {
auto usb = get_usb_protocol();
usb_device_descriptor_t descriptor;
usb.GetDeviceDescriptor(&descriptor);
ASSERT_EQ(descriptor.idVendor, kVendorId);
ASSERT_EQ(descriptor.idProduct, kProductId);
ASSERT_EQ(descriptor.bDeviceClass, kDeviceClass);
ASSERT_EQ(descriptor.bDeviceSubClass, kDeviceSubclass);
ASSERT_EQ(descriptor.bDeviceProtocol, kDeviceProtocol);
}
TEST_F(DeviceTest, GetConfigurationDescriptorLength) {
auto usb = get_usb_protocol();
size_t length;
ASSERT_OK(usb.GetConfigurationDescriptorLength(1, &length));
ASSERT_EQ(length, sizeof(usb_configuration_descriptor_t));
}
TEST_F(DeviceTest, GetConfigurationDescriptor) {
auto usb = get_usb_protocol();
usb_configuration_descriptor_t descriptor;
size_t actual;
ASSERT_OK(usb.GetConfigurationDescriptor(1, &descriptor, sizeof(descriptor), &actual));
ASSERT_EQ(actual, sizeof(descriptor));
ASSERT_EQ(descriptor.bConfigurationValue, 1);
ASSERT_EQ(descriptor.wTotalLength, sizeof(descriptor));
}
TEST_F(DeviceTest, GetDescriptorsLength) {
auto usb = get_usb_protocol();
ASSERT_EQ(usb.GetDescriptorsLength(), sizeof(usb_configuration_descriptor_t));
}
TEST_F(DeviceTest, GetDescriptors) {
auto usb = get_usb_protocol();
usb_configuration_descriptor_t descriptor;
size_t actual;
usb.GetDescriptors(&descriptor, sizeof(descriptor), &actual);
ASSERT_EQ(actual, sizeof(descriptor));
ASSERT_EQ(descriptor.bConfigurationValue, 1);
ASSERT_EQ(descriptor.wTotalLength, sizeof(descriptor));
}
TEST_F(DeviceTest, GetCurrentFrame) {
auto usb = get_usb_protocol();
ASSERT_EQ(usb.GetCurrentFrame(), kCurrentFrame);
}
TEST_F(DeviceTest, GetRequestSize) {
auto usb = get_usb_protocol();
ASSERT_EQ(usb.GetRequestSize(), kRequestSize);
}
TEST_F(DeviceTest, FidlGetSpeed) {
auto& fidl = get_fidl();
auto result = fidl.GetDeviceSpeed();
ASSERT_TRUE(result.ok());
ASSERT_EQ(result->speed, kDeviceSpeed);
}
TEST_F(DeviceTest, FidlGetDescriptor) {
auto& fidl = get_fidl();
auto result = fidl.GetDeviceDescriptor();
usb_device_descriptor_t* descriptor =
reinterpret_cast<usb_device_descriptor_t*>(result->desc.data());
ASSERT_EQ(descriptor->idVendor, kVendorId);
ASSERT_EQ(descriptor->idProduct, kProductId);
ASSERT_EQ(descriptor->bDeviceClass, kDeviceClass);
ASSERT_EQ(descriptor->bDeviceSubClass, kDeviceSubclass);
ASSERT_EQ(descriptor->bDeviceProtocol, kDeviceProtocol);
}
TEST_F(DeviceTest, FidlGetConfigurationDescriptorSize) {
auto& fidl = get_fidl();
auto result = fidl.GetConfigurationDescriptorSize(1);
ASSERT_TRUE(result.ok());
ASSERT_OK(result->s);
ASSERT_EQ(result->size, sizeof(usb_configuration_descriptor_t));
}
TEST_F(DeviceTest, FidlGetConfigurationDescriptor) {
auto& fidl = get_fidl();
const usb_configuration_descriptor_t* descriptor;
auto result = fidl.GetConfigurationDescriptor(1);
ASSERT_TRUE(result.ok());
ASSERT_OK(result->s);
ASSERT_EQ(result->desc.count(), sizeof(*descriptor));
descriptor = reinterpret_cast<const usb_configuration_descriptor_t*>(result->desc.data());
ASSERT_EQ(descriptor->bConfigurationValue, 1);
ASSERT_EQ(descriptor->wTotalLength, sizeof(*descriptor));
}
TEST_F(DeviceTest, FidlGetStringDescriptor) {
auto& fidl = get_fidl();
char golden[128];
size_t dest_len = 128;
utf16_to_utf8(reinterpret_cast<const uint16_t*>(kStringDescriptors[0][0]), 7,
reinterpret_cast<uint8_t*>(golden), &dest_len);
auto expected = MakeConstant<uint16_t, 2>("EN");
auto result = fidl.GetStringDescriptor(1, MakeConstant<uint16_t, 2>("EN"));
ASSERT_TRUE(result.ok());
ASSERT_OK(result->s);
ASSERT_EQ(result->actual_lang_id, expected);
ASSERT_EQ(result->desc.size(), dest_len); // this is way off for some reason
ASSERT_EQ(memcmp(result->desc.data(), golden, dest_len), 0);
dest_len = 128;
utf16_to_utf8(reinterpret_cast<const uint16_t*>(kStringDescriptors[0][1]), 6,
reinterpret_cast<uint8_t*>(golden), &dest_len);
expected = MakeConstant<uint16_t, 2>("ES");
result = fidl.GetStringDescriptor(1, MakeConstant<uint16_t, 2>("ES"));
ASSERT_TRUE(result.ok());
ASSERT_OK(result->s);
ASSERT_EQ(result->actual_lang_id, expected);
ASSERT_EQ(result->desc.size(), dest_len);
ASSERT_EQ(memcmp(result->desc.data(), golden, dest_len), 0);
dest_len = 128;
utf16_to_utf8(reinterpret_cast<const uint16_t*>(kStringDescriptors[1][0]), 6,
reinterpret_cast<uint8_t*>(golden), &dest_len);
expected = MakeConstant<uint16_t, 2>("EN");
result = fidl.GetStringDescriptor(2, MakeConstant<uint16_t, 2>("EN"));
ASSERT_TRUE(result.ok());
ASSERT_OK(result->s);
ASSERT_EQ(result->actual_lang_id, expected);
ASSERT_EQ(result->desc.size(), dest_len);
ASSERT_EQ(memcmp(result->desc.data(), golden, dest_len), 0);
dest_len = 128;
utf16_to_utf8(reinterpret_cast<const uint16_t*>(kStringDescriptors[1][1]), 11,
reinterpret_cast<uint8_t*>(golden), &dest_len);
expected = MakeConstant<uint16_t, 2>("ES");
result = fidl.GetStringDescriptor(2, MakeConstant<uint16_t, 2>("ES"));
ASSERT_TRUE(result.ok());
ASSERT_OK(result->s);
ASSERT_EQ(result->actual_lang_id, expected);
ASSERT_EQ(result->desc.size(), dest_len);
ASSERT_EQ(memcmp(result->desc.data(), golden, dest_len), 0);
}
TEST_F(DeviceTest, FidlSetInterface) {
auto& fidl = get_fidl();
set_custom_control_handling(true);
set_timeout_handler([&](sync_completion_t* completion, zx_duration_t duration) {
EXPECT_EQ(duration, ZX_TIME_INFINITE);
auto requests = get_pending_requests();
auto request = requests.pop();
EXPECT_EQ(request->request()->header.ep_address, 0);
EXPECT_EQ(request->request()->setup.bmRequestType,
USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_INTERFACE);
EXPECT_EQ(request->request()->setup.bRequest, USB_REQ_SET_INTERFACE);
EXPECT_EQ(request->request()->setup.wValue, 5);
EXPECT_EQ(request->request()->setup.wIndex, 98);
request->Complete(ZX_OK, 0);
return sync_completion_wait(completion, ZX_TIME_INFINITE);
});
auto result = fidl.SetInterface(98, 5);
ASSERT_TRUE(result.ok());
ASSERT_OK(result->s);
}
TEST_F(DeviceTest, FidlGetDeviceId) {
auto& fidl = get_fidl();
auto result = fidl.GetDeviceId();
ASSERT_TRUE(result.ok());
ASSERT_EQ(result->device_id, kDeviceId);
}
TEST_F(DeviceTest, FidlGetHubDeviceId) {
auto& fidl = get_fidl();
auto result = fidl.GetHubDeviceId();
ASSERT_TRUE(result.ok());
ASSERT_EQ(result->hub_device_id, kHubId);
}
TEST_F(DeviceTest, FidlGetConfiguration) {
auto& fidl = get_fidl();
auto result = fidl.GetConfiguration();
ASSERT_TRUE(result.ok());
ASSERT_EQ(result->configuration, 1);
}
TEST_F(DeviceTest, FidlSetConfiguration) {
auto& fidl = get_fidl();
auto result = fidl.SetConfiguration(2);
ASSERT_TRUE(result.ok());
ASSERT_OK(result->s);
ASSERT_EQ(get_configuration(), 2);
}
} // namespace usb_bus