// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "device/usb/mojo/device_impl.h"

#include <stddef.h>
#include <stdint.h>

#include <map>
#include <memory>
#include <numeric>
#include <queue>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "base/bind.h"
#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/stl_util.h"
#include "device/usb/mock_usb_device.h"
#include "device/usb/mock_usb_device_handle.h"
#include "device/usb/mojo/mock_permission_provider.h"
#include "device/usb/mojo/type_converters.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "net/base/io_buffer.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::Invoke;
using ::testing::_;

namespace device {
namespace usb {

namespace {

class ConfigBuilder {
 public:
  explicit ConfigBuilder(uint8_t value) : config_(value, false, false, 0) {}

  ConfigBuilder& AddInterface(uint8_t interface_number,
                              uint8_t alternate_setting,
                              uint8_t class_code,
                              uint8_t subclass_code,
                              uint8_t protocol_code) {
    config_.interfaces.emplace_back(interface_number, alternate_setting,
                                    class_code, subclass_code, protocol_code);
    return *this;
  }

  const UsbConfigDescriptor& config() const { return config_; }

 private:
  UsbConfigDescriptor config_;
};

void ExpectOpenAndThen(OpenDeviceError expected,
                       const base::Closure& continuation,
                       OpenDeviceError error) {
  EXPECT_EQ(expected, error);
  continuation.Run();
}

void ExpectDeviceInfoAndThen(const std::string& guid,
                             uint16_t vendor_id,
                             uint16_t product_id,
                             const std::string& manufacturer_name,
                             const std::string& product_name,
                             const std::string& serial_number,
                             const base::Closure& continuation,
                             DeviceInfoPtr device_info) {
  EXPECT_EQ(guid, device_info->guid);
  EXPECT_EQ(vendor_id, device_info->vendor_id);
  EXPECT_EQ(product_id, device_info->product_id);
  EXPECT_EQ(manufacturer_name, device_info->manufacturer_name);
  EXPECT_EQ(product_name, device_info->product_name);
  EXPECT_EQ(serial_number, device_info->serial_number);
  continuation.Run();
}

void ExpectResultAndThen(bool expected_result,
                         const base::Closure& continuation,
                         bool actual_result) {
  EXPECT_EQ(expected_result, actual_result);
  continuation.Run();
}

void ExpectTransferInAndThen(
    TransferStatus expected_status,
    const std::vector<uint8_t>& expected_bytes,
    const base::Closure& continuation,
    TransferStatus actual_status,
    const base::Optional<std::vector<uint8_t>>& actual_bytes) {
  EXPECT_EQ(expected_status, actual_status);
  ASSERT_TRUE(actual_bytes);
  ASSERT_EQ(expected_bytes.size(), actual_bytes->size());
  for (size_t i = 0; i < actual_bytes->size(); ++i) {
    EXPECT_EQ(expected_bytes[i], (*actual_bytes)[i])
        << "Contents differ at index: " << i;
  }
  continuation.Run();
}

void ExpectPacketsOutAndThen(const std::vector<uint32_t>& expected_packets,
                             const base::Closure& continuation,
                             std::vector<IsochronousPacketPtr> actual_packets) {
  ASSERT_EQ(expected_packets.size(), actual_packets.size());
  for (size_t i = 0; i < expected_packets.size(); ++i) {
    EXPECT_EQ(expected_packets[i], actual_packets[i]->transferred_length)
        << "Packet lengths differ at index: " << i;
    EXPECT_EQ(TransferStatus::COMPLETED, actual_packets[i]->status)
        << "Packet at index " << i << " not completed.";
  }
  continuation.Run();
}

void ExpectPacketsInAndThen(
    const std::vector<uint8_t>& expected_bytes,
    const std::vector<uint32_t>& expected_packets,
    const base::Closure& continuation,
    const base::Optional<std::vector<uint8_t>>& actual_bytes,
    std::vector<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_TRUE(actual_bytes);
  ASSERT_EQ(expected_bytes.size(), actual_bytes->size());
  for (size_t i = 0; i < actual_bytes->size(); ++i) {
    EXPECT_EQ(expected_bytes[i], (*actual_bytes)[i])
        << "Contents differ at index: " << i;
  }
  continuation.Run();
}

void ExpectTransferStatusAndThen(TransferStatus expected_status,
                                 const base::Closure& continuation,
                                 TransferStatus actual_status) {
  EXPECT_EQ(expected_status, actual_status);
  continuation.Run();
}

class USBDeviceImplTest : public testing::Test {
 public:
  USBDeviceImplTest()
      : message_loop_(new base::MessageLoop),
        is_device_open_(false),
        allow_reset_(false) {}

