blob: 498e85506897be6437693181fcc873819ea562d8 [file] [log] [blame]
// Copyright 2014 The Chromium 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 "services/device/usb/usb_descriptors.h"
#include <stddef.h>
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/barrier_closure.h"
#include "base/bind.h"
#include "base/memory/ref_counted_memory.h"
#include "services/device/public/cpp/usb/usb_utils.h"
#include "services/device/usb/usb_device_handle.h"
namespace device {
using mojom::UsbAlternateInterfaceInfoPtr;
using mojom::UsbConfigurationInfoPtr;
using mojom::UsbControlTransferRecipient;
using mojom::UsbControlTransferType;
using mojom::UsbDeviceInfoPtr;
using mojom::UsbEndpointInfoPtr;
using mojom::UsbInterfaceInfoPtr;
using mojom::UsbSynchronizationType;
using mojom::UsbTransferDirection;
using mojom::UsbTransferStatus;
using mojom::UsbTransferType;
using mojom::UsbUsageType;
namespace {
using IndexMap = std::map<uint8_t, std::u16string>;
using IndexMapPtr = std::unique_ptr<IndexMap>;
// Standard USB requests and descriptor types:
const uint8_t kGetDescriptorRequest = 0x06;
const uint8_t kDeviceDescriptorType = 0x01;
const uint8_t kConfigurationDescriptorType = 0x02;
const uint8_t kStringDescriptorType = 0x03;
const uint8_t kInterfaceDescriptorType = 0x04;
const uint8_t kEndpointDescriptorType = 0x05;
const uint8_t kInterfaceAssociationDescriptorType = 11;
const uint8_t kDeviceDescriptorLength = 18;
const uint8_t kConfigurationDescriptorLength = 9;
const uint8_t kInterfaceDescriptorLength = 9;
const uint8_t kEndpointDescriptorLength = 7;
const uint8_t kInterfaceAssociationDescriptorLength = 8;
const int kControlTransferTimeoutMs = 2000; // 2 seconds
struct UsbInterfaceAssociationDescriptor {
UsbInterfaceAssociationDescriptor(uint8_t first_interface,
uint8_t interface_count)
: first_interface(first_interface), interface_count(interface_count) {}
bool operator<(const UsbInterfaceAssociationDescriptor& other) const {
return first_interface < other.first_interface;
}
uint8_t first_interface;
uint8_t interface_count;
};
void ParseInterfaceAssociationDescriptors(
const std::vector<uint8_t>& buffer,
std::vector<UsbInterfaceAssociationDescriptor>* functions) {
auto it = buffer.begin();
while (it != buffer.end()) {
// All descriptors must be at least 2 byte which means the length and type
// are safe to read.
if (std::distance(it, buffer.end()) < 2)
return;
uint8_t length = it[0];
if (length > std::distance(it, buffer.end()))
return;
if (it[1] == kInterfaceAssociationDescriptorType &&
length == kInterfaceAssociationDescriptorLength) {
functions->push_back(UsbInterfaceAssociationDescriptor(it[2], it[3]));
}
std::advance(it, length);
}
}
void OnDoneReadingConfigDescriptors(
scoped_refptr<UsbDeviceHandle> device_handle,
std::unique_ptr<UsbDeviceDescriptor> desc,
base::OnceCallback<void(std::unique_ptr<UsbDeviceDescriptor>)> callback) {
if (desc->num_configurations == desc->device_info->configurations.size()) {
std::move(callback).Run(std::move(desc));
} else {
LOG(ERROR) << "Failed to read all configuration descriptors. Expected "
<< static_cast<int>(desc->num_configurations) << ", got "
<< desc->device_info->configurations.size() << ".";
std::move(callback).Run(nullptr);
}
}
void OnReadConfigDescriptor(UsbDeviceDescriptor* desc,
base::OnceClosure closure,
UsbTransferStatus status,
scoped_refptr<base::RefCountedBytes> buffer,
size_t length) {
if (status == UsbTransferStatus::COMPLETED) {
if (!desc->Parse(base::make_span(buffer->front(), length))) {
LOG(ERROR) << "Failed to parse configuration descriptor.";
}
} else {
LOG(ERROR) << "Failed to read configuration descriptor.";
}
std::move(closure).Run();
}
void OnReadConfigDescriptorHeader(scoped_refptr<UsbDeviceHandle> device_handle,
UsbDeviceDescriptor* desc,
uint8_t index,
base::OnceClosure closure,
UsbTransferStatus status,
scoped_refptr<base::RefCountedBytes> header,
size_t length) {
if (status == UsbTransferStatus::COMPLETED && length == 4) {
const uint8_t* data = header->front();
uint16_t total_length = data[2] | data[3] << 8;
auto buffer = base::MakeRefCounted<base::RefCountedBytes>(total_length);
device_handle->ControlTransfer(
UsbTransferDirection::INBOUND, UsbControlTransferType::STANDARD,
UsbControlTransferRecipient::DEVICE, kGetDescriptorRequest,
kConfigurationDescriptorType << 8 | index, 0, buffer,
kControlTransferTimeoutMs,
base::BindOnce(&OnReadConfigDescriptor, desc, std::move(closure)));
} else {
LOG(ERROR) << "Failed to read length for configuration "
<< static_cast<int>(index) << ".";
std::move(closure).Run();
}
}
void OnReadDeviceDescriptor(
scoped_refptr<UsbDeviceHandle> device_handle,
base::OnceCallback<void(std::unique_ptr<UsbDeviceDescriptor>)> callback,
UsbTransferStatus status,
scoped_refptr<base::RefCountedBytes> buffer,
size_t length) {
if (status != UsbTransferStatus::COMPLETED) {
LOG(ERROR) << "Failed to read device descriptor.";
std::move(callback).Run(nullptr);
return;
}
std::unique_ptr<UsbDeviceDescriptor> desc(new UsbDeviceDescriptor());
if (!desc->Parse(base::make_span(buffer->front(), length))) {
LOG(ERROR) << "Device descriptor parsing error.";
std::move(callback).Run(nullptr);
return;
}
if (desc->num_configurations == 0) {
std::move(callback).Run(std::move(desc));
return;
}
uint8_t num_configurations = desc->num_configurations;
UsbDeviceDescriptor* desc_ptr = desc.get();
base::RepeatingClosure closure = base::BarrierClosure(
num_configurations,
base::BindOnce(OnDoneReadingConfigDescriptors, device_handle,
std::move(desc), std::move(callback)));
for (uint8_t i = 0; i < num_configurations; ++i) {
auto header = base::MakeRefCounted<base::RefCountedBytes>(4);
device_handle->ControlTransfer(
UsbTransferDirection::INBOUND, UsbControlTransferType::STANDARD,
UsbControlTransferRecipient::DEVICE, kGetDescriptorRequest,
kConfigurationDescriptorType << 8 | i, 0, header,
kControlTransferTimeoutMs,
base::BindOnce(&OnReadConfigDescriptorHeader, device_handle, desc_ptr,
i, closure));
}
}
void StoreStringDescriptor(IndexMap::iterator it,
base::OnceClosure callback,
const std::u16string& string) {
it->second = string;
std::move(callback).Run();
}
void OnReadStringDescriptor(
base::OnceCallback<void(const std::u16string&)> callback,
UsbTransferStatus status,
scoped_refptr<base::RefCountedBytes> buffer,
size_t length) {
std::u16string string;
if (status == UsbTransferStatus::COMPLETED &&
ParseUsbStringDescriptor(
std::vector<uint8_t>(buffer->front(), buffer->front() + length),
&string)) {
std::move(callback).Run(string);
} else {
std::move(callback).Run(std::u16string());
}
}
void ReadStringDescriptor(
scoped_refptr<UsbDeviceHandle> device_handle,
uint8_t index,
uint16_t language_id,
base::OnceCallback<void(const std::u16string&)> callback) {
auto buffer = base::MakeRefCounted<base::RefCountedBytes>(255);
device_handle->ControlTransfer(
UsbTransferDirection::INBOUND, UsbControlTransferType::STANDARD,
UsbControlTransferRecipient::DEVICE, kGetDescriptorRequest,
kStringDescriptorType << 8 | index, language_id, buffer,
kControlTransferTimeoutMs,
base::BindOnce(&OnReadStringDescriptor, std::move(callback)));
}
void OnReadLanguageIds(scoped_refptr<UsbDeviceHandle> device_handle,
IndexMapPtr index_map,
base::OnceCallback<void(IndexMapPtr)> callback,
const std::u16string& languages) {
// Default to English unless the device provides a language and then just pick
// the first one.
uint16_t language_id = languages.empty() ? 0x0409 : languages[0];
std::map<uint8_t, IndexMap::iterator> iterator_map;
for (auto it = index_map->begin(); it != index_map->end(); ++it)
iterator_map[it->first] = it;
base::RepeatingClosure barrier = base::BarrierClosure(
static_cast<int>(iterator_map.size()),
base::BindOnce(std::move(callback), std::move(index_map)));
for (const auto& map_entry : iterator_map) {
ReadStringDescriptor(
device_handle, map_entry.first, language_id,
base::BindOnce(&StoreStringDescriptor, map_entry.second, barrier));
}
}
} // namespace
CombinedInterfaceInfo::CombinedInterfaceInfo(
const mojom::UsbInterfaceInfo* interface,
const mojom::UsbAlternateInterfaceInfo* alternate)
: interface(interface), alternate(alternate) {}
bool CombinedInterfaceInfo::IsValid() const {
return interface && alternate;
}
UsbDeviceDescriptor::UsbDeviceDescriptor()
: device_info(mojom::UsbDeviceInfo::New()) {}
UsbDeviceDescriptor::~UsbDeviceDescriptor() = default;
bool UsbDeviceDescriptor::Parse(base::span<const uint8_t> buffer) {
mojom::UsbConfigurationInfo* last_config = nullptr;
mojom::UsbInterfaceInfo* last_interface = nullptr;
mojom::UsbEndpointInfo* last_endpoint = nullptr;
for (auto it = buffer.begin(); it != buffer.end();
/* incremented internally */) {
const uint8_t* data = &it[0];
uint8_t length = data[0];
if (length < 2 || length > std::distance(it, buffer.end()))
return false;
it += length;
switch (data[1] /* bDescriptorType */) {
case kDeviceDescriptorType:
if (device_info->configurations.size() > 0 ||
length < kDeviceDescriptorLength) {
return false;
}
device_info->usb_version_minor = data[2] >> 4 & 0xf;
device_info->usb_version_subminor = data[2] & 0xf;
device_info->usb_version_major = data[3];
device_info->class_code = data[4];
device_info->subclass_code = data[5];
device_info->protocol_code = data[6];
device_info->vendor_id = data[8] | data[9] << 8;
device_info->product_id = data[10] | data[11] << 8;
device_info->device_version_minor = data[12] >> 4 & 0xf;
device_info->device_version_subminor = data[12] & 0xf;
device_info->device_version_major = data[13];
i_manufacturer = data[14];
i_product = data[15];
i_serial_number = data[16];
num_configurations = data[17];
break;
case kConfigurationDescriptorType:
if (length < kConfigurationDescriptorLength)
return false;
if (last_config) {
AssignFirstInterfaceNumbers(last_config);
AggregateInterfacesForConfig(last_config);
}
device_info->configurations.push_back(
BuildUsbConfigurationInfoPtr(data));
last_config = device_info->configurations.back().get();
last_interface = nullptr;
last_endpoint = nullptr;
break;
case kInterfaceDescriptorType:
if (!last_config || length < kInterfaceDescriptorLength)
return false;
last_config->interfaces.push_back(BuildUsbInterfaceInfoPtr(data));
last_interface = last_config->interfaces.back().get();
last_endpoint = nullptr;
break;
case kEndpointDescriptorType:
if (!last_interface || length < kEndpointDescriptorLength)
return false;
last_interface->alternates[0]->endpoints.push_back(
BuildUsbEndpointInfoPtr(data));
last_endpoint = last_interface->alternates[0]->endpoints.back().get();
break;
default:
// Append unknown descriptor types to the |extra_data| field of the last
// descriptor.
if (last_endpoint) {
last_endpoint->extra_data.insert(last_endpoint->extra_data.end(),
data, data + length);
} else if (last_interface) {
DCHECK_EQ(1u, last_interface->alternates.size());
last_interface->alternates[0]->extra_data.insert(
last_interface->alternates[0]->extra_data.end(), data,
data + length);
} else if (last_config) {
last_config->extra_data.insert(last_config->extra_data.end(), data,
data + length);
}
}
}
if (last_config) {
AssignFirstInterfaceNumbers(last_config);
AggregateInterfacesForConfig(last_config);
}
return true;
}
void ReadUsbDescriptors(
scoped_refptr<UsbDeviceHandle> device_handle,
base::OnceCallback<void(std::unique_ptr<UsbDeviceDescriptor>)> callback) {
auto buffer =
base::MakeRefCounted<base::RefCountedBytes>(kDeviceDescriptorLength);
device_handle->ControlTransfer(
UsbTransferDirection::INBOUND, UsbControlTransferType::STANDARD,
UsbControlTransferRecipient::DEVICE, kGetDescriptorRequest,
kDeviceDescriptorType << 8, 0, buffer, kControlTransferTimeoutMs,
base::BindOnce(&OnReadDeviceDescriptor, device_handle,
std::move(callback)));
}
bool ParseUsbStringDescriptor(const std::vector<uint8_t>& descriptor,
std::u16string* output) {
if (descriptor.size() < 2 || descriptor[1] != kStringDescriptorType)
return false;
// Let the device return a buffer larger than the actual string but prefer the
// length reported inside the descriptor.
size_t length = descriptor[0];
length = std::min(length, descriptor.size());
if (length < 2)
return false;
// The string is returned by the device in UTF-16LE.
*output =
std::u16string(reinterpret_cast<const char16_t*>(descriptor.data() + 2),
(length - 2) / sizeof(char16_t));
return true;
}
// For each key in |index_map| this function reads that string descriptor from
// |device_handle| and updates the value in in |index_map|.
void ReadUsbStringDescriptors(scoped_refptr<UsbDeviceHandle> device_handle,
IndexMapPtr index_map,
base::OnceCallback<void(IndexMapPtr)> callback) {
if (index_map->empty()) {
std::move(callback).Run(std::move(index_map));
return;
}
ReadStringDescriptor(
device_handle, 0, 0,
base::BindOnce(&OnReadLanguageIds, device_handle, std::move(index_map),
std::move(callback)));
}
UsbEndpointInfoPtr BuildUsbEndpointInfoPtr(const uint8_t* data) {
DCHECK_GE(data[0], kEndpointDescriptorLength);
DCHECK_EQ(data[1], kEndpointDescriptorType);
return BuildUsbEndpointInfoPtr(
data[2] /* bEndpointAddress */, data[3] /* bmAttributes */,
data[4] + (data[5] << 8) /* wMaxPacketSize */, data[6] /* bInterval */);
}
UsbEndpointInfoPtr BuildUsbEndpointInfoPtr(uint8_t address,
uint8_t attributes,
uint16_t maximum_packet_size,
uint8_t polling_interval) {
UsbEndpointInfoPtr endpoint = mojom::UsbEndpointInfo::New();
endpoint->endpoint_number = ConvertEndpointAddressToNumber(address);
// These fields are defined in Table 9-24 of the USB 3.1 Specification.
switch (address & 0x80) {
case 0x00:
endpoint->direction = UsbTransferDirection::OUTBOUND;
break;
case 0x80:
endpoint->direction = UsbTransferDirection::INBOUND;
break;
}
switch (attributes & 0x03) {
case 0x00:
endpoint->type = UsbTransferType::CONTROL;
break;
case 0x01:
endpoint->type = UsbTransferType::ISOCHRONOUS;
break;
case 0x02:
endpoint->type = UsbTransferType::BULK;
break;
case 0x03:
endpoint->type = UsbTransferType::INTERRUPT;
break;
}
switch (attributes & 0x0F) {
// Isochronous endpoints only.
case 0x05:
endpoint->synchronization_type = UsbSynchronizationType::ASYNCHRONOUS;
break;
case 0x09:
endpoint->synchronization_type = UsbSynchronizationType::ADAPTIVE;
break;
case 0x0D:
endpoint->synchronization_type = UsbSynchronizationType::SYNCHRONOUS;
break;
default:
endpoint->synchronization_type = UsbSynchronizationType::NONE;
}
switch (attributes & 0x33) {
// Isochronous endpoint usages.
case 0x01:
endpoint->usage_type = UsbUsageType::DATA;
break;
case 0x11:
endpoint->usage_type = UsbUsageType::FEEDBACK;
break;
case 0x21:
endpoint->usage_type = UsbUsageType::EXPLICIT_FEEDBACK;
break;
// Interrupt endpoint usages.
case 0x03:
endpoint->usage_type = UsbUsageType::PERIODIC;
break;
case 0x13:
endpoint->usage_type = UsbUsageType::NOTIFICATION;
break;
default:
endpoint->usage_type = UsbUsageType::RESERVED;
}
endpoint->packet_size = static_cast<uint32_t>(maximum_packet_size);
endpoint->polling_interval = polling_interval;
return endpoint;
}
UsbInterfaceInfoPtr BuildUsbInterfaceInfoPtr(const uint8_t* data) {
DCHECK_GE(data[0], kInterfaceDescriptorLength);
DCHECK_EQ(data[1], kInterfaceDescriptorType);
return BuildUsbInterfaceInfoPtr(
data[2] /* bInterfaceNumber */, data[3] /* bAlternateSetting */,
data[5] /* bInterfaceClass */, data[6] /* bInterfaceSubClass */,
data[7] /* bInterfaceProtocol */);
}
UsbInterfaceInfoPtr BuildUsbInterfaceInfoPtr(uint8_t interface_number,
uint8_t alternate_setting,
uint8_t interface_class,
uint8_t interface_subclass,
uint8_t interface_protocol) {
UsbInterfaceInfoPtr interface_info = mojom::UsbInterfaceInfo::New();
interface_info->interface_number = interface_number;
interface_info->first_interface = interface_number;
UsbAlternateInterfaceInfoPtr alternate =
mojom::UsbAlternateInterfaceInfo::New();
alternate->alternate_setting = alternate_setting;
alternate->class_code = interface_class;
alternate->subclass_code = interface_subclass;
alternate->protocol_code = interface_protocol;
interface_info->alternates.push_back(std::move(alternate));
return interface_info;
}
// Aggregate each alternate setting into an InterfaceInfo corresponding to its
// interface number.
void AggregateInterfacesForConfig(mojom::UsbConfigurationInfo* config) {
std::map<uint8_t, device::mojom::UsbInterfaceInfo*> interface_map;
std::vector<device::mojom::UsbInterfaceInfoPtr> interfaces =
std::move(config->interfaces);
config->interfaces.clear();
for (size_t i = 0; i < interfaces.size(); ++i) {
// As interfaces should appear in order, this map could be unnecessary,
// but this errs on the side of caution.
auto iter = interface_map.find(interfaces[i]->interface_number);
if (iter == interface_map.end()) {
// This is the first time we're seeing an alternate with this interface
// number, so add a new InterfaceInfo to the array and map the number.
config->interfaces.push_back(std::move(interfaces[i]));
interface_map.insert(
std::make_pair(config->interfaces.back()->interface_number,
config->interfaces.back().get()));
} else {
DCHECK_EQ(1u, interfaces[i]->alternates.size());
iter->second->alternates.push_back(
std::move(interfaces[i]->alternates[0]));
}
}
}
CombinedInterfaceInfo FindInterfaceInfoFromConfig(
const mojom::UsbConfigurationInfo* config,
uint8_t interface_number,
uint8_t alternate_setting) {
CombinedInterfaceInfo interface_info;
if (!config) {
return interface_info;
}
for (const auto& iface : config->interfaces) {
if (iface->interface_number == interface_number) {
interface_info.interface = iface.get();
for (const auto& alternate : iface->alternates) {
if (alternate->alternate_setting == alternate_setting) {
interface_info.alternate = alternate.get();
break;
}
}
}
}
return interface_info;
}
UsbConfigurationInfoPtr BuildUsbConfigurationInfoPtr(const uint8_t* data) {
DCHECK_GE(data[0], kConfigurationDescriptorLength);
DCHECK_EQ(data[1], kConfigurationDescriptorType);
return BuildUsbConfigurationInfoPtr(data[5] /* bConfigurationValue */,
(data[7] & 0x02) != 0 /* bmAttributes */,
(data[7] & 0x04) != 0 /* bmAttributes */,
data[8] /* bMaxPower */);
}
UsbConfigurationInfoPtr BuildUsbConfigurationInfoPtr(
uint8_t configuration_value,
bool self_powered,
bool remote_wakeup,
uint8_t maximum_power) {
UsbConfigurationInfoPtr config = mojom::UsbConfigurationInfo::New();
config->configuration_value = configuration_value;
config->self_powered = self_powered;
config->remote_wakeup = remote_wakeup;
config->maximum_power = maximum_power;
return config;
}
void AssignFirstInterfaceNumbers(mojom::UsbConfigurationInfo* config) {
std::vector<UsbInterfaceAssociationDescriptor> functions;
ParseInterfaceAssociationDescriptors(config->extra_data, &functions);
for (const auto& interface : config->interfaces) {
DCHECK_EQ(1u, interface->alternates.size());
ParseInterfaceAssociationDescriptors(interface->alternates[0]->extra_data,
&functions);
for (auto& endpoint : interface->alternates[0]->endpoints)
ParseInterfaceAssociationDescriptors(endpoint->extra_data, &functions);
}
// libusb has collected interface association descriptors in the |extra_data|
// fields of other descriptor types. This may have disturbed their order
// but sorting by the bFirstInterface should fix it.
std::sort(functions.begin(), functions.end());
uint8_t remaining_interfaces = 0;
auto function_it = functions.cbegin();
auto interface_it = config->interfaces.begin();
while (interface_it != config->interfaces.end()) {
if (remaining_interfaces > 0) {
// Continuation of a previous function. Tag all alternate interfaces
// (which are guaranteed to be contiguous).
for (uint8_t interface_number = (*interface_it)->interface_number;
interface_it != config->interfaces.end() &&
(*interface_it)->interface_number == interface_number;
++interface_it) {
(*interface_it)->first_interface = function_it->first_interface;
}
if (--remaining_interfaces == 0)
++function_it;
} else if (function_it != functions.end() &&
(*interface_it)->interface_number ==
function_it->first_interface) {
// Start of a new function.
(*interface_it)->first_interface = function_it->first_interface;
if (function_it->interface_count > 1)
remaining_interfaces = function_it->interface_count - 1;
else
++function_it;
++interface_it;
} else {
// Unassociated interfaces already have |first_interface| set to
// |interface_number|.
++interface_it;
}
}
}
} // namespace device