diff options
author | reillyg <reillyg@chromium.org> | 2016-02-02 14:50:28 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2016-02-02 22:51:41 +0000 |
commit | ccbaed85d9538dc645535efdb58c0af6ebafdd43 (patch) | |
tree | d8bd6b97eb8f9f9d4e154533db46eb6eb63787de | |
parent | 8d034e56507a74b9030f309455b325dbaeeb5c1c (diff) | |
download | chromium_src-ccbaed85d9538dc645535efdb58c0af6ebafdd43.zip chromium_src-ccbaed85d9538dc645535efdb58c0af6ebafdd43.tar.gz chromium_src-ccbaed85d9538dc645535efdb58c0af6ebafdd43.tar.bz2 |
Reland of Update device/usb and its Mojo interface for variable size ISO packets. (patchset #1 id:1 of https://codereview.chromium.org/1658953003/ )
Reason for revert:
Fixed the uninitialized test buffer.
Original issue's description:
> Revert of Update device/usb and its Mojo interface for variable size ISO packets. (patchset #3 id:120001 of https://codereview.chromium.org/1618393004/ )
>
> Reason for revert:
> Uninit error from UsbApiTest.TransferFailure
>
> BUG=583343
>
> Original issue's description:
> > Update device/usb and its Mojo interface for variable size ISO packets.
> >
> > To support the WebUSB API our underlying USB library needs to support
> > isochronous transfers with full control over the packet size. We also
> > need to know the completion status of each packet which was previously
> > not available.
> >
> > This patch updates the interface to match that provided by libusb and
> > the underlying platform specific APIs.
> >
> > BUG=492204
> >
> > Committed: https://crrev.com/051c98e9d3e843295d659b5676fcfa9dc1be5da6
> > Cr-Commit-Position: refs/heads/master@{#372844}
>
> TBR=scheib@chromium.org,dgozman@chromium.org,reillyg@chromium.org
> # Skipping CQ checks because original CL landed less than 1 days ago.
> NOPRESUBMIT=true
> NOTREECHECKS=true
> NOTRY=true
> BUG=492204
>
> Committed: https://crrev.com/c40c36ef3d3a722e0df920a4bbae25695f0b18e8
> Cr-Commit-Position: refs/heads/master@{#372971}
TBR=scheib@chromium.org,dgozman@chromium.org,zhaoqin@chromium.org
BUG=583343
Review URL: https://codereview.chromium.org/1665453002
Cr-Commit-Position: refs/heads/master@{#373065}
-rw-r--r-- | chrome/browser/devtools/device/usb/android_usb_browsertest.cc | 20 | ||||
-rw-r--r-- | device/devices_app/usb/device_impl.cc | 96 | ||||
-rw-r--r-- | device/devices_app/usb/device_impl.h | 12 | ||||
-rw-r--r-- | device/devices_app/usb/device_impl_unittest.cc | 195 | ||||
-rw-r--r-- | device/devices_app/usb/public/interfaces/device.mojom | 37 | ||||
-rw-r--r-- | device/devices_app/usb/type_converters.cc | 13 | ||||
-rw-r--r-- | device/devices_app/usb/type_converters.h | 12 | ||||
-rw-r--r-- | device/usb/mock_usb_device_handle.h | 23 | ||||
-rw-r--r-- | device/usb/usb_device_handle.h | 32 | ||||
-rw-r--r-- | device/usb/usb_device_handle_impl.cc | 240 | ||||
-rw-r--r-- | device/usb/usb_device_handle_impl.h | 35 | ||||
-rw-r--r-- | extensions/browser/api/usb/usb_api.cc | 95 | ||||
-rw-r--r-- | extensions/browser/api/usb/usb_api.h | 8 | ||||
-rw-r--r-- | extensions/browser/api/usb/usb_apitest.cc | 52 | ||||
-rw-r--r-- | extensions/test/data/api_test/usb/transfer_event/test.js | 4 | ||||
-rw-r--r-- | extensions/test/data/api_test/usb/transfer_failure/test.js | 32 |
16 files changed, 626 insertions, 280 deletions
diff --git a/chrome/browser/devtools/device/usb/android_usb_browsertest.cc b/chrome/browser/devtools/device/usb/android_usb_browsertest.cc index 3029fd6..caa9950 100644 --- a/chrome/browser/devtools/device/usb/android_usb_browsertest.cc +++ b/chrome/browser/devtools/device/usb/android_usb_browsertest.cc @@ -352,14 +352,18 @@ class MockUsbDeviceHandle : public UsbDeviceHandle { query.buffer, query.size)); } - void IsochronousTransfer(UsbEndpointDirection direction, - uint8_t endpoint, - scoped_refptr<net::IOBuffer> buffer, - size_t length, - unsigned int packets, - unsigned int packet_length, - unsigned int timeout, - const TransferCallback& callback) override {} + void IsochronousTransferIn( + uint8_t endpoint_number, + const std::vector<uint32_t>& packet_lengths, + unsigned int timeout, + const IsochronousTransferCallback& callback) override {} + + void IsochronousTransferOut( + uint8_t endpoint_number, + scoped_refptr<net::IOBuffer> buffer, + const std::vector<uint32_t>& packet_lengths, + unsigned int timeout, + const IsochronousTransferCallback& callback) override {} protected: virtual ~MockUsbDeviceHandle() {} diff --git a/device/devices_app/usb/device_impl.cc b/device/devices_app/usb/device_impl.cc index fd2f0ff..ae8ad40 100644 --- a/device/devices_app/usb/device_impl.cc +++ b/device/devices_app/usb/device_impl.cc @@ -5,7 +5,11 @@ #include "device/devices_app/usb/device_impl.h" #include <stddef.h> + +#include <algorithm> +#include <numeric> #include <utility> +#include <vector> #include "base/bind.h" #include "base/callback.h" @@ -128,32 +132,46 @@ void OnControlTransferOutPermissionCheckComplete( } } +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, - uint32_t packet_size, - UsbTransferStatus status, scoped_refptr<net::IOBuffer> buffer, - size_t buffer_size) { - size_t num_packets = buffer_size / packet_size; - mojo::Array<mojo::Array<uint8_t>> packets(num_packets); + const std::vector<UsbDeviceHandle::IsochronousPacket>& packets) { + mojo::Array<uint8_t> data; if (buffer) { - for (size_t i = 0; i < num_packets; ++i) { - size_t packet_index = i * packet_size; - std::vector<uint8_t> bytes(packet_size); - std::copy(buffer->data() + packet_index, - buffer->data() + packet_index + packet_size, bytes.begin()); - packets[i].Swap(&bytes); - } + // 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(mojo::ConvertTo<TransferStatus>(status), std::move(packets)); + callback->Run(std::move(data), + mojo::Array<IsochronousPacketPtr>::From(packets)); } void OnIsochronousTransferOut( scoped_ptr<Device::IsochronousTransferOutCallback> callback, - UsbTransferStatus status, scoped_refptr<net::IOBuffer> buffer, - size_t buffer_size) { - callback->Run(mojo::ConvertTo<TransferStatus>(status)); + const std::vector<UsbDeviceHandle::IsochronousPacket>& packets) { + callback->Run(mojo::Array<IsochronousPacketPtr>::From(packets)); } } // namespace @@ -398,58 +416,46 @@ void DeviceImpl::GenericTransferOut( void DeviceImpl::IsochronousTransferIn( uint8_t endpoint_number, - uint32_t num_packets, - uint32_t packet_length, + mojo::Array<uint32_t> packet_lengths, uint32_t timeout, const IsochronousTransferInCallback& callback) { if (!device_handle_) { - callback.Run(TransferStatus::TRANSFER_ERROR, - mojo::Array<mojo::Array<uint8_t>>()); + 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; - size_t transfer_size = static_cast<size_t>(num_packets) * packet_length; - scoped_refptr<net::IOBuffer> buffer = CreateTransferBuffer(transfer_size); - device_handle_->IsochronousTransfer( - USB_DIRECTION_INBOUND, endpoint_address, buffer, transfer_size, - num_packets, packet_length, timeout, - base::Bind(&OnIsochronousTransferIn, base::Passed(&callback_ptr), - packet_length)); + 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<mojo::Array<uint8_t>> packets, + mojo::Array<uint8_t> data, + mojo::Array<uint32_t> packet_lengths, uint32_t timeout, const IsochronousTransferOutCallback& callback) { if (!device_handle_) { - callback.Run(TransferStatus::TRANSFER_ERROR); + 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; - uint32_t packet_size = 0; - for (size_t i = 0; i < packets.size(); ++i) { - packet_size = - std::max(packet_size, static_cast<uint32_t>(packets[i].size())); - } - size_t transfer_size = packet_size * packets.size(); - scoped_refptr<net::IOBuffer> buffer = CreateTransferBuffer(transfer_size); - memset(buffer->data(), 0, transfer_size); - for (size_t i = 0; i < packets.size(); ++i) { - uint8_t* packet = - reinterpret_cast<uint8_t*>(&buffer->data()[i * packet_size]); - DCHECK_LE(packets[i].size(), static_cast<size_t>(packet_size)); - memcpy(packet, packets[i].storage().data(), packets[i].size()); + 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_->IsochronousTransfer( - USB_DIRECTION_OUTBOUND, endpoint_address, buffer, transfer_size, - static_cast<uint32_t>(packets.size()), packet_size, timeout, + device_handle_->IsochronousTransferOut( + endpoint_address, buffer, packet_lengths.storage(), timeout, base::Bind(&OnIsochronousTransferOut, base::Passed(&callback_ptr))); } diff --git a/device/devices_app/usb/device_impl.h b/device/devices_app/usb/device_impl.h index 5290fa7..2b438c8 100644 --- a/device/devices_app/usb/device_impl.h +++ b/device/devices_app/usb/device_impl.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef DEVICE_USB_DEVICE_IMPL_H_ -#define DEVICE_USB_DEVICE_IMPL_H_ +#ifndef DEVICE_DEVICES_APP_USB_DEVICE_IMPL_H_ +#define DEVICE_DEVICES_APP_USB_DEVICE_IMPL_H_ #include <stdint.h> @@ -84,13 +84,13 @@ class DeviceImpl : public Device { const GenericTransferOutCallback& callback) override; void IsochronousTransferIn( uint8_t endpoint_number, - uint32_t num_packets, - uint32_t packet_length, + mojo::Array<uint32_t> packet_lengths, uint32_t timeout, const IsochronousTransferInCallback& callback) override; void IsochronousTransferOut( uint8_t endpoint_number, - mojo::Array<mojo::Array<uint8_t>> packets, + mojo::Array<uint8_t> data, + mojo::Array<uint32_t> packet_lengths, uint32_t timeout, const IsochronousTransferOutCallback& callback) override; @@ -110,4 +110,4 @@ class DeviceImpl : public Device { } // namespace usb } // namespace device -#endif // DEVICE_USB_DEVICE_IMPL_H_ +#endif // DEVICE_DEVICES_APP_USB_DEVICE_IMPL_H_ diff --git a/device/devices_app/usb/device_impl_unittest.cc b/device/devices_app/usb/device_impl_unittest.cc index 87324d2..e5ee893 100644 --- a/device/devices_app/usb/device_impl_unittest.cc +++ b/device/devices_app/usb/device_impl_unittest.cc @@ -6,9 +6,12 @@ #include <stddef.h> #include <stdint.h> + #include <map> +#include <numeric> #include <queue> #include <set> +#include <string> #include <utility> #include <vector> @@ -34,7 +37,7 @@ namespace { class ConfigBuilder { public: - ConfigBuilder(uint8_t value) { config_.configuration_value = value; } + explicit ConfigBuilder(uint8_t value) { config_.configuration_value = value; } ConfigBuilder& AddInterface(uint8_t interface_number, uint8_t alternate_setting, @@ -102,21 +105,35 @@ void ExpectTransferInAndThen(TransferStatus expected_status, continuation.Run(); } -void ExpectPacketsAndThen( - TransferStatus expected_status, - const std::vector<std::vector<uint8_t>>& expected_packets, - const base::Closure& continuation, - TransferStatus actual_status, - mojo::Array<mojo::Array<uint8_t>> actual_packets) { - EXPECT_EQ(expected_status, actual_status); +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].size(), actual_packets[i].size()) - << "Packet sizes differ at index: " << i; - for (size_t j = 0; j < expected_packets[i].size(); ++j) { - EXPECT_EQ(expected_packets[i][j], actual_packets[i][j]) - << "Contents of packet " << i << " differ at index " << j; - } + 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(); } @@ -186,8 +203,11 @@ class USBDeviceImplTest : public testing::Test { .WillByDefault(Invoke(this, &USBDeviceImplTest::ControlTransfer)); ON_CALL(mock_handle(), GenericTransfer(_, _, _, _, _, _)) .WillByDefault(Invoke(this, &USBDeviceImplTest::GenericTransfer)); - ON_CALL(mock_handle(), IsochronousTransfer(_, _, _, _, _, _, _, _)) - .WillByDefault(Invoke(this, &USBDeviceImplTest::IsochronousTransfer)); + ON_CALL(mock_handle(), IsochronousTransferIn(_, _, _, _)) + .WillByDefault(Invoke(this, &USBDeviceImplTest::IsochronousTransferIn)); + ON_CALL(mock_handle(), IsochronousTransferOut(_, _, _, _, _)) + .WillByDefault( + Invoke(this, &USBDeviceImplTest::IsochronousTransferOut)); return proxy; } @@ -206,10 +226,24 @@ class USBDeviceImplTest : public testing::Test { 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_); @@ -336,18 +370,59 @@ class USBDeviceImplTest : public testing::Test { OutboundTransfer(buffer, length, callback); } - void IsochronousTransfer(UsbEndpointDirection direction, - uint8_t endpoint, - scoped_refptr<net::IOBuffer> buffer, - size_t length, - unsigned int packets, - unsigned int packet_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_; @@ -361,6 +436,10 @@ class USBDeviceImplTest : public testing::Test { 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_; @@ -758,56 +837,52 @@ TEST_F(USBDeviceImplTest, IsochronousTransfer) { loop.Run(); } - std::string outbound_packet_data = "aaaaaaaabbbbbbbbccccccccdddddddd"; - std::vector<uint8_t> fake_outbound_packets(outbound_packet_data.size()); - std::copy(outbound_packet_data.begin(), outbound_packet_data.end(), - fake_outbound_packets.begin()); + 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_packet_data = "ddddddddccccccccbbbbbbbbaaaaaaaa"; - std::vector<uint8_t> fake_inbound_packets(inbound_packet_data.size()); - std::copy(inbound_packet_data.begin(), inbound_packet_data.end(), - fake_inbound_packets.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)); - AddMockOutboundData(fake_outbound_packets); - AddMockInboundData(fake_inbound_packets); + AddMockOutboundPackets(fake_outbound_data, fake_packets); + AddMockInboundPackets(fake_inbound_data, fake_packets); EXPECT_CALL(mock_handle(), - IsochronousTransfer(USB_DIRECTION_OUTBOUND, 0x01, _, - fake_outbound_packets.size(), 4, 8, 0, _)); + IsochronousTransferOut(0x01, _, fake_packet_lengths, 0, _)); { base::RunLoop loop; - mojo::Array<mojo::Array<uint8_t>> packets = - mojo::Array<mojo::Array<uint8_t>>::New(4); - for (size_t i = 0; i < 4; ++i) { - std::vector<uint8_t> bytes(8); - std::copy(outbound_packet_data.begin() + i * 8, - outbound_packet_data.begin() + i * 8 + 8, bytes.begin()); - packets[i].Swap(&bytes); - } device->IsochronousTransferOut( - 1, std::move(packets), 0, - base::Bind(&ExpectTransferStatusAndThen, TransferStatus::COMPLETED, + 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(), - IsochronousTransfer(USB_DIRECTION_INBOUND, 0x81, _, - fake_inbound_packets.size(), 4, 8, 0, _)); + IsochronousTransferIn(0x81, fake_packet_lengths, 0, _)); { base::RunLoop loop; - std::vector<std::vector<uint8_t>> packets(4); - for (size_t i = 0; i < 4; ++i) { - packets[i].resize(8); - std::copy(inbound_packet_data.begin() + i * 8, - inbound_packet_data.begin() + i * 8 + 8, packets[i].begin()); - } device->IsochronousTransferIn( - 1, 4, 8, 0, base::Bind(&ExpectPacketsAndThen, TransferStatus::COMPLETED, - packets, loop.QuitClosure())); + 1, mojo::Array<uint32_t>::From(fake_packet_lengths), 0, + base::Bind(&ExpectPacketsInAndThen, fake_inbound_data, + expected_transferred_lengths, loop.QuitClosure())); loop.Run(); } diff --git a/device/devices_app/usb/public/interfaces/device.mojom b/device/devices_app/usb/public/interfaces/device.mojom index 438aed1..25addb2 100644 --- a/device/devices_app/usb/public/interfaces/device.mojom +++ b/device/devices_app/usb/public/interfaces/device.mojom @@ -139,6 +139,12 @@ enum TransferStatus { 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. @@ -238,22 +244,22 @@ interface Device { // transfers can be initiated on the endpoint. The endpoint must be of type // ISOCHRONOUS. // - // |packet_length| specifies the maximum expected number of bytes to receive - // for each packet in this transfer. |num_packets| specifies the maximum total - // number of packets to receive. + // |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. // - // |packets| contains the set of packets received from the device, in order. - // No received packet's size will exceed |packet_length|, and will be null - // if |status| is neither COMPLETED, BABBLE, or SHORT_PACKET. + // |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, - uint32 num_packets, - uint32 packet_length, + array<uint32> packet_lengths, uint32 timeout) - => (TransferStatus status, array<array<uint8>>? packets); + => (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 @@ -261,14 +267,19 @@ interface Device { // transfers can be initiated on the endpoint. The endpoint must be of type // ISOCHRONOUS. // - // |packets| specifies the series of data packets to send to the device for - // this transfer. + // |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<array<uint8>> packets, + array<uint8> data, + array<uint32> packet_lengths, uint32 timeout) - => (TransferStatus status); + => (array<IsochronousPacket> packets); }; diff --git a/device/devices_app/usb/type_converters.cc b/device/devices_app/usb/type_converters.cc index e7b2ea6..709b0ca 100644 --- a/device/devices_app/usb/type_converters.cc +++ b/device/devices_app/usb/type_converters.cc @@ -267,4 +267,17 @@ TypeConverter<device::usb::DeviceInfoPtr, device::UsbDevice>::Convert( 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/devices_app/usb/type_converters.h b/device/devices_app/usb/type_converters.h index 59c2f4c..15484ea 100644 --- a/device/devices_app/usb/type_converters.h +++ b/device/devices_app/usb/type_converters.h @@ -16,8 +16,9 @@ #include "mojo/public/cpp/bindings/array.h" #include "mojo/public/cpp/bindings/type_converter.h" -// Type converters to convert objects between internal device/usb data types and -// public Mojo interface data types. +// 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; @@ -121,6 +122,13 @@ 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/mock_usb_device_handle.h b/device/usb/mock_usb_device_handle.h index 372068c..26e6190 100644 --- a/device/usb/mock_usb_device_handle.h +++ b/device/usb/mock_usb_device_handle.h @@ -5,11 +5,12 @@ #ifndef DEVICE_USB_MOCK_USB_DEVICE_HANDLE_H_ #define DEVICE_USB_MOCK_USB_DEVICE_HANDLE_H_ -#include "device/usb/usb_device_handle.h" - #include <stddef.h> #include <stdint.h> +#include <vector> + +#include "device/usb/usb_device_handle.h" #include "net/base/io_buffer.h" #include "testing/gmock/include/gmock/gmock.h" @@ -17,7 +18,7 @@ namespace device { class MockUsbDeviceHandle : public UsbDeviceHandle { public: - MockUsbDeviceHandle(UsbDevice* device); + explicit MockUsbDeviceHandle(UsbDevice* device); scoped_refptr<UsbDevice> GetDevice() const override; MOCK_METHOD0(Close, void()); @@ -44,15 +45,17 @@ class MockUsbDeviceHandle : public UsbDeviceHandle { size_t length, unsigned int timeout, const TransferCallback& callback)); - MOCK_METHOD8(IsochronousTransfer, - void(UsbEndpointDirection direction, - uint8_t endpoint, + MOCK_METHOD4(IsochronousTransferIn, + void(uint8_t endpoint, + const std::vector<uint32_t>& packet_lengths, + unsigned int timeout, + const IsochronousTransferCallback& callback)); + MOCK_METHOD5(IsochronousTransferOut, + void(uint8_t endpoint, scoped_refptr<net::IOBuffer> buffer, - size_t length, - unsigned int packets, - unsigned int packet_length, + const std::vector<uint32_t>& packet_lengths, unsigned int timeout, - const TransferCallback& callback)); + const IsochronousTransferCallback& callback)); MOCK_METHOD6(GenericTransfer, void(UsbEndpointDirection direction, uint8_t endpoint, diff --git a/device/usb/usb_device_handle.h b/device/usb/usb_device_handle.h index a97dddc..b206cf1 100644 --- a/device/usb/usb_device_handle.h +++ b/device/usb/usb_device_handle.h @@ -40,9 +40,18 @@ enum UsbTransferStatus { // UsbDeviceHandle class provides basic I/O related functionalities. class UsbDeviceHandle : public base::RefCountedThreadSafe<UsbDeviceHandle> { public: + struct IsochronousPacket { + uint32_t length; + uint32_t transferred_length; + UsbTransferStatus status; + }; + using ResultCallback = base::Callback<void(bool)>; using TransferCallback = base::Callback< void(UsbTransferStatus, scoped_refptr<net::IOBuffer>, size_t)>; + using IsochronousTransferCallback = + base::Callback<void(scoped_refptr<net::IOBuffer>, + const std::vector<IsochronousPacket>& packets)>; enum TransferRequestType { STANDARD, CLASS, VENDOR, RESERVED }; enum TransferRecipient { DEVICE, INTERFACE, ENDPOINT, OTHER }; @@ -81,17 +90,21 @@ class UsbDeviceHandle : public base::RefCountedThreadSafe<UsbDeviceHandle> { unsigned int timeout, const TransferCallback& callback) = 0; - virtual void IsochronousTransfer(UsbEndpointDirection direction, - uint8_t endpoint, - scoped_refptr<net::IOBuffer> buffer, - size_t length, - unsigned int packets, - unsigned int packet_length, - unsigned int timeout, - const TransferCallback& callback) = 0; + virtual void IsochronousTransferIn( + uint8_t endpoint_number, + const std::vector<uint32_t>& packet_lengths, + unsigned int timeout, + const IsochronousTransferCallback& callback) = 0; + + virtual void IsochronousTransferOut( + uint8_t endpoint_number, + scoped_refptr<net::IOBuffer> buffer, + const std::vector<uint32_t>& packet_lengths, + unsigned int timeout, + const IsochronousTransferCallback& callback) = 0; virtual void GenericTransfer(UsbEndpointDirection direction, - uint8_t endpoint, + uint8_t endpoint_number, scoped_refptr<net::IOBuffer> buffer, size_t length, unsigned int timeout, @@ -109,6 +122,7 @@ class UsbDeviceHandle : public base::RefCountedThreadSafe<UsbDeviceHandle> { virtual ~UsbDeviceHandle(); + private: DISALLOW_COPY_AND_ASSIGN(UsbDeviceHandle); }; diff --git a/device/usb/usb_device_handle_impl.cc b/device/usb/usb_device_handle_impl.cc index 338af16..fa4c085 100644 --- a/device/usb/usb_device_handle_impl.cc +++ b/device/usb/usb_device_handle_impl.cc @@ -5,6 +5,7 @@ #include "device/usb/usb_device_handle_impl.h" #include <algorithm> +#include <numeric> #include <utility> #include <vector> @@ -120,6 +121,26 @@ static void RunTransferCallback( } } +void ReportIsochronousTransferError( + scoped_refptr<base::TaskRunner> callback_task_runner, + const UsbDeviceHandle::IsochronousTransferCallback& callback, + const std::vector<uint32_t> packet_lengths, + UsbTransferStatus status) { + std::vector<UsbDeviceHandle::IsochronousPacket> packets( + packet_lengths.size()); + for (size_t i = 0; i < packet_lengths.size(); ++i) { + packets[i].length = packet_lengths[i]; + packets[i].transferred_length = 0; + packets[i].status = status; + } + if (callback_task_runner->RunsTasksOnCurrentThread()) { + callback.Run(nullptr, packets); + } else { + callback_task_runner->PostTask(FROM_HERE, + base::Bind(callback, nullptr, packets)); + } +} + } // namespace class UsbDeviceHandleImpl::InterfaceClaimer @@ -191,11 +212,10 @@ class UsbDeviceHandleImpl::Transfer { uint8_t endpoint, scoped_refptr<net::IOBuffer> buffer, size_t length, - unsigned int packets, - unsigned int packet_length, + const std::vector<uint32_t>& packet_lengths, unsigned int timeout, - scoped_refptr<base::TaskRunner> task_runner, - const TransferCallback& callback); + scoped_refptr<base::TaskRunner> callback_task_runner, + const IsochronousTransferCallback& callback); ~Transfer(); @@ -220,9 +240,16 @@ class UsbDeviceHandleImpl::Transfer { size_t length, scoped_refptr<base::TaskRunner> callback_task_runner, const TransferCallback& callback); + Transfer(scoped_refptr<UsbDeviceHandleImpl> device_handle, + scoped_refptr<InterfaceClaimer> claimed_interface, + scoped_refptr<net::IOBuffer> buffer, + scoped_refptr<base::TaskRunner> callback_task_runner, + const IsochronousTransferCallback& callback); static void LIBUSB_CALL PlatformCallback(PlatformUsbTransferHandle handle); + void IsochronousTransferComplete(); + UsbTransferType transfer_type_; scoped_refptr<UsbDeviceHandleImpl> device_handle_; PlatformUsbTransferHandle platform_transfer_ = nullptr; @@ -233,6 +260,7 @@ class UsbDeviceHandleImpl::Transfer { scoped_refptr<base::SequencedTaskRunner> task_runner_; scoped_refptr<base::TaskRunner> callback_task_runner_; TransferCallback callback_; + IsochronousTransferCallback iso_callback_; }; // static @@ -289,11 +317,11 @@ UsbDeviceHandleImpl::Transfer::CreateBulkTransfer( return nullptr; } - libusb_fill_bulk_transfer( - transfer->platform_transfer_, device_handle->handle_, endpoint, - reinterpret_cast<uint8_t*>(buffer->data()), static_cast<int>(length), - &UsbDeviceHandleImpl::Transfer::PlatformCallback, transfer.get(), - timeout); + libusb_fill_bulk_transfer(transfer->platform_transfer_, + device_handle->handle_, endpoint, + reinterpret_cast<uint8_t*>(buffer->data()), length, + &UsbDeviceHandleImpl::Transfer::PlatformCallback, + transfer.get(), timeout); return transfer; } @@ -320,7 +348,7 @@ UsbDeviceHandleImpl::Transfer::CreateInterruptTransfer( libusb_fill_interrupt_transfer( transfer->platform_transfer_, device_handle->handle_, endpoint, - reinterpret_cast<uint8_t*>(buffer->data()), static_cast<int>(length), + reinterpret_cast<uint8_t*>(buffer->data()), length, &UsbDeviceHandleImpl::Transfer::PlatformCallback, transfer.get(), timeout); @@ -334,20 +362,16 @@ UsbDeviceHandleImpl::Transfer::CreateIsochronousTransfer( uint8_t endpoint, scoped_refptr<net::IOBuffer> buffer, size_t length, - unsigned int packets, - unsigned int packet_length, + const std::vector<uint32_t>& packet_lengths, unsigned int timeout, scoped_refptr<base::TaskRunner> callback_task_runner, - const TransferCallback& callback) { - DCHECK(packets <= length && (packets * packet_length) <= length) - << "transfer length is too small"; - + const IsochronousTransferCallback& callback) { scoped_ptr<Transfer> transfer(new Transfer( device_handle, device_handle->GetClaimedInterfaceForEndpoint(endpoint), - USB_TRANSFER_ISOCHRONOUS, buffer, length, callback_task_runner, - callback)); + buffer, callback_task_runner, callback)); - transfer->platform_transfer_ = libusb_alloc_transfer(packets); + int num_packets = static_cast<int>(packet_lengths.size()); + transfer->platform_transfer_ = libusb_alloc_transfer(num_packets); if (!transfer->platform_transfer_) { USB_LOG(ERROR) << "Failed to allocate isochronous transfer."; return nullptr; @@ -356,8 +380,10 @@ UsbDeviceHandleImpl::Transfer::CreateIsochronousTransfer( libusb_fill_iso_transfer( transfer->platform_transfer_, device_handle->handle_, endpoint, reinterpret_cast<uint8_t*>(buffer->data()), static_cast<int>(length), - packets, &Transfer::PlatformCallback, transfer.get(), timeout); - libusb_set_iso_packet_lengths(transfer->platform_transfer_, packet_length); + num_packets, &Transfer::PlatformCallback, transfer.get(), timeout); + + for (size_t i = 0; i < packet_lengths.size(); ++i) + transfer->platform_transfer_->iso_packet_desc[i].length = packet_lengths[i]; return transfer; } @@ -380,6 +406,21 @@ UsbDeviceHandleImpl::Transfer::Transfer( task_runner_ = base::ThreadTaskRunnerHandle::Get(); } +UsbDeviceHandleImpl::Transfer::Transfer( + scoped_refptr<UsbDeviceHandleImpl> device_handle, + scoped_refptr<InterfaceClaimer> claimed_interface, + scoped_refptr<net::IOBuffer> buffer, + scoped_refptr<base::TaskRunner> callback_task_runner, + const IsochronousTransferCallback& callback) + : transfer_type_(USB_TRANSFER_ISOCHRONOUS), + device_handle_(device_handle), + buffer_(buffer), + claimed_interface_(claimed_interface), + callback_task_runner_(callback_task_runner), + iso_callback_(callback) { + task_runner_ = base::ThreadTaskRunnerHandle::Get(); +} + UsbDeviceHandleImpl::Transfer::~Transfer() { if (platform_transfer_) { libusb_free_transfer(platform_transfer_); @@ -431,46 +472,22 @@ void UsbDeviceHandleImpl::Transfer::ProcessCompletion() { buffer_ = resized_buffer; } } - break; - - case USB_TRANSFER_ISOCHRONOUS: - // Isochronous replies might carry data in the different isoc packets even - // if the transfer actual_data value is zero. Furthermore, not all of the - // received packets might contain data, so we need to calculate how many - // data bytes we are effectively providing and pack the results. - if (actual_length == 0) { - size_t packet_buffer_start = 0; - for (int i = 0; i < platform_transfer_->num_iso_packets; ++i) { - PlatformUsbIsoPacketDescriptor packet = - &platform_transfer_->iso_packet_desc[i]; - if (packet->actual_length > 0) { - // We don't need to copy as long as all packets until now provide - // all the data the packet can hold. - if (actual_length < packet_buffer_start) { - CHECK(packet_buffer_start + packet->actual_length <= length_); - memmove(buffer_->data() + actual_length, - buffer_->data() + packet_buffer_start, - packet->actual_length); - } - actual_length += packet->actual_length; - } - - packet_buffer_start += packet->length; - } - } - break; + // Fall through! case USB_TRANSFER_BULK: case USB_TRANSFER_INTERRUPT: + TransferComplete(ConvertTransferStatus(platform_transfer_->status), + actual_length); + break; + + case USB_TRANSFER_ISOCHRONOUS: + IsochronousTransferComplete(); break; default: NOTREACHED() << "Invalid usb transfer type"; break; } - - TransferComplete(ConvertTransferStatus(platform_transfer_->status), - actual_length); } /* static */ @@ -484,11 +501,37 @@ void LIBUSB_CALL UsbDeviceHandleImpl::Transfer::PlatformCallback( void UsbDeviceHandleImpl::Transfer::TransferComplete(UsbTransferStatus status, size_t bytes_transferred) { + base::Closure closure; + if (transfer_type_ == USB_TRANSFER_ISOCHRONOUS) { + DCHECK_NE(LIBUSB_TRANSFER_COMPLETED, platform_transfer_->status); + std::vector<IsochronousPacket> packets(platform_transfer_->num_iso_packets); + for (size_t i = 0; i < packets.size(); ++i) { + packets[i].length = platform_transfer_->iso_packet_desc[i].length; + packets[i].transferred_length = 0; + packets[i].status = status; + } + closure = base::Bind(iso_callback_, buffer_, packets); + } else { + closure = base::Bind(callback_, status, buffer_, bytes_transferred); + } task_runner_->PostTask( - FROM_HERE, - base::Bind(&UsbDeviceHandleImpl::TransferComplete, device_handle_, - base::Unretained(this), - base::Bind(callback_, status, buffer_, bytes_transferred))); + FROM_HERE, base::Bind(&UsbDeviceHandleImpl::TransferComplete, + device_handle_, base::Unretained(this), closure)); +} + +void UsbDeviceHandleImpl::Transfer::IsochronousTransferComplete() { + std::vector<IsochronousPacket> packets(platform_transfer_->num_iso_packets); + for (size_t i = 0; i < packets.size(); ++i) { + packets[i].length = platform_transfer_->iso_packet_desc[i].length; + packets[i].transferred_length = + platform_transfer_->iso_packet_desc[i].actual_length; + packets[i].status = + ConvertTransferStatus(platform_transfer_->iso_packet_desc[i].status); + } + task_runner_->PostTask( + FROM_HERE, base::Bind(&UsbDeviceHandleImpl::TransferComplete, + device_handle_, base::Unretained(this), + base::Bind(iso_callback_, buffer_, packets))); } scoped_refptr<UsbDevice> UsbDeviceHandleImpl::GetDevice() const { @@ -632,26 +675,42 @@ void UsbDeviceHandleImpl::ControlTransfer(UsbEndpointDirection direction, } } -void UsbDeviceHandleImpl::IsochronousTransfer( - UsbEndpointDirection direction, +void UsbDeviceHandleImpl::IsochronousTransferIn( + uint8_t endpoint_number, + const std::vector<uint32_t>& packet_lengths, + unsigned int timeout, + const IsochronousTransferCallback& callback) { + uint8_t endpoint_address = + ConvertTransferDirection(USB_DIRECTION_INBOUND) | endpoint_number; + if (task_runner_->BelongsToCurrentThread()) { + IsochronousTransferInInternal(endpoint_address, packet_lengths, timeout, + task_runner_, callback); + } else { + task_runner_->PostTask( + FROM_HERE, + base::Bind(&UsbDeviceHandleImpl::IsochronousTransferInInternal, this, + endpoint_address, packet_lengths, timeout, + base::ThreadTaskRunnerHandle::Get(), callback)); + } +} + +void UsbDeviceHandleImpl::IsochronousTransferOut( uint8_t endpoint_number, scoped_refptr<net::IOBuffer> buffer, - size_t length, - unsigned int packets, - unsigned int packet_length, + const std::vector<uint32_t>& packet_lengths, unsigned int timeout, - const TransferCallback& callback) { + const IsochronousTransferCallback& callback) { uint8_t endpoint_address = - ConvertTransferDirection(direction) | endpoint_number; + ConvertTransferDirection(USB_DIRECTION_OUTBOUND) | endpoint_number; if (task_runner_->BelongsToCurrentThread()) { - IsochronousTransferInternal(endpoint_address, buffer, length, packets, - packet_length, timeout, task_runner_, callback); + IsochronousTransferOutInternal(endpoint_address, buffer, packet_lengths, + timeout, task_runner_, callback); } else { task_runner_->PostTask( FROM_HERE, - base::Bind(&UsbDeviceHandleImpl::IsochronousTransferInternal, this, - endpoint_address, buffer, length, packets, packet_length, - timeout, base::ThreadTaskRunnerHandle::Get(), callback)); + base::Bind(&UsbDeviceHandleImpl::IsochronousTransferOutInternal, this, + endpoint_address, buffer, packet_lengths, timeout, + base::ThreadTaskRunnerHandle::Get(), callback)); } } @@ -886,33 +945,50 @@ void UsbDeviceHandleImpl::ControlTransferInternal( SubmitTransfer(std::move(transfer)); } -void UsbDeviceHandleImpl::IsochronousTransferInternal( +void UsbDeviceHandleImpl::IsochronousTransferInInternal( uint8_t endpoint_address, - scoped_refptr<net::IOBuffer> buffer, - size_t length, - unsigned int packets, - unsigned int packet_length, + const std::vector<uint32_t>& packet_lengths, unsigned int timeout, scoped_refptr<base::TaskRunner> callback_task_runner, - const TransferCallback& callback) { + const IsochronousTransferCallback& callback) { DCHECK(thread_checker_.CalledOnValidThread()); if (!device_) { - RunTransferCallback(callback_task_runner, callback, USB_TRANSFER_DISCONNECT, - buffer, 0); + ReportIsochronousTransferError(callback_task_runner, callback, + packet_lengths, USB_TRANSFER_DISCONNECT); return; } - if (!base::IsValueInRangeForNumericType<int>(length)) { - USB_LOG(USER) << "Transfer too long."; - RunTransferCallback(callback_task_runner, callback, USB_TRANSFER_ERROR, - buffer, 0); + size_t length = + std::accumulate(packet_lengths.begin(), packet_lengths.end(), 0u); + scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(length)); + scoped_ptr<Transfer> transfer = Transfer::CreateIsochronousTransfer( + this, endpoint_address, buffer, length, packet_lengths, timeout, + callback_task_runner, callback); + + SubmitTransfer(std::move(transfer)); +} + +void UsbDeviceHandleImpl::IsochronousTransferOutInternal( + uint8_t endpoint_address, + scoped_refptr<net::IOBuffer> buffer, + const std::vector<uint32_t>& packet_lengths, + unsigned int timeout, + scoped_refptr<base::TaskRunner> callback_task_runner, + const IsochronousTransferCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (!device_) { + ReportIsochronousTransferError(callback_task_runner, callback, + packet_lengths, USB_TRANSFER_DISCONNECT); return; } + size_t length = + std::accumulate(packet_lengths.begin(), packet_lengths.end(), 0u); scoped_ptr<Transfer> transfer = Transfer::CreateIsochronousTransfer( - this, endpoint_address, buffer, static_cast<int>(length), packets, - packet_length, timeout, callback_task_runner, callback); + this, endpoint_address, buffer, length, packet_lengths, timeout, + callback_task_runner, callback); SubmitTransfer(std::move(transfer)); } diff --git a/device/usb/usb_device_handle_impl.h b/device/usb/usb_device_handle_impl.h index 7d10e42..ce2daff 100644 --- a/device/usb/usb_device_handle_impl.h +++ b/device/usb/usb_device_handle_impl.h @@ -71,14 +71,18 @@ class UsbDeviceHandleImpl : public UsbDeviceHandle { unsigned int timeout, const TransferCallback& callback) override; - void IsochronousTransfer(UsbEndpointDirection direction, - uint8_t endpoint_number, - scoped_refptr<net::IOBuffer> buffer, - size_t length, - unsigned int packets, - unsigned int packet_length, - unsigned int timeout, - const TransferCallback& callback) override; + void IsochronousTransferIn( + uint8_t endpoint, + const std::vector<uint32_t>& packet_lengths, + unsigned int timeout, + const IsochronousTransferCallback& callback) override; + + void IsochronousTransferOut( + uint8_t endpoint, + scoped_refptr<net::IOBuffer> buffer, + const std::vector<uint32_t>& packet_lengths, + unsigned int timeout, + const IsochronousTransferCallback& callback) override; void GenericTransfer(UsbEndpointDirection direction, uint8_t endpoint_number, @@ -149,15 +153,20 @@ class UsbDeviceHandleImpl : public UsbDeviceHandle { scoped_refptr<base::TaskRunner> callback_task_runner, const TransferCallback& callback); - void IsochronousTransferInternal( + void IsochronousTransferInInternal( + uint8_t endpoint_address, + const std::vector<uint32_t>& packet_lengths, + unsigned int timeout, + scoped_refptr<base::TaskRunner> callback_task_runner, + const IsochronousTransferCallback& callback); + + void IsochronousTransferOutInternal( uint8_t endpoint_address, scoped_refptr<net::IOBuffer> buffer, - size_t length, - unsigned int packets, - unsigned int packet_length, + const std::vector<uint32_t>& packet_lengths, unsigned int timeout, scoped_refptr<base::TaskRunner> callback_task_runner, - const TransferCallback& callback); + const IsochronousTransferCallback& callback); void GenericTransferInternal( uint8_t endpoint_address, diff --git a/extensions/browser/api/usb/usb_api.cc b/extensions/browser/api/usb/usb_api.cc index 16710ddab..3035239 100644 --- a/extensions/browser/api/usb/usb_api.cc +++ b/extensions/browser/api/usb/usb_api.cc @@ -4,6 +4,8 @@ #include "extensions/browser/api/usb/usb_api.h" +#include <algorithm> +#include <numeric> #include <string> #include <utility> #include <vector> @@ -468,7 +470,8 @@ void UsbTransferFunction::OnCompleted(UsbTransferStatus status, } else { scoped_ptr<base::ListValue> error_args(new base::ListValue()); error_args->Append(std::move(transfer_info)); - // Returning arguments with an error is wrong but we're stuck with it. + // Using ErrorWithArguments is discouraged but required to provide the + // detailed transfer info as the transfer may have partially succeeded. Respond(ErrorWithArguments(std::move(error_args), ConvertTransferStatusToApi(status))); } @@ -1134,48 +1137,89 @@ ExtensionFunction::ResponseAction UsbIsochronousTransferFunction::Run() { size_t size = 0; UsbEndpointDirection direction = device::USB_DIRECTION_INBOUND; - if (!ConvertDirectionFromApi(generic_transfer.direction, &direction)) { + if (!ConvertDirectionFromApi(generic_transfer.direction, &direction)) return RespondNow(Error(kErrorConvertDirection)); - } - if (!GetTransferSize(generic_transfer, &size)) { + if (!GetTransferSize(generic_transfer, &size)) return RespondNow(Error(kErrorInvalidTransferLength)); - } - if (transfer.packets < 0 || transfer.packets >= kMaxPackets) { + if (transfer.packets < 0 || transfer.packets >= kMaxPackets) return RespondNow(Error(kErrorInvalidNumberOfPackets)); - } + size_t packets = transfer.packets; - unsigned int packets = transfer.packets; if (transfer.packet_length < 0 || transfer.packet_length >= kMaxPacketLength) { return RespondNow(Error(kErrorInvalidPacketLength)); } - unsigned int packet_length = transfer.packet_length; - const uint64_t total_length = packets * packet_length; - if (packets > size || total_length > size) { + size_t total_length = packets * transfer.packet_length; + if (packets > size || total_length > size) return RespondNow(Error(kErrorTransferLength)); - } - - scoped_refptr<net::IOBuffer> buffer = - CreateBufferForTransfer(generic_transfer, direction, size); - if (!buffer.get()) { - return RespondNow(Error(kErrorMalformedParameters)); - } + std::vector<uint32_t> packet_lengths(packets, transfer.packet_length); int timeout = generic_transfer.timeout ? *generic_transfer.timeout : 0; - if (timeout < 0) { + if (timeout < 0) return RespondNow(Error(kErrorInvalidTimeout)); - } - device_handle->IsochronousTransfer( - direction, generic_transfer.endpoint, buffer.get(), size, packets, - packet_length, timeout, - base::Bind(&UsbIsochronousTransferFunction::OnCompleted, this)); + if (direction == device::USB_DIRECTION_INBOUND) { + device_handle->IsochronousTransferIn( + generic_transfer.endpoint, packet_lengths, timeout, + base::Bind(&UsbIsochronousTransferFunction::OnCompleted, this)); + } else { + scoped_refptr<net::IOBuffer> buffer = CreateBufferForTransfer( + generic_transfer, direction, transfer.packets * transfer.packet_length); + if (!buffer.get()) + return RespondNow(Error(kErrorMalformedParameters)); + + device_handle->IsochronousTransferOut( + generic_transfer.endpoint, buffer.get(), packet_lengths, timeout, + base::Bind(&UsbIsochronousTransferFunction::OnCompleted, this)); + } return RespondLater(); } +void UsbIsochronousTransferFunction::OnCompleted( + scoped_refptr<net::IOBuffer> data, + const std::vector<UsbDeviceHandle::IsochronousPacket>& packets) { + size_t length = std::accumulate( + packets.begin(), packets.end(), 0, + [](const size_t& a, const UsbDeviceHandle::IsochronousPacket& packet) { + return a + packet.transferred_length; + }); + scoped_ptr<char[]> buffer(new char[length]); + + UsbTransferStatus status = device::USB_TRANSFER_COMPLETED; + size_t buffer_offset = 0; + size_t data_offset = 0; + for (const auto& packet : packets) { + // Capture the error status of the first unsuccessful packet. + if (status == device::USB_TRANSFER_COMPLETED && + packet.status != device::USB_TRANSFER_COMPLETED) { + status = packet.status; + } + + memcpy(&buffer[buffer_offset], data->data() + data_offset, + packet.transferred_length); + buffer_offset += packet.transferred_length; + data_offset += packet.length; + } + + scoped_ptr<base::DictionaryValue> transfer_info(new base::DictionaryValue()); + transfer_info->SetInteger(kResultCodeKey, status); + transfer_info->Set(kDataKey, + new base::BinaryValue(std::move(buffer), length)); + if (status == device::USB_TRANSFER_COMPLETED) { + Respond(OneArgument(std::move(transfer_info))); + } else { + scoped_ptr<base::ListValue> error_args(new base::ListValue()); + error_args->Append(std::move(transfer_info)); + // Using ErrorWithArguments is discouraged but required to provide the + // detailed transfer info as the transfer may have partially succeeded. + Respond(ErrorWithArguments(std::move(error_args), + ConvertTransferStatusToApi(status))); + } +} + UsbResetDeviceFunction::UsbResetDeviceFunction() { } @@ -1210,7 +1254,8 @@ void UsbResetDeviceFunction::OnComplete(bool success) { scoped_ptr<base::ListValue> error_args(new base::ListValue()); error_args->AppendBoolean(false); - // Returning arguments with an error is wrong but we're stuck with it. + // Using ErrorWithArguments is discouraged but required to maintain + // compatibility with existing applications. Respond(ErrorWithArguments(std::move(error_args), kErrorResetDevice)); } } diff --git a/extensions/browser/api/usb/usb_api.h b/extensions/browser/api/usb/usb_api.h index 5bea383..3d1dec1 100644 --- a/extensions/browser/api/usb/usb_api.h +++ b/extensions/browser/api/usb/usb_api.h @@ -245,7 +245,7 @@ class UsbClaimInterfaceFunction : public UsbConnectionFunction { UsbClaimInterfaceFunction(); - protected: + private: ~UsbClaimInterfaceFunction() override; // ExtensionFunction: @@ -334,7 +334,7 @@ class UsbInterruptTransferFunction : public UsbTransferFunction { DISALLOW_COPY_AND_ASSIGN(UsbInterruptTransferFunction); }; -class UsbIsochronousTransferFunction : public UsbTransferFunction { +class UsbIsochronousTransferFunction : public UsbConnectionFunction { public: DECLARE_EXTENSION_FUNCTION("usb.isochronousTransfer", USB_ISOCHRONOUSTRANSFER) @@ -346,6 +346,10 @@ class UsbIsochronousTransferFunction : public UsbTransferFunction { // ExtensionFunction: ResponseAction Run() override; + void OnCompleted( + scoped_refptr<net::IOBuffer> data, + const std::vector<device::UsbDeviceHandle::IsochronousPacket>& packets); + DISALLOW_COPY_AND_ASSIGN(UsbIsochronousTransferFunction); }; diff --git a/extensions/browser/api/usb/usb_apitest.cc b/extensions/browser/api/usb/usb_apitest.cc index cc6b47b..30601a9 100644 --- a/extensions/browser/api/usb/usb_apitest.cc +++ b/extensions/browser/api/usb/usb_apitest.cc @@ -4,6 +4,8 @@ #include <stddef.h> +#include <numeric> + #include "chrome/browser/extensions/extension_apitest.h" #include "content/public/browser/browser_thread.h" #include "content/public/test/test_utils.h" @@ -48,6 +50,44 @@ ACTION_TEMPLATE(InvokeUsbTransferCallback, ::std::tr1::get<k>(args).Run(p1, io_buffer, 1); } +ACTION_P2(InvokeUsbIsochronousTransferOutCallback, + transferred_length, + success_packets) { + std::vector<UsbDeviceHandle::IsochronousPacket> packets(arg2.size()); + for (size_t i = 0; i < packets.size(); ++i) { + packets[i].length = arg2[i]; + if (i < success_packets) { + packets[i].transferred_length = transferred_length; + packets[i].status = device::USB_TRANSFER_COMPLETED; + } else { + packets[i].transferred_length = 0; + packets[i].status = device::USB_TRANSFER_ERROR; + } + } + arg4.Run(arg1, packets); +} + +ACTION_P2(InvokeUsbIsochronousTransferInCallback, + transferred_length, + success_packets) { + size_t total_length = std::accumulate(arg1.begin(), arg1.end(), 0u); + net::IOBuffer* io_buffer = new net::IOBuffer(total_length); + memset(io_buffer->data(), 0, total_length); // Avoid uninitialized reads. + std::vector<UsbDeviceHandle::IsochronousPacket> packets(arg1.size()); + for (size_t i = 0; i < packets.size(); ++i) { + packets[i].length = arg1[i]; + packets[i].transferred_length = transferred_length; + if (i < success_packets) { + packets[i].transferred_length = transferred_length; + packets[i].status = device::USB_TRANSFER_COMPLETED; + } else { + packets[i].transferred_length = 0; + packets[i].status = device::USB_TRANSFER_ERROR; + } + } + arg3.Run(io_buffer, packets); +} + class TestDevicePermissionsPrompt : public DevicePermissionsPrompt, public DevicePermissionsPrompt::Prompt::Observer { @@ -165,10 +205,8 @@ IN_PROC_BROWSER_TEST_F(UsbApiTest, TransferEvent) { EXPECT_CALL(*mock_device_handle_.get(), GenericTransfer(device::USB_DIRECTION_OUTBOUND, 2, _, 1, _, _)) .WillOnce(InvokeUsbTransferCallback<5>(device::USB_TRANSFER_COMPLETED)); - EXPECT_CALL( - *mock_device_handle_.get(), - IsochronousTransfer(device::USB_DIRECTION_OUTBOUND, 3, _, 1, 1, 1, _, _)) - .WillOnce(InvokeUsbTransferCallback<7>(device::USB_TRANSFER_COMPLETED)); + EXPECT_CALL(*mock_device_handle_.get(), IsochronousTransferOut(3, _, _, _, _)) + .WillOnce(InvokeUsbIsochronousTransferOutCallback(1, 1u)); EXPECT_CALL(*mock_device_handle_.get(), Close()).Times(AnyNumber()); ASSERT_TRUE(RunAppTest("api_test/usb/transfer_event")); } @@ -181,10 +219,14 @@ IN_PROC_BROWSER_TEST_F(UsbApiTest, ZeroLengthTransfer) { } IN_PROC_BROWSER_TEST_F(UsbApiTest, TransferFailure) { - EXPECT_CALL(*mock_device_handle_.get(), GenericTransfer(_, _, _, _, _, _)) + EXPECT_CALL(*mock_device_handle_.get(), + GenericTransfer(device::USB_DIRECTION_OUTBOUND, 1, _, _, _, _)) .WillOnce(InvokeUsbTransferCallback<5>(device::USB_TRANSFER_COMPLETED)) .WillOnce(InvokeUsbTransferCallback<5>(device::USB_TRANSFER_ERROR)) .WillOnce(InvokeUsbTransferCallback<5>(device::USB_TRANSFER_TIMEOUT)); + EXPECT_CALL(*mock_device_handle_.get(), IsochronousTransferIn(2, _, _, _)) + .WillOnce(InvokeUsbIsochronousTransferInCallback(8, 10u)) + .WillOnce(InvokeUsbIsochronousTransferInCallback(8, 5u)); EXPECT_CALL(*mock_device_handle_.get(), Close()).Times(AnyNumber()); ASSERT_TRUE(RunAppTest("api_test/usb/transfer_failure")); } diff --git a/extensions/test/data/api_test/usb/transfer_event/test.js b/extensions/test/data/api_test/usb/transfer_event/test.js index 294864a..9d065f1 100644 --- a/extensions/test/data/api_test/usb/transfer_event/test.js +++ b/extensions/test/data/api_test/usb/transfer_event/test.js @@ -18,6 +18,7 @@ var tests = [ transfer.data = new ArrayBuffer(1); usb.controlTransfer(device, transfer, function (result) { + chrome.test.assertNoLastError(); chrome.test.succeed(); }); }); @@ -31,6 +32,7 @@ var tests = [ transfer.data = new ArrayBuffer(1); usb.bulkTransfer(device, transfer, function (result) { + chrome.test.assertNoLastError(); chrome.test.succeed(); }); }); @@ -44,6 +46,7 @@ var tests = [ transfer.data = new ArrayBuffer(1); usb.interruptTransfer(device, transfer, function (result) { + chrome.test.assertNoLastError(); chrome.test.succeed(); }); }); @@ -62,6 +65,7 @@ var tests = [ isoTransfer.packetLength = 1; usb.isochronousTransfer(device, isoTransfer, function (result) { + chrome.test.assertNoLastError(); chrome.test.succeed(); }); }); diff --git a/extensions/test/data/api_test/usb/transfer_failure/test.js b/extensions/test/data/api_test/usb/transfer_failure/test.js index 09c5375..9348c18 100644 --- a/extensions/test/data/api_test/usb/transfer_failure/test.js +++ b/extensions/test/data/api_test/usb/transfer_failure/test.js @@ -25,10 +25,42 @@ function createErrorTest(resultCode, errorMessage) { }; } +function createIsochronousErrorTest(resultCode, errorMessage) { + return function() { + usb.findDevices({vendorId: 0, productId: 0}, function(devices) { + var device = devices[0]; + var transfer = { + 'transferInfo': { + 'direction': "in", + 'endpoint': 2, + 'length': 160 + }, + 'packets': 10, + 'packetLength': 16 + }; + usb.isochronousTransfer(device, transfer, function (result) { + if (errorMessage) { + chrome.test.assertLastError(errorMessage); + // Device responds with only 8-byte packets and the second half fail. + chrome.test.assertTrue(result.data.byteLength == 40); + } else { + chrome.test.assertNoLastError(); + // Device responds with a full set of 10 8-byte packets. + chrome.test.assertTrue(result.data.byteLength == 80); + } + chrome.test.assertTrue(resultCode == result.resultCode); + chrome.test.succeed(); + }); + }); + }; +} + var tests = [ createErrorTest(0, undefined), createErrorTest(1, "Transfer failed."), createErrorTest(2, "Transfer timed out."), + createIsochronousErrorTest(0, undefined), + createIsochronousErrorTest(1, "Transfer failed."), ]; chrome.test.runTests(tests); |