summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorreillyg <reillyg@chromium.org>2016-02-01 16:56:15 -0800
committerCommit bot <commit-bot@chromium.org>2016-02-02 00:58:06 +0000
commit051c98e9d3e843295d659b5676fcfa9dc1be5da6 (patch)
tree54414ed43e166fb91bcfb3d6413157ad9cf27fe6
parentea6d840e4d96f6df739f55a89ccf69daa3569a77 (diff)
downloadchromium_src-051c98e9d3e843295d659b5676fcfa9dc1be5da6.zip
chromium_src-051c98e9d3e843295d659b5676fcfa9dc1be5da6.tar.gz
chromium_src-051c98e9d3e843295d659b5676fcfa9dc1be5da6.tar.bz2
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 Review URL: https://codereview.chromium.org/1618393004 Cr-Commit-Position: refs/heads/master@{#372844}
-rw-r--r--chrome/browser/devtools/device/usb/android_usb_browsertest.cc20
-rw-r--r--device/devices_app/usb/device_impl.cc96
-rw-r--r--device/devices_app/usb/device_impl.h12
-rw-r--r--device/devices_app/usb/device_impl_unittest.cc195
-rw-r--r--device/devices_app/usb/public/interfaces/device.mojom37
-rw-r--r--device/devices_app/usb/type_converters.cc13
-rw-r--r--device/devices_app/usb/type_converters.h12
-rw-r--r--device/usb/mock_usb_device_handle.h23
-rw-r--r--device/usb/usb_device_handle.h32
-rw-r--r--device/usb/usb_device_handle_impl.cc240
-rw-r--r--device/usb/usb_device_handle_impl.h35
-rw-r--r--extensions/browser/api/usb/usb_api.cc95
-rw-r--r--extensions/browser/api/usb/usb_api.h8
-rw-r--r--extensions/browser/api/usb/usb_apitest.cc51
-rw-r--r--extensions/test/data/api_test/usb/transfer_event/test.js4
-rw-r--r--extensions/test/data/api_test/usb/transfer_failure/test.js32
16 files changed, 625 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..7d2e2d9 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,43 @@ 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);
+ 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 +204,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 +218,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);