  ~USBDeviceImplTest() override {}

 protected:
  MockPermissionProvider& permission_provider() { return permission_provider_; }
  MockUsbDevice& mock_device() { return *mock_device_.get(); }
  bool is_device_open() const { return is_device_open_; }
  MockUsbDeviceHandle& mock_handle() { return *mock_handle_.get(); }

  void set_allow_reset(bool allow_reset) { allow_reset_ = allow_reset; }

  // Creates a mock device and binds a Device proxy to a Device service impl
  // wrapping the mock device.
  DevicePtr GetMockDeviceProxy(uint16_t vendor_id,
                               uint16_t product_id,
                               const std::string& manufacturer,
                               const std::string& product,
                               const std::string& serial) {
    mock_device_ =
        new MockUsbDevice(vendor_id, product_id, manufacturer, product, serial);
    mock_handle_ = new MockUsbDeviceHandle(mock_device_.get());

    DevicePtr proxy;

    // Owns itself.
    new DeviceImpl(
        mock_device_,
        DeviceInfo::From(static_cast<const UsbDevice&>(*mock_device_)),
        permission_provider_.GetWeakPtr(), mojo::GetProxy(&proxy));

    // Set up mock handle calls to respond based on mock device configs
    // established by the test.
    ON_CALL(mock_device(), Open(_))
        .WillByDefault(Invoke(this, &USBDeviceImplTest::OpenMockHandle));
    ON_CALL(mock_handle(), Close())
        .WillByDefault(Invoke(this, &USBDeviceImplTest::CloseMockHandle));
    ON_CALL(mock_handle(), SetConfiguration(_, _))
        .WillByDefault(Invoke(this, &USBDeviceImplTest::SetConfiguration));
    ON_CALL(mock_handle(), ClaimInterface(_, _))
        .WillByDefault(Invoke(this, &USBDeviceImplTest::ClaimInterface));
    ON_CALL(mock_handle(), ReleaseInterface(_, _))
        .WillByDefault(Invoke(this, &USBDeviceImplTest::ReleaseInterface));
    ON_CALL(mock_handle(), SetInterfaceAlternateSetting(_, _, _))
        .WillByDefault(
            Invoke(this, &USBDeviceImplTest::SetInterfaceAlternateSetting));
    ON_CALL(mock_handle(), ResetDevice(_))
        .WillByDefault(Invoke(this, &USBDeviceImplTest::ResetDevice));
    ON_CALL(mock_handle(), ControlTransfer(_, _, _, _, _, _, _, _, _, _))
        .WillByDefault(Invoke(this, &USBDeviceImplTest::ControlTransfer));
    ON_CALL(mock_handle(), GenericTransfer(_, _, _, _, _, _))
        .WillByDefault(Invoke(this, &USBDeviceImplTest::GenericTransfer));
    ON_CALL(mock_handle(), IsochronousTransferIn(_, _, _, _))
        .WillByDefault(Invoke(this, &USBDeviceImplTest::IsochronousTransferIn));
    ON_CALL(mock_handle(), IsochronousTransferOut(_, _, _, _, _))
        .WillByDefault(
            Invoke(this, &USBDeviceImplTest::IsochronousTransferOut));

    return proxy;
  }

  DevicePtr GetMockDeviceProxy() {
    return GetMockDeviceProxy(0x1234, 0x5678, "ACME", "Frobinator", "ABCDEF");
  }

