diff options
Diffstat (limited to 'device/usb')
-rw-r--r-- | device/usb/mojo/BUILD.gn | 24 | ||||
-rw-r--r-- | device/usb/mojo/DEPS | 3 | ||||
-rw-r--r-- | device/usb/mojo/device_impl.cc | 464 | ||||
-rw-r--r-- | device/usb/mojo/device_impl.h | 113 | ||||
-rw-r--r-- | device/usb/mojo/device_impl_unittest.cc | 895 | ||||
-rw-r--r-- | device/usb/mojo/device_manager_impl.cc | 228 | ||||
-rw-r--r-- | device/usb/mojo/device_manager_impl.h | 111 | ||||
-rw-r--r-- | device/usb/mojo/device_manager_impl_unittest.cc | 218 | ||||
-rw-r--r-- | device/usb/mojo/fake_permission_provider.cc | 46 | ||||
-rw-r--r-- | device/usb/mojo/fake_permission_provider.h | 44 | ||||
-rw-r--r-- | device/usb/mojo/type_converters.cc | 285 | ||||
-rw-r--r-- | device/usb/mojo/type_converters.h | 134 | ||||
-rw-r--r-- | device/usb/public/interfaces/BUILD.gn | 13 | ||||
-rw-r--r-- | device/usb/public/interfaces/device.mojom | 285 | ||||
-rw-r--r-- | device/usb/public/interfaces/device_manager.mojom | 47 | ||||
-rw-r--r-- | device/usb/public/interfaces/permission_provider.mojom | 29 | ||||
-rw-r--r-- | device/usb/usb.gyp | 18 |
17 files changed, 2957 insertions, 0 deletions
diff --git a/device/usb/mojo/BUILD.gn b/device/usb/mojo/BUILD.gn new file mode 100644 index 0000000..d48cf37 --- /dev/null +++ b/device/usb/mojo/BUILD.gn @@ -0,0 +1,24 @@ +# Copyright 2016 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. + +source_set("mojo") { + sources = [ + "device_impl.cc", + "device_impl.h", + "device_manager_impl.cc", + "device_manager_impl.h", + "type_converters.cc", + "type_converters.h", + ] + + deps = [ + "//device/core", + "//device/usb", + "//device/usb/public/interfaces", + "//mojo/common", + "//mojo/public/cpp/bindings", + "//mojo/public/cpp/bindings:callback", + "//net", + ] +} diff --git a/device/usb/mojo/DEPS b/device/usb/mojo/DEPS new file mode 100644 index 0000000..6cfa565 --- /dev/null +++ b/device/usb/mojo/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+mojo/common", +] diff --git a/device/usb/mojo/device_impl.cc b/device/usb/mojo/device_impl.cc new file mode 100644 index 0000000..724e911 --- /dev/null +++ b/device/usb/mojo/device_impl.cc @@ -0,0 +1,464 @@ +// 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 "device/usb/mojo/device_impl.h" + +#include <stddef.h> + +#include <algorithm> +#include <numeric> +#include <utility> +#include <vector> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/stl_util.h" +#include "device/usb/mojo/type_converters.h" +#include "device/usb/usb_descriptors.h" +#include "device/usb/usb_device.h" +#include "net/base/io_buffer.h" + +namespace device { +namespace usb { + +namespace { + +using MojoTransferInCallback = + mojo::Callback<void(TransferStatus, mojo::Array<uint8_t>)>; + +using MojoTransferOutCallback = mojo::Callback<void(TransferStatus)>; + +template <typename... Args> +void CallMojoCallback(scoped_ptr<mojo::Callback<void(Args...)>> callback, + Args... args) { + callback->Run(args...); +} + +// Generic wrapper to convert a Mojo callback to something we can rebind and +// pass around. This is only usable for callbacks with no move-only arguments. +template <typename... Args> +base::Callback<void(Args...)> WrapMojoCallback( + const mojo::Callback<void(Args...)>& callback) { + // mojo::Callback is not thread safe. By wrapping |callback| in a scoped_ptr + // we guarantee that it will be freed when CallMojoCallback is run and not + // retained until the base::Callback is destroyed, which could happen on any + // thread. This pattern is also used below in places where this generic + // wrapper is not used. + auto callback_ptr = + make_scoped_ptr(new mojo::Callback<void(Args...)>(callback)); + return base::Bind(&CallMojoCallback<Args...>, base::Passed(&callback_ptr)); +} + +void OnPermissionCheckComplete( + const base::Callback<void(bool)>& callback, + const base::Callback<void(const base::Callback<void(bool)>&)>& action, + bool allowed) { + if (allowed) + action.Run(callback); + else + callback.Run(false); +} + +scoped_refptr<net::IOBuffer> CreateTransferBuffer(size_t size) { + scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer( + std::max(static_cast<size_t>(1u), static_cast<size_t>(size))); + return buffer; +} + +void OnTransferIn(scoped_ptr<MojoTransferInCallback> callback, + UsbTransferStatus status, + scoped_refptr<net::IOBuffer> buffer, + size_t buffer_size) { + mojo::Array<uint8_t> data; + if (buffer) { + // TODO(rockot/reillyg): We should change UsbDeviceHandle to use a + // std::vector<uint8_t> instead of net::IOBuffer. Then we could move + // instead of copy. + std::vector<uint8_t> bytes(buffer_size); + std::copy(buffer->data(), buffer->data() + buffer_size, bytes.begin()); + data.Swap(&bytes); + } + callback->Run(mojo::ConvertTo<TransferStatus>(status), std::move(data)); +} + +void OnControlTransferInPermissionCheckComplete( + scoped_refptr<UsbDeviceHandle> device_handle, + ControlTransferParamsPtr params, + int length, + int timeout, + scoped_ptr<Device::ControlTransferInCallback> callback, + bool allowed) { + if (allowed) { + scoped_refptr<net::IOBuffer> buffer = CreateTransferBuffer(length); + device_handle->ControlTransfer( + USB_DIRECTION_INBOUND, + mojo::ConvertTo<UsbDeviceHandle::TransferRequestType>(params->type), + mojo::ConvertTo<UsbDeviceHandle::TransferRecipient>(params->recipient), + params->request, params->value, params->index, buffer, length, timeout, + base::Bind(&OnTransferIn, base::Passed(&callback))); + } else { + mojo::Array<uint8_t> data; + callback->Run(TransferStatus::PERMISSION_DENIED, std::move(data)); + } +} + +void OnTransferOut(scoped_ptr<MojoTransferOutCallback> callback, + UsbTransferStatus status, + scoped_refptr<net::IOBuffer> buffer, + size_t buffer_size) { + callback->Run(mojo::ConvertTo<TransferStatus>(status)); +} + +void OnControlTransferOutPermissionCheckComplete( + scoped_refptr<UsbDeviceHandle> device_handle, + ControlTransferParamsPtr params, + mojo::Array<uint8_t> data, + int timeout, + scoped_ptr<Device::ControlTransferOutCallback> callback, + bool allowed) { + if (allowed) { + scoped_refptr<net::IOBuffer> buffer = CreateTransferBuffer(data.size()); + const std::vector<uint8_t>& storage = data.storage(); + std::copy(storage.begin(), storage.end(), buffer->data()); + device_handle->ControlTransfer( + USB_DIRECTION_OUTBOUND, + mojo::ConvertTo<UsbDeviceHandle::TransferRequestType>(params->type), + mojo::ConvertTo<UsbDeviceHandle::TransferRecipient>(params->recipient), + params->request, params->value, params->index, buffer, data.size(), + timeout, base::Bind(&OnTransferOut, base::Passed(&callback))); + } else { + callback->Run(TransferStatus::PERMISSION_DENIED); + } +} + +mojo::Array<IsochronousPacketPtr> BuildIsochronousPacketArray( + mojo::Array<uint32_t> packet_lengths, + TransferStatus status) { + mojo::Array<IsochronousPacketPtr> packets(packet_lengths.size()); + for (size_t i = 0; i < packet_lengths.size(); ++i) { + packets[i] = IsochronousPacket::New(); + packets[i]->length = packet_lengths[i]; + packets[i]->status = status; + } + return packets; +} + +void OnIsochronousTransferIn( + scoped_ptr<Device::IsochronousTransferInCallback> callback, + scoped_refptr<net::IOBuffer> buffer, + const std::vector<UsbDeviceHandle::IsochronousPacket>& packets) { + mojo::Array<uint8_t> data; + if (buffer) { + // TODO(rockot/reillyg): We should change UsbDeviceHandle to use a + // std::vector<uint8_t> instead of net::IOBuffer. Then we could move + // instead of copy. + uint32_t buffer_size = + std::accumulate(packets.begin(), packets.end(), 0u, + [](const uint32_t& a, + const UsbDeviceHandle::IsochronousPacket& packet) { + return a + packet.length; + }); + std::vector<uint8_t> bytes(buffer_size); + std::copy(buffer->data(), buffer->data() + buffer_size, bytes.begin()); + data.Swap(&bytes); + } + callback->Run(std::move(data), + mojo::Array<IsochronousPacketPtr>::From(packets)); +} + +void OnIsochronousTransferOut( + scoped_ptr<Device::IsochronousTransferOutCallback> callback, + scoped_refptr<net::IOBuffer> buffer, + const std::vector<UsbDeviceHandle::IsochronousPacket>& packets) { + callback->Run(mojo::Array<IsochronousPacketPtr>::From(packets)); +} + +} // namespace + +DeviceImpl::DeviceImpl(scoped_refptr<UsbDevice> device, + PermissionProviderPtr permission_provider, + mojo::InterfaceRequest<Device> request) + : binding_(this, std::move(request)), + device_(device), + permission_provider_(std::move(permission_provider)), + weak_factory_(this) { + // This object owns itself and will be destroyed if either the message pipe + // it is bound to is closed or the PermissionProvider it depends on is + // unavailable. + binding_.set_connection_error_handler([this]() { delete this; }); + permission_provider_.set_connection_error_handler([this]() { delete this; }); +} + +DeviceImpl::~DeviceImpl() { + CloseHandle(); +} + +void DeviceImpl::CloseHandle() { + if (device_handle_) + device_handle_->Close(); + device_handle_ = nullptr; +} + +void DeviceImpl::HasControlTransferPermission( + ControlTransferRecipient recipient, + uint16_t index, + const base::Callback<void(bool)>& callback) { + DCHECK(device_handle_); + const UsbConfigDescriptor* config = device_->GetActiveConfiguration(); + + if (recipient == ControlTransferRecipient::INTERFACE || + recipient == ControlTransferRecipient::ENDPOINT) { + if (!config) { + callback.Run(false); + return; + } + + uint8_t interface_number = index & 0xff; + if (recipient == ControlTransferRecipient::ENDPOINT) { + if (!device_handle_->FindInterfaceByEndpoint(index & 0xff, + &interface_number)) { + callback.Run(false); + return; + } + } + + permission_provider_->HasInterfacePermission( + interface_number, config->configuration_value, + DeviceInfo::From(*device_), callback); + } else if (config) { + permission_provider_->HasConfigurationPermission( + config->configuration_value, DeviceInfo::From(*device_), callback); + } else { + // Client must already have device permission to have gotten this far. + callback.Run(true); + } +} + +void DeviceImpl::OnOpen(const OpenCallback& callback, + scoped_refptr<UsbDeviceHandle> handle) { + device_handle_ = handle; + callback.Run(handle ? OpenDeviceError::OK : OpenDeviceError::ACCESS_DENIED); +} + +void DeviceImpl::GetDeviceInfo(const GetDeviceInfoCallback& callback) { + callback.Run(DeviceInfo::From(*device_)); +} + +void DeviceImpl::GetConfiguration(const GetConfigurationCallback& callback) { + const UsbConfigDescriptor* config = device_->GetActiveConfiguration(); + callback.Run(config ? config->configuration_value : 0); +} + +void DeviceImpl::Open(const OpenCallback& callback) { + device_->Open( + base::Bind(&DeviceImpl::OnOpen, weak_factory_.GetWeakPtr(), callback)); +} + +void DeviceImpl::Close(const CloseCallback& callback) { + CloseHandle(); + callback.Run(); +} + +void DeviceImpl::SetConfiguration(uint8_t value, + const SetConfigurationCallback& callback) { + if (!device_handle_) { + callback.Run(false); + return; + } + + auto set_configuration = + base::Bind(&UsbDeviceHandle::SetConfiguration, device_handle_, value); + permission_provider_->HasConfigurationPermission( + value, DeviceInfo::From(*device_), + base::Bind(&OnPermissionCheckComplete, WrapMojoCallback(callback), + set_configuration)); +} + +void DeviceImpl::ClaimInterface(uint8_t interface_number, + const ClaimInterfaceCallback& callback) { + if (!device_handle_) { + callback.Run(false); + return; + } + + const UsbConfigDescriptor* config = device_->GetActiveConfiguration(); + if (!config) { + callback.Run(false); + return; + } + + auto claim_interface = base::Bind(&UsbDeviceHandle::ClaimInterface, + device_handle_, interface_number); + permission_provider_->HasInterfacePermission( + interface_number, config->configuration_value, DeviceInfo::From(*device_), + base::Bind(&OnPermissionCheckComplete, WrapMojoCallback(callback), + claim_interface)); +} + +void DeviceImpl::ReleaseInterface(uint8_t interface_number, + const ReleaseInterfaceCallback& callback) { + if (!device_handle_) { + callback.Run(false); + return; + } + + device_handle_->ReleaseInterface(interface_number, + WrapMojoCallback(callback)); +} + +void DeviceImpl::SetInterfaceAlternateSetting( + uint8_t interface_number, + uint8_t alternate_setting, + const SetInterfaceAlternateSettingCallback& callback) { + if (!device_handle_) { + callback.Run(false); + return; + } + + device_handle_->SetInterfaceAlternateSetting( + interface_number, alternate_setting, WrapMojoCallback(callback)); +} + +void DeviceImpl::Reset(const ResetCallback& callback) { + if (!device_handle_) { + callback.Run(false); + return; + } + + device_handle_->ResetDevice(WrapMojoCallback(callback)); +} + +void DeviceImpl::ClearHalt(uint8_t endpoint, + const ClearHaltCallback& callback) { + if (!device_handle_) { + callback.Run(false); + return; + } + + device_handle_->ClearHalt(endpoint, WrapMojoCallback(callback)); +} + +void DeviceImpl::ControlTransferIn(ControlTransferParamsPtr params, + uint32_t length, + uint32_t timeout, + const ControlTransferInCallback& callback) { + if (!device_handle_) { + callback.Run(TransferStatus::TRANSFER_ERROR, mojo::Array<uint8_t>()); + return; + } + + auto callback_ptr = make_scoped_ptr(new ControlTransferInCallback(callback)); + ControlTransferRecipient recipient = params->recipient; + uint16_t index = params->index; + HasControlTransferPermission( + recipient, index, + base::Bind(&OnControlTransferInPermissionCheckComplete, device_handle_, + base::Passed(¶ms), length, timeout, + base::Passed(&callback_ptr))); +} + +void DeviceImpl::ControlTransferOut( + ControlTransferParamsPtr params, + mojo::Array<uint8_t> data, + uint32_t timeout, + const ControlTransferOutCallback& callback) { + if (!device_handle_) { + callback.Run(TransferStatus::TRANSFER_ERROR); + return; + } + + auto callback_ptr = make_scoped_ptr(new ControlTransferOutCallback(callback)); + ControlTransferRecipient recipient = params->recipient; + uint16_t index = params->index; + HasControlTransferPermission( + recipient, index, + base::Bind(&OnControlTransferOutPermissionCheckComplete, device_handle_, + base::Passed(¶ms), base::Passed(&data), timeout, + base::Passed(&callback_ptr))); +} + +void DeviceImpl::GenericTransferIn(uint8_t endpoint_number, + uint32_t length, + uint32_t timeout, + const GenericTransferInCallback& callback) { + if (!device_handle_) { + callback.Run(TransferStatus::TRANSFER_ERROR, mojo::Array<uint8_t>()); + return; + } + + auto callback_ptr = make_scoped_ptr(new GenericTransferInCallback(callback)); + uint8_t endpoint_address = endpoint_number | 0x80; + scoped_refptr<net::IOBuffer> buffer = CreateTransferBuffer(length); + device_handle_->GenericTransfer( + USB_DIRECTION_INBOUND, endpoint_address, buffer, length, timeout, + base::Bind(&OnTransferIn, base::Passed(&callback_ptr))); +} + +void DeviceImpl::GenericTransferOut( + uint8_t endpoint_number, + mojo::Array<uint8_t> data, + uint32_t timeout, + const GenericTransferOutCallback& callback) { + if (!device_handle_) { + callback.Run(TransferStatus::TRANSFER_ERROR); + return; + } + + auto callback_ptr = make_scoped_ptr(new GenericTransferOutCallback(callback)); + uint8_t endpoint_address = endpoint_number; + scoped_refptr<net::IOBuffer> buffer = CreateTransferBuffer(data.size()); + const std::vector<uint8_t>& storage = data.storage(); + std::copy(storage.begin(), storage.end(), buffer->data()); + device_handle_->GenericTransfer( + USB_DIRECTION_OUTBOUND, endpoint_address, buffer, data.size(), timeout, + base::Bind(&OnTransferOut, base::Passed(&callback_ptr))); +} + +void DeviceImpl::IsochronousTransferIn( + uint8_t endpoint_number, + mojo::Array<uint32_t> packet_lengths, + uint32_t timeout, + const IsochronousTransferInCallback& callback) { + if (!device_handle_) { + callback.Run(mojo::Array<uint8_t>(), + BuildIsochronousPacketArray(std::move(packet_lengths), + TransferStatus::TRANSFER_ERROR)); + return; + } + + auto callback_ptr = + make_scoped_ptr(new IsochronousTransferInCallback(callback)); + uint8_t endpoint_address = endpoint_number | 0x80; + device_handle_->IsochronousTransferIn( + endpoint_address, packet_lengths.storage(), timeout, + base::Bind(&OnIsochronousTransferIn, base::Passed(&callback_ptr))); +} + +void DeviceImpl::IsochronousTransferOut( + uint8_t endpoint_number, + mojo::Array<uint8_t> data, + mojo::Array<uint32_t> packet_lengths, + uint32_t timeout, + const IsochronousTransferOutCallback& callback) { + if (!device_handle_) { + callback.Run(BuildIsochronousPacketArray(std::move(packet_lengths), + TransferStatus::TRANSFER_ERROR)); + return; + } + + auto callback_ptr = + make_scoped_ptr(new IsochronousTransferOutCallback(callback)); + uint8_t endpoint_address = endpoint_number; + scoped_refptr<net::IOBuffer> buffer = CreateTransferBuffer(data.size()); + { + const std::vector<uint8_t>& storage = data.storage(); + std::copy(storage.begin(), storage.end(), buffer->data()); + } + device_handle_->IsochronousTransferOut( + endpoint_address, buffer, packet_lengths.storage(), timeout, + base::Bind(&OnIsochronousTransferOut, base::Passed(&callback_ptr))); +} + +} // namespace usb +} // namespace device diff --git a/device/usb/mojo/device_impl.h b/device/usb/mojo/device_impl.h new file mode 100644 index 0000000..e19feef --- /dev/null +++ b/device/usb/mojo/device_impl.h @@ -0,0 +1,113 @@ +// 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. + +#ifndef DEVICE_USB_MOJO_DEVICE_IMPL_H_ +#define DEVICE_USB_MOJO_DEVICE_IMPL_H_ + +#include <stdint.h> + +#include "base/callback_forward.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "device/usb/public/interfaces/device.mojom.h" +#include "device/usb/public/interfaces/permission_provider.mojom.h" +#include "device/usb/usb_device_handle.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/callback.h" +#include "mojo/public/cpp/bindings/interface_request.h" + +namespace net { +class IOBuffer; +} + +namespace device { +namespace usb { + +// Implementation of the public Device interface. Instances of this class are +// constructed by DeviceManagerImpl and are strongly bound to their MessagePipe +// lifetime. +class DeviceImpl : public Device { + public: + DeviceImpl(scoped_refptr<UsbDevice> device, + PermissionProviderPtr permission_provider, + mojo::InterfaceRequest<Device> request); + ~DeviceImpl() override; + + private: + // Closes the device if it's open. This will always set |device_handle_| to + // null. + void CloseHandle(); + + // Checks interface permissions for control transfers. + void HasControlTransferPermission(ControlTransferRecipient recipient, + uint16_t index, + const base::Callback<void(bool)>& callback); + + // Handles completion of an open request. + void OnOpen(const OpenCallback& callback, + scoped_refptr<device::UsbDeviceHandle> handle); + + // Device implementation: + void GetDeviceInfo(const GetDeviceInfoCallback& callback) override; + void GetConfiguration(const GetConfigurationCallback& callback) override; + void Open(const OpenCallback& callback) override; + void Close(const CloseCallback& callback) override; + void SetConfiguration(uint8_t value, + const SetConfigurationCallback& callback) override; + void ClaimInterface(uint8_t interface_number, + const ClaimInterfaceCallback& callback) override; + void ReleaseInterface(uint8_t interface_number, + const ReleaseInterfaceCallback& callback) override; + void SetInterfaceAlternateSetting( + uint8_t interface_number, + uint8_t alternate_setting, + const SetInterfaceAlternateSettingCallback& callback) override; + void Reset(const ResetCallback& callback) override; + void ClearHalt(uint8_t endpoint, const ClearHaltCallback& callback) override; + void ControlTransferIn(ControlTransferParamsPtr params, + uint32_t length, + uint32_t timeout, + const ControlTransferInCallback& callback) override; + void ControlTransferOut(ControlTransferParamsPtr params, + mojo::Array<uint8_t> data, + uint32_t timeout, + const ControlTransferOutCallback& callback) override; + void GenericTransferIn(uint8_t endpoint_number, + uint32_t length, + uint32_t timeout, + const GenericTransferInCallback& callback) override; + void GenericTransferOut(uint8_t endpoint_number, + mojo::Array<uint8_t> data, + uint32_t timeout, + const GenericTransferOutCallback& callback) override; + void IsochronousTransferIn( + uint8_t endpoint_number, + mojo::Array<uint32_t> packet_lengths, + uint32_t timeout, + const IsochronousTransferInCallback& callback) override; + void IsochronousTransferOut( + uint8_t endpoint_number, + mojo::Array<uint8_t> data, + mojo::Array<uint32_t> packet_lengths, + uint32_t timeout, + const IsochronousTransferOutCallback& callback) override; + + mojo::Binding<Device> binding_; + + scoped_refptr<UsbDevice> device_; + // The device handle. Will be null before the device is opened and after it + // has been closed. + scoped_refptr<UsbDeviceHandle> device_handle_; + PermissionProviderPtr permission_provider_; + + base::WeakPtrFactory<DeviceImpl> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(DeviceImpl); +}; + +} // namespace usb +} // namespace device + +#endif // DEVICE_USB_MOJO_DEVICE_IMPL_H_ diff --git a/device/usb/mojo/device_impl_unittest.cc b/device/usb/mojo/device_impl_unittest.cc new file mode 100644 index 0000000..626edd9 --- /dev/null +++ b/device/usb/mojo/device_impl_unittest.cc @@ -0,0 +1,895 @@ +// 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 "device/usb/mojo/device_impl.h" + +#include <stddef.h> +#include <stdint.h> + +#include <map> +#include <numeric> +#include <queue> +#include <set> +#include <string> +#include <utility> +#include <vector> + +#include "base/bind.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/stl_util.h" +#include "device/usb/mock_usb_device.h" +#include "device/usb/mock_usb_device_handle.h" +#include "device/usb/mojo/fake_permission_provider.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "net/base/io_buffer.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::Invoke; +using ::testing::_; + +namespace device { +namespace usb { + +namespace { + +class ConfigBuilder { + public: + explicit ConfigBuilder(uint8_t value) { config_.configuration_value = value; } + + ConfigBuilder& AddInterface(uint8_t interface_number, + uint8_t alternate_setting, + uint8_t class_code, + uint8_t subclass_code, + uint8_t protocol_code) { + UsbInterfaceDescriptor interface; + interface.interface_number = interface_number; + interface.alternate_setting = alternate_setting; + interface.interface_class = class_code; + interface.interface_subclass = subclass_code; + interface.interface_protocol = protocol_code; + config_.interfaces.push_back(interface); + return *this; + } + + const UsbConfigDescriptor& config() const { return config_; } + + private: + UsbConfigDescriptor config_; +}; + +void ExpectOpenAndThen(OpenDeviceError expected, + const base::Closure& continuation, + OpenDeviceError error) { + EXPECT_EQ(expected, error); + continuation.Run(); +} + +void ExpectDeviceInfoAndThen(const std::string& guid, + uint16_t vendor_id, + uint16_t product_id, + const std::string& manufacturer_name, + const std::string& product_name, + const std::string& serial_number, + const base::Closure& continuation, + DeviceInfoPtr device_info) { + EXPECT_EQ(guid, device_info->guid); + EXPECT_EQ(vendor_id, device_info->vendor_id); + EXPECT_EQ(product_id, device_info->product_id); + EXPECT_EQ(manufacturer_name, device_info->manufacturer_name); + EXPECT_EQ(product_name, device_info->product_name); + EXPECT_EQ(serial_number, device_info->serial_number); + continuation.Run(); +} + +void ExpectResultAndThen(bool expected_result, + const base::Closure& continuation, + bool actual_result) { + EXPECT_EQ(expected_result, actual_result); + continuation.Run(); +} + +void ExpectTransferInAndThen(TransferStatus expected_status, + const std::vector<uint8_t>& expected_bytes, + const base::Closure& continuation, + TransferStatus actual_status, + mojo::Array<uint8_t> actual_bytes) { + EXPECT_EQ(expected_status, actual_status); + ASSERT_EQ(expected_bytes.size(), actual_bytes.size()); + for (size_t i = 0; i < actual_bytes.size(); ++i) { + EXPECT_EQ(expected_bytes[i], actual_bytes[i]) + << "Contents differ at index: " << i; + } + continuation.Run(); +} + +void ExpectPacketsOutAndThen(const std::vector<uint32_t>& expected_packets, + const base::Closure& continuation, + mojo::Array<IsochronousPacketPtr> actual_packets) { + ASSERT_EQ(expected_packets.size(), actual_packets.size()); + for (size_t i = 0; i < expected_packets.size(); ++i) { + EXPECT_EQ(expected_packets[i], actual_packets[i]->transferred_length) + << "Packet lengths differ at index: " << i; + EXPECT_EQ(TransferStatus::COMPLETED, actual_packets[i]->status) + << "Packet at index " << i << " not completed."; + } + continuation.Run(); +} + +void ExpectPacketsInAndThen(const std::vector<uint8_t>& expected_bytes, + const std::vector<uint32_t>& expected_packets, + const base::Closure& continuation, + mojo::Array<uint8_t> actual_bytes, + mojo::Array<IsochronousPacketPtr> actual_packets) { + ASSERT_EQ(expected_packets.size(), actual_packets.size()); + for (size_t i = 0; i < expected_packets.size(); ++i) { + EXPECT_EQ(expected_packets[i], actual_packets[i]->transferred_length) + << "Packet lengths differ at index: " << i; + EXPECT_EQ(TransferStatus::COMPLETED, actual_packets[i]->status) + << "Packet at index " << i << " not completed."; + } + ASSERT_EQ(expected_bytes.size(), actual_bytes.size()); + for (size_t i = 0; i < expected_bytes.size(); ++i) { + EXPECT_EQ(expected_bytes[i], actual_bytes[i]) + << "Contents differ at index: " << i; + } + continuation.Run(); +} + +void ExpectTransferStatusAndThen(TransferStatus expected_status, + const base::Closure& continuation, + TransferStatus actual_status) { + EXPECT_EQ(expected_status, actual_status); + continuation.Run(); +} + +class USBDeviceImplTest : public testing::Test { + public: + USBDeviceImplTest() + : message_loop_(new base::MessageLoop), + is_device_open_(false), + allow_reset_(false), + current_config_(0) {} + + ~USBDeviceImplTest() override {} + + protected: + MockUsbDevice& mock_device() { return *mock_device_.get(); } + bool is_device_open() const { return is_device_open_; } + MockUsbDeviceHandle& mock_handle() { return *mock_handle_.get(); } + + void set_allow_reset(bool allow_reset) { allow_reset_ = allow_reset; } + + // Creates a mock device and binds a Device proxy to a Device service impl + // wrapping the mock device. + DevicePtr GetMockDeviceProxy(uint16_t vendor_id, + uint16_t product_id, + const std::string& manufacturer, + const std::string& product, + const std::string& serial) { + mock_device_ = + new MockUsbDevice(vendor_id, product_id, manufacturer, product, serial); + mock_handle_ = new MockUsbDeviceHandle(mock_device_.get()); + + PermissionProviderPtr permission_provider; + permission_provider_.Bind(mojo::GetProxy(&permission_provider)); + DevicePtr proxy; + new DeviceImpl(mock_device_, std::move(permission_provider), + mojo::GetProxy(&proxy)); + + // Set up mock handle calls to respond based on mock device configs + // established by the test. + ON_CALL(mock_device(), Open(_)) + .WillByDefault(Invoke(this, &USBDeviceImplTest::OpenMockHandle)); + ON_CALL(mock_device(), GetActiveConfiguration()) + .WillByDefault( + Invoke(this, &USBDeviceImplTest::GetActiveConfiguration)); + ON_CALL(mock_handle(), Close()) + .WillByDefault(Invoke(this, &USBDeviceImplTest::CloseMockHandle)); + ON_CALL(mock_handle(), SetConfiguration(_, _)) + .WillByDefault(Invoke(this, &USBDeviceImplTest::SetConfiguration)); + ON_CALL(mock_handle(), ClaimInterface(_, _)) + .WillByDefault(Invoke(this, &USBDeviceImplTest::ClaimInterface)); + ON_CALL(mock_handle(), ReleaseInterface(_, _)) + .WillByDefault(Invoke(this, &USBDeviceImplTest::ReleaseInterface)); + ON_CALL(mock_handle(), SetInterfaceAlternateSetting(_, _, _)) + .WillByDefault( + Invoke(this, &USBDeviceImplTest::SetInterfaceAlternateSetting)); + ON_CALL(mock_handle(), ResetDevice(_)) + .WillByDefault(Invoke(this, &USBDeviceImplTest::ResetDevice)); + ON_CALL(mock_handle(), ControlTransfer(_, _, _, _, _, _, _, _, _, _)) + .WillByDefault(Invoke(this, &USBDeviceImplTest::ControlTransfer)); + ON_CALL(mock_handle(), GenericTransfer(_, _, _, _, _, _)) + .WillByDefault(Invoke(this, &USBDeviceImplTest::GenericTransfer)); + ON_CALL(mock_handle(), IsochronousTransferIn(_, _, _, _)) + .WillByDefault(Invoke(this, &USBDeviceImplTest::IsochronousTransferIn)); + ON_CALL(mock_handle(), IsochronousTransferOut(_, _, _, _, _)) + .WillByDefault( + Invoke(this, &USBDeviceImplTest::IsochronousTransferOut)); + + return proxy; + } + + DevicePtr GetMockDeviceProxy() { + return GetMockDeviceProxy(0x1234, 0x5678, "ACME", "Frobinator", "ABCDEF"); + } + + void AddMockConfig(const ConfigBuilder& builder) { + const UsbConfigDescriptor& config = builder.config(); + DCHECK(!ContainsKey(mock_configs_, config.configuration_value)); + mock_configs_[config.configuration_value] = config; + } + + void AddMockInboundData(const std::vector<uint8_t>& data) { + mock_inbound_data_.push(data); + } + + void AddMockInboundPackets( + const std::vector<uint8_t>& data, + const std::vector<UsbDeviceHandle::IsochronousPacket>& packets) { + mock_inbound_data_.push(data); + mock_inbound_packets_.push(packets); + } + + void AddMockOutboundData(const std::vector<uint8_t>& data) { + mock_outbound_data_.push(data); + } + + void AddMockOutboundPackets( + const std::vector<uint8_t>& data, + const std::vector<UsbDeviceHandle::IsochronousPacket>& packets) { + mock_outbound_data_.push(data); + mock_outbound_packets_.push(packets); + } + + private: + void OpenMockHandle(const UsbDevice::OpenCallback& callback) { + EXPECT_FALSE(is_device_open_); + is_device_open_ = true; + callback.Run(mock_handle_); + } + + void CloseMockHandle() { + EXPECT_TRUE(is_device_open_); + is_device_open_ = false; + } + + const UsbConfigDescriptor* GetActiveConfiguration() { + if (current_config_ == 0) { + return nullptr; + } else { + const auto it = mock_configs_.find(current_config_); + EXPECT_TRUE(it != mock_configs_.end()); + return &it->second; + } + } + + void SetConfiguration(uint8_t value, + const UsbDeviceHandle::ResultCallback& callback) { + if (mock_configs_.find(value) != mock_configs_.end()) { + current_config_ = value; + callback.Run(true); + } else { + callback.Run(false); + } + } + + void ClaimInterface(uint8_t interface_number, + const UsbDeviceHandle::ResultCallback& callback) { + for (const auto& config : mock_configs_) { + for (const auto& interface : config.second.interfaces) { + if (interface.interface_number == interface_number) { + claimed_interfaces_.insert(interface_number); + callback.Run(true); + return; + } + } + } + callback.Run(false); + } + + void ReleaseInterface(uint8_t interface_number, + const UsbDeviceHandle::ResultCallback& callback) { + if (ContainsKey(claimed_interfaces_, interface_number)) { + claimed_interfaces_.erase(interface_number); + callback.Run(true); + } else { + callback.Run(false); + } + } + + void SetInterfaceAlternateSetting( + uint8_t interface_number, + uint8_t alternate_setting, + const UsbDeviceHandle::ResultCallback& callback) { + for (const auto& config : mock_configs_) { + for (const auto& interface : config.second.interfaces) { + if (interface.interface_number == interface_number && + interface.alternate_setting == alternate_setting) { + callback.Run(true); + return; + } + } + } + callback.Run(false); + } + + void ResetDevice(const UsbDeviceHandle::ResultCallback& callback) { + callback.Run(allow_reset_); + } + + void InboundTransfer(const UsbDeviceHandle::TransferCallback& callback) { + ASSERT_GE(mock_inbound_data_.size(), 1u); + const std::vector<uint8_t>& bytes = mock_inbound_data_.front(); + size_t length = bytes.size(); + scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(length); + std::copy(bytes.begin(), bytes.end(), buffer->data()); + mock_inbound_data_.pop(); + callback.Run(USB_TRANSFER_COMPLETED, buffer, length); + } + + void OutboundTransfer(scoped_refptr<net::IOBuffer> buffer, + size_t length, + const UsbDeviceHandle::TransferCallback& callback) { + ASSERT_GE(mock_outbound_data_.size(), 1u); + const std::vector<uint8_t>& bytes = mock_outbound_data_.front(); + ASSERT_EQ(bytes.size(), length); + for (size_t i = 0; i < length; ++i) { + EXPECT_EQ(bytes[i], buffer->data()[i]) << "Contents differ at index: " + << i; + } + mock_outbound_data_.pop(); + callback.Run(USB_TRANSFER_COMPLETED, buffer, length); + } + + void ControlTransfer(UsbEndpointDirection direction, + UsbDeviceHandle::TransferRequestType request_type, + UsbDeviceHandle::TransferRecipient recipient, + uint8_t request, + uint16_t value, + uint16_t index, + scoped_refptr<net::IOBuffer> buffer, + size_t length, + unsigned int timeout, + const UsbDeviceHandle::TransferCallback& callback) { + if (direction == USB_DIRECTION_INBOUND) + InboundTransfer(callback); + else + OutboundTransfer(buffer, length, callback); + } + + void GenericTransfer(UsbEndpointDirection direction, + uint8_t endpoint, + scoped_refptr<net::IOBuffer> buffer, + size_t length, + unsigned int timeout, + const UsbDeviceHandle::TransferCallback& callback) { + if (direction == USB_DIRECTION_INBOUND) + InboundTransfer(callback); + else + OutboundTransfer(buffer, length, callback); + } + + void IsochronousTransferIn( + uint8_t endpoint_number, + const std::vector<uint32_t>& packet_lengths, + unsigned int timeout, + const UsbDeviceHandle::IsochronousTransferCallback& callback) { + ASSERT_FALSE(mock_inbound_data_.empty()); + const std::vector<uint8_t>& bytes = mock_inbound_data_.front(); + size_t length = bytes.size(); + scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(length); + std::copy(bytes.begin(), bytes.end(), buffer->data()); + mock_inbound_data_.pop(); + + ASSERT_FALSE(mock_inbound_packets_.empty()); + std::vector<UsbDeviceHandle::IsochronousPacket> packets = + mock_inbound_packets_.front(); + ASSERT_EQ(packets.size(), packet_lengths.size()); + for (size_t i = 0; i < packets.size(); ++i) { + EXPECT_EQ(packets[i].length, packet_lengths[i]) + << "Packet lengths differ at index: " << i; + } + mock_inbound_packets_.pop(); + + callback.Run(buffer, packets); + } + + void IsochronousTransferOut( + uint8_t endpoint_number, + scoped_refptr<net::IOBuffer> buffer, + const std::vector<uint32_t>& packet_lengths, + unsigned int timeout, + const UsbDeviceHandle::IsochronousTransferCallback& callback) { + ASSERT_FALSE(mock_outbound_data_.empty()); + const std::vector<uint8_t>& bytes = mock_outbound_data_.front(); + size_t length = + std::accumulate(packet_lengths.begin(), packet_lengths.end(), 0u); + ASSERT_EQ(bytes.size(), length); + for (size_t i = 0; i < length; ++i) { + EXPECT_EQ(bytes[i], buffer->data()[i]) << "Contents differ at index: " + << i; + } + mock_outbound_data_.pop(); + + ASSERT_FALSE(mock_outbound_packets_.empty()); + std::vector<UsbDeviceHandle::IsochronousPacket> packets = + mock_outbound_packets_.front(); + ASSERT_EQ(packets.size(), packet_lengths.size()); + for (size_t i = 0; i < packets.size(); ++i) { + EXPECT_EQ(packets[i].length, packet_lengths[i]) + << "Packet lengths differ at index: " << i; + } + mock_outbound_packets_.pop(); + + callback.Run(buffer, packets); + } + + scoped_ptr<base::MessageLoop> message_loop_; + scoped_refptr<MockUsbDevice> mock_device_; + scoped_refptr<MockUsbDeviceHandle> mock_handle_; + bool is_device_open_; + bool allow_reset_; + + std::map<uint8_t, UsbConfigDescriptor> mock_configs_; + uint8_t current_config_; + + std::queue<std::vector<uint8_t>> mock_inbound_data_; + std::queue<std::vector<uint8_t>> mock_outbound_data_; + std::queue<std::vector<UsbDeviceHandle::IsochronousPacket>> + mock_inbound_packets_; + std::queue<std::vector<UsbDeviceHandle::IsochronousPacket>> + mock_outbound_packets_; + + std::set<uint8_t> claimed_interfaces_; + + FakePermissionProvider permission_provider_; + + DISALLOW_COPY_AND_ASSIGN(USBDeviceImplTest); +}; + +} // namespace + +TEST_F(USBDeviceImplTest, Open) { + DevicePtr device = GetMockDeviceProxy(); + + EXPECT_FALSE(is_device_open()); + + EXPECT_CALL(mock_device(), Open(_)); + + base::RunLoop loop; + device->Open( + base::Bind(&ExpectOpenAndThen, OpenDeviceError::OK, loop.QuitClosure())); + loop.Run(); + + EXPECT_CALL(mock_handle(), Close()); +} + +TEST_F(USBDeviceImplTest, Close) { + DevicePtr device = GetMockDeviceProxy(); + + EXPECT_FALSE(is_device_open()); + + EXPECT_CALL(mock_device(), Open(_)); + + { + base::RunLoop loop; + device->Open(base::Bind(&ExpectOpenAndThen, OpenDeviceError::OK, + loop.QuitClosure())); + loop.Run(); + } + + EXPECT_CALL(mock_handle(), Close()); + + { + base::RunLoop loop; + device->Close(loop.QuitClosure()); + loop.Run(); + } + + EXPECT_FALSE(is_device_open()); +} + +// Test that the information returned via the Device::GetDeviceInfo matches that +// of the underlying device. +TEST_F(USBDeviceImplTest, GetDeviceInfo) { + DevicePtr device = + GetMockDeviceProxy(0x1234, 0x5678, "ACME", "Frobinator", "ABCDEF"); + + base::RunLoop loop; + device->GetDeviceInfo(base::Bind(&ExpectDeviceInfoAndThen, + mock_device().guid(), 0x1234, 0x5678, "ACME", + "Frobinator", "ABCDEF", loop.QuitClosure())); + loop.Run(); +} + +TEST_F(USBDeviceImplTest, SetInvalidConfiguration) { + DevicePtr device = GetMockDeviceProxy(); + + EXPECT_CALL(mock_device(), Open(_)); + + { + base::RunLoop loop; + device->Open(base::Bind(&ExpectOpenAndThen, OpenDeviceError::OK, + loop.QuitClosure())); + loop.Run(); + } + + EXPECT_CALL(mock_handle(), SetConfiguration(42, _)); + + { + // SetConfiguration should fail because 42 is not a valid mock + // configuration. + base::RunLoop loop; + device->SetConfiguration( + 42, base::Bind(&ExpectResultAndThen, false, loop.QuitClosure())); + loop.Run(); + } + + EXPECT_CALL(mock_handle(), Close()); +} + +TEST_F(USBDeviceImplTest, SetValidConfiguration) { + DevicePtr device = GetMockDeviceProxy(); + + EXPECT_CALL(mock_device(), Open(_)); + + { + base::RunLoop loop; + device->Open(base::Bind(&ExpectOpenAndThen, OpenDeviceError::OK, + loop.QuitClosure())); + loop.Run(); + } + + EXPECT_CALL(mock_handle(), SetConfiguration(42, _)); + + AddMockConfig(ConfigBuilder(42)); + + { + // SetConfiguration should succeed because 42 is a valid mock configuration. + base::RunLoop loop; + device->SetConfiguration( + 42, base::Bind(&ExpectResultAndThen, true, loop.QuitClosure())); + loop.Run(); + } + + EXPECT_CALL(mock_handle(), Close()); +} + +// Verify that the result of Reset() reflects the underlying UsbDeviceHandle's +// ResetDevice() result. +TEST_F(USBDeviceImplTest, Reset) { + DevicePtr device = GetMockDeviceProxy(); + + EXPECT_CALL(mock_device(), Open(_)); + + { + base::RunLoop loop; + device->Open(base::Bind(&ExpectOpenAndThen, OpenDeviceError::OK, + loop.QuitClosure())); + loop.Run(); + } + + EXPECT_CALL(mock_handle(), ResetDevice(_)); + + set_allow_reset(true); + + { + base::RunLoop loop; + device->Reset(base::Bind(&ExpectResultAndThen, true, loop.QuitClosure())); + loop.Run(); + } + + EXPECT_CALL(mock_handle(), ResetDevice(_)); + + set_allow_reset(false); + + { + base::RunLoop loop; + device->Reset(base::Bind(&ExpectResultAndThen, false, loop.QuitClosure())); + loop.Run(); + } + + EXPECT_CALL(mock_handle(), Close()); +} + +TEST_F(USBDeviceImplTest, ClaimAndReleaseInterface) { + DevicePtr device = GetMockDeviceProxy(); + + EXPECT_CALL(mock_device(), Open(_)); + + { + base::RunLoop loop; + device->Open(base::Bind(&ExpectOpenAndThen, OpenDeviceError::OK, + loop.QuitClosure())); + loop.Run(); + } + + // Now add a mock interface #1. + AddMockConfig(ConfigBuilder(1).AddInterface(1, 0, 1, 2, 3)); + + EXPECT_CALL(mock_handle(), SetConfiguration(1, _)); + + { + base::RunLoop loop; + device->SetConfiguration( + 1, base::Bind(&ExpectResultAndThen, true, loop.QuitClosure())); + loop.Run(); + } + + EXPECT_CALL(mock_device(), GetActiveConfiguration()); + EXPECT_CALL(mock_handle(), ClaimInterface(2, _)); + + { + // Try to claim an invalid interface and expect failure. + base::RunLoop loop; + device->ClaimInterface( + 2, base::Bind(&ExpectResultAndThen, false, loop.QuitClosure())); + loop.Run(); + } + + EXPECT_CALL(mock_device(), GetActiveConfiguration()); + EXPECT_CALL(mock_handle(), ClaimInterface(1, _)); + + { + base::RunLoop loop; + device->ClaimInterface( + 1, base::Bind(&ExpectResultAndThen, true, loop.QuitClosure())); + loop.Run(); + } + + EXPECT_CALL(mock_handle(), ReleaseInterface(2, _)); + + { + // Releasing a non-existent interface should fail. + base::RunLoop loop; + device->ReleaseInterface( + 2, base::Bind(&ExpectResultAndThen, false, loop.QuitClosure())); + loop.Run(); + } + + EXPECT_CALL(mock_handle(), ReleaseInterface(1, _)); + + { + // Now this should release the claimed interface and close the handle. + base::RunLoop loop; + device->ReleaseInterface( + 1, base::Bind(&ExpectResultAndThen, true, loop.QuitClosure())); + loop.Run(); + } + + EXPECT_CALL(mock_handle(), Close()); +} + +TEST_F(USBDeviceImplTest, SetInterfaceAlternateSetting) { + DevicePtr device = GetMockDeviceProxy(); + + EXPECT_CALL(mock_device(), Open(_)); + + { + base::RunLoop loop; + device->Open(base::Bind(&ExpectOpenAndThen, OpenDeviceError::OK, + loop.QuitClosure())); + loop.Run(); + } + + AddMockConfig(ConfigBuilder(1) + .AddInterface(1, 0, 1, 2, 3) + .AddInterface(1, 42, 1, 2, 3) + .AddInterface(2, 0, 1, 2, 3)); + + EXPECT_CALL(mock_handle(), SetInterfaceAlternateSetting(1, 42, _)); + + { + base::RunLoop loop; + device->SetInterfaceAlternateSetting( + 1, 42, base::Bind(&ExpectResultAndThen, true, loop.QuitClosure())); + loop.Run(); + } + + EXPECT_CALL(mock_handle(), SetInterfaceAlternateSetting(1, 100, _)); + + { + base::RunLoop loop; + device->SetInterfaceAlternateSetting( + 1, 100, base::Bind(&ExpectResultAndThen, false, loop.QuitClosure())); + loop.Run(); + } + + EXPECT_CALL(mock_handle(), Close()); +} + +TEST_F(USBDeviceImplTest, ControlTransfer) { + DevicePtr device = GetMockDeviceProxy(); + + EXPECT_CALL(mock_device(), Open(_)); + + { + base::RunLoop loop; + device->Open(base::Bind(&ExpectOpenAndThen, OpenDeviceError::OK, + loop.QuitClosure())); + loop.Run(); + } + + AddMockConfig(ConfigBuilder(1).AddInterface(7, 0, 1, 2, 3)); + + EXPECT_CALL(mock_device(), GetActiveConfiguration()); + EXPECT_CALL(mock_handle(), SetConfiguration(1, _)); + + { + base::RunLoop loop; + device->SetConfiguration( + 1, base::Bind(&ExpectResultAndThen, true, loop.QuitClosure())); + loop.Run(); + } + + std::vector<uint8_t> fake_data; + fake_data.push_back(41); + fake_data.push_back(42); + fake_data.push_back(43); + + AddMockInboundData(fake_data); + + EXPECT_CALL(mock_handle(), + ControlTransfer(USB_DIRECTION_INBOUND, UsbDeviceHandle::STANDARD, + UsbDeviceHandle::DEVICE, 5, 6, 7, _, _, 0, _)); + + { + auto params = ControlTransferParams::New(); + params->type = ControlTransferType::STANDARD; + params->recipient = ControlTransferRecipient::DEVICE; + params->request = 5; + params->value = 6; + params->index = 7; + base::RunLoop loop; + device->ControlTransferIn( + std::move(params), static_cast<uint32_t>(fake_data.size()), 0, + base::Bind(&ExpectTransferInAndThen, TransferStatus::COMPLETED, + fake_data, loop.QuitClosure())); + loop.Run(); + } + + AddMockOutboundData(fake_data); + + EXPECT_CALL(mock_device(), GetActiveConfiguration()); + EXPECT_CALL(mock_handle(), + ControlTransfer(USB_DIRECTION_OUTBOUND, UsbDeviceHandle::STANDARD, + UsbDeviceHandle::INTERFACE, 5, 6, 7, _, _, 0, _)); + + { + auto params = ControlTransferParams::New(); + params->type = ControlTransferType::STANDARD; + params->recipient = ControlTransferRecipient::INTERFACE; + params->request = 5; + params->value = 6; + params->index = 7; + base::RunLoop loop; + device->ControlTransferOut( + std::move(params), mojo::Array<uint8_t>::From(fake_data), 0, + base::Bind(&ExpectTransferStatusAndThen, TransferStatus::COMPLETED, + loop.QuitClosure())); + loop.Run(); + } + + EXPECT_CALL(mock_handle(), Close()); +} + +TEST_F(USBDeviceImplTest, GenericTransfer) { + DevicePtr device = GetMockDeviceProxy(); + + EXPECT_CALL(mock_device(), Open(_)); + + { + base::RunLoop loop; + device->Open(base::Bind(&ExpectOpenAndThen, OpenDeviceError::OK, + loop.QuitClosure())); + loop.Run(); + } + + std::string message1 = "say hello please"; + std::vector<uint8_t> fake_outbound_data(message1.size()); + std::copy(message1.begin(), message1.end(), fake_outbound_data.begin()); + + std::string message2 = "hello world!"; + std::vector<uint8_t> fake_inbound_data(message2.size()); + std::copy(message2.begin(), message2.end(), fake_inbound_data.begin()); + + AddMockConfig(ConfigBuilder(1).AddInterface(7, 0, 1, 2, 3)); + AddMockOutboundData(fake_outbound_data); + AddMockInboundData(fake_inbound_data); + + EXPECT_CALL(mock_handle(), GenericTransfer(USB_DIRECTION_OUTBOUND, 0x01, _, + fake_outbound_data.size(), 0, _)); + + { + base::RunLoop loop; + device->GenericTransferOut( + 1, mojo::Array<uint8_t>::From(fake_outbound_data), 0, + base::Bind(&ExpectTransferStatusAndThen, TransferStatus::COMPLETED, + loop.QuitClosure())); + loop.Run(); + } + + EXPECT_CALL(mock_handle(), GenericTransfer(USB_DIRECTION_INBOUND, 0x81, _, + fake_inbound_data.size(), 0, _)); + + { + base::RunLoop loop; + device->GenericTransferIn( + 1, static_cast<uint32_t>(fake_inbound_data.size()), 0, + base::Bind(&ExpectTransferInAndThen, TransferStatus::COMPLETED, + fake_inbound_data, loop.QuitClosure())); + loop.Run(); + } + + EXPECT_CALL(mock_handle(), Close()); +} + +TEST_F(USBDeviceImplTest, IsochronousTransfer) { + DevicePtr device = GetMockDeviceProxy(); + + EXPECT_CALL(mock_device(), Open(_)); + + { + base::RunLoop loop; + device->Open(base::Bind(&ExpectOpenAndThen, OpenDeviceError::OK, + loop.QuitClosure())); + loop.Run(); + } + + std::vector<UsbDeviceHandle::IsochronousPacket> fake_packets(4); + for (size_t i = 0; i < fake_packets.size(); ++i) { + fake_packets[i].length = 8; + fake_packets[i].transferred_length = 8; + fake_packets[i].status = USB_TRANSFER_COMPLETED; + } + std::vector<uint32_t> fake_packet_lengths(4, 8); + + std::vector<uint32_t> expected_transferred_lengths(4, 8); + + std::string outbound_data = "aaaaaaaabbbbbbbbccccccccdddddddd"; + std::vector<uint8_t> fake_outbound_data(outbound_data.size()); + std::copy(outbound_data.begin(), outbound_data.end(), + fake_outbound_data.begin()); + + std::string inbound_data = "ddddddddccccccccbbbbbbbbaaaaaaaa"; + std::vector<uint8_t> fake_inbound_data(inbound_data.size()); + std::copy(inbound_data.begin(), inbound_data.end(), + fake_inbound_data.begin()); + + AddMockConfig(ConfigBuilder(1).AddInterface(7, 0, 1, 2, 3)); + AddMockOutboundPackets(fake_outbound_data, fake_packets); + AddMockInboundPackets(fake_inbound_data, fake_packets); + + EXPECT_CALL(mock_handle(), + IsochronousTransferOut(0x01, _, fake_packet_lengths, 0, _)); + + { + base::RunLoop loop; + device->IsochronousTransferOut( + 1, mojo::Array<uint8_t>::From(fake_outbound_data), + mojo::Array<uint32_t>::From(fake_packet_lengths), 0, + base::Bind(&ExpectPacketsOutAndThen, expected_transferred_lengths, + loop.QuitClosure())); + loop.Run(); + } + + EXPECT_CALL(mock_handle(), + IsochronousTransferIn(0x81, fake_packet_lengths, 0, _)); + + { + base::RunLoop loop; + device->IsochronousTransferIn( + 1, mojo::Array<uint32_t>::From(fake_packet_lengths), 0, + base::Bind(&ExpectPacketsInAndThen, fake_inbound_data, + expected_transferred_lengths, loop.QuitClosure())); + loop.Run(); + } + + EXPECT_CALL(mock_handle(), Close()); +} + +} // namespace usb +} // namespace device diff --git a/device/usb/mojo/device_manager_impl.cc b/device/usb/mojo/device_manager_impl.cc new file mode 100644 index 0000000..fdb4d5c --- /dev/null +++ b/device/usb/mojo/device_manager_impl.cc @@ -0,0 +1,228 @@ +// 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 "device/usb/mojo/device_manager_impl.h" + +#include <stddef.h> +#include <utility> + +#include "base/bind.h" +#include "base/location.h" +#include "base/memory/scoped_ptr.h" +#include "base/stl_util.h" +#include "device/core/device_client.h" +#include "device/usb/mojo/device_impl.h" +#include "device/usb/mojo/type_converters.h" +#include "device/usb/public/interfaces/device.mojom.h" +#include "device/usb/usb_device.h" +#include "device/usb/usb_device_filter.h" +#include "device/usb/usb_service.h" +#include "mojo/public/cpp/bindings/array.h" +#include "mojo/public/cpp/bindings/interface_request.h" + +namespace device { +namespace usb { + +namespace { + +using DeviceList = DeviceManagerImpl::DeviceList; +using DeviceMap = DeviceManagerImpl::DeviceMap; + +void FilterAndConvertDevicesAndThen( + const DeviceMap& devices, + const DeviceManagerImpl::GetDevicesCallback& callback, + mojo::Array<mojo::String> allowed_guids) { + mojo::Array<DeviceInfoPtr> allowed_devices(allowed_guids.size()); + for (size_t i = 0; i < allowed_guids.size(); ++i) { + const auto it = devices.find(allowed_guids[i]); + DCHECK(it != devices.end()); + allowed_devices[i] = DeviceInfo::From(*it->second); + } + + callback.Run(std::move(allowed_devices)); +} + +} // namespace + +// static +void DeviceManagerImpl::Create(PermissionProviderPtr permission_provider, + mojo::InterfaceRequest<DeviceManager> request) { + // The created object is owned by its binding. + new DeviceManagerImpl(std::move(permission_provider), std::move(request)); +} + +DeviceManagerImpl::DeviceManagerImpl( + PermissionProviderPtr permission_provider, + mojo::InterfaceRequest<DeviceManager> request) + : permission_provider_(std::move(permission_provider)), + observer_(this), + binding_(this, std::move(request)), + weak_factory_(this) { + // This object owns itself and will be destroyed if either the message pipe + // it is bound to is closed or the PermissionProvider it depends on is + // unavailable. + binding_.set_connection_error_handler([this]() { delete this; }); + permission_provider_.set_connection_error_handler([this]() { delete this; }); + + DCHECK(DeviceClient::Get()); + usb_service_ = DeviceClient::Get()->GetUsbService(); + if (usb_service_) + observer_.Add(usb_service_); +} + +DeviceManagerImpl::~DeviceManagerImpl() { + connection_error_handler_.Run(); +} + +void DeviceManagerImpl::GetDevices(EnumerationOptionsPtr options, + const GetDevicesCallback& callback) { + if (!usb_service_) { + mojo::Array<DeviceInfoPtr> no_devices; + callback.Run(std::move(no_devices)); + return; + } + + usb_service_->GetDevices(base::Bind(&DeviceManagerImpl::OnGetDevices, + weak_factory_.GetWeakPtr(), + base::Passed(&options), callback)); +} + +void DeviceManagerImpl::GetDeviceChanges( + const GetDeviceChangesCallback& callback) { + device_change_callbacks_.push(callback); + MaybeRunDeviceChangesCallback(); +} + +void DeviceManagerImpl::GetDevice( + const mojo::String& guid, + mojo::InterfaceRequest<Device> device_request) { + if (!usb_service_) + return; + + scoped_refptr<UsbDevice> device = usb_service_->GetDevice(guid); + if (!device) + return; + + mojo::Array<DeviceInfoPtr> requested_devices(1); + requested_devices[0] = DeviceInfo::From(*device); + permission_provider_->HasDevicePermission( + std::move(requested_devices), + base::Bind(&DeviceManagerImpl::OnGetDevicePermissionCheckComplete, + base::Unretained(this), device, + base::Passed(&device_request))); +} + +void DeviceManagerImpl::OnGetDevicePermissionCheckComplete( + scoped_refptr<UsbDevice> device, + mojo::InterfaceRequest<Device> device_request, + mojo::Array<mojo::String> allowed_guids) { + if (allowed_guids.size() == 0) + return; + + DCHECK(allowed_guids.size() == 1); + PermissionProviderPtr permission_provider; + permission_provider_->Bind(mojo::GetProxy(&permission_provider)); + new DeviceImpl(device, std::move(permission_provider), + std::move(device_request)); +} + +void DeviceManagerImpl::OnGetDevices(EnumerationOptionsPtr options, + const GetDevicesCallback& callback, + const DeviceList& devices) { + std::vector<UsbDeviceFilter> filters; + if (options) + filters = options->filters.To<std::vector<UsbDeviceFilter>>(); + + std::map<std::string, scoped_refptr<UsbDevice>> device_map; + mojo::Array<DeviceInfoPtr> requested_devices(0); + for (const auto& device : devices) { + if (filters.empty() || UsbDeviceFilter::MatchesAny(device, filters)) { + device_map[device->guid()] = device; + requested_devices.push_back(DeviceInfo::From(*device)); + } + } + + permission_provider_->HasDevicePermission( + std::move(requested_devices), + base::Bind(&FilterAndConvertDevicesAndThen, device_map, callback)); +} + +void DeviceManagerImpl::OnDeviceAdded(scoped_refptr<UsbDevice> device) { + DCHECK(!ContainsKey(devices_removed_, device->guid())); + devices_added_[device->guid()] = device; + MaybeRunDeviceChangesCallback(); +} + +void DeviceManagerImpl::OnDeviceRemoved(scoped_refptr<UsbDevice> device) { + if (devices_added_.erase(device->guid()) == 0) + devices_removed_[device->guid()] = device; + MaybeRunDeviceChangesCallback(); +} + +void DeviceManagerImpl::WillDestroyUsbService() { + observer_.RemoveAll(); + usb_service_ = nullptr; +} + +void DeviceManagerImpl::MaybeRunDeviceChangesCallback() { + if (!permission_request_pending_ && !device_change_callbacks_.empty()) { + DeviceMap devices_added; + devices_added.swap(devices_added_); + DeviceMap devices_removed; + devices_removed.swap(devices_removed_); + + mojo::Array<DeviceInfoPtr> requested_devices(devices_added.size() + + devices_removed.size()); + { + size_t i = 0; + for (const auto& map_entry : devices_added) + requested_devices[i++] = DeviceInfo::From(*map_entry.second); + for (const auto& map_entry : devices_removed) + requested_devices[i++] = DeviceInfo::From(*map_entry.second); + } + + permission_request_pending_ = true; + permission_provider_->HasDevicePermission( + std::move(requested_devices), + base::Bind(&DeviceManagerImpl::OnEnumerationPermissionCheckComplete, + base::Unretained(this), devices_added, devices_removed)); + } +} + +void DeviceManagerImpl::OnEnumerationPermissionCheckComplete( + const DeviceMap& devices_added, + const DeviceMap& devices_removed, + mojo::Array<mojo::String> allowed_guids) { + permission_request_pending_ = false; + + if (allowed_guids.size() > 0) { + DeviceChangeNotificationPtr notification = DeviceChangeNotification::New(); + notification->devices_added.resize(0); + notification->devices_removed.resize(0); + + for (size_t i = 0; i < allowed_guids.size(); ++i) { + const mojo::String& guid = allowed_guids[i]; + auto it = devices_added.find(guid); + if (it != devices_added.end()) { + DCHECK(!ContainsKey(devices_removed, guid)); + notification->devices_added.push_back(DeviceInfo::From(*it->second)); + } else { + it = devices_removed.find(guid); + DCHECK(it != devices_removed.end()); + notification->devices_removed.push_back(DeviceInfo::From(*it->second)); + } + } + + DCHECK(!device_change_callbacks_.empty()); + const GetDeviceChangesCallback& callback = device_change_callbacks_.front(); + callback.Run(std::move(notification)); + device_change_callbacks_.pop(); + } + + if (devices_added_.size() > 0 || !devices_removed_.empty()) + MaybeRunDeviceChangesCallback(); +} + +} // namespace usb +} // namespace device diff --git a/device/usb/mojo/device_manager_impl.h b/device/usb/mojo/device_manager_impl.h new file mode 100644 index 0000000..85e46ce --- /dev/null +++ b/device/usb/mojo/device_manager_impl.h @@ -0,0 +1,111 @@ +// 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. + +#ifndef DEVICE_USB_MOJO_DEVICE_MANAGER_IMPL_H_ +#define DEVICE_USB_MOJO_DEVICE_MANAGER_IMPL_H_ + +#include <queue> +#include <set> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/scoped_observer.h" +#include "device/usb/public/interfaces/device_manager.mojom.h" +#include "device/usb/public/interfaces/permission_provider.mojom.h" +#include "device/usb/usb_service.h" +#include "mojo/public/cpp/bindings/array.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/interface_request.h" + +namespace base { +class SequencedTaskRunner; +} + +namespace device { + +class UsbDevice; +class UsbDeviceFilter; +class UsbDeviceHandle; + +namespace usb { + +class DeviceManagerDelegate; + +// Implementation of the public DeviceManager interface. This interface can be +// requested from the devices app located at "mojo:devices", if available. +class DeviceManagerImpl : public DeviceManager, + public device::UsbService::Observer { + public: + using DeviceList = std::vector<scoped_refptr<UsbDevice>>; + using DeviceMap = std::map<std::string, scoped_refptr<device::UsbDevice>>; + + static void Create(PermissionProviderPtr permission_provider, + mojo::InterfaceRequest<DeviceManager> request); + + DeviceManagerImpl(PermissionProviderPtr permission_provider, + mojo::InterfaceRequest<DeviceManager> request); + ~DeviceManagerImpl() override; + + void set_connection_error_handler(const mojo::Closure& error_handler) { + connection_error_handler_ = error_handler; + } + + private: + // DeviceManager implementation: + void GetDevices(EnumerationOptionsPtr options, + const GetDevicesCallback& callback) override; + void GetDeviceChanges(const GetDeviceChangesCallback& callback) override; + void GetDevice(const mojo::String& guid, + mojo::InterfaceRequest<Device> device_request) override; + + // Callbacks to handle the async responses from the underlying UsbService. + void OnGetDevicePermissionCheckComplete( + scoped_refptr<device::UsbDevice> device, + mojo::InterfaceRequest<Device> device_request, + mojo::Array<mojo::String> allowed_guids); + void OnGetDevices(EnumerationOptionsPtr options, + const GetDevicesCallback& callback, + const DeviceList& devices); + + // UsbService::Observer implementation: + void OnDeviceAdded(scoped_refptr<device::UsbDevice> device) override; + void OnDeviceRemoved(scoped_refptr<device::UsbDevice> device) override; + void WillDestroyUsbService() override; + + void MaybeRunDeviceChangesCallback(); + void OnEnumerationPermissionCheckComplete( + const DeviceMap& devices_added, + const DeviceMap& devices_removed, + mojo::Array<mojo::String> allowed_guids); + + PermissionProviderPtr permission_provider_; + + // If there are unfinished calls to GetDeviceChanges their callbacks + // are stored in |device_change_callbacks_|. Otherwise device changes + // are collected in |devices_added_| and |devices_removed_| until the + // next call to GetDeviceChanges. + std::queue<GetDeviceChangesCallback> device_change_callbacks_; + DeviceMap devices_added_; + DeviceMap devices_removed_; + // To ensure that GetDeviceChangesCallbacks are called in the correct order + // only perform a single request to |permission_provider_| at a time. + bool permission_request_pending_ = false; + + UsbService* usb_service_; + ScopedObserver<device::UsbService, device::UsbService::Observer> observer_; + + mojo::Closure connection_error_handler_; + + mojo::Binding<DeviceManager> binding_; + base::WeakPtrFactory<DeviceManagerImpl> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(DeviceManagerImpl); +}; + +} // namespace usb +} // namespace device + +#endif // DEVICE_USB_MOJO_DEVICE_MANAGER_IMPL_H_ diff --git a/device/usb/mojo/device_manager_impl_unittest.cc b/device/usb/mojo/device_manager_impl_unittest.cc new file mode 100644 index 0000000..139f5a03 --- /dev/null +++ b/device/usb/mojo/device_manager_impl_unittest.cc @@ -0,0 +1,218 @@ +// 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 "device/usb/mojo/device_manager_impl.h" + +#include <stddef.h> +#include <set> +#include <string> +#include <utility> + +#include "base/bind.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/thread_task_runner_handle.h" +#include "device/core/mock_device_client.h" +#include "device/usb/mock_usb_device.h" +#include "device/usb/mock_usb_device_handle.h" +#include "device/usb/mock_usb_service.h" +#include "device/usb/mojo/device_impl.h" +#include "device/usb/mojo/fake_permission_provider.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::Invoke; +using ::testing::_; + +namespace device { +namespace usb { + +namespace { + +class USBDeviceManagerImplTest : public testing::Test { + public: + USBDeviceManagerImplTest() : message_loop_(new base::MessageLoop) {} + ~USBDeviceManagerImplTest() override {} + + protected: + DeviceManagerPtr ConnectToDeviceManager() { + PermissionProviderPtr permission_provider; + permission_provider_.Bind(mojo::GetProxy(&permission_provider)); + DeviceManagerPtr device_manager; + DeviceManagerImpl::Create(std::move(permission_provider), + mojo::GetProxy(&device_manager)); + return device_manager; + } + + MockDeviceClient device_client_; + + private: + FakePermissionProvider permission_provider_; + scoped_ptr<base::MessageLoop> message_loop_; +}; + +void ExpectDevicesAndThen(const std::set<std::string>& expected_guids, + const base::Closure& continuation, + mojo::Array<DeviceInfoPtr> results) { + EXPECT_EQ(expected_guids.size(), results.size()); + std::set<std::string> actual_guids; + for (size_t i = 0; i < results.size(); ++i) + actual_guids.insert(results[i]->guid); + EXPECT_EQ(expected_guids, actual_guids); + continuation.Run(); +} + +void ExpectDeviceChangesAndThen( + const std::set<std::string>& expected_added_guids, + const std::set<std::string>& expected_removed_guids, + const base::Closure& continuation, + DeviceChangeNotificationPtr results) { + EXPECT_EQ(expected_added_guids.size(), results->devices_added.size()); + std::set<std::string> actual_added_guids; + for (size_t i = 0; i < results->devices_added.size(); ++i) + actual_added_guids.insert(results->devices_added[i]->guid); + EXPECT_EQ(expected_added_guids, actual_added_guids); + EXPECT_EQ(expected_removed_guids.size(), results->devices_removed.size()); + std::set<std::string> actual_removed_guids; + for (size_t i = 0; i < results->devices_removed.size(); ++i) + actual_removed_guids.insert(results->devices_removed[i]->guid); + EXPECT_EQ(expected_removed_guids, actual_removed_guids); + continuation.Run(); +} + +void ExpectDeviceInfoAndThen(const std::string& expected_guid, + const base::Closure& continuation, + DeviceInfoPtr device_info) { + ASSERT_TRUE(device_info); + EXPECT_EQ(expected_guid, device_info->guid); + continuation.Run(); +} + +} // namespace + +// Test basic GetDevices functionality to ensure that all mock devices are +// returned by the service. +TEST_F(USBDeviceManagerImplTest, GetDevices) { + scoped_refptr<MockUsbDevice> device0 = + new MockUsbDevice(0x1234, 0x5678, "ACME", "Frobinator", "ABCDEF"); + scoped_refptr<MockUsbDevice> device1 = + new MockUsbDevice(0x1234, 0x5679, "ACME", "Frobinator+", "GHIJKL"); + scoped_refptr<MockUsbDevice> device2 = + new MockUsbDevice(0x1234, 0x567a, "ACME", "Frobinator Mk II", "MNOPQR"); + + device_client_.usb_service()->AddDevice(device0); + device_client_.usb_service()->AddDevice(device1); + device_client_.usb_service()->AddDevice(device2); + + DeviceManagerPtr device_manager = ConnectToDeviceManager(); + + EnumerationOptionsPtr options = EnumerationOptions::New(); + options->filters = mojo::Array<DeviceFilterPtr>::New(1); + options->filters[0] = DeviceFilter::New(); + options->filters[0]->has_vendor_id = true; + options->filters[0]->vendor_id = 0x1234; + + std::set<std::string> guids; + guids.insert(device0->guid()); + guids.insert(device1->guid()); + guids.insert(device2->guid()); + + base::RunLoop loop; + device_manager->GetDevices( + std::move(options), + base::Bind(&ExpectDevicesAndThen, guids, loop.QuitClosure())); + loop.Run(); +} + +// Test requesting a single Device by GUID. +TEST_F(USBDeviceManagerImplTest, GetDevice) { + scoped_refptr<MockUsbDevice> mock_device = + new MockUsbDevice(0x1234, 0x5678, "ACME", "Frobinator", "ABCDEF"); + + device_client_.usb_service()->AddDevice(mock_device); + + DeviceManagerPtr device_manager = ConnectToDeviceManager(); + + { + base::RunLoop loop; + DevicePtr device; + device_manager->GetDevice(mock_device->guid(), mojo::GetProxy(&device)); + device->GetDeviceInfo(base::Bind(&ExpectDeviceInfoAndThen, + mock_device->guid(), loop.QuitClosure())); + loop.Run(); + } + + DevicePtr bad_device; + device_manager->GetDevice("not a real guid", mojo::GetProxy(&bad_device)); + + { + base::RunLoop loop; + bad_device.set_connection_error_handler(loop.QuitClosure()); + loop.Run(); + } +} + +// Test requesting device enumeration updates with GetDeviceChanges. +TEST_F(USBDeviceManagerImplTest, GetDeviceChanges) { + scoped_refptr<MockUsbDevice> device0 = + new MockUsbDevice(0x1234, 0x5678, "ACME", "Frobinator", "ABCDEF"); + scoped_refptr<MockUsbDevice> device1 = + new MockUsbDevice(0x1234, 0x5679, "ACME", "Frobinator+", "GHIJKL"); + scoped_refptr<MockUsbDevice> device2 = + new MockUsbDevice(0x1234, 0x567a, "ACME", "Frobinator Mk II", "MNOPQR"); + scoped_refptr<MockUsbDevice> device3 = + new MockUsbDevice(0x1234, 0x567b, "ACME", "Frobinator Xtreme", "STUVWX"); + + device_client_.usb_service()->AddDevice(device0); + + DeviceManagerPtr device_manager = ConnectToDeviceManager(); + + { + // Call GetDevices once to make sure the device manager is up and running + // or else we could end up waiting forever for device changes as the next + // block races with the ServiceThreadHelper startup. + std::set<std::string> guids; + guids.insert(device0->guid()); + base::RunLoop loop; + device_manager->GetDevices( + nullptr, base::Bind(&ExpectDevicesAndThen, guids, loop.QuitClosure())); + loop.Run(); + } + + device_client_.usb_service()->AddDevice(device1); + device_client_.usb_service()->AddDevice(device2); + device_client_.usb_service()->RemoveDevice(device1); + + { + std::set<std::string> added_guids; + std::set<std::string> removed_guids; + added_guids.insert(device2->guid()); + base::RunLoop loop; + device_manager->GetDeviceChanges(base::Bind(&ExpectDeviceChangesAndThen, + added_guids, removed_guids, + loop.QuitClosure())); + loop.Run(); + } + + device_client_.usb_service()->RemoveDevice(device0); + device_client_.usb_service()->RemoveDevice(device2); + device_client_.usb_service()->AddDevice(device3); + + { + std::set<std::string> added_guids; + std::set<std::string> removed_guids; + added_guids.insert(device3->guid()); + removed_guids.insert(device0->guid()); + removed_guids.insert(device2->guid()); + base::RunLoop loop; + device_manager->GetDeviceChanges(base::Bind(&ExpectDeviceChangesAndThen, + added_guids, removed_guids, + loop.QuitClosure())); + loop.Run(); + } +} + +} // namespace usb +} // namespace device diff --git a/device/usb/mojo/fake_permission_provider.cc b/device/usb/mojo/fake_permission_provider.cc new file mode 100644 index 0000000..9dd7b4f --- /dev/null +++ b/device/usb/mojo/fake_permission_provider.cc @@ -0,0 +1,46 @@ +// 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 "device/usb/mojo/fake_permission_provider.h" + +#include <stddef.h> +#include <utility> + +namespace device { +namespace usb { + +FakePermissionProvider::FakePermissionProvider() {} + +FakePermissionProvider::~FakePermissionProvider() {} + +void FakePermissionProvider::HasDevicePermission( + mojo::Array<DeviceInfoPtr> requested_devices, + const HasDevicePermissionCallback& callback) { + mojo::Array<mojo::String> allowed_guids(requested_devices.size()); + for (size_t i = 0; i < requested_devices.size(); ++i) + allowed_guids[i] = requested_devices[i]->guid; + callback.Run(std::move(allowed_guids)); +} + +void FakePermissionProvider::HasConfigurationPermission( + uint8_t requested_configuration, + device::usb::DeviceInfoPtr device, + const HasInterfacePermissionCallback& callback) { + callback.Run(true); +} +void FakePermissionProvider::HasInterfacePermission( + uint8_t requested_interface, + uint8_t configuration_value, + device::usb::DeviceInfoPtr device, + const HasInterfacePermissionCallback& callback) { + callback.Run(true); +} + +void FakePermissionProvider::Bind( + mojo::InterfaceRequest<PermissionProvider> request) { + bindings_.AddBinding(this, std::move(request)); +} + +} // namespace usb +} // namespace device diff --git a/device/usb/mojo/fake_permission_provider.h b/device/usb/mojo/fake_permission_provider.h new file mode 100644 index 0000000..22292ee --- /dev/null +++ b/device/usb/mojo/fake_permission_provider.h @@ -0,0 +1,44 @@ +// 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. + +#ifndef DEVICE_USB_FAKE_PERMISSION_PROVIDER_H_ +#define DEVICE_USB_FAKE_PERMISSION_PROVIDER_H_ + +#include <stdint.h> + +#include "device/usb/public/interfaces/permission_provider.mojom.h" +#include "mojo/common/weak_binding_set.h" +#include "mojo/public/cpp/bindings/array.h" +#include "mojo/public/cpp/bindings/interface_request.h" + +namespace device { +namespace usb { + +class FakePermissionProvider : public PermissionProvider { + public: + FakePermissionProvider(); + ~FakePermissionProvider() override; + + void HasDevicePermission( + mojo::Array<DeviceInfoPtr> requested_devices, + const HasDevicePermissionCallback& callback) override; + void HasConfigurationPermission( + uint8_t requested_configuration, + device::usb::DeviceInfoPtr device, + const HasInterfacePermissionCallback& callback) override; + void HasInterfacePermission( + uint8_t requested_interface, + uint8_t configuration_value, + device::usb::DeviceInfoPtr device, + const HasInterfacePermissionCallback& callback) override; + void Bind(mojo::InterfaceRequest<PermissionProvider> request) override; + + private: + mojo::WeakBindingSet<PermissionProvider> bindings_; +}; + +} // namespace usb +} // namespace device + +#endif // DEVICE_USB_FAKE_PERMISSION_PROVIDER_H_ diff --git a/device/usb/mojo/type_converters.cc b/device/usb/mojo/type_converters.cc new file mode 100644 index 0000000..0145abd --- /dev/null +++ b/device/usb/mojo/type_converters.cc @@ -0,0 +1,285 @@ +// 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 "device/usb/mojo/type_converters.h" + +#include <stddef.h> +#include <stdint.h> + +#include <map> +#include <utility> + +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "device/usb/usb_device.h" +#include "mojo/common/url_type_converters.h" +#include "mojo/public/cpp/bindings/array.h" + +namespace mojo { + +// static +device::UsbDeviceFilter +TypeConverter<device::UsbDeviceFilter, device::usb::DeviceFilterPtr>::Convert( + const device::usb::DeviceFilterPtr& mojo_filter) { + device::UsbDeviceFilter filter; + if (mojo_filter->has_vendor_id) + filter.SetVendorId(mojo_filter->vendor_id); + if (mojo_filter->has_product_id) + filter.SetProductId(mojo_filter->product_id); + if (mojo_filter->has_class_code) + filter.SetInterfaceClass(mojo_filter->class_code); + if (mojo_filter->has_subclass_code) + filter.SetInterfaceSubclass(mojo_filter->subclass_code); + if (mojo_filter->has_protocol_code) + filter.SetInterfaceProtocol(mojo_filter->protocol_code); + return filter; +} + +// static +device::usb::TransferDirection +TypeConverter<device::usb::TransferDirection, device::UsbEndpointDirection>:: + Convert(const device::UsbEndpointDirection& direction) { + if (direction == device::USB_DIRECTION_INBOUND) + return device::usb::TransferDirection::INBOUND; + DCHECK(direction == device::USB_DIRECTION_OUTBOUND); + return device::usb::TransferDirection::OUTBOUND; +} + +// static +device::usb::TransferStatus +TypeConverter<device::usb::TransferStatus, device::UsbTransferStatus>::Convert( + const device::UsbTransferStatus& status) { + switch (status) { + case device::USB_TRANSFER_COMPLETED: + return device::usb::TransferStatus::COMPLETED; + case device::USB_TRANSFER_ERROR: + return device::usb::TransferStatus::TRANSFER_ERROR; + case device::USB_TRANSFER_TIMEOUT: + return device::usb::TransferStatus::TIMEOUT; + case device::USB_TRANSFER_CANCELLED: + return device::usb::TransferStatus::CANCELLED; + case device::USB_TRANSFER_STALLED: + return device::usb::TransferStatus::STALLED; + case device::USB_TRANSFER_DISCONNECT: + return device::usb::TransferStatus::DISCONNECT; + case device::USB_TRANSFER_OVERFLOW: + return device::usb::TransferStatus::BABBLE; + case device::USB_TRANSFER_LENGTH_SHORT: + return device::usb::TransferStatus::SHORT_PACKET; + default: + NOTREACHED(); + return device::usb::TransferStatus::TRANSFER_ERROR; + } +} + +// static +device::UsbDeviceHandle::TransferRequestType +TypeConverter<device::UsbDeviceHandle::TransferRequestType, + device::usb::ControlTransferType>:: + Convert(const device::usb::ControlTransferType& type) { + switch (type) { + case device::usb::ControlTransferType::STANDARD: + return device::UsbDeviceHandle::STANDARD; + case device::usb::ControlTransferType::CLASS: + return device::UsbDeviceHandle::CLASS; + case device::usb::ControlTransferType::VENDOR: + return device::UsbDeviceHandle::VENDOR; + case device::usb::ControlTransferType::RESERVED: + return device::UsbDeviceHandle::RESERVED; + default: + NOTREACHED(); + return device::UsbDeviceHandle::RESERVED; + } +} + +// static +device::UsbDeviceHandle::TransferRecipient +TypeConverter<device::UsbDeviceHandle::TransferRecipient, + device::usb::ControlTransferRecipient>:: + Convert(const device::usb::ControlTransferRecipient& recipient) { + switch (recipient) { + case device::usb::ControlTransferRecipient::DEVICE: + return device::UsbDeviceHandle::DEVICE; + case device::usb::ControlTransferRecipient::INTERFACE: + return device::UsbDeviceHandle::INTERFACE; + case device::usb::ControlTransferRecipient::ENDPOINT: + return device::UsbDeviceHandle::ENDPOINT; + case device::usb::ControlTransferRecipient::OTHER: + return device::UsbDeviceHandle::OTHER; + default: + NOTREACHED(); + return device::UsbDeviceHandle::OTHER; + } +} + +// static +device::usb::EndpointType +TypeConverter<device::usb::EndpointType, device::UsbTransferType>::Convert( + const device::UsbTransferType& type) { + switch (type) { + case device::USB_TRANSFER_ISOCHRONOUS: + return device::usb::EndpointType::ISOCHRONOUS; + case device::USB_TRANSFER_BULK: + return device::usb::EndpointType::BULK; + case device::USB_TRANSFER_INTERRUPT: + return device::usb::EndpointType::INTERRUPT; + // Note that we do not expose control transfer in the public interface + // because control endpoints are implied rather than explicitly enumerated + // there. + default: + NOTREACHED(); + return device::usb::EndpointType::BULK; + } +} + +// static +device::usb::EndpointInfoPtr +TypeConverter<device::usb::EndpointInfoPtr, device::UsbEndpointDescriptor>:: + Convert(const device::UsbEndpointDescriptor& endpoint) { + device::usb::EndpointInfoPtr info = device::usb::EndpointInfo::New(); + info->endpoint_number = endpoint.address; + info->direction = + ConvertTo<device::usb::TransferDirection>(endpoint.direction); + info->type = ConvertTo<device::usb::EndpointType>(endpoint.transfer_type); + info->packet_size = static_cast<uint32_t>(endpoint.maximum_packet_size); + return info; +} + +// static +device::usb::AlternateInterfaceInfoPtr +TypeConverter<device::usb::AlternateInterfaceInfoPtr, + device::UsbInterfaceDescriptor>:: + Convert(const device::UsbInterfaceDescriptor& interface) { + device::usb::AlternateInterfaceInfoPtr info = + device::usb::AlternateInterfaceInfo::New(); + info->alternate_setting = interface.alternate_setting; + info->class_code = interface.interface_class; + info->subclass_code = interface.interface_subclass; + info->protocol_code = interface.interface_protocol; + + // Filter out control endpoints for the public interface. + info->endpoints = mojo::Array<device::usb::EndpointInfoPtr>::New(0); + for (const auto& endpoint : interface.endpoints) { + if (endpoint.transfer_type != device::USB_TRANSFER_CONTROL) + info->endpoints.push_back(device::usb::EndpointInfo::From(endpoint)); + } + + return info; +} + +// static +mojo::Array<device::usb::InterfaceInfoPtr> +TypeConverter<mojo::Array<device::usb::InterfaceInfoPtr>, + std::vector<device::UsbInterfaceDescriptor>>:: + Convert(const std::vector<device::UsbInterfaceDescriptor>& interfaces) { + auto infos = mojo::Array<device::usb::InterfaceInfoPtr>::New(0); + + // Aggregate each alternate setting into an InterfaceInfo corresponding to its + // interface number. + std::map<uint8_t, device::usb::InterfaceInfo*> interface_map; + for (size_t i = 0; i < interfaces.size(); ++i) { + auto alternate = device::usb::AlternateInterfaceInfo::From(interfaces[i]); + auto iter = interface_map.find(interfaces[i].interface_number); + if (iter == interface_map.end()) { + // This is the first time we're seeing an alternate with this interface + // number, so add a new InterfaceInfo to the array and map the number. + auto info = device::usb::InterfaceInfo::New(); + iter = interface_map + .insert( + std::make_pair(interfaces[i].interface_number, info.get())) + .first; + infos.push_back(std::move(info)); + } + iter->second->alternates.push_back(std::move(alternate)); + } + + return infos; +} + +// static +device::usb::ConfigurationInfoPtr +TypeConverter<device::usb::ConfigurationInfoPtr, device::UsbConfigDescriptor>:: + Convert(const device::UsbConfigDescriptor& config) { + device::usb::ConfigurationInfoPtr info = + device::usb::ConfigurationInfo::New(); + info->configuration_value = config.configuration_value; + info->interfaces = + mojo::Array<device::usb::InterfaceInfoPtr>::From(config.interfaces); + return info; +} + +// static +device::usb::WebUsbFunctionSubsetPtr TypeConverter< + device::usb::WebUsbFunctionSubsetPtr, + device::WebUsbFunctionSubset>::Convert(const device::WebUsbFunctionSubset& + function) { + device::usb::WebUsbFunctionSubsetPtr info = + device::usb::WebUsbFunctionSubset::New(); + info->first_interface = function.first_interface; + info->origins = mojo::Array<mojo::String>::From(function.origins); + return info; +} + +// static +device::usb::WebUsbConfigurationSubsetPtr +TypeConverter<device::usb::WebUsbConfigurationSubsetPtr, + device::WebUsbConfigurationSubset>:: + Convert(const device::WebUsbConfigurationSubset& config) { + device::usb::WebUsbConfigurationSubsetPtr info = + device::usb::WebUsbConfigurationSubset::New(); + info->configuration_value = config.configuration_value; + info->origins = mojo::Array<mojo::String>::From(config.origins); + info->functions = + mojo::Array<device::usb::WebUsbFunctionSubsetPtr>::From(config.functions); + return info; +} + +// static +device::usb::WebUsbDescriptorSetPtr TypeConverter< + device::usb::WebUsbDescriptorSetPtr, + device::WebUsbAllowedOrigins>::Convert(const device::WebUsbAllowedOrigins& + allowed_origins) { + device::usb::WebUsbDescriptorSetPtr info = + device::usb::WebUsbDescriptorSet::New(); + info->origins = mojo::Array<mojo::String>::From(allowed_origins.origins); + info->configurations = + mojo::Array<device::usb::WebUsbConfigurationSubsetPtr>::From( + allowed_origins.configurations); + return info; +} + +// static +device::usb::DeviceInfoPtr +TypeConverter<device::usb::DeviceInfoPtr, device::UsbDevice>::Convert( + const device::UsbDevice& device) { + device::usb::DeviceInfoPtr info = device::usb::DeviceInfo::New(); + info->guid = device.guid(); + info->vendor_id = device.vendor_id(); + info->product_id = device.product_id(); + info->manufacturer_name = base::UTF16ToUTF8(device.manufacturer_string()); + info->product_name = base::UTF16ToUTF8(device.product_string()); + info->serial_number = base::UTF16ToUTF8(device.serial_number()); + info->configurations = mojo::Array<device::usb::ConfigurationInfoPtr>::From( + device.configurations()); + if (device.webusb_allowed_origins()) { + info->webusb_allowed_origins = device::usb::WebUsbDescriptorSet::From( + *device.webusb_allowed_origins()); + } + return info; +} + +// static +device::usb::IsochronousPacketPtr +TypeConverter<device::usb::IsochronousPacketPtr, + device::UsbDeviceHandle::IsochronousPacket>:: + Convert(const device::UsbDeviceHandle::IsochronousPacket& packet) { + device::usb::IsochronousPacketPtr info = + device::usb::IsochronousPacket::New(); + info->length = packet.length; + info->transferred_length = packet.transferred_length; + info->status = mojo::ConvertTo<device::usb::TransferStatus>(packet.status); + return info; +} + +} // namespace mojo diff --git a/device/usb/mojo/type_converters.h b/device/usb/mojo/type_converters.h new file mode 100644 index 0000000..4984c6e --- /dev/null +++ b/device/usb/mojo/type_converters.h @@ -0,0 +1,134 @@ +// 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. + +#ifndef DEVICE_USB_MOJO_TYPE_CONVERTERS_H_ +#define DEVICE_USB_MOJO_TYPE_CONVERTERS_H_ + +#include <vector> + +#include "device/usb/public/interfaces/device.mojom.h" +#include "device/usb/public/interfaces/device_manager.mojom.h" +#include "device/usb/usb_descriptors.h" +#include "device/usb/usb_device_filter.h" +#include "device/usb/usb_device_handle.h" +#include "device/usb/webusb_descriptors.h" +#include "mojo/public/cpp/bindings/array.h" +#include "mojo/public/cpp/bindings/type_converter.h" + +// Type converters to translate between internal device/usb data types and +// public Mojo interface data types. This must be included by any source file +// that uses these conversions explicitly or implicitly. + +namespace device { +class UsbDevice; +} + +namespace mojo { + +template <> +struct TypeConverter<device::UsbDeviceFilter, device::usb::DeviceFilterPtr> { + static device::UsbDeviceFilter Convert( + const device::usb::DeviceFilterPtr& mojo_filter); +}; + +template <> +struct TypeConverter<device::usb::TransferDirection, + device::UsbEndpointDirection> { + static device::usb::TransferDirection Convert( + const device::UsbEndpointDirection& direction); +}; + +template <> +struct TypeConverter<device::usb::TransferStatus, device::UsbTransferStatus> { + static device::usb::TransferStatus Convert( + const device::UsbTransferStatus& status); +}; + +template <> +struct TypeConverter<device::UsbDeviceHandle::TransferRequestType, + device::usb::ControlTransferType> { + static device::UsbDeviceHandle::TransferRequestType Convert( + const device::usb::ControlTransferType& type); +}; + +template <> +struct TypeConverter<device::UsbDeviceHandle::TransferRecipient, + device::usb::ControlTransferRecipient> { + static device::UsbDeviceHandle::TransferRecipient Convert( + const device::usb::ControlTransferRecipient& recipient); +}; + +template <> +struct TypeConverter<device::usb::EndpointType, device::UsbTransferType> { + static device::usb::EndpointType Convert(const device::UsbTransferType& type); +}; + +template <> +struct TypeConverter<device::usb::EndpointInfoPtr, + device::UsbEndpointDescriptor> { + static device::usb::EndpointInfoPtr Convert( + const device::UsbEndpointDescriptor& endpoint); +}; + +template <> +struct TypeConverter<device::usb::AlternateInterfaceInfoPtr, + device::UsbInterfaceDescriptor> { + static device::usb::AlternateInterfaceInfoPtr Convert( + const device::UsbInterfaceDescriptor& interface); +}; + +// Note that this is an explicit vector-to-array conversion, as +// UsbInterfaceDescriptor collections are flattened to contain all alternate +// settings, whereas InterfaceInfos contain their own sets of alternates with +// a different structure type. +template <> +struct TypeConverter<mojo::Array<device::usb::InterfaceInfoPtr>, + std::vector<device::UsbInterfaceDescriptor>> { + static mojo::Array<device::usb::InterfaceInfoPtr> Convert( + const std::vector<device::UsbInterfaceDescriptor>& interfaces); +}; + +template <> +struct TypeConverter<device::usb::ConfigurationInfoPtr, + device::UsbConfigDescriptor> { + static device::usb::ConfigurationInfoPtr Convert( + const device::UsbConfigDescriptor& config); +}; + +template <> +struct TypeConverter<device::usb::WebUsbFunctionSubsetPtr, + device::WebUsbFunctionSubset> { + static device::usb::WebUsbFunctionSubsetPtr Convert( + const device::WebUsbFunctionSubset& function); +}; + +template <> +struct TypeConverter<device::usb::WebUsbConfigurationSubsetPtr, + device::WebUsbConfigurationSubset> { + static device::usb::WebUsbConfigurationSubsetPtr Convert( + const device::WebUsbConfigurationSubset& config); +}; + +template <> +struct TypeConverter<device::usb::WebUsbDescriptorSetPtr, + device::WebUsbAllowedOrigins> { + static device::usb::WebUsbDescriptorSetPtr Convert( + const device::WebUsbAllowedOrigins& allowed_origins); +}; + +template <> +struct TypeConverter<device::usb::DeviceInfoPtr, device::UsbDevice> { + static device::usb::DeviceInfoPtr Convert(const device::UsbDevice& device); +}; + +template <> +struct TypeConverter<device::usb::IsochronousPacketPtr, + device::UsbDeviceHandle::IsochronousPacket> { + static device::usb::IsochronousPacketPtr Convert( + const device::UsbDeviceHandle::IsochronousPacket& packet); +}; + +} // namespace mojo + +#endif // DEVICE_DEVICES_APP_USB_TYPE_CONVERTERS_H_ diff --git a/device/usb/public/interfaces/BUILD.gn b/device/usb/public/interfaces/BUILD.gn new file mode 100644 index 0000000..f9d6e0c --- /dev/null +++ b/device/usb/public/interfaces/BUILD.gn @@ -0,0 +1,13 @@ +# 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. + +import("//mojo/public/tools/bindings/mojom.gni") + +mojom("interfaces") { + sources = [ + "device.mojom", + "device_manager.mojom", + "permission_provider.mojom", + ] +} diff --git a/device/usb/public/interfaces/device.mojom b/device/usb/public/interfaces/device.mojom new file mode 100644 index 0000000..25addb2 --- /dev/null +++ b/device/usb/public/interfaces/device.mojom @@ -0,0 +1,285 @@ +// 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. + +module device.usb; + +enum OpenDeviceError { + // Opening the device succeeded. + OK, + + // The operating system denied access to the device. + ACCESS_DENIED, +}; + +enum TransferDirection { + INBOUND, + OUTBOUND, +}; + +enum ControlTransferType { + STANDARD, + CLASS, + VENDOR, + RESERVED +}; + +enum ControlTransferRecipient { + DEVICE, + INTERFACE, + ENDPOINT, + OTHER +}; + +enum EndpointType { + BULK, + INTERRUPT, + ISOCHRONOUS, +}; + +struct EndpointInfo { + uint8 endpoint_number; + TransferDirection direction; + EndpointType type; + uint32 packet_size; +}; + +struct AlternateInterfaceInfo { + uint8 alternate_setting; + uint8 class_code; + uint8 subclass_code; + uint8 protocol_code; + string? interface_name; + array<EndpointInfo> endpoints; +}; + +struct InterfaceInfo { + uint8 interface_number; + array<AlternateInterfaceInfo> alternates; +}; + +struct ConfigurationInfo { + uint8 configuration_value; + string? configuration_name; + array<InterfaceInfo> interfaces; +}; + +struct WebUsbFunctionSubset { + uint8 first_interface; + array<string> origins; +}; + +struct WebUsbConfigurationSubset { + uint8 configuration_value; + array<string> origins; + array<WebUsbFunctionSubset> functions; +}; + +struct WebUsbDescriptorSet { + array<string> origins; + array<WebUsbConfigurationSubset> configurations; +}; + +struct DeviceInfo { + string guid; + uint8 usb_version_major; + uint8 usb_version_minor; + uint8 usb_version_subminor; + uint8 class_code; + uint8 subclass_code; + uint8 protocol_code; + uint16 vendor_id; + uint16 product_id; + uint8 device_version_major; + uint8 device_version_minor; + uint8 device_version_subminor; + string? manufacturer_name; + string? product_name; + string? serial_number; + array<ConfigurationInfo> configurations; + WebUsbDescriptorSet? webusb_allowed_origins; +}; + +struct ControlTransferParams { + ControlTransferType type; + ControlTransferRecipient recipient; + uint8 request; + uint16 value; + uint16 index; +}; + +enum TransferStatus { + // The transfer completed successfully. + COMPLETED, + + // The transfer failed due to a non-specific error. + TRANSFER_ERROR, + + // The transfer was not allowed. + PERMISSION_DENIED, + + // The transfer timed out. + TIMEOUT, + + // The transfer was cancelled. + CANCELLED, + + // The transfer stalled. + STALLED, + + // The transfer failed because the device was disconnected from the host. + DISCONNECT, + + // The transfer succeeded, but the device sent more data than was requested. + // This applies only to inbound transfers. + BABBLE, + + // The transfer succeeded, but the device sent less data than was requested. + // This applies only to inbound transfers. + SHORT_PACKET, +}; + +struct IsochronousPacket { + uint32 length; + uint32 transferred_length; + TransferStatus status; +}; + +interface Device { + // Retrieve a DeviceInfo struct containing metadata about the device, + // including the set of all available device configurations. + GetDeviceInfo() => (DeviceInfo? info); + + // Retrieves the |configuration_value| of the device's currently active + // configuration. Will return 0 if the device is unconfigured. + GetConfiguration() => (uint8 value); + + // Opens the device. Methods below require the device be opened first. + Open() => (OpenDeviceError error); + + // Closes the device. + Close() => (); + + // Initiates a device control transfer to set the device's configuration to + // one with the configuration value |value|. + SetConfiguration(uint8 value) => (bool success); + + // Claims a single interface in the current device configuration. + ClaimInterface(uint8 interface_number) => (bool success); + + // Releases a claimed interface in the current device configuration. + ReleaseInterface(uint8 interface_number) => (bool success); + + // Selects an alternate setting for a given claimed interface. + SetInterfaceAlternateSetting(uint8 interface_number, uint8 alternate_setting) + => (bool success); + + // Resets the device. + Reset() => (bool success); + + // Clear the halt/stall condition for an endpoint. + ClearHalt(uint8 endpoint) => (bool success); + + // Initiates an inbound control transfer request. |params| determine the + // details of the request. Transfers to recipients other than DEVICE require a + // corresponding interface to be claimed. + // + // |length| specifies the expected number of bytes to receive for this + // transfer. The size of |data| will never exceed |length|, and |data| will be + // null if |status| is neither COMPLETED, BABBLE, or SHORT_PACKET. + // + // |timeout| specifies the request timeout in milliseconds. A timeout of 0 + // indicates no timeout: the request will remain pending indefinitely until + // completed or otherwise terminated. + ControlTransferIn(ControlTransferParams params, uint32 length, uint32 timeout) + => (TransferStatus status, array<uint8>? data); + + // Initiates an inbound control transfer request. |params| determine the + // details of the request. Transfers to recipients other than DEVICE require a + // corresponding interface to be claimed. + // + // |data| specifies the bytes to send the device in the body of the request. + // + // |timeout| specifies the request timeout in milliseconds. A timeout of 0 + // indicates no timeout: the request will remain pending indefinitely until + // completed or otherwise terminated. + ControlTransferOut(ControlTransferParams params, + array<uint8> data, + uint32 timeout) + => (TransferStatus status); + + // Initiates an inbound generic transfer request on a specific endpoint. The + // interface to which |endpoint_number| belongs must be claimed, and the + // appropriate alternate setting must be set on that interface before + // transfers can be initiated on the endpoint. The endpoint must be of type + // BULK or INTERRUPT. + // + // |length| specifies the expected number of bytes to receive for this + // transfer. The size of |data| will never exceed |length|, and |data| will be + // null if |status| is neither COMPLETED, BABBLE, or SHORT_PACKET. + // + // |timeout| specifies the request timeout in milliseconds. A timeout of 0 + // indicates no timeout: the request will remain pending indefinitely until + // completed or otherwise terminated. + GenericTransferIn(uint8 endpoint_number, uint32 length, uint32 timeout) + => (TransferStatus status, array<uint8>? data); + + // Initiates an outbound generic transfer request on a specific endpoint. The + // interface to which |endpoint_number| belongs must be claimed, and the + // appropriate alternate setting must be set on that interface before + // transfers can be initiated on the endpoint. The endpoint must be of type + // BULK or INTERRUPT. + // + // |data| specifies the bytes to send the device in the body of the request. + // + // |timeout| specifies the request timeout in milliseconds. A timeout of 0 + // indicates no timeout: the request will remain pending indefinitely until + // completed or otherwise terminated. + GenericTransferOut(uint8 endpoint_number, array<uint8> data, uint32 timeout) + => (TransferStatus status); + + // Initiates an inbound isochronous transfer request on a specific endpoint. + // The interface to which |endpoint_number| belongs must be claimed, and the + // appropriate alternate setting must be set on that interface before + // transfers can be initiated on the endpoint. The endpoint must be of type + // ISOCHRONOUS. + // + // |packet_lengths| specifies the maximum expected number of bytes to receive + // for each packet in this transfer. + // + // |timeout| specifies the request timeout in milliseconds. A timeout of 0 + // indicates no timeout: the request will remain pending indefinitely until + // completed or otherwise terminated. + // + // |data| contains the data received from the device, if any. |packets| + // contains the status of each packet received from the device, in order. The + // length of the packet indicates its position in |data| while it's + // transferred length gives the amount of data actually received from the + // device. + IsochronousTransferIn(uint8 endpoint_number, + array<uint32> packet_lengths, + uint32 timeout) + => (array<uint8>? data, array<IsochronousPacket> packets); + + // Initiates an outbound isochronous transfer request on a specific endpoint. + // The interface to which |endpoint_number| belongs must be claimed, and the + // appropriate alternate setting must be set on that interface before + // transfers can be initiated on the endpoint. The endpoint must be of type + // ISOCHRONOUS. + // + // |data| specifies the bytes to send to the device. + // + // |packet_lengths| specifies how |data| should be separated into packets when + // it is sent to the device. + // + // |timeout| specifies the request timeout in milliseconds. A timeout of 0 + // indicates no timeout: the request will remain pending indefinitely until + // completed or otherwise terminated. + + // |packets| contains the status of each packet sent to the device, in order. + IsochronousTransferOut(uint8 endpoint_number, + array<uint8> data, + array<uint32> packet_lengths, + uint32 timeout) + => (array<IsochronousPacket> packets); +}; diff --git a/device/usb/public/interfaces/device_manager.mojom b/device/usb/public/interfaces/device_manager.mojom new file mode 100644 index 0000000..fa8bb75 --- /dev/null +++ b/device/usb/public/interfaces/device_manager.mojom @@ -0,0 +1,47 @@ +// 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. + +module device.usb; + +import "device.mojom"; + +struct DeviceFilter { + bool has_vendor_id; + uint16 vendor_id; + + bool has_product_id; + uint16 product_id; + + bool has_class_code; + uint8 class_code; + + bool has_subclass_code; + uint8 subclass_code; + + bool has_protocol_code; + uint8 protocol_code; +}; + +struct EnumerationOptions { + array<DeviceFilter>? filters; +}; + +struct DeviceChangeNotification { + array<DeviceInfo> devices_added; + array<DeviceInfo> devices_removed; +}; + +interface DeviceManager { + // Retrieves information about all devices available to the DeviceManager + // implementation. + GetDevices(EnumerationOptions? options) => (array<DeviceInfo> results); + + // Retrieves information about changes to the set of devices available to the + // DeviceManager since the last call to this method. A device that is added + // and removed between calls will not be included. + GetDeviceChanges() => (DeviceChangeNotification changes); + + // Requests a device by guid. + GetDevice(string guid, Device& device_request); +}; diff --git a/device/usb/public/interfaces/permission_provider.mojom b/device/usb/public/interfaces/permission_provider.mojom new file mode 100644 index 0000000..f5d7111 --- /dev/null +++ b/device/usb/public/interfaces/permission_provider.mojom @@ -0,0 +1,29 @@ +// 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. + +module device.usb; + +import "device.mojom"; + +interface PermissionProvider { + // Filters a set of |requested_devices| down to the set of |allowed_guids| + // that should be accessible to clients of the DeviceManager instance. + HasDevicePermission(array<DeviceInfo> requested_devices) + => (array<string> allowed_guids); + + // Returns whether or not the client has permission to access + // |requested_configuration| on |device|. + HasConfigurationPermission(uint8 requested_configuration, + DeviceInfo device) => (bool allowed); + + // Returns whether or not the client has permission to access + // |requested_interface| on |device| when it is in configuration + // |configuration_value|. + HasInterfacePermission(uint8 requested_interface, + uint8 configuration_value, + DeviceInfo device) => (bool allowed); + + // Requests a new binding to this service. + Bind(PermissionProvider& request); +}; diff --git a/device/usb/usb.gyp b/device/usb/usb.gyp index a68b187..7f5e676 100644 --- a/device/usb/usb.gyp +++ b/device/usb/usb.gyp @@ -22,6 +22,12 @@ 'sources': [ 'android/usb_jni_registrar.cc', 'android/usb_jni_registrar.h', + 'mojo/device_impl.cc', + 'mojo/device_impl.h', + 'mojo/device_manager_impl.cc', + 'mojo/device_manager_impl.h', + 'mojo/type_converters.cc', + 'mojo/type_converters.h', 'usb_configuration_android.cc', 'usb_configuration_android.h', 'usb_context.cc', @@ -116,6 +122,18 @@ ] }, { + 'target_name': 'device_usb_mojo_bindings', + 'type': 'static_library', + 'sources': [ + 'public/interfaces/device.mojom', + 'public/interfaces/device_manager.mojom', + 'public/interfaces/permission_provider.mojom', + ], + 'includes': [ + '../../mojo/mojom_bindings_generator.gypi', + ], + }, + { 'target_name': 'device_usb_mocks', 'type': 'static_library', 'include_dirs': [ |