// 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 "content/renderer/usb/web_usb_device_impl.h" #include #include "base/bind.h" #include "base/callback.h" #include "base/strings/utf_string_conversions.h" #include "content/child/mojo/type_converters.h" #include "content/child/scoped_web_callbacks.h" #include "content/renderer/usb/type_converters.h" #include "mojo/shell/public/cpp/connect.h" #include "mojo/shell/public/interfaces/connector.mojom.h" #include "third_party/WebKit/public/platform/WebVector.h" #include "third_party/WebKit/public/platform/modules/webusb/WebUSBDeviceInfo.h" #include "third_party/WebKit/public/platform/modules/webusb/WebUSBTransferInfo.h" namespace content { namespace { const char kClaimInterfaceFailed[] = "Unable to claim interface."; const char kClearHaltFailed[] = "Unable to clear endpoint."; const char kDeviceAlreadyOpen[] = "Device has already been opened."; const char kDeviceNoAccess[] = "Access denied."; const char kDeviceUnavailable[] = "Device unavailable."; const char kDeviceResetFailed[] = "Unable to reset the device."; const char kReleaseInterfaceFailed[] = "Unable to release interface."; const char kSetConfigurationFailed[] = "Unable to set device configuration."; const char kSetInterfaceFailed[] = "Unable to set device interface."; const char kTransferFailed[] = "Transfer failed."; // Generic default rejection handler for any WebUSB callbacks type. Assumes // |CallbacksType| is a blink::WebCallbacks // for any type |T|. template void RejectWithError(const blink::WebUSBError& error, scoped_ptr callbacks) { callbacks->onError(error); } template void RejectWithTransferError(scoped_ptr callbacks) { RejectWithError(blink::WebUSBError(blink::WebUSBError::Error::Network, base::ASCIIToUTF16(kTransferFailed)), std::move(callbacks)); } // Create a new ScopedWebCallbacks for WebUSB device callbacks, defaulting to // a "device unavailable" rejection. template ScopedWebCallbacks MakeScopedUSBCallbacks( CallbacksType* callbacks) { return make_scoped_web_callbacks( callbacks, base::Bind(&RejectWithError, blink::WebUSBError(blink::WebUSBError::Error::NotFound, base::ASCIIToUTF16(kDeviceUnavailable)))); } void OnOpenDevice( ScopedWebCallbacks callbacks, device::usb::OpenDeviceError error) { auto scoped_callbacks = callbacks.PassCallbacks(); switch(error) { case device::usb::OpenDeviceError::OK: scoped_callbacks->onSuccess(); break; case device::usb::OpenDeviceError::ACCESS_DENIED: scoped_callbacks->onError(blink::WebUSBError( blink::WebUSBError::Error::Security, base::ASCIIToUTF16(kDeviceNoAccess))); break; case device::usb::OpenDeviceError::ALREADY_OPEN: scoped_callbacks->onError(blink::WebUSBError( blink::WebUSBError::Error::InvalidState, base::ASCIIToUTF16(kDeviceAlreadyOpen))); break; default: NOTREACHED(); } } void OnDeviceClosed( ScopedWebCallbacks callbacks) { callbacks.PassCallbacks()->onSuccess(); } void HandlePassFailDeviceOperation( ScopedWebCallbacks> callbacks, const std::string& failure_message, bool success) { auto scoped_callbacks = callbacks.PassCallbacks(); if (success) { scoped_callbacks->onSuccess(); } else { RejectWithError(blink::WebUSBError(blink::WebUSBError::Error::Network, base::ASCIIToUTF16(failure_message)), std::move(scoped_callbacks)); } } void OnTransferIn( ScopedWebCallbacks callbacks, device::usb::TransferStatus status, mojo::Array data) { auto scoped_callbacks = callbacks.PassCallbacks(); blink::WebUSBTransferInfo::Status web_status; switch (status) { case device::usb::TransferStatus::COMPLETED: web_status = blink::WebUSBTransferInfo::Status::Ok; break; case device::usb::TransferStatus::STALLED: web_status = blink::WebUSBTransferInfo::Status::Stall; break; case device::usb::TransferStatus::BABBLE: web_status = blink::WebUSBTransferInfo::Status::Babble; break; default: RejectWithTransferError(std::move(scoped_callbacks)); return; } scoped_ptr info(new blink::WebUSBTransferInfo()); info->status.assign( std::vector(1, web_status)); info->data.assign(data); scoped_callbacks->onSuccess(adoptWebPtr(info.release())); } void OnTransferOut( ScopedWebCallbacks callbacks, size_t bytes_written, device::usb::TransferStatus status) { auto scoped_callbacks = callbacks.PassCallbacks(); blink::WebUSBTransferInfo::Status web_status; switch (status) { case device::usb::TransferStatus::COMPLETED: web_status = blink::WebUSBTransferInfo::Status::Ok; break; case device::usb::TransferStatus::STALLED: web_status = blink::WebUSBTransferInfo::Status::Stall; break; default: RejectWithTransferError(std::move(scoped_callbacks)); return; } // TODO(rockot): Device::ControlTransferOut should expose the number of bytes // actually transferred so we can send it from here. scoped_ptr info(new blink::WebUSBTransferInfo()); info->status.assign( std::vector(1, web_status)); info->bytesTransferred.assign(std::vector(1, bytes_written)); scoped_callbacks->onSuccess(adoptWebPtr(info.release())); } void OnIsochronousTransferIn( ScopedWebCallbacks callbacks, mojo::Array data, mojo::Array packets) { auto scoped_callbacks = callbacks.PassCallbacks(); scoped_ptr info(new blink::WebUSBTransferInfo()); info->data.assign(data); info->status = blink::WebVector(packets.size()); info->packetLength = blink::WebVector(packets.size()); info->bytesTransferred = blink::WebVector(packets.size()); for (size_t i = 0; i < packets.size(); ++i) { switch (packets[i]->status) { case device::usb::TransferStatus::COMPLETED: info->status[i] = blink::WebUSBTransferInfo::Status::Ok; break; case device::usb::TransferStatus::STALLED: info->status[i] = blink::WebUSBTransferInfo::Status::Stall; break; case device::usb::TransferStatus::BABBLE: info->status[i] = blink::WebUSBTransferInfo::Status::Babble; break; default: RejectWithTransferError(std::move(scoped_callbacks)); return; } info->packetLength[i] = packets[i]->length; info->bytesTransferred[i] = packets[i]->transferred_length; } scoped_callbacks->onSuccess(adoptWebPtr(info.release())); } void OnIsochronousTransferOut( ScopedWebCallbacks callbacks, mojo::Array packets) { auto scoped_callbacks = callbacks.PassCallbacks(); scoped_ptr info(new blink::WebUSBTransferInfo()); info->status = blink::WebVector(packets.size()); info->bytesTransferred = blink::WebVector(packets.size()); for (size_t i = 0; i < packets.size(); ++i) { switch (packets[i]->status) { case device::usb::TransferStatus::COMPLETED: info->status[i] = blink::WebUSBTransferInfo::Status::Ok; break; case device::usb::TransferStatus::STALLED: info->status[i] = blink::WebUSBTransferInfo::Status::Stall; break; default: RejectWithTransferError(std::move(scoped_callbacks)); return; } info->bytesTransferred[i] = packets[i]->transferred_length; } scoped_callbacks->onSuccess(adoptWebPtr(info.release())); } } // namespace WebUSBDeviceImpl::WebUSBDeviceImpl(device::usb::DevicePtr device, const blink::WebUSBDeviceInfo& device_info) : device_(std::move(device)), device_info_(device_info), weak_factory_(this) { if (device_) device_.set_connection_error_handler([this]() { device_.reset(); }); } WebUSBDeviceImpl::~WebUSBDeviceImpl() {} const blink::WebUSBDeviceInfo& WebUSBDeviceImpl::info() const { return device_info_; } void WebUSBDeviceImpl::open(blink::WebUSBDeviceOpenCallbacks* callbacks) { auto scoped_callbacks = MakeScopedUSBCallbacks(callbacks); if (device_) device_->Open(base::Bind(&OnOpenDevice, base::Passed(&scoped_callbacks))); } void WebUSBDeviceImpl::close(blink::WebUSBDeviceCloseCallbacks* callbacks) { auto scoped_callbacks = MakeScopedUSBCallbacks(callbacks); if (device_) device_->Close( base::Bind(&OnDeviceClosed, base::Passed(&scoped_callbacks))); } void WebUSBDeviceImpl::setConfiguration( uint8_t configuration_value, blink::WebUSBDeviceSetConfigurationCallbacks* callbacks) { auto scoped_callbacks = MakeScopedUSBCallbacks(callbacks); if (device_) device_->SetConfiguration( configuration_value, base::Bind(&HandlePassFailDeviceOperation, base::Passed(&scoped_callbacks), kSetConfigurationFailed)); } void WebUSBDeviceImpl::claimInterface( uint8_t interface_number, blink::WebUSBDeviceClaimInterfaceCallbacks* callbacks) { auto scoped_callbacks = MakeScopedUSBCallbacks(callbacks); if (device_) device_->ClaimInterface( interface_number, base::Bind(&HandlePassFailDeviceOperation, base::Passed(&scoped_callbacks), kClaimInterfaceFailed)); } void WebUSBDeviceImpl::releaseInterface( uint8_t interface_number, blink::WebUSBDeviceReleaseInterfaceCallbacks* callbacks) { auto scoped_callbacks = MakeScopedUSBCallbacks(callbacks); if (device_) device_->ReleaseInterface( interface_number, base::Bind(&HandlePassFailDeviceOperation, base::Passed(&scoped_callbacks), kReleaseInterfaceFailed)); } void WebUSBDeviceImpl::setInterface( uint8_t interface_number, uint8_t alternate_setting, blink::WebUSBDeviceSetInterfaceAlternateSettingCallbacks* callbacks) { auto scoped_callbacks = MakeScopedUSBCallbacks(callbacks); if (device_) device_->SetInterfaceAlternateSetting( interface_number, alternate_setting, base::Bind(&HandlePassFailDeviceOperation, base::Passed(&scoped_callbacks), kSetInterfaceFailed)); } void WebUSBDeviceImpl::clearHalt( uint8_t endpoint_number, blink::WebUSBDeviceClearHaltCallbacks* callbacks) { auto scoped_callbacks = MakeScopedUSBCallbacks(callbacks); if (device_) device_->ClearHalt( endpoint_number, base::Bind(&HandlePassFailDeviceOperation, base::Passed(&scoped_callbacks), kClearHaltFailed)); } void WebUSBDeviceImpl::controlTransfer( const blink::WebUSBDevice::ControlTransferParameters& parameters, uint8_t* data, size_t data_size, unsigned int timeout, blink::WebUSBDeviceTransferCallbacks* callbacks) { auto scoped_callbacks = MakeScopedUSBCallbacks(callbacks); if (!device_) return; device::usb::ControlTransferParamsPtr params = device::usb::ControlTransferParams::From(parameters); switch (parameters.direction) { case WebUSBDevice::TransferDirection::In: device_->ControlTransferIn( std::move(params), data_size, timeout, base::Bind(&OnTransferIn, base::Passed(&scoped_callbacks))); break; case WebUSBDevice::TransferDirection::Out: { std::vector bytes; if (data) bytes.assign(data, data + data_size); mojo::Array mojo_bytes; mojo_bytes.Swap(&bytes); device_->ControlTransferOut( std::move(params), std::move(mojo_bytes), timeout, base::Bind(&OnTransferOut, base::Passed(&scoped_callbacks), data_size)); break; } default: NOTREACHED(); } } void WebUSBDeviceImpl::transfer( blink::WebUSBDevice::TransferDirection direction, uint8_t endpoint_number, uint8_t* data, size_t data_size, unsigned int timeout, blink::WebUSBDeviceTransferCallbacks* callbacks) { auto scoped_callbacks = MakeScopedUSBCallbacks(callbacks); if (!device_) return; switch (direction) { case WebUSBDevice::TransferDirection::In: device_->GenericTransferIn( endpoint_number, data_size, timeout, base::Bind(&OnTransferIn, base::Passed(&scoped_callbacks))); break; case WebUSBDevice::TransferDirection::Out: { std::vector bytes; if (data) bytes.assign(data, data + data_size); mojo::Array mojo_bytes; mojo_bytes.Swap(&bytes); device_->GenericTransferOut( endpoint_number, std::move(mojo_bytes), timeout, base::Bind(&OnTransferOut, base::Passed(&scoped_callbacks), data_size)); break; } default: NOTREACHED(); } } void WebUSBDeviceImpl::isochronousTransfer( blink::WebUSBDevice::TransferDirection direction, uint8_t endpoint_number, uint8_t* data, size_t data_size, blink::WebVector packet_lengths, unsigned int timeout, blink::WebUSBDeviceTransferCallbacks* callbacks) { auto scoped_callbacks = MakeScopedUSBCallbacks(callbacks); if (!device_) return; switch (direction) { case WebUSBDevice::TransferDirection::In: device_->IsochronousTransferIn( endpoint_number, mojo::Array::From(packet_lengths), timeout, base::Bind(&OnIsochronousTransferIn, base::Passed(&scoped_callbacks))); break; case WebUSBDevice::TransferDirection::Out: { std::vector bytes; if (data) bytes.assign(data, data + data_size); mojo::Array mojo_bytes; mojo_bytes.Swap(&bytes); device_->IsochronousTransferOut( endpoint_number, std::move(mojo_bytes), mojo::Array::From(packet_lengths), timeout, base::Bind(&OnIsochronousTransferOut, base::Passed(&scoped_callbacks))); break; } default: NOTREACHED(); } } void WebUSBDeviceImpl::reset(blink::WebUSBDeviceResetCallbacks* callbacks) { auto scoped_callbacks = MakeScopedUSBCallbacks(callbacks); if (device_) device_->Reset(base::Bind(&HandlePassFailDeviceOperation, base::Passed(&scoped_callbacks), kDeviceResetFailed)); } } // namespace content