blob: 57468182278ebb3d1bbf7c554698a97db1b8780d [file] [log] [blame]
// Copyright 2015 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 "device/usb/webusb_descriptors.h"
#include <stddef.h>
#include <iterator>
#include <map>
#include <memory>
#include <set>
#include "base/barrier_closure.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "components/device_event_log/device_event_log.h"
#include "device/usb/usb_device_handle.h"
#include "net/base/io_buffer.h"
using net::IOBufferWithSize;
namespace device {
namespace {
// These constants are defined by the Universal Serial Device 3.0 Specification
// Revision 1.0.
const uint8_t kGetDescriptorRequest = 0x06;
const uint8_t kBosDescriptorType = 0x0F;
const uint8_t kDeviceCapabilityDescriptorType = 0x10;
const uint8_t kPlatformDevCapabilityType = 0x05;
// These constants are defined by the WebUSB specification:
// http://wicg.github.io/webusb/
const uint8_t kGetAllowedOriginsRequest = 0x01;
const uint8_t kGetUrlRequest = 0x02;
const uint8_t kWebUsbCapabilityUUID[16] = {
// Little-endian encoding of {3408b638-09a9-47a0-8bfd-a0768815b665}.
0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47,
0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65};
const int kControlTransferTimeout = 60000; // 1 minute
using ReadWebUsbDescriptorsCallback =
base::Callback<void(std::unique_ptr<WebUsbAllowedOrigins> allowed_origins,
const GURL& landing_page)>;
using ReadWebUsbAllowedOriginsCallback =
base::Callback<void(std::unique_ptr<WebUsbAllowedOrigins> allowed_origins)>;
// Parses a WebUSB Function Subset Header:
// http://wicg.github.io/webusb/#dfn-function-subset-header
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | length | type | 1st interface | origin[0] |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | origin[1] | ...
// +-+-+-+-+-+-+-+-+-+-+-+------
bool ParseFunction(WebUsbFunctionSubset* function,
std::vector<uint8_t>::const_iterator* it,
std::vector<uint8_t>::const_iterator end) {
const uint8_t kDescriptorType = 0x02;
const uint8_t kDescriptorMinLength = 3;
// If this isn't the end of the buffer then there must be at least one byte
// available, the length of the descriptor.
if (*it == end)
return false;
uint8_t length = (*it)[0];
// Is this a valid Function Subset Header? It must be long enough, fit within
// the buffer and have the right descriptor type.
if (length < kDescriptorMinLength || std::distance(*it, end) < length ||
(*it)[1] != kDescriptorType) {
return false;
}
function->first_interface = (*it)[2];
// Everything after the mandatory fields are origin indicies.
std::advance(*it, kDescriptorMinLength);
uint8_t num_origins = length - kDescriptorMinLength;
function->origin_ids.reserve(num_origins);
for (size_t i = 0; i < num_origins; ++i) {
uint8_t index = *(*it)++;
if (index == 0)
return false;
function->origin_ids.push_back(index);
}
return true;
}
// Parses a WebUSB Configuration Subset Header:
// http://wicg.github.io/webusb/#dfn-configuration-subset-header
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | length | type | config value | num functions |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | origin[0] | origin[1] | ...
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+------
bool ParseConfiguration(WebUsbConfigurationSubset* configuration,
std::vector<uint8_t>::const_iterator* it,
std::vector<uint8_t>::const_iterator end) {
const uint8_t kDescriptorType = 0x01;
const uint8_t kDescriptorMinLength = 4;
// If this isn't the end of the buffer then there must be at least one byte
// available, the length of the descriptor.
if (*it == end)
return false;
uint8_t length = (*it)[0];
// Is this a valid Configuration Subset Header? It must be long enough, fit
// within the buffer and have the right descriptor type.
if (length < kDescriptorMinLength || std::distance(*it, end) < length ||
(*it)[1] != kDescriptorType) {
return false;
}
configuration->configuration_value = (*it)[2];
uint8_t num_functions = (*it)[3];
// The next |length - 4| bytes after the mandatory fields are origin indicies.
std::advance(*it, kDescriptorMinLength);
uint8_t num_origins = length - kDescriptorMinLength;
configuration->origin_ids.reserve(num_origins);
for (size_t i = 0; i < num_origins; ++i) {
uint8_t index = *(*it)++;
if (index == 0)
return false;
configuration->origin_ids.push_back(index);
}
// |num_functions| function descriptors then follow the descriptor.
for (size_t i = 0; i < num_functions; ++i) {
WebUsbFunctionSubset function;
if (!ParseFunction(&function, it, end))
return false;
configuration->functions.push_back(function);
}
return true;
}
void OnDoneReadingUrls(std::unique_ptr<WebUsbAllowedOrigins> allowed_origins,
uint8_t landing_page_id,
std::unique_ptr<std::map<uint8_t, GURL>> url_map,
const ReadWebUsbDescriptorsCallback& callback) {
for (uint8_t origin_id : allowed_origins->origin_ids) {
const auto& it = url_map->find(origin_id);
if (it != url_map->end())
allowed_origins->origins.push_back(it->second.GetOrigin());
}
for (auto& configuration : allowed_origins->configurations) {
for (uint8_t origin_id : configuration.origin_ids) {
const auto& it = url_map->find(origin_id);
if (it != url_map->end())
configuration.origins.push_back(it->second.GetOrigin());
}
for (auto& function : configuration.functions) {
for (uint8_t origin_id : function.origin_ids) {
const auto& it = url_map->find(origin_id);
if (it != url_map->end())
function.origins.push_back(it->second.GetOrigin());
}
}
}
GURL landing_page;
if (landing_page_id != 0)
landing_page = (*url_map)[landing_page_id];
callback.Run(std::move(allowed_origins), landing_page);
}
void OnReadUrlDescriptor(std::map<uint8_t, GURL>* url_map,
uint8_t index,
const base::Closure& callback,
UsbTransferStatus status,
scoped_refptr<net::IOBuffer> buffer,
size_t length) {
if (status != USB_TRANSFER_COMPLETED) {
USB_LOG(EVENT) << "Failed to read WebUSB URL descriptor: " << index;
callback.Run();
return;
}
GURL url;
if (ParseWebUsbUrlDescriptor(
std::vector<uint8_t>(buffer->data(), buffer->data() + length),
&url)) {
(*url_map)[index] = url;
}
callback.Run();
}
// Reads the descriptor with |index| from the device, adds the value to
// |url_map| and then runs |callback|.
void ReadUrlDescriptor(scoped_refptr<UsbDeviceHandle> device_handle,
uint8_t vendor_code,
std::map<uint8_t, GURL>* url_map,
uint8_t index,
const base::Closure& callback) {
scoped_refptr<IOBufferWithSize> buffer = new IOBufferWithSize(255);
device_handle->ControlTransfer(
USB_DIRECTION_INBOUND, UsbDeviceHandle::VENDOR, UsbDeviceHandle::DEVICE,
vendor_code, index, kGetUrlRequest, buffer, buffer->size(),
kControlTransferTimeout,
base::Bind(&OnReadUrlDescriptor, url_map, index, callback));
}
// Reads URL descriptors from the device so that it can fill |allowed_origins|
// with the GURLs matching the indicies already collected.
void ReadUrlDescriptors(scoped_refptr<UsbDeviceHandle> device_handle,
uint8_t vendor_code,
uint8_t landing_page_id,
const ReadWebUsbDescriptorsCallback& callback,
std::unique_ptr<WebUsbAllowedOrigins> allowed_origins) {
if (!allowed_origins) {
callback.Run(nullptr, GURL());
return;
}
std::set<uint8_t> to_request;
if (landing_page_id != 0)
to_request.insert(landing_page_id);
to_request.insert(allowed_origins->origin_ids.begin(),
allowed_origins->origin_ids.end());
for (auto& config : allowed_origins->configurations) {
to_request.insert(config.origin_ids.begin(), config.origin_ids.end());
for (auto& function : config.functions) {
to_request.insert(function.origin_ids.begin(), function.origin_ids.end());
}
}
std::unique_ptr<std::map<uint8_t, GURL>> url_map(
new std::map<uint8_t, GURL>());
std::map<uint8_t, GURL>* url_map_ptr = url_map.get();
base::Closure barrier = base::BarrierClosure(
static_cast<int>(to_request.size()),
base::Bind(&OnDoneReadingUrls, base::Passed(&allowed_origins),
landing_page_id, base::Passed(&url_map), callback));
for (uint8_t index : to_request) {
ReadUrlDescriptor(device_handle, vendor_code, url_map_ptr, index, barrier);
}
}
void OnReadWebUsbAllowedOrigins(
const ReadWebUsbAllowedOriginsCallback& callback,
UsbTransferStatus status,
scoped_refptr<net::IOBuffer> buffer,
size_t length) {
if (status != USB_TRANSFER_COMPLETED) {
USB_LOG(EVENT) << "Failed to read WebUSB allowed origins.";
callback.Run(nullptr);
return;
}
std::unique_ptr<WebUsbAllowedOrigins> allowed_origins(
new WebUsbAllowedOrigins());
if (allowed_origins->Parse(
std::vector<uint8_t>(buffer->data(), buffer->data() + length))) {
callback.Run(std::move(allowed_origins));
} else {
callback.Run(nullptr);
}
}
void OnReadWebUsbAllowedOriginsHeader(
scoped_refptr<UsbDeviceHandle> device_handle,
const ReadWebUsbAllowedOriginsCallback& callback,
uint8_t vendor_code,
UsbTransferStatus status,
scoped_refptr<net::IOBuffer> buffer,
size_t length) {
if (status != USB_TRANSFER_COMPLETED || length != 4) {
USB_LOG(EVENT) << "Failed to read WebUSB allowed origins header.";
callback.Run(nullptr);
return;
}
const uint8_t* data = reinterpret_cast<uint8_t*>(buffer->data());
uint16_t new_length = data[2] | (data[3] << 8);
scoped_refptr<IOBufferWithSize> new_buffer = new IOBufferWithSize(new_length);
device_handle->ControlTransfer(
USB_DIRECTION_INBOUND, UsbDeviceHandle::VENDOR, UsbDeviceHandle::DEVICE,
vendor_code, 0, kGetAllowedOriginsRequest, new_buffer, new_buffer->size(),
kControlTransferTimeout,
base::Bind(&OnReadWebUsbAllowedOrigins, callback));
}
void ReadWebUsbAllowedOrigins(
scoped_refptr<UsbDeviceHandle> device_handle,
uint8_t vendor_code,
const ReadWebUsbAllowedOriginsCallback& callback) {
scoped_refptr<IOBufferWithSize> buffer = new IOBufferWithSize(4);
device_handle->ControlTransfer(
USB_DIRECTION_INBOUND, UsbDeviceHandle::VENDOR, UsbDeviceHandle::DEVICE,
vendor_code, 0, kGetAllowedOriginsRequest, buffer, buffer->size(),
kControlTransferTimeout,
base::Bind(&OnReadWebUsbAllowedOriginsHeader, device_handle, callback,
vendor_code));
}
void OnReadBosDescriptor(scoped_refptr<UsbDeviceHandle> device_handle,
const ReadWebUsbDescriptorsCallback& callback,
UsbTransferStatus status,
scoped_refptr<net::IOBuffer> buffer,
size_t length) {
if (status != USB_TRANSFER_COMPLETED) {
USB_LOG(EVENT) << "Failed to read BOS descriptor.";
callback.Run(nullptr, GURL());
return;
}
WebUsbPlatformCapabilityDescriptor descriptor;
if (!descriptor.ParseFromBosDescriptor(
std::vector<uint8_t>(buffer->data(), buffer->data() + length))) {
callback.Run(nullptr, GURL());
return;
}
ReadWebUsbAllowedOrigins(
device_handle, descriptor.vendor_code,
base::Bind(&ReadUrlDescriptors, device_handle, descriptor.vendor_code,
descriptor.landing_page_id, callback));
}
void OnReadBosDescriptorHeader(scoped_refptr<UsbDeviceHandle> device_handle,
const ReadWebUsbDescriptorsCallback& callback,
UsbTransferStatus status,
scoped_refptr<net::IOBuffer> buffer,
size_t length) {
if (status != USB_TRANSFER_COMPLETED || length != 5) {
USB_LOG(EVENT) << "Failed to read BOS descriptor header.";
callback.Run(nullptr, GURL());
return;
}
const uint8_t* data = reinterpret_cast<uint8_t*>(buffer->data());
uint16_t new_length = data[2] | (data[3] << 8);
scoped_refptr<IOBufferWithSize> new_buffer = new IOBufferWithSize(new_length);
device_handle->ControlTransfer(
USB_DIRECTION_INBOUND, UsbDeviceHandle::STANDARD, UsbDeviceHandle::DEVICE,
kGetDescriptorRequest, kBosDescriptorType << 8, 0, new_buffer,
new_buffer->size(), kControlTransferTimeout,
base::Bind(&OnReadBosDescriptor, device_handle, callback));
}
} // namespace
WebUsbFunctionSubset::WebUsbFunctionSubset() : first_interface(0) {}
WebUsbFunctionSubset::WebUsbFunctionSubset(const WebUsbFunctionSubset& other) =
default;
WebUsbFunctionSubset::~WebUsbFunctionSubset() {}
WebUsbConfigurationSubset::WebUsbConfigurationSubset()
: configuration_value(0) {}
WebUsbConfigurationSubset::WebUsbConfigurationSubset(
const WebUsbConfigurationSubset& other) = default;
WebUsbConfigurationSubset::~WebUsbConfigurationSubset() {}
WebUsbAllowedOrigins::WebUsbAllowedOrigins() {}
WebUsbAllowedOrigins::~WebUsbAllowedOrigins() {}
// Parses a WebUSB Allowed Origins Header:
// http://wicg.github.io/webusb/#dfn-allowed-origins-header
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | length | type | total length |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | num configs | origin[0] | origin[1] | ...
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+------
bool WebUsbAllowedOrigins::Parse(const std::vector<uint8_t>& bytes) {
const uint8_t kDescriptorType = 0x00;
const uint8_t kDescriptorMinLength = 5;
// The buffer must be at least the length of this descriptor's mandatory
// fields.
if (bytes.size() < kDescriptorMinLength)
return false;
// Validate that the length of this descriptor and the total length of the
// entire block of descriptors is consistent with the length of the buffer.
uint8_t length = bytes[0];
uint16_t total_length = bytes[2] + (bytes[3] << 8);
if (length < 5 || length > bytes.size() || // bLength
bytes[1] != kDescriptorType || // bDescriptorType
total_length < length || total_length > bytes.size()) { // wTotalLength
return false;
}
std::vector<uint8_t>::const_iterator it = bytes.begin();
uint8_t num_configurations = bytes[4];
// The next |length - 5| bytes after the mandatory fields are origin indicies.
std::advance(it, kDescriptorMinLength);
uint8_t num_origins = length - kDescriptorMinLength;
origin_ids.reserve(num_origins);
for (size_t i = 0; i < num_origins; ++i) {
uint8_t index = *it++;
if (index == 0)
return false;
origin_ids.push_back(index);
}
// |num_configurations| configuration descriptors then follow the descriptor.
for (size_t i = 0; i < num_configurations; ++i) {
WebUsbConfigurationSubset configuration;
if (!ParseConfiguration(&configuration, &it, bytes.end()))
return false;
configurations.push_back(configuration);
}
return true;
}
WebUsbPlatformCapabilityDescriptor::WebUsbPlatformCapabilityDescriptor()
: version(0), vendor_code(0) {}
WebUsbPlatformCapabilityDescriptor::~WebUsbPlatformCapabilityDescriptor() {}
bool WebUsbPlatformCapabilityDescriptor::ParseFromBosDescriptor(
const std::vector<uint8_t>& bytes) {
if (bytes.size() < 5) {
// Too short for the BOS descriptor header.
return false;
}
// Validate the BOS descriptor, defined in Table 9-12 of the Universal Serial
// Bus 3.1 Specification, Revision 1.0.
uint16_t total_length = bytes[2] + (bytes[3] << 8);
if (bytes[0] != 5 || // bLength
bytes[1] != kBosDescriptorType || // bDescriptorType
5 > total_length || total_length > bytes.size()) { // wTotalLength
return false;
}
uint8_t num_device_caps = bytes[4];
std::vector<uint8_t>::const_iterator it = bytes.begin();
std::vector<uint8_t>::const_iterator end = it + total_length;
std::advance(it, 5);
uint8_t length = 0;
for (size_t i = 0; i < num_device_caps; ++i, std::advance(it, length)) {
if (it == end) {
return false;
}
// Validate the Device Capability descriptor, defined in Table 9-13 of the
// Universal Serial Bus 3.1 Specification, Revision 1.0.
length = it[0];
if (length < 3 || std::distance(it, end) < length || // bLength
it[1] != kDeviceCapabilityDescriptorType) { // bDescriptorType
return false;
}
if (it[2] != kPlatformDevCapabilityType) { // bDevCapabilityType
continue;
}
// Validate the Platform Capability Descriptor, defined in Table 9-18 of the
// Universal Serial Bus 3.1 Specification, Revision 1.0.
if (length < 20) {
// Platform capability descriptors must be at least 20 bytes.
return false;
}
if (memcmp(&it[4], kWebUsbCapabilityUUID, sizeof(kWebUsbCapabilityUUID)) !=
0) { // PlatformCapabilityUUID
continue;
}
if (length < 22) {
// The WebUSB capability descriptor must be at least 22 bytes (to allow
// for future versions).
return false;
}
version = it[20] + (it[21] << 8); // bcdVersion
if (version < 0x0100) {
continue;
}
// Version 1.0 defines two fields for a total length of 24 bytes.
if (length != 24) {
return false;
}
vendor_code = it[22];
landing_page_id = it[23];
return true;
}
return false;
}
// Parses a WebUSB URL Descriptor:
// http://wicg.github.io/webusb/#dfn-url-descriptor
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | length | type | prefix | data[0] |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | data[1] | ...
// +-+-+-+-+-+-+-+-+-+-+-+------
bool ParseWebUsbUrlDescriptor(const std::vector<uint8_t>& bytes, GURL* output) {
const uint8_t kDescriptorType = 0x03;
const uint8_t kDescriptorMinLength = 3;
if (bytes.size() < kDescriptorMinLength) {
return false;
}
// Validate that the length is consistent and fits within the buffer.
uint8_t length = bytes[0];
if (length < kDescriptorMinLength || length > bytes.size() ||
bytes[1] != kDescriptorType) {
return false;
}
// Look up the URL prefix and append the rest of the data in the descriptor.
std::string url;
switch (bytes[2]) {
case 0:
url.append("http://");
break;
case 1:
url.append("https://");
break;
default:
return false;
}
url.append(reinterpret_cast<const char*>(bytes.data() + 3), length - 3);
*output = GURL(url);
if (!output->is_valid()) {
return false;
}
return true;
}
void ReadWebUsbDescriptors(scoped_refptr<UsbDeviceHandle> device_handle,
const ReadWebUsbDescriptorsCallback& callback) {
scoped_refptr<IOBufferWithSize> buffer = new IOBufferWithSize(5);
device_handle->ControlTransfer(
USB_DIRECTION_INBOUND, UsbDeviceHandle::STANDARD, UsbDeviceHandle::DEVICE,
kGetDescriptorRequest, kBosDescriptorType << 8, 0, buffer, buffer->size(),
kControlTransferTimeout,
base::Bind(&OnReadBosDescriptorHeader, device_handle, callback));
}
bool FindInWebUsbAllowedOrigins(
const device::WebUsbAllowedOrigins* allowed_origins,
const GURL& origin) {
if (!allowed_origins)
return false;
if (base::ContainsValue(allowed_origins->origins, origin))
return true;
for (const auto& config : allowed_origins->configurations) {
if (base::ContainsValue(config.origins, origin))
return true;
for (const auto& function : config.functions) {
if (base::ContainsValue(function.origins, origin))
return true;
}
}
return false;
}
} // namespace device