blob: 5717c764323c9a6c5622bb71e7f4509a9079e6bc [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/browser/api/usb/usb_api.h"
#include <algorithm>
#include <memory>
#include <numeric>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/barrier_closure.h"
#include "base/functional/bind.h"
#include "base/values.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/api/api_resource_manager.h"
#include "extensions/browser/api/device_permissions_manager.h"
#include "extensions/browser/api/device_permissions_prompt.h"
#include "extensions/browser/api/extensions_api_client.h"
#include "extensions/browser/api/usb/usb_device_resource.h"
#include "extensions/browser/extension_function_constants.h"
#include "extensions/common/api/usb.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/permissions/usb_device_permission.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "services/device/public/cpp/usb/usb_utils.h"
#include "services/device/public/mojom/usb_device.mojom.h"
#include "services/device/public/mojom/usb_enumeration_options.mojom.h"
namespace usb = extensions::api::usb;
namespace BulkTransfer = usb::BulkTransfer;
namespace ClaimInterface = usb::ClaimInterface;
namespace CloseDevice = usb::CloseDevice;
namespace ControlTransfer = usb::ControlTransfer;
namespace FindDevices = usb::FindDevices;
namespace GetConfigurations = usb::GetConfigurations;
namespace GetDevices = usb::GetDevices;
namespace GetUserSelectedDevices = usb::GetUserSelectedDevices;
namespace InterruptTransfer = usb::InterruptTransfer;
namespace IsochronousTransfer = usb::IsochronousTransfer;
namespace SetConfiguration = usb::SetConfiguration;
namespace GetConfiguration = usb::GetConfiguration;
namespace ListInterfaces = usb::ListInterfaces;
namespace OpenDevice = usb::OpenDevice;
namespace ReleaseInterface = usb::ReleaseInterface;
namespace RequestAccess = usb::RequestAccess;
namespace ResetDevice = usb::ResetDevice;
namespace SetInterfaceAlternateSetting = usb::SetInterfaceAlternateSetting;
using content::BrowserThread;
using device::mojom::UsbClaimInterfaceResult;
using device::mojom::UsbControlTransferParams;
using device::mojom::UsbControlTransferRecipient;
using device::mojom::UsbControlTransferType;
using device::mojom::UsbDeviceFilterPtr;
using device::mojom::UsbIsochronousPacketPtr;
using device::mojom::UsbSynchronizationType;
using device::mojom::UsbTransferDirection;
using device::mojom::UsbTransferStatus;
using device::mojom::UsbTransferType;
using device::mojom::UsbUsageType;
using std::string;
using std::vector;
using usb::ConfigDescriptor;
using usb::ConnectionHandle;
using usb::ControlTransferInfo;
using usb::Device;
using usb::Direction;
using usb::EndpointDescriptor;
using usb::GenericTransferInfo;
using usb::InterfaceDescriptor;
using usb::IsochronousTransferInfo;
using usb::Recipient;
using usb::RequestType;
using usb::SynchronizationType;
using usb::TransferType;
using usb::UsageType;
namespace extensions {
namespace {
const char kDataKey[] = "data";
const char kResultCodeKey[] = "resultCode";
const char kErrorInitService[] = "Failed to initialize USB service.";
const char kErrorOpen[] = "Failed to open device.";
const char kErrorCancelled[] = "Transfer was cancelled.";
const char kErrorDisconnect[] = "Device disconnected.";
const char kErrorGeneric[] = "Transfer failed.";
const char kErrorNotSupported[] = "Not supported on this platform.";
const char kErrorNotConfigured[] = "The device is not in a configured state.";
const char kErrorOverflow[] = "Inbound transfer overflow.";
const char kErrorStalled[] = "Transfer stalled.";
const char kErrorTimeout[] = "Transfer timed out.";
const char kErrorTransferLength[] = "Transfer length is insufficient.";
const char kErrorCannotSetConfiguration[] =
"Error setting device configuration.";
const char kErrorCannotClaimInterface[] = "Error claiming interface.";
const char kErrorCannotReleaseInterface[] = "Error releasing interface.";
const char kErrorCannotSetInterfaceAlternateSetting[] =
"Error setting alternate interface setting.";
const char kErrorConvertDirection[] = "Invalid transfer direction.";
const char kErrorConvertRecipient[] = "Invalid transfer recipient.";
const char kErrorConvertRequestType[] = "Invalid request type.";
const char kErrorMalformedParameters[] = "Error parsing parameters.";
const char kErrorNoConnection[] = "No such connection.";
const char kErrorNoDevice[] = "No such device.";
const char kErrorPermissionDenied[] = "Permission to access device was denied";
const char kErrorInvalidTransferLength[] =
"Transfer length must be a positive number less than 104,857,600.";
const char kErrorInvalidNumberOfPackets[] =
"Number of packets must be a positive number less than 4,194,304.";
const char kErrorInvalidPacketLength[] =
"Packet length must be a positive number less than 65,536.";
const char kErrorInvalidTimeout[] =
"Transfer timeout must be greater than or equal to 0.";
const char kErrorResetDevice[] =
"Error resetting the device. The device has been closed.";
const size_t kMaxTransferLength = 100 * 1024 * 1024;
const int kMaxPackets = 4 * 1024 * 1024;
const int kMaxPacketLength = 64 * 1024;
bool ConvertDirectionFromApi(const Direction& input,
UsbTransferDirection* output) {
switch (input) {
case usb::Direction::kIn:
*output = UsbTransferDirection::INBOUND;
return true;
case usb::Direction::kOut:
*output = UsbTransferDirection::OUTBOUND;
return true;
default:
NOTREACHED();
}
}
bool ConvertRequestTypeFromApi(const RequestType& input,
UsbControlTransferType* output) {
switch (input) {
case usb::RequestType::kStandard:
*output = UsbControlTransferType::STANDARD;
return true;
case usb::RequestType::kClass:
*output = UsbControlTransferType::CLASS;
return true;
case usb::RequestType::kVendor:
*output = UsbControlTransferType::VENDOR;
return true;
case usb::RequestType::kReserved:
*output = UsbControlTransferType::RESERVED;
return true;
default:
NOTREACHED();
}
}
bool ConvertRecipientFromApi(const Recipient& input,
UsbControlTransferRecipient* output) {
switch (input) {
case usb::Recipient::kDevice:
*output = UsbControlTransferRecipient::DEVICE;
return true;
case usb::Recipient::kInterface:
*output = UsbControlTransferRecipient::INTERFACE;
return true;
case usb::Recipient::kEndpoint:
*output = UsbControlTransferRecipient::ENDPOINT;
return true;
case usb::Recipient::kOther:
*output = UsbControlTransferRecipient::OTHER;
return true;
default:
NOTREACHED();
}
}
template <class T>
bool GetTransferInSize(const T& input, uint32_t* output) {
const auto& length = input.length;
if (length && *length >= 0 &&
static_cast<uint32_t>(*length) < kMaxTransferLength) {
*output = *length;
return true;
}
return false;
}
const char* ConvertTransferStatusToApi(const UsbTransferStatus status) {
switch (status) {
case UsbTransferStatus::COMPLETED:
return "";
case UsbTransferStatus::TRANSFER_ERROR:
return kErrorGeneric;
case UsbTransferStatus::TIMEOUT:
return kErrorTimeout;
case UsbTransferStatus::CANCELLED:
return kErrorCancelled;
case UsbTransferStatus::STALLED:
return kErrorStalled;
case UsbTransferStatus::DISCONNECT:
return kErrorDisconnect;
case UsbTransferStatus::BABBLE:
return kErrorOverflow;
case UsbTransferStatus::SHORT_PACKET:
return kErrorTransferLength;
default:
DUMP_WILL_BE_NOTREACHED();
return "";
}
}
base::Value::Dict PopulateConnectionHandle(int handle,
int vendor_id,
int product_id) {
ConnectionHandle result;
result.handle = handle;
result.vendor_id = vendor_id;
result.product_id = product_id;
return result.ToValue();
}
TransferType ConvertTransferTypeToApi(const UsbTransferType& input) {
switch (input) {
case UsbTransferType::CONTROL:
return usb::TransferType::kControl;
case UsbTransferType::INTERRUPT:
return usb::TransferType::kInterrupt;
case UsbTransferType::ISOCHRONOUS:
return usb::TransferType::kIsochronous;
case UsbTransferType::BULK:
return usb::TransferType::kBulk;
default:
NOTREACHED();
}
}
Direction ConvertDirectionToApi(const UsbTransferDirection& input) {
switch (input) {
case UsbTransferDirection::INBOUND:
return usb::Direction::kIn;
case UsbTransferDirection::OUTBOUND:
return usb::Direction::kOut;
default:
NOTREACHED();
}
}
SynchronizationType ConvertSynchronizationTypeToApi(
const UsbSynchronizationType& input) {
switch (input) {
case UsbSynchronizationType::NONE:
return usb::SynchronizationType::kNone;
case UsbSynchronizationType::ASYNCHRONOUS:
return usb::SynchronizationType::kAsynchronous;
case UsbSynchronizationType::ADAPTIVE:
return usb::SynchronizationType::kAdaptive;
case UsbSynchronizationType::SYNCHRONOUS:
return usb::SynchronizationType::kSynchronous;
default:
NOTREACHED();
}
}
usb::UsageType ConvertUsageTypeToApi(const UsbUsageType& input) {
switch (input) {
case UsbUsageType::DATA:
return usb::UsageType::kData;
case UsbUsageType::FEEDBACK:
return usb::UsageType::kFeedback;
case UsbUsageType::EXPLICIT_FEEDBACK:
return usb::UsageType::kExplicitFeedback;
case UsbUsageType::PERIODIC:
return usb::UsageType::kPeriodic;
case UsbUsageType::NOTIFICATION:
return usb::UsageType::kNotification;
case UsbUsageType::RESERVED:
return usb::UsageType::kNone;
default:
NOTREACHED();
}
}
EndpointDescriptor ConvertEndpointDescriptor(
const device::mojom::UsbEndpointInfo& input) {
EndpointDescriptor output;
output.address = device::ConvertEndpointNumberToAddress(input);
output.type = ConvertTransferTypeToApi(input.type);
output.direction = ConvertDirectionToApi(input.direction);
output.maximum_packet_size = input.packet_size;
output.synchronization =
ConvertSynchronizationTypeToApi(input.synchronization_type);
output.usage = ConvertUsageTypeToApi(input.usage_type);
output.polling_interval = input.polling_interval;
output.extra_data.assign(input.extra_data.begin(), input.extra_data.end());
return output;
}
InterfaceDescriptor ConvertInterfaceDescriptor(
uint8_t interface_number,
const device::mojom::UsbAlternateInterfaceInfo& input) {
InterfaceDescriptor output;
output.interface_number = interface_number;
output.alternate_setting = input.alternate_setting;
output.interface_class = input.class_code;
output.interface_subclass = input.subclass_code;
output.interface_protocol = input.protocol_code;
for (const auto& input_endpoint : input.endpoints) {
DCHECK(input_endpoint);
output.endpoints.push_back(ConvertEndpointDescriptor(*input_endpoint));
}
output.extra_data.assign(input.extra_data.begin(), input.extra_data.end());
return output;
}
ConfigDescriptor ConvertConfigDescriptor(
const device::mojom::UsbConfigurationInfo& input) {
ConfigDescriptor output;
output.configuration_value = input.configuration_value;
output.self_powered = input.self_powered;
output.remote_wakeup = input.remote_wakeup;
output.max_power = input.maximum_power;
for (const auto& input_interface : input.interfaces) {
DCHECK(input_interface);
// device::mojom::UsbInterfaceInfo aggregated all alternate settings
// with the same interface number.
for (const auto& alternate : input_interface->alternates) {
DCHECK(alternate);
output.interfaces.push_back(ConvertInterfaceDescriptor(
input_interface->interface_number, *alternate));
}
}
output.extra_data.assign(input.extra_data.begin(), input.extra_data.end());
return output;
}
device::mojom::UsbDeviceFilterPtr ConvertDeviceFilter(
const usb::DeviceFilter& input) {
auto output = device::mojom::UsbDeviceFilter::New();
if (input.vendor_id) {
output->has_vendor_id = true;
output->vendor_id = *input.vendor_id;
}
if (input.product_id) {
output->has_product_id = true;
output->product_id = *input.product_id;
}
if (input.interface_class) {
output->has_class_code = true;
output->class_code = *input.interface_class;
}
if (input.interface_subclass) {
output->has_subclass_code = true;
output->subclass_code = *input.interface_subclass;
}
if (input.interface_protocol) {
output->has_protocol_code = true;
output->protocol_code = *input.interface_protocol;
}
return output;
}
} // namespace
UsbExtensionFunction::UsbExtensionFunction() = default;
UsbExtensionFunction::~UsbExtensionFunction() = default;
UsbDeviceManager* UsbExtensionFunction::usb_device_manager() {
if (!usb_device_manager_) {
usb_device_manager_ = UsbDeviceManager::Get(browser_context());
}
return usb_device_manager_;
}
bool UsbExtensionFunction::IsUsbDeviceAllowedByPolicy(int vendor_id,
int product_id) {
ExtensionsBrowserClient* client = ExtensionsBrowserClient::Get();
DCHECK(client);
return client->IsUsbDeviceAllowedByPolicy(browser_context(), extension_id(),
vendor_id, product_id);
}
UsbPermissionCheckingFunction::UsbPermissionCheckingFunction()
: device_permissions_manager_(nullptr) {}
UsbPermissionCheckingFunction::~UsbPermissionCheckingFunction() = default;
bool UsbPermissionCheckingFunction::HasDevicePermission(
const device::mojom::UsbDeviceInfo& device) {
if (!device_permissions_manager_) {
device_permissions_manager_ =
DevicePermissionsManager::Get(browser_context());
}
DevicePermissions* device_permissions =
device_permissions_manager_->GetForExtension(extension_id());
DCHECK(device_permissions);
permission_entry_ = device_permissions->FindUsbDeviceEntry(device);
if (permission_entry_.get()) {
return true;
}
std::unique_ptr<UsbDevicePermission::CheckParam> param =
UsbDevicePermission::CheckParam::ForUsbDevice(extension(), device);
if (extension()->permissions_data()->CheckAPIPermissionWithParam(
mojom::APIPermissionID::kUsbDevice, param.get())) {
return true;
}
if (IsUsbDeviceAllowedByPolicy(device.vendor_id, device.product_id)) {
return true;
}
return false;
}
void UsbPermissionCheckingFunction::RecordDeviceLastUsed() {
if (permission_entry_.get()) {
device_permissions_manager_->UpdateLastUsed(extension_id(),
permission_entry_);
}
}
UsbConnectionFunction::UsbConnectionFunction() = default;
UsbConnectionFunction::~UsbConnectionFunction() = default;
UsbDeviceResource* UsbConnectionFunction::GetResourceFromHandle(
const ConnectionHandle& handle) {
ApiResourceManager<UsbDeviceResource>* manager =
ApiResourceManager<UsbDeviceResource>::Get(browser_context());
if (!manager) {
return nullptr;
}
return manager->Get(extension_id(), handle.handle);
}
device::mojom::UsbDevice* UsbConnectionFunction::GetDeviceFromHandle(
const ConnectionHandle& handle) {
UsbDeviceResource* resource = GetResourceFromHandle(handle);
if (!resource) {
return nullptr;
}
return resource->device();
}
const device::mojom::UsbDeviceInfo*
UsbConnectionFunction::GetDeviceInfoFromHandle(const ConnectionHandle& handle) {
UsbDeviceResource* resource = GetResourceFromHandle(handle);
if (!resource || !resource->device()) {
return nullptr;
}
auto* device_manager = usb_device_manager();
if (!device_manager) {
return nullptr;
}
return device_manager->GetDeviceInfo(resource->guid());
}
void UsbConnectionFunction::ReleaseDeviceResource(
const ConnectionHandle& handle) {
ApiResourceManager<UsbDeviceResource>* manager =
ApiResourceManager<UsbDeviceResource>::Get(browser_context());
manager->Remove(extension_id(), handle.handle);
}
UsbTransferFunction::UsbTransferFunction() = default;
UsbTransferFunction::~UsbTransferFunction() = default;
void UsbTransferFunction::OnCompleted(UsbTransferStatus status,
base::Value::Dict transfer_info) {
if (status == UsbTransferStatus::COMPLETED) {
Respond(WithArguments(std::move(transfer_info)));
} else {
base::Value::List error_args;
error_args.Append(std::move(transfer_info));
// Using ErrorWithArguments is discouraged but required to provide the
// detailed transfer info as the transfer may have partially succeeded.
Respond(ErrorWithArgumentsDoNotUse(std::move(error_args),
ConvertTransferStatusToApi(status)));
}
}
void UsbTransferFunction::OnTransferInCompleted(
UsbTransferStatus status,
base::span<const uint8_t> data) {
base::Value::Dict transfer_info;
transfer_info.Set(kResultCodeKey, static_cast<int>(status));
transfer_info.Set(kDataKey, base::Value(data));
OnCompleted(status, std::move(transfer_info));
}
void UsbTransferFunction::OnTransferOutCompleted(UsbTransferStatus status) {
base::Value::Dict transfer_info;
transfer_info.Set(kResultCodeKey, static_cast<int>(status));
transfer_info.Set(kDataKey, base::Value(base::Value::Type::BINARY));
OnCompleted(status, std::move(transfer_info));
}
void UsbTransferFunction::OnDisconnect() {
const auto status = UsbTransferStatus::DISCONNECT;
base::Value::Dict transfer_info;
transfer_info.Set(kResultCodeKey, static_cast<int>(status));
OnCompleted(status, std::move(transfer_info));
}
UsbGenericTransferFunction::UsbGenericTransferFunction() = default;
UsbGenericTransferFunction::~UsbGenericTransferFunction() = default;
// const usb::InterruptTransfer::Params* or
// const usb::BulkTransfer::Params*
template <typename T>
ExtensionFunction::ResponseAction UsbGenericTransferFunction::DoTransfer(
const T& params) {
device::mojom::UsbDevice* device = GetDeviceFromHandle(params->handle);
if (!device) {
return RespondNow(Error(kErrorNoConnection));
}
const GenericTransferInfo& transfer = params->transfer_info;
UsbTransferDirection direction = UsbTransferDirection::INBOUND;
if (!ConvertDirectionFromApi(transfer.direction, &direction)) {
return RespondNow(Error(kErrorConvertDirection));
}
int timeout = transfer.timeout ? *transfer.timeout : 0;
if (timeout < 0) {
return RespondNow(Error(kErrorInvalidTimeout));
}
if (direction == UsbTransferDirection::INBOUND) {
uint32_t size = 0;
if (!GetTransferInSize(transfer, &size)) {
return RespondNow(Error(kErrorInvalidTransferLength));
}
device->GenericTransferIn(
transfer.endpoint, size, timeout,
mojo::WrapCallbackWithDropHandler(
base::BindOnce(&UsbGenericTransferFunction::OnTransferInCompleted,
this),
base::BindOnce(&UsbGenericTransferFunction::OnDisconnect, this)));
} else {
// For case direction == UsbTransferDirection::OUTBOUND.
if (!transfer.data) {
return RespondNow(Error(kErrorMalformedParameters));
}
device->GenericTransferOut(
transfer.endpoint, *transfer.data, timeout,
mojo::WrapCallbackWithDropHandler(
base::BindOnce(&UsbGenericTransferFunction::OnTransferOutCompleted,
this),
base::BindOnce(&UsbGenericTransferFunction::OnDisconnect, this)));
}
return RespondLater();
}
UsbFindDevicesFunction::UsbFindDevicesFunction() = default;
UsbFindDevicesFunction::~UsbFindDevicesFunction() = default;
ExtensionFunction::ResponseAction UsbFindDevicesFunction::Run() {
std::optional<usb::FindDevices::Params> parameters =
FindDevices::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(parameters);
vendor_id_ = parameters->options.vendor_id;
product_id_ = parameters->options.product_id;
int interface_id = parameters->options.interface_id.value_or(
UsbDevicePermissionData::SPECIAL_VALUE_ANY);
// Bail out early if there is no chance that the app has manifest permission
// for the USB device described by vendor ID, product ID, and interface ID.
// Note that this will match any permission filter that has only interface
// class specified - in order to match interface class information about
// device interfaces is needed, which is not known at this point; the
// permission will have to be checked again when the USB device info is
// fetched.
std::unique_ptr<UsbDevicePermission::CheckParam> param =
UsbDevicePermission::CheckParam::ForDeviceWithAnyInterfaceClass(
extension(), vendor_id_, product_id_, interface_id);
if (!extension()->permissions_data()->CheckAPIPermissionWithParam(
mojom::APIPermissionID::kUsbDevice, param.get()) &&
!IsUsbDeviceAllowedByPolicy(vendor_id_, product_id_)) {
return RespondNow(Error(kErrorPermissionDenied));
}
auto* device_manager = usb_device_manager();
if (!device_manager) {
return RespondNow(Error(kErrorInitService));
}
device_manager->GetDevices(
base::BindOnce(&UsbFindDevicesFunction::OnGetDevicesComplete, this));
return RespondLater();
}
void UsbFindDevicesFunction::OnGetDevicesComplete(
std::vector<device::mojom::UsbDeviceInfoPtr> devices) {
barrier_ = base::BarrierClosure(
devices.size(),
base::BindOnce(&UsbFindDevicesFunction::OpenComplete, this));
for (const auto& device_info : devices) {
// Skip the device whose vendor and product ID do not match the target one.
if (device_info->vendor_id != vendor_id_ ||
device_info->product_id != product_id_) {
barrier_.Run();
continue;
}
// Verify that the app has permission for the device again, this time taking
// device's interface classes into account - in case there is a USB device
// permission specifying only interfaceClass, permissions check in |Run|
// might have passed even though the app did not have permission for
// specified vendor and product ID (as actual permissions check had to be
// deferred until device's interface classes are known).
std::unique_ptr<UsbDevicePermission::CheckParam> param =
UsbDevicePermission::CheckParam::ForUsbDevice(extension(),
*device_info);
if (!extension()->permissions_data()->CheckAPIPermissionWithParam(
mojom::APIPermissionID::kUsbDevice, param.get()) &&
!IsUsbDeviceAllowedByPolicy(vendor_id_, product_id_)) {
barrier_.Run();
} else {
mojo::Remote<device::mojom::UsbDevice> device;
usb_device_manager()->GetDevice(device_info->guid,
device.BindNewPipeAndPassReceiver());
auto* device_raw = device.get();
device_raw->Open(mojo::WrapCallbackWithDropHandler(
base::BindOnce(&UsbFindDevicesFunction::OnDeviceOpened, this,
device_info->guid, std::move(device)),
base::BindOnce(&UsbFindDevicesFunction::OnDisconnect, this)));
}
}
}
void UsbFindDevicesFunction::OnDeviceOpened(
const std::string& guid,
mojo::Remote<device::mojom::UsbDevice> device,
device::mojom::UsbOpenDeviceResultPtr result) {
if (result->is_success() && device) {
ApiResourceManager<UsbDeviceResource>* manager =
ApiResourceManager<UsbDeviceResource>::Get(browser_context());
UsbDeviceResource* resource =
new UsbDeviceResource(extension_id(), guid, std::move(device));
result_.Append(PopulateConnectionHandle(manager->Add(resource), vendor_id_,
product_id_));
}
barrier_.Run();
}
void UsbFindDevicesFunction::OnDisconnect() {
barrier_.Run();
}
void UsbFindDevicesFunction::OpenComplete() {
Respond(WithArguments(std::move(result_)));
}
UsbGetDevicesFunction::UsbGetDevicesFunction() = default;
UsbGetDevicesFunction::~UsbGetDevicesFunction() = default;
ExtensionFunction::ResponseAction UsbGetDevicesFunction::Run() {
std::optional<usb::GetDevices::Params> parameters =
GetDevices::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(parameters);
if (parameters->options.filters) {
filters_.reserve(parameters->options.filters->size());
for (const auto& filter : *parameters->options.filters)
filters_.push_back(ConvertDeviceFilter(filter));
}
if (parameters->options.vendor_id) {
auto filter = device::mojom::UsbDeviceFilter::New();
filter->has_vendor_id = true;
filter->vendor_id = *parameters->options.vendor_id;
if (parameters->options.product_id) {
filter->has_product_id = true;
filter->product_id = *parameters->options.product_id;
}
filters_.push_back(std::move(filter));
}
auto* device_manager = usb_device_manager();
if (!device_manager) {
return RespondNow(Error(kErrorInitService));
}
device_manager->GetDevices(
base::BindOnce(&UsbGetDevicesFunction::OnGetDevicesComplete, this));
return RespondLater();
}
void UsbGetDevicesFunction::OnGetDevicesComplete(
std::vector<device::mojom::UsbDeviceInfoPtr> devices) {
base::Value::List result;
for (const auto& device : devices) {
if (device::UsbDeviceFilterMatchesAny(filters_, *device) &&
HasDevicePermission(*device)) {
Device api_device;
usb_device_manager()->GetApiDevice(*device, &api_device);
result.Append(api_device.ToValue());
}
}
Respond(WithArguments(std::move(result)));
}
UsbGetUserSelectedDevicesFunction::UsbGetUserSelectedDevicesFunction() =
default;
UsbGetUserSelectedDevicesFunction::~UsbGetUserSelectedDevicesFunction() =
default;
ExtensionFunction::ResponseAction UsbGetUserSelectedDevicesFunction::Run() {
std::optional<usb::GetUserSelectedDevices::Params> parameters =
GetUserSelectedDevices::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(parameters);
if (!user_gesture()) {
return RespondNow(WithArguments(base::Value::List()));
}
bool multiple = false;
if (parameters->options.multiple) {
multiple = *parameters->options.multiple;
}
std::vector<UsbDeviceFilterPtr> filters;
if (parameters->options.filters) {
filters.reserve(parameters->options.filters->size());
for (const auto& filter : *parameters->options.filters)
filters.push_back(ConvertDeviceFilter(filter));
}
content::WebContents* web_contents = GetSenderWebContents();
if (!web_contents) {
return RespondNow(
Error(function_constants::kCouldNotFindSenderWebContents));
}
prompt_ =
ExtensionsAPIClient::Get()->CreateDevicePermissionsPrompt(web_contents);
if (!prompt_) {
return RespondNow(Error(kErrorNotSupported));
}
prompt_->AskForUsbDevices(
extension(), browser_context(), multiple, std::move(filters),
base::BindOnce(&UsbGetUserSelectedDevicesFunction::OnDevicesChosen,
this));
return RespondLater();
}
void UsbGetUserSelectedDevicesFunction::OnDevicesChosen(
std::vector<device::mojom::UsbDeviceInfoPtr> devices) {
base::Value::List result;
auto* device_manager = usb_device_manager();
DCHECK(device_manager);
for (const auto& device : devices) {
Device api_device;
device_manager->GetApiDevice(*device, &api_device);
result.Append(api_device.ToValue());
}
Respond(WithArguments(std::move(result)));
}
UsbGetConfigurationsFunction::UsbGetConfigurationsFunction() = default;
UsbGetConfigurationsFunction::~UsbGetConfigurationsFunction() = default;
ExtensionFunction::ResponseAction UsbGetConfigurationsFunction::Run() {
std::optional<usb::GetConfigurations::Params> parameters =
GetConfigurations::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(parameters);
auto* device_manager = usb_device_manager();
if (!device_manager) {
return RespondNow(Error(kErrorInitService));
}
std::string guid;
if (!device_manager->GetGuidFromId(parameters->device.device, &guid)) {
return RespondNow(Error(kErrorNoDevice));
}
const auto* device_info = device_manager->GetDeviceInfo(guid);
if (!device_info) {
return RespondNow(Error(kErrorNoDevice));
}
if (!HasDevicePermission(*device_info)) {
// This function must act as if there is no such device. Otherwise it can be
// used to fingerprint unauthorized devices.
return RespondNow(Error(kErrorNoDevice));
}
base::Value::List configs;
uint8_t active_config_value = device_info->active_configuration;
for (const auto& config : device_info->configurations) {
DCHECK(config);
ConfigDescriptor api_config = ConvertConfigDescriptor(*config);
if (active_config_value &&
config->configuration_value == active_config_value) {
api_config.active = true;
}
configs.Append(api_config.ToValue());
}
return RespondNow(WithArguments(std::move(configs)));
}
UsbRequestAccessFunction::UsbRequestAccessFunction() = default;
UsbRequestAccessFunction::~UsbRequestAccessFunction() = default;
ExtensionFunction::ResponseAction UsbRequestAccessFunction::Run() {
std::optional<usb::RequestAccess::Params> parameters =
RequestAccess::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(parameters);
return RespondNow(WithArguments(true));
}
UsbOpenDeviceFunction::UsbOpenDeviceFunction() = default;
UsbOpenDeviceFunction::~UsbOpenDeviceFunction() = default;
ExtensionFunction::ResponseAction UsbOpenDeviceFunction::Run() {
std::optional<usb::OpenDevice::Params> parameters =
OpenDevice::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(parameters);
auto* device_manager = usb_device_manager();
if (!device_manager) {
return RespondNow(Error(kErrorInitService));
}
std::string guid;
if (!device_manager->GetGuidFromId(parameters->device.device, &guid)) {
return RespondNow(Error(kErrorNoDevice));
}
const device::mojom::UsbDeviceInfo* device_info =
device_manager->GetDeviceInfo(guid);
if (!device_info) {
return RespondNow(Error(kErrorNoDevice));
}
if (!HasDevicePermission(*device_info)) {
// This function must act as if there is no such device. Otherwise it can be
// used to fingerprint unauthorized devices.
return RespondNow(Error(kErrorNoDevice));
}
mojo::Remote<device::mojom::UsbDevice> device;
device_manager->GetDevice(device_info->guid,
device.BindNewPipeAndPassReceiver());
auto* device_raw = device.get();
device_raw->Open(mojo::WrapCallbackWithDropHandler(
base::BindOnce(&UsbOpenDeviceFunction::OnDeviceOpened, this,
device_info->guid, std::move(device)),
base::BindOnce(&UsbOpenDeviceFunction::OnDisconnect, this)));
return RespondLater();
}
void UsbOpenDeviceFunction::OnDeviceOpened(
std::string guid,
mojo::Remote<device::mojom::UsbDevice> device,
device::mojom::UsbOpenDeviceResultPtr result) {
if (result->is_error() || !device) {
Respond(Error(kErrorOpen));
return;
}
RecordDeviceLastUsed();
ApiResourceManager<UsbDeviceResource>* manager =
ApiResourceManager<UsbDeviceResource>::Get(browser_context());
const device::mojom::UsbDeviceInfo* device_info =
usb_device_manager()->GetDeviceInfo(guid);
DCHECK(device_info);
UsbDeviceResource* resource = new UsbDeviceResource(
extension_id(), device_info->guid, std::move(device));
Respond(WithArguments(PopulateConnectionHandle(manager->Add(resource),
device_info->vendor_id,
device_info->product_id)));
}
void UsbOpenDeviceFunction::OnDisconnect() {
Respond(Error(kErrorDisconnect));
}
UsbSetConfigurationFunction::UsbSetConfigurationFunction() = default;
UsbSetConfigurationFunction::~UsbSetConfigurationFunction() = default;
ExtensionFunction::ResponseAction UsbSetConfigurationFunction::Run() {
std::optional<usb::SetConfiguration::Params> parameters =
SetConfiguration::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(parameters);
UsbDeviceResource* resource = GetResourceFromHandle(parameters->handle);
if (!resource || !resource->device()) {
return RespondNow(Error(kErrorNoConnection));
}
if (parameters->configuration_value < 0) {
return RespondNow(Error(kErrorMalformedParameters));
}
uint8_t config_value = parameters->configuration_value;
resource->device()->SetConfiguration(
config_value, mojo::WrapCallbackWithDefaultInvokeIfNotRun(
base::BindOnce(&UsbSetConfigurationFunction::OnComplete,
this, resource->guid(), config_value),
false));
return RespondLater();
}
void UsbSetConfigurationFunction::OnComplete(const std::string& guid,
uint8_t config_value,
bool success) {
if (success) {
bool updated_config =
usb_device_manager()->UpdateActiveConfig(guid, config_value);
DCHECK(updated_config);
Respond(NoArguments());
} else {
Respond(Error(kErrorCannotSetConfiguration));
}
}
UsbGetConfigurationFunction::UsbGetConfigurationFunction() = default;
UsbGetConfigurationFunction::~UsbGetConfigurationFunction() = default;
ExtensionFunction::ResponseAction UsbGetConfigurationFunction::Run() {
std::optional<usb::GetConfiguration::Params> parameters =
GetConfiguration::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(parameters);
const device::mojom::UsbDeviceInfo* device_info =
GetDeviceInfoFromHandle(parameters->handle);
if (!device_info) {
return RespondNow(Error(kErrorNoConnection));
}
uint8_t active_config_value = device_info->active_configuration;
if (active_config_value) {
for (const auto& config : device_info->configurations) {
DCHECK(config);
if (config->configuration_value == active_config_value) {
ConfigDescriptor api_config = ConvertConfigDescriptor(*config);
return RespondNow(WithArguments(api_config.ToValue()));
}
}
}
// Respond with an error if there is no active config or the config object
// can't be found according to |active_config_value|.
return RespondNow(Error(kErrorNotConfigured));
}
UsbListInterfacesFunction::UsbListInterfacesFunction() = default;
UsbListInterfacesFunction::~UsbListInterfacesFunction() = default;
ExtensionFunction::ResponseAction UsbListInterfacesFunction::Run() {
std::optional<usb::ListInterfaces::Params> parameters =
ListInterfaces::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(parameters);
const device::mojom::UsbDeviceInfo* device_info =
GetDeviceInfoFromHandle(parameters->handle);
if (!device_info) {
return RespondNow(Error(kErrorNoConnection));
}
uint8_t active_config_value = device_info->active_configuration;
if (!active_config_value) {
return RespondNow(Error(kErrorNotConfigured));
}
for (const auto& config : device_info->configurations) {
DCHECK(config);
if (config->configuration_value == active_config_value) {
ConfigDescriptor api_config = ConvertConfigDescriptor(*config);
base::Value::List result;
for (const auto& interface : api_config.interfaces) {
result.Append(interface.ToValue());
}
return RespondNow(WithArguments(std::move(result)));
}
}
// Respond with an error if the config object can't be found according to
// |active_config_value|.
return RespondNow(Error(kErrorNotConfigured));
}
UsbCloseDeviceFunction::UsbCloseDeviceFunction() = default;
UsbCloseDeviceFunction::~UsbCloseDeviceFunction() = default;
ExtensionFunction::ResponseAction UsbCloseDeviceFunction::Run() {
std::optional<usb::CloseDevice::Params> parameters =
CloseDevice::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(parameters);
device::mojom::UsbDevice* device = GetDeviceFromHandle(parameters->handle);
if (!device) {
return RespondNow(Error(kErrorNoConnection));
}
// The device handle is closed when the resource is destroyed.
ReleaseDeviceResource(parameters->handle);
return RespondNow(NoArguments());
}
UsbClaimInterfaceFunction::UsbClaimInterfaceFunction() = default;
UsbClaimInterfaceFunction::~UsbClaimInterfaceFunction() = default;
ExtensionFunction::ResponseAction UsbClaimInterfaceFunction::Run() {
std::optional<usb::ClaimInterface::Params> parameters =
ClaimInterface::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(parameters);
device::mojom::UsbDevice* device = GetDeviceFromHandle(parameters->handle);
if (!device) {
return RespondNow(Error(kErrorNoConnection));
}
device->ClaimInterface(
parameters->interface_number,
mojo::WrapCallbackWithDefaultInvokeIfNotRun(
base::BindOnce(&UsbClaimInterfaceFunction::OnComplete, this),
UsbClaimInterfaceResult::kFailure));
return RespondLater();
}
void UsbClaimInterfaceFunction::OnComplete(UsbClaimInterfaceResult result) {
if (result == UsbClaimInterfaceResult::kSuccess) {
Respond(NoArguments());
} else {
Respond(Error(kErrorCannotClaimInterface));
}
}
UsbReleaseInterfaceFunction::UsbReleaseInterfaceFunction() = default;
UsbReleaseInterfaceFunction::~UsbReleaseInterfaceFunction() = default;
ExtensionFunction::ResponseAction UsbReleaseInterfaceFunction::Run() {
std::optional<usb::ReleaseInterface::Params> parameters =
ReleaseInterface::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(parameters);
device::mojom::UsbDevice* device = GetDeviceFromHandle(parameters->handle);
if (!device) {
return RespondNow(Error(kErrorNoConnection));
}
device->ReleaseInterface(
parameters->interface_number,
mojo::WrapCallbackWithDefaultInvokeIfNotRun(
base::BindOnce(&UsbReleaseInterfaceFunction::OnComplete, this),
false));
return RespondLater();
}
void UsbReleaseInterfaceFunction::OnComplete(bool success) {
if (success)
Respond(NoArguments());
else
Respond(Error(kErrorCannotReleaseInterface));
}
UsbSetInterfaceAlternateSettingFunction::
UsbSetInterfaceAlternateSettingFunction() = default;
UsbSetInterfaceAlternateSettingFunction::
~UsbSetInterfaceAlternateSettingFunction() = default;
ExtensionFunction::ResponseAction
UsbSetInterfaceAlternateSettingFunction::Run() {
std::optional<usb::SetInterfaceAlternateSetting::Params> parameters =
SetInterfaceAlternateSetting::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(parameters);
device::mojom::UsbDevice* device = GetDeviceFromHandle(parameters->handle);
if (!device) {
return RespondNow(Error(kErrorNoConnection));
}
device->SetInterfaceAlternateSetting(
parameters->interface_number, parameters->alternate_setting,
mojo::WrapCallbackWithDefaultInvokeIfNotRun(
base::BindOnce(&UsbSetInterfaceAlternateSettingFunction::OnComplete,
this),
false));
return RespondLater();
}
void UsbSetInterfaceAlternateSettingFunction::OnComplete(bool success) {
if (success) {
Respond(NoArguments());
} else {
Respond(Error(kErrorCannotSetInterfaceAlternateSetting));
}
}
UsbControlTransferFunction::UsbControlTransferFunction() = default;
UsbControlTransferFunction::~UsbControlTransferFunction() = default;
ExtensionFunction::ResponseAction UsbControlTransferFunction::Run() {
std::optional<usb::ControlTransfer::Params> parameters =
ControlTransfer::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(parameters);
device::mojom::UsbDevice* device = GetDeviceFromHandle(parameters->handle);
if (!device) {
return RespondNow(Error(kErrorNoConnection));
}
const ControlTransferInfo& transfer = parameters->transfer_info;
UsbTransferDirection direction = UsbTransferDirection::INBOUND;
UsbControlTransferType request_type;
UsbControlTransferRecipient recipient;
if (!ConvertDirectionFromApi(transfer.direction, &direction)) {
return RespondNow(Error(kErrorConvertDirection));
}
if (!ConvertRequestTypeFromApi(transfer.request_type, &request_type)) {
return RespondNow(Error(kErrorConvertRequestType));
}
if (!ConvertRecipientFromApi(transfer.recipient, &recipient)) {
return RespondNow(Error(kErrorConvertRecipient));
}
int timeout = transfer.timeout ? *transfer.timeout : 0;
if (timeout < 0) {
return RespondNow(Error(kErrorInvalidTimeout));
}
auto mojo_parameters =
UsbControlTransferParams::New(request_type, recipient, transfer.request,
transfer.value, transfer.index);
if (direction == UsbTransferDirection::INBOUND) {
uint32_t size = 0;
if (!GetTransferInSize(transfer, &size)) {
return RespondNow(Error(kErrorInvalidTransferLength));
}
device->ControlTransferIn(
std::move(mojo_parameters), size, timeout,
mojo::WrapCallbackWithDropHandler(
base::BindOnce(&UsbControlTransferFunction::OnTransferInCompleted,
this),
base::BindOnce(&UsbControlTransferFunction::OnDisconnect, this)));
} else {
// For case direction == UsbTransferDirection::OUTBOUND.
if (!transfer.data) {
return RespondNow(Error(kErrorMalformedParameters));
}
device->ControlTransferOut(
std::move(mojo_parameters), *transfer.data, timeout,
mojo::WrapCallbackWithDropHandler(
base::BindOnce(&UsbControlTransferFunction::OnTransferOutCompleted,
this),
base::BindOnce(&UsbControlTransferFunction::OnDisconnect, this)));
}
return RespondLater();
}
UsbBulkTransferFunction::UsbBulkTransferFunction() = default;
UsbBulkTransferFunction::~UsbBulkTransferFunction() = default;
ExtensionFunction::ResponseAction UsbBulkTransferFunction::Run() {
std::optional<usb::BulkTransfer::Params> parameters =
BulkTransfer::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(parameters);
return DoTransfer<const std::optional<usb::BulkTransfer::Params>>(parameters);
}
UsbInterruptTransferFunction::UsbInterruptTransferFunction() = default;
UsbInterruptTransferFunction::~UsbInterruptTransferFunction() = default;
ExtensionFunction::ResponseAction UsbInterruptTransferFunction::Run() {
std::optional<usb::InterruptTransfer::Params> parameters =
InterruptTransfer::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(parameters);
return DoTransfer<const std::optional<usb::InterruptTransfer::Params>>(
parameters);
}
UsbIsochronousTransferFunction::UsbIsochronousTransferFunction() = default;
UsbIsochronousTransferFunction::~UsbIsochronousTransferFunction() = default;
ExtensionFunction::ResponseAction UsbIsochronousTransferFunction::Run() {
std::optional<usb::IsochronousTransfer::Params> parameters =
IsochronousTransfer::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(parameters);
device::mojom::UsbDevice* device = GetDeviceFromHandle(parameters->handle);
if (!device) {
return RespondNow(Error(kErrorNoConnection));
}
const IsochronousTransferInfo& transfer = parameters->transfer_info;
const GenericTransferInfo& generic_transfer = transfer.transfer_info;
UsbTransferDirection direction = UsbTransferDirection::INBOUND;
if (!ConvertDirectionFromApi(generic_transfer.direction, &direction))
return RespondNow(Error(kErrorConvertDirection));
uint32_t size = 0;
if (direction == UsbTransferDirection::INBOUND) {
if (!GetTransferInSize(generic_transfer, &size))
return RespondNow(Error(kErrorInvalidTransferLength));
} else {
if (!generic_transfer.data)
return RespondNow(Error(kErrorMalformedParameters));
size = generic_transfer.data->size();
}
if (transfer.packets < 0 || transfer.packets >= kMaxPackets)
return RespondNow(Error(kErrorInvalidNumberOfPackets));
size_t packets = transfer.packets;
if (transfer.packet_length < 0 ||
transfer.packet_length >= kMaxPacketLength) {
return RespondNow(Error(kErrorInvalidPacketLength));
}
size_t total_length = packets * transfer.packet_length;
if (packets > size || total_length > size)
return RespondNow(Error(kErrorTransferLength));
std::vector<uint32_t> packet_lengths(packets, transfer.packet_length);
int timeout = generic_transfer.timeout ? *generic_transfer.timeout : 0;
if (timeout < 0)
return RespondNow(Error(kErrorInvalidTimeout));
if (direction == UsbTransferDirection::INBOUND) {
device->IsochronousTransferIn(
generic_transfer.endpoint, packet_lengths, timeout,
mojo::WrapCallbackWithDropHandler(
base::BindOnce(
&UsbIsochronousTransferFunction::OnTransferInCompleted, this),
base::BindOnce(&UsbIsochronousTransferFunction::OnDisconnect,
this)));
} else {
device->IsochronousTransferOut(
generic_transfer.endpoint, *generic_transfer.data, packet_lengths,
timeout,
mojo::WrapCallbackWithDropHandler(
base::BindOnce(
&UsbIsochronousTransferFunction::OnTransferOutCompleted, this),
base::BindOnce(&UsbIsochronousTransferFunction::OnDisconnect,
this)));
}
return RespondLater();
}
void UsbIsochronousTransferFunction::OnTransferInCompleted(
base::span<const uint8_t> data,
std::vector<UsbIsochronousPacketPtr> packets) {
size_t length = std::accumulate(packets.begin(), packets.end(), 0,
[](const size_t& a, const auto& packet) {
return a + packet->transferred_length;
});
std::vector<char> buffer;
buffer.reserve(length);
UsbTransferStatus status = UsbTransferStatus::COMPLETED;
size_t index = 0;
for (const auto& packet : packets) {
// Capture the error status of the first unsuccessful packet.
if (status == UsbTransferStatus::COMPLETED &&
packet->status != UsbTransferStatus::COMPLETED) {
status = packet->status;
}
buffer.insert(buffer.end(), reinterpret_cast<const char*>(&data[index]),
reinterpret_cast<const char*>(
&data[index + packet->transferred_length]));
index += packet->transferred_length;
}
base::Value::Dict transfer_info;
transfer_info.Set(kResultCodeKey, base::Value(static_cast<int>(status)));
transfer_info.Set(kDataKey, base::Value(std::move(buffer)));
OnCompleted(status, std::move(transfer_info));
}
void UsbIsochronousTransferFunction::OnTransferOutCompleted(
std::vector<UsbIsochronousPacketPtr> packets) {
UsbTransferStatus status = UsbTransferStatus::COMPLETED;
for (const auto& packet : packets) {
// Capture the error status of the first unsuccessful packet.
if (status == UsbTransferStatus::COMPLETED &&
packet->status != UsbTransferStatus::COMPLETED) {
status = packet->status;
}
}
base::Value::Dict transfer_info;
transfer_info.Set(kResultCodeKey, base::Value(static_cast<int>(status)));
OnCompleted(status, std::move(transfer_info));
}
UsbResetDeviceFunction::UsbResetDeviceFunction() = default;
UsbResetDeviceFunction::~UsbResetDeviceFunction() = default;
ExtensionFunction::ResponseAction UsbResetDeviceFunction::Run() {
parameters_ = ResetDevice::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(parameters_);
device::mojom::UsbDevice* device = GetDeviceFromHandle(parameters_->handle);
if (!device) {
return RespondNow(Error(kErrorNoConnection));
}
device->Reset(mojo::WrapCallbackWithDefaultInvokeIfNotRun(
base::BindOnce(&UsbResetDeviceFunction::OnComplete, this), false));
return RespondLater();
}
void UsbResetDeviceFunction::OnComplete(bool success) {
if (success) {
Respond(WithArguments(true));
} else {
ReleaseDeviceResource(parameters_->handle);
base::Value::List error_args;
error_args.Append(false);
// Using ErrorWithArguments is discouraged but required to maintain
// compatibility with existing applications.
Respond(
ErrorWithArgumentsDoNotUse(std::move(error_args), kErrorResetDevice));
}
}
} // namespace extensions