  void AddMockConfig(const ConfigBuilder& builder) {
    const UsbConfigDescriptor& config = builder.config();
    DCHECK(!base::ContainsKey(mock_configs_, config.configuration_value));
    mock_configs_.insert(std::make_pair(config.configuration_value, config));
    mock_device_->AddMockConfig(config);
  }

  void AddMockInboundData(const std::vector<uint8_t>& data) {
    mock_inbound_data_.push(data);
  }

  void AddMockInboundPackets(
      const std::vector<uint8_t>& data,
      const std::vector<UsbDeviceHandle::IsochronousPacket>& packets) {
    mock_inbound_data_.push(data);
    mock_inbound_packets_.push(packets);
  }

  void AddMockOutboundData(const std::vector<uint8_t>& data) {
    mock_outbound_data_.push(data);
  }

  void AddMockOutboundPackets(
      const std::vector<uint8_t>& data,
      const std::vector<UsbDeviceHandle::IsochronousPacket>& packets) {
    mock_outbound_data_.push(data);
    mock_outbound_packets_.push(packets);
  }

 private:
  void OpenMockHandle(const UsbDevice::OpenCallback& callback) {
    EXPECT_FALSE(is_device_open_);
    is_device_open_ = true;
    callback.Run(mock_handle_);
  }

  void CloseMockHandle() {
    EXPECT_TRUE(is_device_open_);
    is_device_open_ = false;
  }

  void SetConfiguration(uint8_t value,
                        const UsbDeviceHandle::ResultCallback& callback) {
    if (mock_configs_.find(value) != mock_configs_.end()) {
      mock_device_->ActiveConfigurationChanged(value);
      callback.Run(true);
    } else {
      callback.Run(false);
    }
  }

  void ClaimInterface(uint8_t interface_number,
                      const UsbDeviceHandle::ResultCallback& callback) {
    for (const auto& config : mock_configs_) {
      for (const auto& interface : config.second.interfaces) {
        if (interface.interface_number == interface_number) {
          claimed_interfaces_.insert(interface_number);
          callback.Run(true);
          return;
        }
      }
    }
    callback.Run(false);
  }

  void ReleaseInterface(uint8_t interface_number,
                        const UsbDeviceHandle::ResultCallback& callback) {
    if (base::ContainsKey(claimed_interfaces_, interface_number)) {
      claimed_interfaces_.erase(interface_number);
      callback.Run(true);
    } else {
      callback.Run(false);
    }
  }

  void SetInterfaceAlternateSetting(
      uint8_t interface_number,
      uint8_t alternate_setting,
      const UsbDeviceHandle::ResultCallback& callback) {
    for (const auto& config : mock_configs_) {
      for (const auto& interface : config.second.interfaces) {
        if (interface.interface_number == interface_number &&
            interface.alternate_setting == alternate_setting) {
          callback.Run(true);
          return;
        }
      }
    }
    callback.Run(false);
  }

  void ResetDevice(const UsbDeviceHandle::ResultCallback& callback) {
    callback.Run(allow_reset_);
  }

  void InboundTransfer(const UsbDeviceHandle::TransferCallback& callback) {
    ASSERT_GE(mock_inbound_data_.size(), 1u);
    const std::vector<uint8_t>& bytes = mock_inbound_data_.front();
    size_t length = bytes.size();
    scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(length);
    std::copy(bytes.begin(), bytes.end(), buffer->data());
    mock_inbound_data_.pop();
    callback.Run(USB_TRANSFER_COMPLETED, buffer, length);
  }

  void OutboundTransfer(scoped_refptr<net::IOBuffer> buffer,
                        size_t length,
                        const UsbDeviceHandle::TransferCallback& callback) {
    ASSERT_GE(mock_outbound_data_.size(), 1u);
    const std::vector<uint8_t>& bytes = mock_outbound_data_.front();
    ASSERT_EQ(bytes.size(), length);
    for (size_t i = 0; i < length; ++i) {
      EXPECT_EQ(bytes[i], buffer->data()[i]) << "Contents differ at index: "
                                             << i;
    }
    mock_outbound_data_.pop();
    callback.Run(USB_TRANSFER_COMPLETED, buffer, length);
  }

