blob: 18654d3900f1b8268aeb4326ce09c943559ef056 [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// 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 <limits>
#include <optional>
#include <utility>
#include "base/containers/span.h"
#include "third_party/blink/public/common/features.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_traits.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_union_arraybuffer_arraybufferview.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_usb_control_transfer_parameters.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_usb_direction.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.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/garbage_collected.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
using device::mojom::blink::UsbClaimInterfaceResult;
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 kAccessDeniedError[] = "Access denied.";
const char kPacketLengthsTooBig[] =
"The total packet length exceeded the maximum size.";
const char kBufferSizeMismatch[] =
"The data buffer size must match the total packet length.";
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.";
const char kProtectedInterfaceClassError[] =
"The requested interface implements a protected class.";
const char kTransferPermissionDeniedError[] = "The transfer was not allowed.";
const int kUsbTransferLengthLimit = 32 * 1024 * 1024;
bool CheckFatalTransferStatus(ScriptPromiseResolverBase* resolver,
const UsbTransferStatus& status) {
switch (status) {
case UsbTransferStatus::TRANSFER_ERROR:
resolver->RejectWithDOMException(DOMExceptionCode::kNetworkError,
"A transfer error has occurred.");
return true;
case UsbTransferStatus::PERMISSION_DENIED:
resolver->RejectWithSecurityError(kTransferPermissionDeniedError,
kTransferPermissionDeniedError);
return true;
case UsbTransferStatus::TIMEOUT:
resolver->RejectWithDOMException(DOMExceptionCode::kTimeoutError,
"The transfer timed out.");
return true;
case UsbTransferStatus::CANCELLED:
resolver->RejectWithDOMException(DOMExceptionCode::kAbortError,
"The transfer was cancelled.");
return true;
case UsbTransferStatus::DISCONNECT:
resolver->RejectWithDOMException(DOMExceptionCode::kNotFoundError,
kDeviceDisconnected);
return true;
case UsbTransferStatus::COMPLETED:
case UsbTransferStatus::STALLED:
case UsbTransferStatus::BABBLE:
case UsbTransferStatus::SHORT_PACKET:
return false;
default:
NOTREACHED();
}
}
V8USBTransferStatus ConvertTransferStatus(const UsbTransferStatus& status) {
switch (status) {
case UsbTransferStatus::COMPLETED:
case UsbTransferStatus::SHORT_PACKET:
return V8USBTransferStatus(V8USBTransferStatus::Enum::kOk);
case UsbTransferStatus::STALLED:
return V8USBTransferStatus(V8USBTransferStatus::Enum::kStall);
case UsbTransferStatus::BABBLE:
return V8USBTransferStatus(V8USBTransferStatus::Enum::kBabble);
case UsbTransferStatus::TRANSFER_ERROR:
case UsbTransferStatus::PERMISSION_DENIED:
case UsbTransferStatus::TIMEOUT:
case UsbTransferStatus::CANCELLED:
case UsbTransferStatus::DISCONNECT:
NOTREACHED();
}
}
// Returns the sum of `packet_lengths`, or nullopt if the sum would overflow.
std::optional<uint32_t> TotalPacketLength(
const Vector<unsigned>& packet_lengths) {
uint32_t total_bytes = 0;
for (const auto packet_length : packet_lengths) {
// Check for overflow.
if (std::numeric_limits<uint32_t>::max() - total_bytes < packet_length) {
return std::nullopt;
}
total_bytes += packet_length;
}
return total_bytes;
}
bool ShouldRejectUsbTransferLength(size_t length,
ExceptionState& exception_state) {
if (!base::FeatureList::IsEnabled(
blink::features::kWebUSBTransferSizeLimit)) {
return false;
}
if (length <= kUsbTransferLengthLimit) {
return false;
}
exception_state.ThrowDOMException(
DOMExceptionCode::kDataError,
String::Format(
"The data buffer exceeded supported maximum size of %d bytes",
kUsbTransferLengthLimit));
return true;
}
} // namespace
USBDevice::USBDevice(USB* parent,
UsbDeviceInfoPtr device_info,
mojo::PendingRemote<UsbDevice> device,
ExecutionContext* context)
: ExecutionContextLifecycleObserver(context),
parent_(parent),
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(
BindOnce(&USBDevice::OnConnectionError, WrapWeakPersistent(this)));
}
for (wtf_size_t i = 0; i < Info().configurations.size(); ++i)
configurations_.push_back(USBConfiguration::Create(this, i));
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_.empty());
}
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::SelectedAlternateInterfaceIndex(
wtf_size_t interface_index) const {
return selected_alternate_indices_[interface_index];
}
USBConfiguration* USBDevice::configuration() const {
if (configuration_index_ == kNotFound)
return nullptr;
DCHECK_LT(configuration_index_, configurations_.size());
return configurations_[configuration_index_].Get();
}
HeapVector<Member<USBConfiguration>> USBDevice::configurations() const {
return configurations_;
}
ScriptPromise<IDLUndefined> USBDevice::open(ScriptState* script_state,
ExceptionState& exception_state) {
EnsureNoDeviceOrInterfaceChangeInProgress(exception_state);
if (exception_state.HadException())
return EmptyPromise();
if (opened_)
return ToResolvedUndefinedPromise(script_state);
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
script_state, exception_state.GetContext());
auto promise = resolver->Promise();
device_state_change_in_progress_ = true;
device_requests_.insert(resolver);
device_->Open(BindOnce(&USBDevice::AsyncOpen, WrapPersistent(this),
WrapPersistent(resolver)));
return promise;
}
ScriptPromise<IDLUndefined> USBDevice::close(ScriptState* script_state,
ExceptionState& exception_state) {
EnsureNoDeviceOrInterfaceChangeInProgress(exception_state);
if (exception_state.HadException())
return EmptyPromise();
if (!opened_)
return ToResolvedUndefinedPromise(script_state);
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
script_state, exception_state.GetContext());
auto promise = resolver->Promise();
device_state_change_in_progress_ = true;
device_requests_.insert(resolver);
device_->Close(BindOnce(&USBDevice::AsyncClose, WrapPersistent(this),
WrapPersistent(resolver)));
return promise;
}
ScriptPromise<IDLUndefined> USBDevice::forget(ScriptState* script_state,
ExceptionState& exception_state) {
if (!GetExecutionContext()) {
exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
"Script context has shut down.");
return EmptyPromise();
}
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
script_state, exception_state.GetContext());
auto promise = resolver->Promise();
parent_->ForgetDevice(device_info_->guid, BindOnce(&USBDevice::AsyncForget,
WrapPersistent(resolver)));
return promise;
}
ScriptPromise<IDLUndefined> USBDevice::selectConfiguration(
ScriptState* script_state,
uint8_t configuration_value,
ExceptionState& exception_state) {
EnsureNoDeviceOrInterfaceChangeInProgress(exception_state);
if (exception_state.HadException())
return EmptyPromise();
if (!opened_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kOpenRequired);
return EmptyPromise();
}
wtf_size_t configuration_index = FindConfigurationIndex(configuration_value);
if (configuration_index == kNotFound) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotFoundError,
"The configuration value provided is not supported by the device.");
return EmptyPromise();
}
if (configuration_index_ == configuration_index)
return ToResolvedUndefinedPromise(script_state);
device_state_change_in_progress_ = true;
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
script_state, exception_state.GetContext());
auto promise = resolver->Promise();
device_requests_.insert(resolver);
device_->SetConfiguration(
configuration_value,
BindOnce(&USBDevice::AsyncSelectConfiguration, WrapPersistent(this),
configuration_index, WrapPersistent(resolver)));
return promise;
}
ScriptPromise<IDLUndefined> USBDevice::claimInterface(
ScriptState* script_state,
uint8_t interface_number,
ExceptionState& exception_state) {
EnsureDeviceConfigured(exception_state);
if (exception_state.HadException())
return EmptyPromise();
wtf_size_t interface_index = FindInterfaceIndex(interface_number);
if (interface_index == kNotFound) {
exception_state.ThrowDOMException(DOMExceptionCode::kNotFoundError,
kInterfaceNotFound);
return EmptyPromise();
}
if (interface_state_change_in_progress_[interface_index]) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kInterfaceStateChangeInProgress);
return EmptyPromise();
}
if (claimed_interfaces_[interface_index])
return ToResolvedUndefinedPromise(script_state);
interface_state_change_in_progress_[interface_index] = true;
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
script_state, exception_state.GetContext());
auto promise = resolver->Promise();
device_requests_.insert(resolver);
device_->ClaimInterface(
interface_number,
BindOnce(&USBDevice::AsyncClaimInterface, WrapPersistent(this),
interface_index, WrapPersistent(resolver)));
return promise;
}
ScriptPromise<IDLUndefined> USBDevice::releaseInterface(
ScriptState* script_state,
uint8_t interface_number,
ExceptionState& exception_state) {
EnsureDeviceConfigured(exception_state);
if (exception_state.HadException())
return EmptyPromise();
wtf_size_t interface_index = FindInterfaceIndex(interface_number);
if (interface_index == kNotFound) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotFoundError,
"The interface number provided is not supported by the device in its "
"current configuration.");
return EmptyPromise();
}
if (interface_state_change_in_progress_[interface_index]) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kInterfaceStateChangeInProgress);
return EmptyPromise();
}
if (!claimed_interfaces_[interface_index])
return ToResolvedUndefinedPromise(script_state);
// Mark this interface's endpoints unavailable while its state is
// changing.
SetEndpointsForInterface(interface_index, false);
interface_state_change_in_progress_[interface_index] = true;
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
script_state, exception_state.GetContext());
auto promise = resolver->Promise();
device_requests_.insert(resolver);
device_->ReleaseInterface(
interface_number,
BindOnce(&USBDevice::AsyncReleaseInterface, WrapPersistent(this),
interface_index, WrapPersistent(resolver)));
return promise;
}
ScriptPromise<IDLUndefined> USBDevice::selectAlternateInterface(
ScriptState* script_state,
uint8_t interface_number,
uint8_t alternate_setting,
ExceptionState& exception_state) {
EnsureInterfaceClaimed(interface_number, exception_state);
if (exception_state.HadException())
return EmptyPromise();
// 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) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotFoundError,
"The alternate setting provided is not supported by the device in its "
"current configuration.");
return EmptyPromise();
}
// 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;
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
script_state, exception_state.GetContext());
auto promise = resolver->Promise();
device_requests_.insert(resolver);
device_->SetInterfaceAlternateSetting(
interface_number, alternate_setting,
BindOnce(&USBDevice::AsyncSelectAlternateInterface, WrapPersistent(this),
interface_index, alternate_index, WrapPersistent(resolver)));
return promise;
}
ScriptPromise<USBInTransferResult> USBDevice::controlTransferIn(
ScriptState* script_state,
const USBControlTransferParameters* setup,
uint16_t length,
ExceptionState& exception_state) {
EnsureNoDeviceOrInterfaceChangeInProgress(exception_state);
if (exception_state.HadException())
return EmptyPromise();
if (!opened_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kOpenRequired);
return EmptyPromise();
}
auto parameters = ConvertControlTransferParameters(setup, exception_state);
if (!parameters)
return EmptyPromise();
auto* resolver =
MakeGarbageCollected<ScriptPromiseResolver<USBInTransferResult>>(
script_state, exception_state.GetContext());
auto promise = resolver->Promise();
device_requests_.insert(resolver);
device_->ControlTransferIn(
std::move(parameters), length, 0,
resolver->WrapCallbackInScriptScope(blink::BindOnce(
&USBDevice::AsyncControlTransferIn, WrapPersistent(this))));
return promise;
}
ScriptPromise<USBOutTransferResult> USBDevice::controlTransferOut(
ScriptState* script_state,
const USBControlTransferParameters* setup,
ExceptionState& exception_state) {
return controlTransferOut(script_state, setup, {}, exception_state);
}
ScriptPromise<USBOutTransferResult> USBDevice::controlTransferOut(
ScriptState* script_state,
const USBControlTransferParameters* setup,
base::span<const uint8_t> data,
ExceptionState& exception_state) {
EnsureNoDeviceOrInterfaceChangeInProgress(exception_state);
if (exception_state.HadException()) {
return EmptyPromise();
}
if (!opened_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kOpenRequired);
return EmptyPromise();
}
auto parameters = ConvertControlTransferParameters(setup, exception_state);
if (!parameters) {
return EmptyPromise();
}
if (ShouldRejectUsbTransferLength(data.size(), exception_state)) {
return EmptyPromise();
}
auto* resolver =
MakeGarbageCollected<ScriptPromiseResolver<USBOutTransferResult>>(
script_state, exception_state.GetContext());
auto promise = resolver->Promise();
device_requests_.insert(resolver);
device_->ControlTransferOut(
std::move(parameters), data, 0,
resolver->WrapCallbackInScriptScope(
BindOnce(&USBDevice::AsyncControlTransferOut, WrapPersistent(this),
static_cast<uint32_t>(data.size()))));
return promise;
}
ScriptPromise<IDLUndefined> USBDevice::clearHalt(
ScriptState* script_state,
const V8USBDirection& direction,
uint8_t endpoint_number,
ExceptionState& exception_state) {
UsbTransferDirection mojo_direction =
direction.AsEnum() == V8USBDirection::Enum::kIn
? UsbTransferDirection::INBOUND
: UsbTransferDirection::OUTBOUND;
EnsureEndpointAvailable(mojo_direction == UsbTransferDirection::INBOUND,
endpoint_number, exception_state);
if (exception_state.HadException())
return EmptyPromise();
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
script_state, exception_state.GetContext());
auto promise = resolver->Promise();
device_requests_.insert(resolver);
device_->ClearHalt(mojo_direction, endpoint_number,
BindOnce(&USBDevice::AsyncClearHalt, WrapPersistent(this),
WrapPersistent(resolver)));
return promise;
}
ScriptPromise<USBInTransferResult> USBDevice::transferIn(
ScriptState* script_state,
uint8_t endpoint_number,
unsigned length,
ExceptionState& exception_state) {
if (ShouldRejectUsbTransferLength(length, exception_state)) {
return EmptyPromise();
}
EnsureEndpointAvailable(/*in_transfer=*/true, endpoint_number,
exception_state);
if (exception_state.HadException())
return EmptyPromise();
auto* resolver =
MakeGarbageCollected<ScriptPromiseResolver<USBInTransferResult>>(
script_state, exception_state.GetContext());
auto promise = resolver->Promise();
device_requests_.insert(resolver);
device_->GenericTransferIn(
endpoint_number, length, 0,
resolver->WrapCallbackInScriptScope(
blink::BindOnce(&USBDevice::AsyncTransferIn, WrapPersistent(this))));
return promise;
}
ScriptPromise<USBOutTransferResult> USBDevice::transferOut(
ScriptState* script_state,
uint8_t endpoint_number,
base::span<const uint8_t> data,
ExceptionState& exception_state) {
EnsureEndpointAvailable(/*in_transfer=*/false, endpoint_number,
exception_state);
if (exception_state.HadException()) {
return EmptyPromise();
}
if (ShouldRejectUsbTransferLength(data.size(), exception_state)) {
return EmptyPromise();
}
auto* resolver =
MakeGarbageCollected<ScriptPromiseResolver<USBOutTransferResult>>(
script_state, exception_state.GetContext());
auto promise = resolver->Promise();
device_requests_.insert(resolver);
device_->GenericTransferOut(
endpoint_number, data, 0,
resolver->WrapCallbackInScriptScope(
BindOnce(&USBDevice::AsyncTransferOut, WrapPersistent(this),
static_cast<uint32_t>(data.size()))));
return promise;
}
ScriptPromise<USBIsochronousInTransferResult> USBDevice::isochronousTransferIn(
ScriptState* script_state,
uint8_t endpoint_number,
Vector<unsigned> packet_lengths,
ExceptionState& exception_state) {
EnsureEndpointAvailable(/*in_transfer=*/true, endpoint_number,
exception_state);
if (exception_state.HadException())
return EmptyPromise();
std::optional<uint32_t> total_bytes = TotalPacketLength(packet_lengths);
if (!total_bytes.has_value()) {
exception_state.ThrowDOMException(DOMExceptionCode::kDataError,
kPacketLengthsTooBig);
return EmptyPromise();
}
if (ShouldRejectUsbTransferLength(total_bytes.value(), exception_state)) {
return EmptyPromise();
}
auto* resolver = MakeGarbageCollected<
ScriptPromiseResolver<USBIsochronousInTransferResult>>(
script_state, exception_state.GetContext());
auto promise = resolver->Promise();
device_requests_.insert(resolver);
device_->IsochronousTransferIn(
endpoint_number, packet_lengths, 0,
resolver->WrapCallbackInScriptScope(blink::BindOnce(
&USBDevice::AsyncIsochronousTransferIn, WrapPersistent(this))));
return promise;
}
ScriptPromise<USBIsochronousOutTransferResult>
USBDevice::isochronousTransferOut(ScriptState* script_state,
uint8_t endpoint_number,
base::span<const uint8_t> data,
Vector<unsigned> packet_lengths,
ExceptionState& exception_state) {
EnsureEndpointAvailable(/*in_transfer=*/false, endpoint_number,
exception_state);
if (exception_state.HadException()) {
return EmptyPromise();
}
std::optional<uint32_t> total_bytes = TotalPacketLength(packet_lengths);
if (!total_bytes.has_value()) {
exception_state.ThrowDOMException(DOMExceptionCode::kDataError,
kPacketLengthsTooBig);
return EmptyPromise();
}
if (ShouldRejectUsbTransferLength(total_bytes.value(), exception_state)) {
return EmptyPromise();
}
if (total_bytes.value() != data.size()) {
exception_state.ThrowDOMException(DOMExceptionCode::kDataError,
kBufferSizeMismatch);
return EmptyPromise();
}
auto* resolver = MakeGarbageCollected<
ScriptPromiseResolver<USBIsochronousOutTransferResult>>(
script_state, exception_state.GetContext());
auto promise = resolver->Promise();
device_requests_.insert(resolver);
device_->IsochronousTransferOut(
endpoint_number, data, packet_lengths, 0,
resolver->WrapCallbackInScriptScope(BindOnce(
&USBDevice::AsyncIsochronousTransferOut, WrapPersistent(this))));
return promise;
}
ScriptPromise<IDLUndefined> USBDevice::reset(ScriptState* script_state,
ExceptionState& exception_state) {
EnsureNoDeviceOrInterfaceChangeInProgress(exception_state);
if (exception_state.HadException())
return EmptyPromise();
if (!opened_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kOpenRequired);
return EmptyPromise();
}
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
script_state, exception_state.GetContext());
auto promise = resolver->Promise();
device_requests_.insert(resolver);
device_->Reset(BindOnce(&USBDevice::AsyncReset, WrapPersistent(this),
WrapPersistent(resolver)));
return promise;
}
void USBDevice::ContextDestroyed() {
device_requests_.clear();
}
void USBDevice::Trace(Visitor* visitor) const {
visitor->Trace(parent_);
visitor->Trace(device_);
visitor->Trace(device_requests_);
visitor->Trace(configurations_);
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;
}
void USBDevice::EnsureNoDeviceChangeInProgress(
ExceptionState& exception_state) const {
if (!device_.is_bound()) {
exception_state.ThrowDOMException(DOMExceptionCode::kNotFoundError,
kDeviceDisconnected);
return;
}
if (device_state_change_in_progress_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kDeviceStateChangeInProgress);
return;
}
}
void USBDevice::EnsureNoDeviceOrInterfaceChangeInProgress(
ExceptionState& exception_state) const {
EnsureNoDeviceChangeInProgress(exception_state);
if (exception_state.HadException())
return;
if (AnyInterfaceChangeInProgress()) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kInterfaceStateChangeInProgress);
return;
}
}
void USBDevice::EnsureDeviceConfigured(ExceptionState& exception_state) const {
EnsureNoDeviceChangeInProgress(exception_state);
if (exception_state.HadException())
return;
if (!opened_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kOpenRequired);
return;
}
if (configuration_index_ == kNotFound) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"The device must have a configuration selected.");
return;
}
}
void USBDevice::EnsureInterfaceClaimed(uint8_t interface_number,
ExceptionState& exception_state) const {
EnsureDeviceConfigured(exception_state);
if (exception_state.HadException())
return;
wtf_size_t interface_index = FindInterfaceIndex(interface_number);
if (interface_index == kNotFound) {
exception_state.ThrowDOMException(DOMExceptionCode::kNotFoundError,
kInterfaceNotFound);
return;
}
if (interface_state_change_in_progress_[interface_index]) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kInterfaceStateChangeInProgress);
return;
}
if (!claimed_interfaces_[interface_index]) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"The specified interface has not been claimed.");
return;
}
}
void USBDevice::EnsureEndpointAvailable(bool in_transfer,
uint8_t endpoint_number,
ExceptionState& exception_state) const {
EnsureDeviceConfigured(exception_state);
if (exception_state.HadException())
return;
if (endpoint_number == 0 || endpoint_number >= kEndpointsBitsNumber) {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
"The specified endpoint number is out of range.");
return;
}
auto& bit_vector = in_transfer ? in_endpoints_ : out_endpoints_;
if (!bit_vector[endpoint_number - 1]) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotFoundError,
"The specified endpoint is not part of a claimed and selected "
"alternate interface.");
return;
}
}
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,
ExceptionState& exception_state) const {
auto mojo_parameters = device::mojom::blink::UsbControlTransferParams::New();
switch (parameters->requestType().AsEnum()) {
case V8USBRequestType::Enum::kStandard:
mojo_parameters->type = UsbControlTransferType::STANDARD;
break;
case V8USBRequestType::Enum::kClass:
mojo_parameters->type = UsbControlTransferType::CLASS;
break;
case V8USBRequestType::Enum::kVendor:
mojo_parameters->type = UsbControlTransferType::VENDOR;
break;
}
switch (parameters->recipient().AsEnum()) {
case V8USBRecipient::Enum::kDevice:
mojo_parameters->recipient = UsbControlTransferRecipient::DEVICE;
break;
case V8USBRecipient::Enum::kInterface: {
uint8_t interface_number = parameters->index() & 0xff;
EnsureInterfaceClaimed(interface_number, exception_state);
if (exception_state.HadException())
return nullptr;
mojo_parameters->recipient = UsbControlTransferRecipient::INTERFACE;
break;
}
case V8USBRecipient::Enum::kEndpoint: {
bool in_transfer = parameters->index() & 0x80;
uint8_t endpoint_number = parameters->index() & 0x0f;
EnsureEndpointAvailable(in_transfer, endpoint_number, exception_state);
if (exception_state.HadException())
return nullptr;
mojo_parameters->recipient = UsbControlTransferRecipient::ENDPOINT;
break;
}
case V8USBRecipient::Enum::kOther:
mojo_parameters->recipient = UsbControlTransferRecipient::OTHER;
break;
}
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_alternate_indices_[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<IDLUndefined>* resolver,
device::mojom::blink::UsbOpenDeviceResultPtr result) {
MarkRequestComplete(resolver);
if (result->is_success()) {
OnDeviceOpenedOrClosed(/*opened=*/true);
resolver->Resolve();
return;
}
DCHECK(result->is_error());
switch (result->get_error()) {
case UsbOpenDeviceError::ACCESS_DENIED:
OnDeviceOpenedOrClosed(false /* not opened */);
resolver->RejectWithSecurityError(kAccessDeniedError, kAccessDeniedError);
break;
case UsbOpenDeviceError::ALREADY_OPEN:
// This class keeps track of open state and won't try to open a device
// that is already open.
NOTREACHED();
}
}
void USBDevice::AsyncClose(ScriptPromiseResolver<IDLUndefined>* resolver) {
MarkRequestComplete(resolver);
OnDeviceOpenedOrClosed(false /* closed */);
resolver->Resolve();
}
void USBDevice::AsyncForget(ScriptPromiseResolver<IDLUndefined>* resolver) {
resolver->Resolve();
}
void USBDevice::OnDeviceOpenedOrClosed(bool opened) {
opened_ = opened;
if (!opened_) {
claimed_interfaces_.Fill(false);
selected_alternate_indices_.Fill(0);
in_endpoints_.reset();
out_endpoints_.reset();
}
device_state_change_in_progress_ = false;
}
void USBDevice::AsyncSelectConfiguration(
wtf_size_t configuration_index,
ScriptPromiseResolver<IDLUndefined>* resolver,
bool success) {
MarkRequestComplete(resolver);
OnConfigurationSelected(success, configuration_index);
if (success) {
resolver->Resolve();
} else {
resolver->RejectWithDOMException(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_alternate_indices_.resize(num_interfaces);
selected_alternate_indices_.Fill(0);
in_endpoints_.reset();
out_endpoints_.reset();
}
device_state_change_in_progress_ = false;
}
void USBDevice::AsyncClaimInterface(
wtf_size_t interface_index,
ScriptPromiseResolver<IDLUndefined>* resolver,
device::mojom::blink::UsbClaimInterfaceResult result) {
MarkRequestComplete(resolver);
OnInterfaceClaimedOrUnclaimed(result == UsbClaimInterfaceResult::kSuccess,
interface_index);
switch (result) {
case UsbClaimInterfaceResult::kSuccess:
resolver->Resolve();
break;
case UsbClaimInterfaceResult::kProtectedClass:
GetExecutionContext()->AddConsoleMessage(
MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kJavaScript,
mojom::blink::ConsoleMessageLevel::kWarning,
"An attempt to claim a USB device interface "
"has been blocked because it "
"implements a protected interface class."));
resolver->RejectWithSecurityError(kProtectedInterfaceClassError,
kProtectedInterfaceClassError);
break;
case UsbClaimInterfaceResult::kFailure:
resolver->RejectWithDOMException(DOMExceptionCode::kNetworkError,
"Unable to claim interface.");
break;
}
}
void USBDevice::AsyncReleaseInterface(
wtf_size_t interface_index,
ScriptPromiseResolver<IDLUndefined>* resolver,
bool success) {
MarkRequestComplete(resolver);
OnInterfaceClaimedOrUnclaimed(!success, interface_index);
if (success) {
resolver->Resolve();
} else {
resolver->RejectWithDOMException(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_alternate_indices_[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<IDLUndefined>* resolver,
bool success) {
MarkRequestComplete(resolver);
if (success)
selected_alternate_indices_[interface_index] = alternate_index;
SetEndpointsForInterface(interface_index, success);
interface_state_change_in_progress_[interface_index] = false;
if (success) {
resolver->Resolve();
} else {
resolver->RejectWithDOMException(DOMExceptionCode::kNetworkError,
"Unable to set device interface.");
}
}
void USBDevice::AsyncControlTransferIn(
ScriptPromiseResolver<USBInTransferResult>* resolver,
UsbTransferStatus status,
base::span<const uint8_t> data) {
MarkRequestComplete(resolver);
if (CheckFatalTransferStatus(resolver, status))
return;
resolver->Resolve(
USBInTransferResult::Create(ConvertTransferStatus(status), data));
}
void USBDevice::AsyncControlTransferOut(
uint32_t transfer_length,
ScriptPromiseResolver<USBOutTransferResult>* resolver,
UsbTransferStatus status) {
MarkRequestComplete(resolver);
if (CheckFatalTransferStatus(resolver, status))
return;
resolver->Resolve(USBOutTransferResult::Create(ConvertTransferStatus(status),
transfer_length));
}
void USBDevice::AsyncClearHalt(ScriptPromiseResolver<IDLUndefined>* resolver,
bool success) {
MarkRequestComplete(resolver);
if (success) {
resolver->Resolve();
} else {
resolver->RejectWithDOMException(DOMExceptionCode::kNetworkError,
"Unable to clear endpoint.");
}
}
void USBDevice::AsyncTransferIn(
ScriptPromiseResolver<USBInTransferResult>* resolver,
UsbTransferStatus status,
base::span<const uint8_t> data) {
MarkRequestComplete(resolver);
if (CheckFatalTransferStatus(resolver, status))
return;
resolver->Resolve(
USBInTransferResult::Create(ConvertTransferStatus(status), data));
}
void USBDevice::AsyncTransferOut(
uint32_t transfer_length,
ScriptPromiseResolver<USBOutTransferResult>* resolver,
UsbTransferStatus status) {
MarkRequestComplete(resolver);
if (CheckFatalTransferStatus(resolver, status))
return;
resolver->Resolve(USBOutTransferResult::Create(ConvertTransferStatus(status),
transfer_length));
}
void USBDevice::AsyncIsochronousTransferIn(
ScriptPromiseResolver<USBIsochronousInTransferResult>* resolver,
base::span<const uint8_t> data,
Vector<UsbIsochronousPacketPtr> mojo_packets) {
MarkRequestComplete(resolver);
DOMArrayBuffer* buffer = DOMArrayBuffer::Create(data);
HeapVector<Member<USBIsochronousInTransferPacket>> packets;
packets.reserve(mojo_packets.size());
uint32_t byte_offset = 0;
for (const auto& packet : mojo_packets) {
if (CheckFatalTransferStatus(resolver, packet->status))
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<USBIsochronousOutTransferResult>* resolver,
Vector<UsbIsochronousPacketPtr> mojo_packets) {
MarkRequestComplete(resolver);
HeapVector<Member<USBIsochronousOutTransferPacket>> packets;
packets.reserve(mojo_packets.size());
for (const auto& packet : mojo_packets) {
if (CheckFatalTransferStatus(resolver, packet->status))
return;
packets.push_back(USBIsochronousOutTransferPacket::Create(
ConvertTransferStatus(packet->status), packet->transferred_length));
}
resolver->Resolve(USBIsochronousOutTransferResult::Create(packets));
}
void USBDevice::AsyncReset(ScriptPromiseResolver<IDLUndefined>* resolver,
bool success) {
MarkRequestComplete(resolver);
if (success) {
resolver->Resolve();
} else {
resolver->RejectWithDOMException(DOMExceptionCode::kNetworkError,
"Unable to reset the device.");
}
}
void USBDevice::OnConnectionError() {
device_.reset();
opened_ = false;
for (auto& resolver : device_requests_) {
ScriptState* script_state = resolver->GetScriptState();
if (IsInParallelAlgorithmRunnable(resolver->GetExecutionContext(),
script_state)) {
ScriptState::Scope script_state_scope(script_state);
resolver->RejectWithDOMException(DOMExceptionCode::kNotFoundError,
kDeviceDisconnected);
}
}
device_requests_.clear();
}
void USBDevice::MarkRequestComplete(ScriptPromiseResolverBase* resolver) {
auto request_entry = device_requests_.find(resolver);
// Since all callbacks are wrapped with a check that the execution context is
// still valid we can guarantee that `device_requests_` hasn't been cleared
// yet if we are in this function.
CHECK(request_entry != device_requests_.end());
device_requests_.erase(request_entry);
}
} // namespace blink