blob: bf24edc43e40052e97cb2ea699b01d8990764509 [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 "third_party/blink/renderer/modules/webusb/usb_device.h"
#include <algorithm>
#include <iterator>
#include <utility>
#include "build/chromeos_buildflags.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_usb_control_transfer_parameters.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer_view.h"
#include "third_party/blink/renderer/modules/webusb/usb_configuration.h"
#include "third_party/blink/renderer/modules/webusb/usb_in_transfer_result.h"
#include "third_party/blink/renderer/modules/webusb/usb_isochronous_in_transfer_result.h"
#include "third_party/blink/renderer/modules/webusb/usb_isochronous_out_transfer_result.h"
#include "third_party/blink/renderer/modules/webusb/usb_out_transfer_result.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/mojo/mojo_helper.h"
#include "third_party/blink/renderer/platform/wtf/assertions.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
using device::mojom::blink::UsbControlTransferParamsPtr;
using device::mojom::blink::UsbControlTransferRecipient;
using device::mojom::blink::UsbControlTransferType;
using device::mojom::blink::UsbDevice;
using device::mojom::blink::UsbDeviceInfoPtr;
using device::mojom::blink::UsbIsochronousPacketPtr;
using device::mojom::blink::UsbOpenDeviceError;
using device::mojom::blink::UsbTransferDirection;
using device::mojom::blink::UsbTransferStatus;
namespace blink {
namespace {
const char kBufferTooBig[] = "The data buffer exceeded its maximum size.";
const char kDetachedBuffer[] = "The data buffer has been detached.";
const char kDeviceStateChangeInProgress[] =
"An operation that changes the device state is in progress.";
const char kDeviceDisconnected[] = "The device was disconnected.";
const char kInterfaceNotFound[] =
"The interface number provided is not supported by the device in its "
"current configuration.";
const char kInterfaceStateChangeInProgress[] =
"An operation that changes interface state is in progress.";
const char kOpenRequired[] = "The device must be opened first.";
#if BUILDFLAG(IS_CHROMEOS_ASH)
const char kExtensionProtocol[] = "chrome-extension";
// These Imprivata extensions can claim the protected HID interface class (used
// as badge readers), see crbug.com/1065112 and crbug.com/995294.
// This list needs to be alphabetically sorted for quick access via binary
// search.
const char* kImprivataExtensionIds[] = {
"baobpecgllpajfeojepgedjdlnlfffde", "bnfoibgpjolimhppjmligmcgklpboloj",
"cdgickkdpbekbnalbmpgochbninibkko", "cjakdianfealdjlapagfagpdpemoppba",
"cokoeepjbmmnhgdhlkpahohdaiedfjgn", "dahgfgiifpnaoajmloofonkndaaafacp",
"dbknmmkopacopifbkgookcdbhfnggjjh", "ddcjglpbfbibgepfffpklmpihphbcdco",
"dhodapiemamlmhlhblgcibabhdkohlen", "dlahpllbhpbkfnoiedkgombmegnnjopi",
"egfpnfjeaopimgpiioeedbpmojdapaip", "fnbibocngjnefolmcodjkkghijpdlnfm",
"jcnflhjcfjkplgkcinikhbgbhfldkadl", "jkfjfbelolphkjckiolfcakgalloegek",
"kmhpgpnbglclbaccjjgoioogjlnfgbne", "lpimkpkllnkdlcigdbgmabfplniahkgm",
"odehonhhkcjnbeaomlodfkjaecbmhklm", "olnmflhcfkifkgbiegcoabineoknmbjc",
"omificdfgpipkkpdhbjmefgfgbppehke", "phjobickjiififdadeoepbdaciefacfj",
"pkeacbojooejnjolgjdecbpnloibpafm", "pllbepacblmgialkkpcceohmjakafnbb",
"plpogimmgnkkiflhpidbibfmgpkaofec", "pmhiabnkkchjeaehcodceadhdpfejmmd",
};
const char** kExtensionNameMappingsEnd = std::end(kImprivataExtensionIds);
bool IsCStrBefore(const char* first, const char* second) {
return strcmp(first, second) < 0;
}
bool IsClassAllowedForExtension(uint8_t class_code, const KURL& url) {
if (url.Protocol() != kExtensionProtocol)
return false;
switch (class_code) {
case 0x03: // HID
DCHECK(std::is_sorted(kImprivataExtensionIds, kExtensionNameMappingsEnd,
IsCStrBefore));
return std::binary_search(kImprivataExtensionIds,
kExtensionNameMappingsEnd,
url.Host().Utf8().c_str(), IsCStrBefore);
default:
return false;
}
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
DOMException* ConvertFatalTransferStatus(const UsbTransferStatus& status) {
switch (status) {
case UsbTransferStatus::TRANSFER_ERROR:
return MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNetworkError, "A transfer error has occurred.");
case UsbTransferStatus::PERMISSION_DENIED:
return MakeGarbageCollected<DOMException>(
DOMExceptionCode::kSecurityError, "The transfer was not allowed.");
case UsbTransferStatus::TIMEOUT:
return MakeGarbageCollected<DOMException>(DOMExceptionCode::kTimeoutError,
"The transfer timed out.");
case UsbTransferStatus::CANCELLED:
return MakeGarbageCollected<DOMException>(DOMExceptionCode::kAbortError,
"The transfer was cancelled.");
case UsbTransferStatus::DISCONNECT:
return MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNotFoundError, kDeviceDisconnected);
case UsbTransferStatus::COMPLETED:
case UsbTransferStatus::STALLED:
case UsbTransferStatus::BABBLE:
case UsbTransferStatus::SHORT_PACKET:
return nullptr;
default:
NOTREACHED();
return nullptr;
}
}
String ConvertTransferStatus(const UsbTransferStatus& status) {
switch (status) {
case UsbTransferStatus::COMPLETED:
case UsbTransferStatus::SHORT_PACKET:
return "ok";
case UsbTransferStatus::STALLED:
return "stall";
case UsbTransferStatus::BABBLE:
return "babble";
default:
NOTREACHED();
return "";
}
}
bool ConvertBufferSource(const ArrayBufferOrArrayBufferView& buffer_source,
Vector<uint8_t>* vector,
ScriptPromiseResolver* resolver) {
DCHECK(!buffer_source.IsNull());
if (buffer_source.IsArrayBuffer()) {
DOMArrayBuffer* array_buffer = buffer_source.GetAsArrayBuffer();
if (array_buffer->IsDetached()) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kInvalidStateError, kDetachedBuffer));
return false;
}
if (array_buffer->ByteLength() > std::numeric_limits<wtf_size_t>::max()) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kDataError, kBufferTooBig));
return false;
}
vector->Append(static_cast<uint8_t*>(array_buffer->Data()),
static_cast<wtf_size_t>(array_buffer->ByteLength()));
} else {
DOMArrayBufferView* view = buffer_source.GetAsArrayBufferView().Get();
if (!view->buffer() || view->buffer()->IsDetached()) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kInvalidStateError, kDetachedBuffer));
return false;
}
if (view->byteLength() > std::numeric_limits<wtf_size_t>::max()) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kDataError, kBufferTooBig));
return false;
}
vector->Append(static_cast<uint8_t*>(view->BaseAddress()),
static_cast<wtf_size_t>(view->byteLength()));
}
return true;
}
} // namespace
USBDevice::USBDevice(UsbDeviceInfoPtr device_info,
mojo::PendingRemote<UsbDevice> device,
ExecutionContext* context)
: ExecutionContextLifecycleObserver(context),
device_info_(std::move(device_info)),
device_(context),
opened_(false),
device_state_change_in_progress_(false),
configuration_index_(kNotFound) {
device_.Bind(std::move(device),
context->GetTaskRunner(TaskType::kMiscPlatformAPI));
if (device_.is_bound()) {
device_.set_disconnect_handler(
WTF::Bind(&USBDevice::OnConnectionError, WrapWeakPersistent(this)));
}
wtf_size_t configuration_index =
FindConfigurationIndex(Info().active_configuration);
if (configuration_index != kNotFound)
OnConfigurationSelected(true /* success */, configuration_index);
}
USBDevice::~USBDevice() {
// |m_device| may still be valid but there should be no more outstanding
// requests because each holds a persistent handle to this object.
DCHECK(device_requests_.IsEmpty());
}
bool USBDevice::IsInterfaceClaimed(wtf_size_t configuration_index,
wtf_size_t interface_index) const {
return configuration_index_ != kNotFound &&
configuration_index_ == configuration_index &&
claimed_interfaces_[interface_index];
}
wtf_size_t USBDevice::SelectedAlternateInterface(
wtf_size_t interface_index) const {
return selected_alternates_[interface_index];
}
USBConfiguration* USBDevice::configuration() const {
if (configuration_index_ != kNotFound)
return USBConfiguration::Create(this, configuration_index_);
return nullptr;
}
HeapVector<Member<USBConfiguration>> USBDevice::configurations() const {
wtf_size_t num_configurations = Info().configurations.size();
HeapVector<Member<USBConfiguration>> configurations(num_configurations);
for (wtf_size_t i = 0; i < num_configurations; ++i)
configurations[i] = USBConfiguration::Create(this, i);
return configurations;
}
ScriptPromise USBDevice::open(ScriptState* script_state) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
if (EnsureNoDeviceOrInterfaceChangeInProgress(resolver)) {
if (opened_) {
resolver->Resolve();
} else {
device_state_change_in_progress_ = true;
device_requests_.insert(resolver);
device_->Open(WTF::Bind(&USBDevice::AsyncOpen, WrapPersistent(this),
WrapPersistent(resolver)));
}
}
return promise;
}
ScriptPromise USBDevice::close(ScriptState* script_state) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
if (EnsureNoDeviceOrInterfaceChangeInProgress(resolver)) {
if (!opened_) {
resolver->Resolve();
} else {
device_state_change_in_progress_ = true;
device_requests_.insert(resolver);
device_->Close(WTF::Bind(&USBDevice::AsyncClose, WrapPersistent(this),
WrapPersistent(resolver)));
}
}
return promise;
}
ScriptPromise USBDevice::selectConfiguration(ScriptState* script_state,
uint8_t configuration_value) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
if (EnsureNoDeviceOrInterfaceChangeInProgress(resolver)) {
if (!opened_) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kInvalidStateError, kOpenRequired));
} else {
wtf_size_t configuration_index =
FindConfigurationIndex(configuration_value);
if (configuration_index == kNotFound) {
resolver->Reject(
MakeGarbageCollected<DOMException>(DOMExceptionCode::kNotFoundError,
"The configuration value "
"provided is not supported by "
"the device."));
} else if (configuration_index_ == configuration_index) {
resolver->Resolve();
} else {
device_state_change_in_progress_ = true;
device_requests_.insert(resolver);
device_->SetConfiguration(
configuration_value,
WTF::Bind(&USBDevice::AsyncSelectConfiguration,
WrapPersistent(this), configuration_index,
WrapPersistent(resolver)));
}
}
}
return promise;
}
ScriptPromise USBDevice::claimInterface(ScriptState* script_state,
uint8_t interface_number) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
if (EnsureDeviceConfigured(resolver)) {
wtf_size_t interface_index = FindInterfaceIndex(interface_number);
if (interface_index == kNotFound) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNotFoundError, kInterfaceNotFound));
} else if (interface_state_change_in_progress_[interface_index]) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kInvalidStateError,
kInterfaceStateChangeInProgress));
} else if (claimed_interfaces_[interface_index]) {
resolver->Resolve();
} else if (IsProtectedInterfaceClass(interface_index)) {
GetExecutionContext()->AddConsoleMessage(
MakeGarbageCollected<ConsoleMessage>(
mojom::ConsoleMessageSource::kJavaScript,
mojom::ConsoleMessageLevel::kWarning,
"An attempt to claim a USB device interface "
"has been blocked because it "
"implements a protected interface class."));
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kSecurityError,
"The requested interface implements a protected class."));
} else {
interface_state_change_in_progress_[interface_index] = true;
device_requests_.insert(resolver);
device_->ClaimInterface(
interface_number,
WTF::Bind(&USBDevice::AsyncClaimInterface, WrapPersistent(this),
interface_index, WrapPersistent(resolver)));
}
}
return promise;
}
ScriptPromise USBDevice::releaseInterface(ScriptState* script_state,
uint8_t interface_number) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
if (EnsureDeviceConfigured(resolver)) {
wtf_size_t interface_index = FindInterfaceIndex(interface_number);
if (interface_index == kNotFound) {
resolver->Reject(
MakeGarbageCollected<DOMException>(DOMExceptionCode::kNotFoundError,
"The interface number provided is "
"not supported by the device in "
"its current configuration."));
} else if (interface_state_change_in_progress_[interface_index]) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kInvalidStateError,
kInterfaceStateChangeInProgress));
} else if (!claimed_interfaces_[interface_index]) {
resolver->Resolve();
} else {
// Mark this interface's endpoints unavailable while its state is
// changing.
SetEndpointsForInterface(interface_index, false);
interface_state_change_in_progress_[interface_index] = true;
device_requests_.insert(resolver);
device_->ReleaseInterface(
interface_number,
WTF::Bind(&USBDevice::AsyncReleaseInterface, WrapPersistent(this),
interface_index, WrapPersistent(resolver)));
}
}
return promise;
}
ScriptPromise USBDevice::selectAlternateInterface(ScriptState* script_state,
uint8_t interface_number,
uint8_t alternate_setting) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
if (EnsureInterfaceClaimed(interface_number, resolver)) {
// TODO(reillyg): This is duplicated work.
wtf_size_t interface_index = FindInterfaceIndex(interface_number);
DCHECK_NE(interface_index, kNotFound);
wtf_size_t alternate_index =
FindAlternateIndex(interface_index, alternate_setting);
if (alternate_index == kNotFound) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNotFoundError,
"The alternate setting provided is "
"not supported by the device in "
"its current configuration."));
} else {
// Mark this old alternate interface's endpoints unavailable while
// the change is in progress.
SetEndpointsForInterface(interface_index, false);
interface_state_change_in_progress_[interface_index] = true;
device_requests_.insert(resolver);
device_->SetInterfaceAlternateSetting(
interface_number, alternate_setting,
WTF::Bind(&USBDevice::AsyncSelectAlternateInterface,
WrapPersistent(this), interface_number, alternate_setting,
WrapPersistent(resolver)));
}
}
return promise;
}
ScriptPromise USBDevice::controlTransferIn(
ScriptState* script_state,
const USBControlTransferParameters* setup,
unsigned length) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
if (!EnsureNoDeviceOrInterfaceChangeInProgress(resolver))
return promise;
if (!opened_) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kInvalidStateError, kOpenRequired));
return promise;
}
auto parameters = ConvertControlTransferParameters(setup, resolver);
if (!parameters)
return promise;
device_requests_.insert(resolver);
device_->ControlTransferIn(
std::move(parameters), length, 0,
WTF::Bind(&USBDevice::AsyncControlTransferIn, WrapPersistent(this),
WrapPersistent(resolver)));
return promise;
}
ScriptPromise USBDevice::controlTransferOut(
ScriptState* script_state,
const USBControlTransferParameters* setup) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
if (!EnsureNoDeviceOrInterfaceChangeInProgress(resolver))
return promise;
if (!opened_) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kInvalidStateError, kOpenRequired));
return promise;
}
auto parameters = ConvertControlTransferParameters(setup, resolver);
if (!parameters)
return promise;
device_requests_.insert(resolver);
device_->ControlTransferOut(
std::move(parameters), Vector<uint8_t>(), 0,
WTF::Bind(&USBDevice::AsyncControlTransferOut, WrapPersistent(this), 0,
WrapPersistent(resolver)));
return promise;
}
ScriptPromise USBDevice::controlTransferOut(
ScriptState* script_state,
const USBControlTransferParameters* setup,
const ArrayBufferOrArrayBufferView& data) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
if (!EnsureNoDeviceOrInterfaceChangeInProgress(resolver))
return promise;
if (!opened_) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kInvalidStateError, kOpenRequired));
return promise;
}
auto parameters = ConvertControlTransferParameters(setup, resolver);
if (!parameters)
return promise;
Vector<uint8_t> buffer;
if (!ConvertBufferSource(data, &buffer, resolver))
return promise;
unsigned transfer_length = buffer.size();
device_requests_.insert(resolver);
device_->ControlTransferOut(
std::move(parameters), buffer, 0,
WTF::Bind(&USBDevice::AsyncControlTransferOut, WrapPersistent(this),
transfer_length, WrapPersistent(resolver)));
return promise;
}
ScriptPromise USBDevice::clearHalt(ScriptState* script_state,
String direction,
uint8_t endpoint_number) {
UsbTransferDirection mojo_direction = direction == "in"
? UsbTransferDirection::INBOUND
: UsbTransferDirection::OUTBOUND;
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
if (EnsureEndpointAvailable(direction == "in", endpoint_number, resolver)) {
device_requests_.insert(resolver);
device_->ClearHalt(
mojo_direction, endpoint_number,
WTF::Bind(&USBDevice::AsyncClearHalt, WrapPersistent(this),
WrapPersistent(resolver)));
}
return promise;
}
ScriptPromise USBDevice::transferIn(ScriptState* script_state,
uint8_t endpoint_number,
unsigned length) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
if (EnsureEndpointAvailable(true /* in */, endpoint_number, resolver)) {
device_requests_.insert(resolver);
device_->GenericTransferIn(
endpoint_number, length, 0,
WTF::Bind(&USBDevice::AsyncTransferIn, WrapPersistent(this),
WrapPersistent(resolver)));
}
return promise;
}
ScriptPromise USBDevice::transferOut(ScriptState* script_state,
uint8_t endpoint_number,
const ArrayBufferOrArrayBufferView& data) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
if (!EnsureEndpointAvailable(false /* out */, endpoint_number, resolver))
return promise;
Vector<uint8_t> buffer;
if (!ConvertBufferSource(data, &buffer, resolver))
return promise;
unsigned transfer_length = buffer.size();
device_requests_.insert(resolver);
device_->GenericTransferOut(
endpoint_number, buffer, 0,
WTF::Bind(&USBDevice::AsyncTransferOut, WrapPersistent(this),
transfer_length, WrapPersistent(resolver)));
return promise;
}
ScriptPromise USBDevice::isochronousTransferIn(
ScriptState* script_state,
uint8_t endpoint_number,
Vector<unsigned> packet_lengths) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
if (EnsureEndpointAvailable(true /* in */, endpoint_number, resolver)) {
device_requests_.insert(resolver);
device_->IsochronousTransferIn(
endpoint_number, packet_lengths, 0,
WTF::Bind(&USBDevice::AsyncIsochronousTransferIn, WrapPersistent(this),
WrapPersistent(resolver)));
}
return promise;
}
ScriptPromise USBDevice::isochronousTransferOut(
ScriptState* script_state,
uint8_t endpoint_number,
const ArrayBufferOrArrayBufferView& data,
Vector<unsigned> packet_lengths) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
if (!EnsureEndpointAvailable(false /* out */, endpoint_number, resolver))
return promise;
Vector<uint8_t> buffer;
if (!ConvertBufferSource(data, &buffer, resolver))
return promise;
device_requests_.insert(resolver);
device_->IsochronousTransferOut(
endpoint_number, buffer, packet_lengths, 0,
WTF::Bind(&USBDevice::AsyncIsochronousTransferOut, WrapPersistent(this),
WrapPersistent(resolver)));
return promise;
}
ScriptPromise USBDevice::reset(ScriptState* script_state) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
if (EnsureNoDeviceOrInterfaceChangeInProgress(resolver)) {
if (!opened_) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kInvalidStateError, kOpenRequired));
} else {
device_requests_.insert(resolver);
device_->Reset(WTF::Bind(&USBDevice::AsyncReset, WrapPersistent(this),
WrapPersistent(resolver)));
}
}
return promise;
}
void USBDevice::ContextDestroyed() {
device_requests_.clear();
}
void USBDevice::Trace(Visitor* visitor) const {
visitor->Trace(device_);
visitor->Trace(device_requests_);
ScriptWrappable::Trace(visitor);
ExecutionContextLifecycleObserver::Trace(visitor);
}
wtf_size_t USBDevice::FindConfigurationIndex(
uint8_t configuration_value) const {
const auto& configurations = Info().configurations;
for (wtf_size_t i = 0; i < configurations.size(); ++i) {
if (configurations[i]->configuration_value == configuration_value)
return i;
}
return kNotFound;
}
wtf_size_t USBDevice::FindInterfaceIndex(uint8_t interface_number) const {
DCHECK_NE(configuration_index_, kNotFound);
const auto& interfaces =
Info().configurations[configuration_index_]->interfaces;
for (wtf_size_t i = 0; i < interfaces.size(); ++i) {
if (interfaces[i]->interface_number == interface_number)
return i;
}
return kNotFound;
}
wtf_size_t USBDevice::FindAlternateIndex(uint32_t interface_index,
uint8_t alternate_setting) const {
DCHECK_NE(configuration_index_, kNotFound);
const auto& alternates = Info()
.configurations[configuration_index_]
->interfaces[interface_index]
->alternates;
for (wtf_size_t i = 0; i < alternates.size(); ++i) {
if (alternates[i]->alternate_setting == alternate_setting)
return i;
}
return kNotFound;
}
bool USBDevice::IsProtectedInterfaceClass(wtf_size_t interface_index) const {
DCHECK_NE(configuration_index_, kNotFound);
DCHECK_NE(interface_index, kNotFound);
// USB Class Codes are defined by the USB-IF:
// https://www.usb.org/defined-class-codes
const uint8_t kProtectedClasses[] = {
0x01, // Audio
0x03, // HID
0x08, // Mass Storage
0x0B, // Smart Card
0x0E, // Video
0x10, // Audio/Video
0xE0, // Wireless Controller (Bluetooth and Wireless USB)
};
DCHECK(std::is_sorted(std::begin(kProtectedClasses),
std::end(kProtectedClasses)));
const auto& alternates = Info()
.configurations[configuration_index_]
->interfaces[interface_index]
->alternates;
for (const auto& alternate : alternates) {
if (std::binary_search(std::begin(kProtectedClasses),
std::end(kProtectedClasses),
alternate->class_code)) {
#if BUILDFLAG(IS_CHROMEOS_ASH)
return !IsClassAllowedForExtension(alternate->class_code,
GetExecutionContext()->Url());
#else
return true;
#endif
}
}
return false;
}
bool USBDevice::EnsureNoDeviceChangeInProgress(
ScriptPromiseResolver* resolver) const {
if (!device_.is_bound()) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNotFoundError, kDeviceDisconnected));
return false;
}
if (device_state_change_in_progress_) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kInvalidStateError, kDeviceStateChangeInProgress));
return false;
}
return true;
}
bool USBDevice::EnsureNoDeviceOrInterfaceChangeInProgress(
ScriptPromiseResolver* resolver) const {
if (!EnsureNoDeviceChangeInProgress(resolver))
return false;
if (AnyInterfaceChangeInProgress()) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kInvalidStateError, kInterfaceStateChangeInProgress));
return false;
}
return true;
}
bool USBDevice::EnsureDeviceConfigured(ScriptPromiseResolver* resolver) const {
if (!EnsureNoDeviceChangeInProgress(resolver))
return false;
if (!opened_) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kInvalidStateError, kOpenRequired));
return false;
}
if (configuration_index_ == kNotFound) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kInvalidStateError,
"The device must have a configuration selected."));
return false;
}
return true;
}
bool USBDevice::EnsureInterfaceClaimed(uint8_t interface_number,
ScriptPromiseResolver* resolver) const {
if (!EnsureDeviceConfigured(resolver))
return false;
wtf_size_t interface_index = FindInterfaceIndex(interface_number);
if (interface_index == kNotFound) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNotFoundError, kInterfaceNotFound));
return false;
}
if (interface_state_change_in_progress_[interface_index]) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kInvalidStateError, kInterfaceStateChangeInProgress));
return false;
}
if (!claimed_interfaces_[interface_index]) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kInvalidStateError,
"The specified interface has not been claimed."));
return false;
}
return true;
}
bool USBDevice::EnsureEndpointAvailable(bool in_transfer,
uint8_t endpoint_number,
ScriptPromiseResolver* resolver) const {
if (!EnsureDeviceConfigured(resolver))
return false;
if (endpoint_number == 0 || endpoint_number >= kEndpointsBitsNumber) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kIndexSizeError,
"The specified endpoint number is out of range."));
return false;
}
auto& bit_vector = in_transfer ? in_endpoints_ : out_endpoints_;
if (!bit_vector[endpoint_number - 1]) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNotFoundError,
"The specified endpoint is not part "
"of a claimed and selected alternate "
"interface."));
return false;
}
return true;
}
bool USBDevice::AnyInterfaceChangeInProgress() const {
for (wtf_size_t i = 0; i < interface_state_change_in_progress_.size(); ++i) {
if (interface_state_change_in_progress_[i])
return true;
}
return false;
}
UsbControlTransferParamsPtr USBDevice::ConvertControlTransferParameters(
const USBControlTransferParameters* parameters,
ScriptPromiseResolver* resolver) const {
auto mojo_parameters = device::mojom::blink::UsbControlTransferParams::New();
if (parameters->requestType() == "standard") {
mojo_parameters->type = UsbControlTransferType::STANDARD;
} else if (parameters->requestType() == "class") {
mojo_parameters->type = UsbControlTransferType::CLASS;
} else if (parameters->requestType() == "vendor") {
mojo_parameters->type = UsbControlTransferType::VENDOR;
} else {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kTypeMismatchError,
"The control transfer requestType parameter is invalid."));
return nullptr;
}
if (parameters->recipient() == "device") {
mojo_parameters->recipient = UsbControlTransferRecipient::DEVICE;
} else if (parameters->recipient() == "interface") {
uint8_t interface_number = parameters->index() & 0xff;
if (!EnsureInterfaceClaimed(interface_number, resolver))
return nullptr;
mojo_parameters->recipient = UsbControlTransferRecipient::INTERFACE;
} else if (parameters->recipient() == "endpoint") {
bool in_transfer = parameters->index() & 0x80;
uint8_t endpoint_number = parameters->index() & 0x0f;
if (!EnsureEndpointAvailable(in_transfer, endpoint_number, resolver))
return nullptr;
mojo_parameters->recipient = UsbControlTransferRecipient::ENDPOINT;
} else if (parameters->recipient() == "other") {
mojo_parameters->recipient = UsbControlTransferRecipient::OTHER;
} else {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kTypeMismatchError,
"The control transfer recipient parameter is invalid."));
return nullptr;
}
mojo_parameters->request = parameters->request();
mojo_parameters->value = parameters->value();
mojo_parameters->index = parameters->index();
return mojo_parameters;
}
void USBDevice::SetEndpointsForInterface(wtf_size_t interface_index, bool set) {
const auto& configuration = *Info().configurations[configuration_index_];
const auto& interface = *configuration.interfaces[interface_index];
const auto& alternate =
*interface.alternates[selected_alternates_[interface_index]];
for (const auto& endpoint : alternate.endpoints) {
uint8_t endpoint_number = endpoint->endpoint_number;
if (endpoint_number == 0 || endpoint_number >= kEndpointsBitsNumber)
continue; // Ignore endpoints with invalid indices.
auto& bit_vector = endpoint->direction == UsbTransferDirection::INBOUND
? in_endpoints_
: out_endpoints_;
if (set)
bit_vector.set(endpoint_number - 1);
else
bit_vector.reset(endpoint_number - 1);
}
}
void USBDevice::AsyncOpen(ScriptPromiseResolver* resolver,
UsbOpenDeviceError error) {
if (!MarkRequestComplete(resolver))
return;
switch (error) {
case UsbOpenDeviceError::ALREADY_OPEN:
NOTREACHED();
FALLTHROUGH;
case UsbOpenDeviceError::OK:
OnDeviceOpenedOrClosed(true /* opened */);
resolver->Resolve();
return;
case UsbOpenDeviceError::ACCESS_DENIED:
OnDeviceOpenedOrClosed(false /* not opened */);
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kSecurityError, "Access denied."));
return;
}
}
void USBDevice::AsyncClose(ScriptPromiseResolver* resolver) {
if (!MarkRequestComplete(resolver))
return;
OnDeviceOpenedOrClosed(false /* closed */);
resolver->Resolve();
}
void USBDevice::OnDeviceOpenedOrClosed(bool opened) {
opened_ = opened;
if (!opened_) {
claimed_interfaces_.Fill(false);
selected_alternates_.Fill(0);
in_endpoints_.reset();
out_endpoints_.reset();
}
device_state_change_in_progress_ = false;
}
void USBDevice::AsyncSelectConfiguration(wtf_size_t configuration_index,
ScriptPromiseResolver* resolver,
bool success) {
if (!MarkRequestComplete(resolver))
return;
OnConfigurationSelected(success, configuration_index);
if (success) {
resolver->Resolve();
} else {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNetworkError,
"Unable to set device configuration."));
}
}
void USBDevice::OnConfigurationSelected(bool success,
wtf_size_t configuration_index) {
if (success) {
configuration_index_ = configuration_index;
wtf_size_t num_interfaces =
Info().configurations[configuration_index_]->interfaces.size();
claimed_interfaces_.resize(num_interfaces);
claimed_interfaces_.Fill(false);
interface_state_change_in_progress_.resize(num_interfaces);
interface_state_change_in_progress_.Fill(false);
selected_alternates_.resize(num_interfaces);
selected_alternates_.Fill(0);
in_endpoints_.reset();
out_endpoints_.reset();
}
device_state_change_in_progress_ = false;
}
void USBDevice::AsyncClaimInterface(wtf_size_t interface_index,
ScriptPromiseResolver* resolver,
bool success) {
if (!MarkRequestComplete(resolver))
return;
OnInterfaceClaimedOrUnclaimed(success, interface_index);
if (success) {
resolver->Resolve();
} else {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNetworkError, "Unable to claim interface."));
}
}
void USBDevice::AsyncReleaseInterface(wtf_size_t interface_index,
ScriptPromiseResolver* resolver,
bool success) {
if (!MarkRequestComplete(resolver))
return;
OnInterfaceClaimedOrUnclaimed(!success, interface_index);
if (success) {
resolver->Resolve();
} else {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNetworkError, "Unable to release interface."));
}
}
void USBDevice::OnInterfaceClaimedOrUnclaimed(bool claimed,
wtf_size_t interface_index) {
if (claimed) {
claimed_interfaces_[interface_index] = true;
} else {
claimed_interfaces_[interface_index] = false;
selected_alternates_[interface_index] = 0;
}
SetEndpointsForInterface(interface_index, claimed);
interface_state_change_in_progress_[interface_index] = false;
}
void USBDevice::AsyncSelectAlternateInterface(wtf_size_t interface_index,
wtf_size_t alternate_index,
ScriptPromiseResolver* resolver,
bool success) {
if (!MarkRequestComplete(resolver))
return;
if (success)
selected_alternates_[interface_index] = alternate_index;
SetEndpointsForInterface(interface_index, success);
interface_state_change_in_progress_[interface_index] = false;
if (success) {
resolver->Resolve();
} else {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNetworkError, "Unable to set device interface."));
}
}
void USBDevice::AsyncControlTransferIn(ScriptPromiseResolver* resolver,
UsbTransferStatus status,
const Vector<uint8_t>& data) {
if (!MarkRequestComplete(resolver))
return;
DOMException* error = ConvertFatalTransferStatus(status);
if (error) {
resolver->Reject(error);
} else {
resolver->Resolve(
USBInTransferResult::Create(ConvertTransferStatus(status), data));
}
}
void USBDevice::AsyncControlTransferOut(unsigned transfer_length,
ScriptPromiseResolver* resolver,
UsbTransferStatus status) {
if (!MarkRequestComplete(resolver))
return;
DOMException* error = ConvertFatalTransferStatus(status);
if (error) {
resolver->Reject(error);
} else {
resolver->Resolve(USBOutTransferResult::Create(
ConvertTransferStatus(status), transfer_length));
}
}
void USBDevice::AsyncClearHalt(ScriptPromiseResolver* resolver, bool success) {
if (!MarkRequestComplete(resolver))
return;
if (success) {
resolver->Resolve();
} else {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNetworkError, "Unable to clear endpoint."));
}
}
void USBDevice::AsyncTransferIn(ScriptPromiseResolver* resolver,
UsbTransferStatus status,
const Vector<uint8_t>& data) {
if (!MarkRequestComplete(resolver))
return;
DOMException* error = ConvertFatalTransferStatus(status);
if (error) {
resolver->Reject(error);
} else {
resolver->Resolve(
USBInTransferResult::Create(ConvertTransferStatus(status), data));
}
}
void USBDevice::AsyncTransferOut(unsigned transfer_length,
ScriptPromiseResolver* resolver,
UsbTransferStatus status) {
if (!MarkRequestComplete(resolver))
return;
DOMException* error = ConvertFatalTransferStatus(status);
if (error) {
resolver->Reject(error);
} else {
resolver->Resolve(USBOutTransferResult::Create(
ConvertTransferStatus(status), transfer_length));
}
}
void USBDevice::AsyncIsochronousTransferIn(
ScriptPromiseResolver* resolver,
const Vector<uint8_t>& data,
Vector<UsbIsochronousPacketPtr> mojo_packets) {
if (!MarkRequestComplete(resolver))
return;
DOMArrayBuffer* buffer = DOMArrayBuffer::Create(data.data(), data.size());
HeapVector<Member<USBIsochronousInTransferPacket>> packets;
packets.ReserveCapacity(mojo_packets.size());
uint32_t byte_offset = 0;
for (const auto& packet : mojo_packets) {
DOMException* error = ConvertFatalTransferStatus(packet->status);
if (error) {
resolver->Reject(error);
return;
}
DOMDataView* data_view = nullptr;
if (buffer) {
data_view =
DOMDataView::Create(buffer, byte_offset, packet->transferred_length);
}
packets.push_back(USBIsochronousInTransferPacket::Create(
ConvertTransferStatus(packet->status),
NotShared<DOMDataView>(data_view)));
byte_offset += packet->length;
}
resolver->Resolve(USBIsochronousInTransferResult::Create(buffer, packets));
}
void USBDevice::AsyncIsochronousTransferOut(
ScriptPromiseResolver* resolver,
Vector<UsbIsochronousPacketPtr> mojo_packets) {
if (!MarkRequestComplete(resolver))
return;
HeapVector<Member<USBIsochronousOutTransferPacket>> packets;
packets.ReserveCapacity(mojo_packets.size());
for (const auto& packet : mojo_packets) {
DOMException* error = ConvertFatalTransferStatus(packet->status);
if (error) {
resolver->Reject(error);
return;
}
packets.push_back(USBIsochronousOutTransferPacket::Create(
ConvertTransferStatus(packet->status), packet->transferred_length));
}
resolver->Resolve(USBIsochronousOutTransferResult::Create(packets));
}
void USBDevice::AsyncReset(ScriptPromiseResolver* resolver, bool success) {
if (!MarkRequestComplete(resolver))
return;
if (success) {
resolver->Resolve();
} else {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNetworkError, "Unable to reset the device."));
}
}
void USBDevice::OnConnectionError() {
device_.reset();
opened_ = false;
// Move the set to a local variable to prevent script execution in Reject()
// from invalidating the iterator used by the loop.
HeapHashSet<Member<ScriptPromiseResolver>> device_requests;
device_requests.swap(device_requests_);
for (auto& resolver : device_requests) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNotFoundError, kDeviceDisconnected));
}
}
bool USBDevice::MarkRequestComplete(ScriptPromiseResolver* resolver) {
auto request_entry = device_requests_.find(resolver);
if (request_entry == device_requests_.end())
return false;
device_requests_.erase(request_entry);
return true;
}
} // namespace blink