  void ControlTransfer(UsbEndpointDirection direction,
                       UsbDeviceHandle::TransferRequestType request_type,
                       UsbDeviceHandle::TransferRecipient recipient,
                       uint8_t request,
                       uint16_t value,
                       uint16_t index,
                       scoped_refptr<net::IOBuffer> buffer,
                       size_t length,
                       unsigned int timeout,
                       const UsbDeviceHandle::TransferCallback& callback) {
    if (direction == USB_DIRECTION_INBOUND)
      InboundTransfer(callback);
    else
      OutboundTransfer(buffer, length, callback);
  }

  void GenericTransfer(UsbEndpointDirection direction,
                       uint8_t endpoint,
                       scoped_refptr<net::IOBuffer> buffer,
                       size_t length,
                       unsigned int timeout,
                       const UsbDeviceHandle::TransferCallback& callback) {
    if (direction == USB_DIRECTION_INBOUND)
      InboundTransfer(callback);
    else
      OutboundTransfer(buffer, length, callback);
  }

  void IsochronousTransferIn(
      uint8_t endpoint_number,
      const std::vector<uint32_t>& packet_lengths,
      unsigned int timeout,
      const UsbDeviceHandle::IsochronousTransferCallback& callback) {
    ASSERT_FALSE(mock_inbound_data_.empty());
    const std::vector<uint8_t>& bytes = mock_inbound_data_.front();
    size_t length = bytes.size();
    scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(length);
    std::copy(bytes.begin(), bytes.end(), buffer->data());
    mock_inbound_data_.pop();

    ASSERT_FALSE(mock_inbound_packets_.empty());
    std::vector<UsbDeviceHandle::IsochronousPacket> packets =
        mock_inbound_packets_.front();
    ASSERT_EQ(packets.size(), packet_lengths.size());
    for (size_t i = 0; i < packets.size(); ++i) {
      EXPECT_EQ(packets[i].length, packet_lengths[i])
          << "Packet lengths differ at index: " << i;
    }
    mock_inbound_packets_.pop();

    callback.Run(buffer, packets);
  }

  void IsochronousTransferOut(
      uint8_t endpoint_number,
      scoped_refptr<net::IOBuffer> buffer,
      const std::vector<uint32_t>& packet_lengths,
      unsigned int timeout,
      const UsbDeviceHandle::IsochronousTransferCallback& callback) {
    ASSERT_FALSE(mock_outbound_data_.empty());
    const std::vector<uint8_t>& bytes = mock_outbound_data_.front();
    size_t length =
        std::accumulate(packet_lengths.begin(), packet_lengths.end(), 0u);
    ASSERT_EQ(bytes.size(), length);
    for (size_t i = 0; i < length; ++i) {
      EXPECT_EQ(bytes[i], buffer->data()[i]) << "Contents differ at index: "
                                             << i;
    }
    mock_outbound_data_.pop();

    ASSERT_FALSE(mock_outbound_packets_.empty());
    std::vector<UsbDeviceHandle::IsochronousPacket> packets =
        mock_outbound_packets_.front();
    ASSERT_EQ(packets.size(), packet_lengths.size());
    for (size_t i = 0; i < packets.size(); ++i) {
      EXPECT_EQ(packets[i].length, packet_lengths[i])
          << "Packet lengths differ at index: " << i;
    }
    mock_outbound_packets_.pop();

    callback.Run(buffer, packets);
  }

  std::unique_ptr<base::MessageLoop> message_loop_;
  scoped_refptr<MockUsbDevice> mock_device_;
  scoped_refptr<MockUsbDeviceHandle> mock_handle_;
  bool is_device_open_;
  bool allow_reset_;

  std::map<uint8_t, UsbConfigDescriptor> mock_configs_;

  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_;

  MockPermissionProvider permission_provider_;

