summaryrefslogtreecommitdiffstats
path: root/device/usb
diff options
context:
space:
mode:
Diffstat (limited to 'device/usb')
-rw-r--r--device/usb/mojo/BUILD.gn24
-rw-r--r--device/usb/mojo/DEPS3
-rw-r--r--device/usb/mojo/device_impl.cc464
-rw-r--r--device/usb/mojo/device_impl.h113
-rw-r--r--device/usb/mojo/device_impl_unittest.cc895
-rw-r--r--device/usb/mojo/device_manager_impl.cc228
-rw-r--r--device/usb/mojo/device_manager_impl.h111
-rw-r--r--device/usb/mojo/device_manager_impl_unittest.cc218
-rw-r--r--device/usb/mojo/fake_permission_provider.cc46
-rw-r--r--device/usb/mojo/fake_permission_provider.h44
-rw-r--r--device/usb/mojo/type_converters.cc285
-rw-r--r--device/usb/mojo/type_converters.h134
-rw-r--r--device/usb/public/interfaces/BUILD.gn13
-rw-r--r--device/usb/public/interfaces/device.mojom285
-rw-r--r--device/usb/public/interfaces/device_manager.mojom47
-rw-r--r--device/usb/public/interfaces/permission_provider.mojom29
-rw-r--r--device/usb/usb.gyp18
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(&params), 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(&params), 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': [