#include "services/device/usb/webusb_descriptors.h"
#include <limits>
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/memory/ref_counted_memory.h"
#include "components/device_event_log/device_event_log.h"
#include "services/device/usb/usb_device_handle.h"
#include "url/gurl.h"
namespace device {
using mojom::UsbControlTransferRecipient;
using mojom::UsbControlTransferType;
using mojom::UsbTransferDirection;
using mojom::UsbTransferStatus;
namespace {
const uint8_t kGetDescriptorRequest = 0x06;
const uint8_t kBosDescriptorType = 0x0F;
const uint8_t kDeviceCapabilityDescriptorType = 0x10;
const uint8_t kPlatformDevCapabilityType = 0x05;
const uint8_t kGetUrlRequest = 0x02;
const uint8_t kWebUsbCapabilityUUID[16] = {
0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47,
0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65};
const size_t kMaxControlTransferLength = std::numeric_limits<uint8_t>::max();
const int kControlTransferTimeoutMs = 2000;
using ReadCompatabilityDescriptorCallback = base::OnceCallback<void(
const std::optional<WebUsbPlatformCapabilityDescriptor>& descriptor)>;
using ReadLandingPageCallback =
base::OnceCallback<void(const GURL& landing_page)>;
void OnReadLandingPage(uint8_t landing_page_id,
ReadLandingPageCallback callback,
UsbTransferStatus status,
scoped_refptr<base::RefCountedBytes> buffer,
size_t length) {
if (status != UsbTransferStatus::COMPLETED) {
USB_LOG(EVENT) << "Failed to read WebUSB URL descriptor: "
<< static_cast<int>(landing_page_id);
std::move(callback).Run(GURL());
return;
}
GURL url;
ParseWebUsbUrlDescriptor(UNSAFE_TODO(base::span(buffer->data(), length)),
&url);
std::move(callback).Run(url);
}
void OnReadBosDescriptor(scoped_refptr<UsbDeviceHandle> device_handle,
ReadCompatabilityDescriptorCallback callback,
UsbTransferStatus status,
scoped_refptr<base::RefCountedBytes> buffer,
size_t length) {
if (status != UsbTransferStatus::COMPLETED) {
USB_LOG(EVENT) << "Failed to read BOS descriptor.";
std::move(callback).Run(std::nullopt);
return;
}
WebUsbPlatformCapabilityDescriptor descriptor;
if (!descriptor.ParseFromBosDescriptor(
UNSAFE_TODO(base::span(buffer->data(), length)))) {
std::move(callback).Run(std::nullopt);
return;
}
std::move(callback).Run(descriptor);
}
void OnReadBosDescriptorHeader(scoped_refptr<UsbDeviceHandle> device_handle,
ReadCompatabilityDescriptorCallback callback,
UsbTransferStatus status,
scoped_refptr<base::RefCountedBytes> buffer,
size_t length) {
if (status != UsbTransferStatus::COMPLETED || length != 5) {
USB_LOG(EVENT) << "Failed to read BOS descriptor header.";
std::move(callback).Run(std::nullopt);
return;
}
base::span<const uint8_t> data = *buffer;
uint16_t new_length = data[2] | (data[3] << 8);
auto new_buffer = base::MakeRefCounted<base::RefCountedBytes>(new_length);
device_handle->ControlTransfer(
UsbTransferDirection::INBOUND, UsbControlTransferType::STANDARD,
UsbControlTransferRecipient::DEVICE, kGetDescriptorRequest,
kBosDescriptorType << 8, 0, new_buffer, kControlTransferTimeoutMs,
base::BindOnce(&OnReadBosDescriptor, device_handle, std::move(callback)));
}
void OnReadWebUsbCapabilityDescriptor(
scoped_refptr<UsbDeviceHandle> device_handle,
ReadLandingPageCallback callback,
const std::optional<WebUsbPlatformCapabilityDescriptor>& descriptor) {
if (!descriptor || !descriptor->landing_page_id) {
std::move(callback).Run(GURL());
return;
}
ReadWebUsbLandingPage(descriptor->vendor_code, descriptor->landing_page_id,
device_handle, std::move(callback));
}
}
WebUsbPlatformCapabilityDescriptor::WebUsbPlatformCapabilityDescriptor()
: version(0), vendor_code(0) {}
WebUsbPlatformCapabilityDescriptor::~WebUsbPlatformCapabilityDescriptor() =
default;
bool WebUsbPlatformCapabilityDescriptor::ParseFromBosDescriptor(
base::span<const uint8_t> bytes) {
if (bytes.size() < 5) {
return false;
}
uint16_t total_length = bytes[2] + (bytes[3] << 8);
if (bytes[0] != 5 ||
bytes[1] != kBosDescriptorType ||
5 > total_length || total_length > bytes.size()) {
return false;
}
uint8_t num_device_caps = bytes[4];
auto it = bytes.begin();
auto 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;
}
length = it[0];
if (length < 3 || std::distance(it, end) < length ||
it[1] != kDeviceCapabilityDescriptorType) {
return false;
}
if (it[2] != kPlatformDevCapabilityType) {
continue;
}
if (length < 20) {
return false;
}
if (UNSAFE_TODO(memcmp(&it[4], kWebUsbCapabilityUUID,
sizeof(kWebUsbCapabilityUUID))) !=
0) {
continue;
}
if (length < 22) {
return false;
}
version = it[20] + (it[21] << 8);
if (version < 0x0100) {
continue;
}
if (length != 24) {
return false;
}
vendor_code = it[22];
landing_page_id = it[23];
return true;
}
return false;
}
bool ParseWebUsbUrlDescriptor(base::span<const uint8_t> bytes, GURL* output) {
const uint8_t kDescriptorType = 0x03;
const uint8_t kDescriptorMinLength = 3;
if (bytes.size() < kDescriptorMinLength) {
return false;
}
uint8_t length = bytes[0];
if (length < kDescriptorMinLength || length > bytes.size() ||
bytes[1] != kDescriptorType) {
return false;
}
std::string url;
switch (bytes[2]) {
case 0:
url.append("http://");
break;
case 1:
url.append("https://");
break;
case 255:
break;
default:
return false;
}
url.append(reinterpret_cast<const char*>(UNSAFE_TODO(bytes.data() + 3)),
length - 3);
*output = GURL(url);
if (!output->is_valid()) {
return false;
}
return true;
}
void ReadWebUsbLandingPage(uint8_t vendor_code,
uint8_t landing_page_id,
scoped_refptr<UsbDeviceHandle> device_handle,
ReadLandingPageCallback callback) {
auto buffer =
base::MakeRefCounted<base::RefCountedBytes>(kMaxControlTransferLength);
device_handle->ControlTransfer(
UsbTransferDirection::INBOUND, UsbControlTransferType::VENDOR,
UsbControlTransferRecipient::DEVICE, vendor_code, landing_page_id,
kGetUrlRequest, buffer, kControlTransferTimeoutMs,
base::BindOnce(&OnReadLandingPage, landing_page_id, std::move(callback)));
}
void ReadWebUsbCapabilityDescriptor(
scoped_refptr<UsbDeviceHandle> device_handle,
ReadCompatabilityDescriptorCallback callback) {
auto buffer = base::MakeRefCounted<base::RefCountedBytes>(5);
device_handle->ControlTransfer(
UsbTransferDirection::INBOUND, UsbControlTransferType::STANDARD,
UsbControlTransferRecipient::DEVICE, kGetDescriptorRequest,
kBosDescriptorType << 8, 0, buffer, kControlTransferTimeoutMs,
base::BindOnce(&OnReadBosDescriptorHeader, device_handle,
std::move(callback)));
}
void ReadWebUsbDescriptors(scoped_refptr<UsbDeviceHandle> device_handle,
ReadLandingPageCallback callback) {
ReadWebUsbCapabilityDescriptor(
device_handle, base::BindOnce(&OnReadWebUsbCapabilityDescriptor,
device_handle, std::move(callback)));
}
}