  DISALLOW_COPY_AND_ASSIGN(USBDeviceImplTest);
};

}  // namespace

TEST_F(USBDeviceImplTest, Disconnect) {
  DevicePtr device = GetMockDeviceProxy();

  base::RunLoop loop;
  device.set_connection_error_handler(loop.QuitClosure());
  mock_device().NotifyDeviceRemoved();
  loop.Run();
}

TEST_F(USBDeviceImplTest, Open) {
  DevicePtr device = GetMockDeviceProxy();

  EXPECT_FALSE(is_device_open());

  EXPECT_CALL(mock_device(), Open(_));

  {
    base::RunLoop loop;
    device->Open(base::Bind(&ExpectOpenAndThen, OpenDeviceError::OK,
                            loop.QuitClosure()));
    loop.Run();
  }

  {
    base::RunLoop loop;
    device->Open(base::Bind(&ExpectOpenAndThen, OpenDeviceError::ALREADY_OPEN,
                            loop.QuitClosure()));
    loop.Run();
  }

  EXPECT_CALL(mock_handle(), Close());
}

TEST_F(USBDeviceImplTest, Close) {
  DevicePtr device = GetMockDeviceProxy();

  EXPECT_FALSE(is_device_open());

  EXPECT_CALL(mock_device(), Open(_));

  {
    base::RunLoop loop;
    device->Open(base::Bind(&ExpectOpenAndThen, OpenDeviceError::OK,
                            loop.QuitClosure()));
    loop.Run();
  }

  EXPECT_CALL(mock_handle(), Close());

  {
    base::RunLoop loop;
    device->Close(loop.QuitClosure());
    loop.Run();
  }

  EXPECT_FALSE(is_device_open());
}

// Test that the information returned via the Device::GetDeviceInfo matches that
// of the underlying device.
TEST_F(USBDeviceImplTest, GetDeviceInfo) {
  DevicePtr device =
      GetMockDeviceProxy(0x1234, 0x5678, "ACME", "Frobinator", "ABCDEF");

  base::RunLoop loop;
  device->GetDeviceInfo(base::Bind(&ExpectDeviceInfoAndThen,
                                   mock_device().guid(), 0x1234, 0x5678, "ACME",
                                   "Frobinator", "ABCDEF", loop.QuitClosure()));
  loop.Run();
}

TEST_F(USBDeviceImplTest, SetInvalidConfiguration) {
  DevicePtr device = GetMockDeviceProxy();

  EXPECT_CALL(mock_device(), Open(_));

  {
    base::RunLoop loop;
    device->Open(base::Bind(&ExpectOpenAndThen, OpenDeviceError::OK,
                            loop.QuitClosure()));
    loop.Run();
  }

  EXPECT_CALL(mock_handle(), SetConfiguration(42, _));
  EXPECT_CALL(permission_provider(), HasConfigurationPermission(42, _));

  {
    // SetConfiguration should fail because 42 is not a valid mock
    // configuration.
    base::RunLoop loop;
    device->SetConfiguration(
        42, base::Bind(&ExpectResultAndThen, false, loop.QuitClosure()));
    loop.Run();
  }

  EXPECT_CALL(mock_handle(), Close());
}

TEST_F(USBDeviceImplTest, SetValidConfiguration) {
  DevicePtr device = GetMockDeviceProxy();

  EXPECT_CALL(mock_device(), Open(_));

  {
    base::RunLoop loop;
    device->Open(base::Bind(&ExpectOpenAndThen, OpenDeviceError::OK,
                            loop.QuitClosure()));
    loop.Run();
  }

  EXPECT_CALL(mock_handle(), SetConfiguration(42, _));
  EXPECT_CALL(permission_provider(), HasConfigurationPermission(42, _));

  AddMockConfig(ConfigBuilder(42));

  {
    // SetConfiguration should succeed because 42 is a valid mock configuration.
    base::RunLoop loop;
    device->SetConfiguration(
        42, base::Bind(&ExpectResultAndThen, true, loop.QuitClosure()));
    loop.Run();
  }

  EXPECT_CALL(mock_handle(), Close());
}

// Verify that the result of Reset() reflects the underlying UsbDeviceHandle's
// ResetDevice() result.
TEST_F(USBDeviceImplTest, Reset) {
  DevicePtr device = GetMockDeviceProxy();

  EXPECT_CALL(mock_device(), Open(_));

  {
    base::RunLoop loop;
    device->Open(base::Bind(&ExpectOpenAndThen, OpenDeviceError::OK,
                            loop.QuitClosure()));
    loop.Run();
  }

  EXPECT_CALL(mock_handle(), ResetDevice(_));

  set_allow_reset(true);

  {
    base::RunLoop loop;
    device->Reset(base::Bind(&ExpectResultAndThen, true, loop.QuitClosure()));
    loop.Run();
  }

  EXPECT_CALL(mock_handle(), ResetDevice(_));

  set_allow_reset(false);

  {
    base::RunLoop loop;
    device->Reset(base::Bind(&ExpectResultAndThen, false, loop.QuitClosure()));
    loop.Run();
  }

  EXPECT_CALL(mock_handle(), Close());
}

TEST_F(USBDeviceImplTest, ClaimAndReleaseInterface) {
  DevicePtr device = GetMockDeviceProxy();

  EXPECT_CALL(mock_device(), Open(_));

  {
    base::RunLoop loop;
    device->Open(base::Bind(&ExpectOpenAndThen, OpenDeviceError::OK,
                            loop.QuitClosure()));
    loop.Run();
  }

  // Now add a mock interface #1.
  AddMockConfig(ConfigBuilder(1).AddInterface(1, 0, 1, 2, 3));

  EXPECT_CALL(mock_handle(), SetConfiguration(1, _));
  EXPECT_CALL(permission_provider(), HasConfigurationPermission(1, _));

  {
    base::RunLoop loop;
    device->SetConfiguration(
        1, base::Bind(&ExpectResultAndThen, true, loop.QuitClosure()));
    loop.Run();
  }

  {
    // Try to claim an invalid interface and expect failure.
    base::RunLoop loop;
    device->ClaimInterface(
        2, base::Bind(&ExpectResultAndThen, false, loop.QuitClosure()));
    loop.Run();
  }

  EXPECT_CALL(mock_handle(), ClaimInterface(1, _));
  EXPECT_CALL(permission_provider(), HasFunctionPermission(1, 1, _));

  {
    base::RunLoop loop;
    device->ClaimInterface(
        1, base::Bind(&ExpectResultAndThen, true, loop.QuitClosure()));
    loop.Run();
  }

  EXPECT_CALL(mock_handle(), ReleaseInterface(2, _));

  {
    // Releasing a non-existent interface should fail.
    base::RunLoop loop;
    device->ReleaseInterface(
        2, base::Bind(&ExpectResultAndThen, false, loop.QuitClosure()));
    loop.Run();
  }

  EXPECT_CALL(mock_handle(), ReleaseInterface(1, _));

  {
    // Now this should release the claimed interface and close the handle.
    base::RunLoop loop;
    device->ReleaseInterface(
        1, base::Bind(&ExpectResultAndThen, true, loop.QuitClosure()));
    loop.Run();
  }

  EXPECT_CALL(mock_handle(), Close());
}

TEST_F(USBDeviceImplTest, SetInterfaceAlternateSetting) {
  DevicePtr device = GetMockDeviceProxy();

  EXPECT_CALL(mock_device(), Open(_));

  {
    base::RunLoop loop;
    device->Open(base::Bind(&ExpectOpenAndThen, OpenDeviceError::OK,
                            loop.QuitClosure()));
    loop.Run();
  }

  AddMockConfig(ConfigBuilder(1)
                    .AddInterface(1, 0, 1, 2, 3)
                    .AddInterface(1, 42, 1, 2, 3)
                    .AddInterface(2, 0, 1, 2, 3));

  EXPECT_CALL(mock_handle(), SetInterfaceAlternateSetting(1, 42, _));

  {
    base::RunLoop loop;
    device->SetInterfaceAlternateSetting(
        1, 42, base::Bind(&ExpectResultAndThen, true, loop.QuitClosure()));
    loop.Run();
  }

  EXPECT_CALL(mock_handle(), SetInterfaceAlternateSetting(1, 100, _));

  {
    base::RunLoop loop;
    device->SetInterfaceAlternateSetting(
        1, 100, base::Bind(&ExpectResultAndThen, false, loop.QuitClosure()));
    loop.Run();
  }

  EXPECT_CALL(mock_handle(), Close());
}

TEST_F(USBDeviceImplTest, ControlTransfer) {
  DevicePtr device = GetMockDeviceProxy();

  EXPECT_CALL(mock_device(), Open(_));

  {
    base::RunLoop loop;
    device->Open(base::Bind(&ExpectOpenAndThen, OpenDeviceError::OK,
                            loop.QuitClosure()));
    loop.Run();
  }

  AddMockConfig(ConfigBuilder(1).AddInterface(7, 0, 1, 2, 3));

  EXPECT_CALL(mock_handle(), SetConfiguration(1, _));
  EXPECT_CALL(permission_provider(), HasConfigurationPermission(1, _));

  {
    base::RunLoop loop;
    device->SetConfiguration(
        1, base::Bind(&ExpectResultAndThen, true, loop.QuitClosure()));
    loop.Run();
  }

  std::vector<uint8_t> fake_data;
  fake_data.push_back(41);
  fake_data.push_back(42);
  fake_data.push_back(43);

  AddMockInboundData(fake_data);

  EXPECT_CALL(mock_handle(),
              ControlTransfer(USB_DIRECTION_INBOUND, UsbDeviceHandle::STANDARD,
                              UsbDeviceHandle::DEVICE, 5, 6, 7, _, _, 0, _));
  EXPECT_CALL(permission_provider(), HasConfigurationPermission(1, _));

  {
    auto params = ControlTransferParams::New();
    params->type = ControlTransferType::STANDARD;
    params->recipient = ControlTransferRecipient::DEVICE;
    params->request = 5;
    params->value = 6;
    params->index = 7;
    base::RunLoop loop;
    device->ControlTransferIn(
        std::move(params), static_cast<uint32_t>(fake_data.size()), 0,
        base::Bind(&ExpectTransferInAndThen, TransferStatus::COMPLETED,
                   fake_data, loop.QuitClosure()));
    loop.Run();
  }

  AddMockOutboundData(fake_data);

  EXPECT_CALL(mock_handle(),
              ControlTransfer(USB_DIRECTION_OUTBOUND, UsbDeviceHandle::STANDARD,
                              UsbDeviceHandle::INTERFACE, 5, 6, 7, _, _, 0, _));
  EXPECT_CALL(permission_provider(), HasFunctionPermission(7, 1, _));

  {
    auto params = ControlTransferParams::New();
    params->type = ControlTransferType::STANDARD;
    params->recipient = ControlTransferRecipient::INTERFACE;
    params->request = 5;
    params->value = 6;
    params->index = 7;
    base::RunLoop loop;
    device->ControlTransferOut(
        std::move(params), fake_data, 0,
        base::Bind(&ExpectTransferStatusAndThen, TransferStatus::COMPLETED,
                   loop.QuitClosure()));
    loop.Run();
  }

  EXPECT_CALL(mock_handle(), Close());
}

TEST_F(USBDeviceImplTest, GenericTransfer) {
  DevicePtr device = GetMockDeviceProxy();

  EXPECT_CALL(mock_device(), Open(_));

  {
    base::RunLoop loop;
    device->Open(base::Bind(&ExpectOpenAndThen, OpenDeviceError::OK,
                            loop.QuitClosure()));
    loop.Run();
  }

  std::string message1 = "say hello please";
  std::vector<uint8_t> fake_outbound_data(message1.size());
  std::copy(message1.begin(), message1.end(), fake_outbound_data.begin());

  std::string message2 = "hello world!";
  std::vector<uint8_t> fake_inbound_data(message2.size());
  std::copy(message2.begin(), message2.end(), fake_inbound_data.begin());

  AddMockConfig(ConfigBuilder(1).AddInterface(7, 0, 1, 2, 3));
  AddMockOutboundData(fake_outbound_data);
  AddMockInboundData(fake_inbound_data);

  EXPECT_CALL(mock_handle(), GenericTransfer(USB_DIRECTION_OUTBOUND, 0x01, _,
                                             fake_outbound_data.size(), 0, _));

  {
    base::RunLoop loop;
    device->GenericTransferOut(
        1, fake_outbound_data, 0,
        base::Bind(&ExpectTransferStatusAndThen, TransferStatus::COMPLETED,
                   loop.QuitClosure()));
    loop.Run();
  }

  EXPECT_CALL(mock_handle(), GenericTransfer(USB_DIRECTION_INBOUND, 0x81, _,
                                             fake_inbound_data.size(), 0, _));

  {
    base::RunLoop loop;
    device->GenericTransferIn(
        1, static_cast<uint32_t>(fake_inbound_data.size()), 0,
        base::Bind(&ExpectTransferInAndThen, TransferStatus::COMPLETED,
                   fake_inbound_data, loop.QuitClosure()));
    loop.Run();
  }

  EXPECT_CALL(mock_handle(), Close());
}

TEST_F(USBDeviceImplTest, IsochronousTransfer) {
  DevicePtr device = GetMockDeviceProxy();

  EXPECT_CALL(mock_device(), Open(_));

  {
    base::RunLoop loop;
    device->Open(base::Bind(&ExpectOpenAndThen, OpenDeviceError::OK,
                            loop.QuitClosure()));
    loop.Run();
  }

  std::vector<UsbDeviceHandle::IsochronousPacket> fake_packets(4);
  for (size_t i = 0; i < fake_packets.size(); ++i) {
    fake_packets[i].length = 8;
    fake_packets[i].transferred_length = 8;
    fake_packets[i].status = USB_TRANSFER_COMPLETED;
  }
  std::vector<uint32_t> fake_packet_lengths(4, 8);

  std::vector<uint32_t> expected_transferred_lengths(4, 8);

  std::string outbound_data = "aaaaaaaabbbbbbbbccccccccdddddddd";
  std::vector<uint8_t> fake_outbound_data(outbound_data.size());
  std::copy(outbound_data.begin(), outbound_data.end(),
            fake_outbound_data.begin());

  std::string inbound_data = "ddddddddccccccccbbbbbbbbaaaaaaaa";
  std::vector<uint8_t> fake_inbound_data(inbound_data.size());
  std::copy(inbound_data.begin(), inbound_data.end(),
            fake_inbound_data.begin());

  AddMockConfig(ConfigBuilder(1).AddInterface(7, 0, 1, 2, 3));
  AddMockOutboundPackets(fake_outbound_data, fake_packets);
  AddMockInboundPackets(fake_inbound_data, fake_packets);

  EXPECT_CALL(mock_handle(),
              IsochronousTransferOut(0x01, _, fake_packet_lengths, 0, _));

  {
    base::RunLoop loop;
    device->IsochronousTransferOut(
        1, fake_outbound_data, fake_packet_lengths, 0,
        base::Bind(&ExpectPacketsOutAndThen, expected_transferred_lengths,
                   loop.QuitClosure()));
    loop.Run();
  }

  EXPECT_CALL(mock_handle(),
              IsochronousTransferIn(0x81, fake_packet_lengths, 0, _));

  {
    base::RunLoop loop;
    device->IsochronousTransferIn(
        1, fake_packet_lengths, 0,
        base::Bind(&ExpectPacketsInAndThen, fake_inbound_data,
                   expected_transferred_lengths, loop.QuitClosure()));
    loop.Run();
  }

  EXPECT_CALL(mock_handle(), Close());
}

}  // namespace usb
}  // namespace device
