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

#include "device/usb/usb_device_handle_usbfs.h"

#if defined(OS_ANDROID) && __ANDROID_API__ < 21
#include <linux/usb_ch9.h>
#else
#include <linux/usb/ch9.h>
#endif

#include <linux/usbdevice_fs.h>
#include <sys/ioctl.h>

#include <numeric>

#include "base/bind.h"
#include "base/cancelable_callback.h"
#include "base/files/file_descriptor_watcher_posix.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/posix/eintr_wrapper.h"
#include "base/stl_util.h"
#include "base/threading/thread_restrictions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/device_event_log/device_event_log.h"
#include "device/usb/usb_device_linux.h"
#include "net/base/io_buffer.h"

namespace device {

namespace {

uint8_t ConvertEndpointDirection(UsbEndpointDirection direction) {
  switch (direction) {
    case USB_DIRECTION_INBOUND:
      return USB_DIR_IN;
    case USB_DIRECTION_OUTBOUND:
      return USB_DIR_OUT;
  }
  NOTREACHED();
  return 0;
}

uint8_t ConvertRequestType(UsbDeviceHandle::TransferRequestType request_type) {
  switch (request_type) {
    case UsbDeviceHandle::STANDARD:
      return USB_TYPE_STANDARD;
    case UsbDeviceHandle::CLASS:
      return USB_TYPE_CLASS;
    case UsbDeviceHandle::VENDOR:
      return USB_TYPE_VENDOR;
    case UsbDeviceHandle::RESERVED:
      return USB_TYPE_RESERVED;
  }
  NOTREACHED();
  return 0;
}

uint8_t ConvertRecipient(UsbDeviceHandle::TransferRecipient recipient) {
  switch (recipient) {
    case UsbDeviceHandle::DEVICE:
      return USB_RECIP_DEVICE;
    case UsbDeviceHandle::INTERFACE:
      return USB_RECIP_INTERFACE;
    case UsbDeviceHandle::ENDPOINT:
      return USB_RECIP_ENDPOINT;
    case UsbDeviceHandle::OTHER:
      return USB_RECIP_OTHER;
  }
  NOTREACHED();
  return 0;
}

scoped_refptr<net::IOBuffer> BuildControlTransferBuffer(
    UsbEndpointDirection direction,
    UsbDeviceHandle::TransferRequestType request_type,
    UsbDeviceHandle::TransferRecipient recipient,
    uint8_t request,
    uint16_t value,
    uint16_t index,
    scoped_refptr<net::IOBuffer> original_buffer,
    size_t length) {
  scoped_refptr<net::IOBuffer> new_buffer(
      new net::IOBuffer(length + sizeof(usb_ctrlrequest)));
  usb_ctrlrequest* setup =
      reinterpret_cast<usb_ctrlrequest*>(new_buffer->data());
  setup->bRequestType = ConvertEndpointDirection(direction) |
                        ConvertRequestType(request_type) |
                        ConvertRecipient(recipient);
  setup->bRequest = request;
  setup->wValue = value;
  setup->wIndex = index;
  setup->wLength = length;
  memcpy(new_buffer->data() + sizeof(usb_ctrlrequest), original_buffer->data(),
         length);
  return new_buffer;
}

uint8_t ConvertTransferType(UsbTransferType type) {
  switch (type) {
    case USB_TRANSFER_CONTROL:
      return USBDEVFS_URB_TYPE_CONTROL;
    case USB_TRANSFER_ISOCHRONOUS:
      return USBDEVFS_URB_TYPE_ISO;
    case USB_TRANSFER_BULK:
      return USBDEVFS_URB_TYPE_BULK;
    case USB_TRANSFER_INTERRUPT:
      return USBDEVFS_URB_TYPE_INTERRUPT;
  }
  NOTREACHED();
  return 0;
}

UsbTransferStatus ConvertTransferResult(int rc) {
  switch (rc) {
    case 0:
      return USB_TRANSFER_COMPLETED;
    case EPIPE:
      return USB_TRANSFER_STALLED;
    case ENODEV:
    case ESHUTDOWN:
    case EPROTO:
      return USB_TRANSFER_DISCONNECT;
    default:
      // TODO(reillyg): Add a specific error message whenever one of the cases
      // above fails to match.
      USB_LOG(ERROR) << "Unknown system error: "
                     << logging::SystemErrorCodeToString(rc);
      return USB_TRANSFER_ERROR;
  }
}

}  // namespace

class UsbDeviceHandleUsbfs::FileThreadHelper
    : public base::MessageLoop::DestructionObserver {
 public:
  FileThreadHelper(int fd,
                   scoped_refptr<UsbDeviceHandleUsbfs> device_handle,
                   scoped_refptr<base::SequencedTaskRunner> task_runner);
  ~FileThreadHelper() override;

  static void Start(std::unique_ptr<FileThreadHelper> self);

  // base::MessageLoop::DestructionObserver overrides.
  void WillDestroyCurrentMessageLoop() override;

 private:
  // Called when |fd_| is writable without blocking.
  void OnFileCanWriteWithoutBlocking();

  int fd_;
  scoped_refptr<UsbDeviceHandleUsbfs> device_handle_;
  scoped_refptr<base::SequencedTaskRunner> task_runner_;
  std::unique_ptr<base::FileDescriptorWatcher::Controller> watch_controller_;
  base::ThreadChecker thread_checker_;

  DISALLOW_COPY_AND_ASSIGN(FileThreadHelper);
};

UsbDeviceHandleUsbfs::FileThreadHelper::FileThreadHelper(
    int fd,
    scoped_refptr<UsbDeviceHandleUsbfs> device_handle,
    scoped_refptr<base::SequencedTaskRunner> task_runner)
    : fd_(fd), device_handle_(device_handle), task_runner_(task_runner) {}

UsbDeviceHandleUsbfs::FileThreadHelper::~FileThreadHelper() {
  DCHECK(thread_checker_.CalledOnValidThread());
  base::MessageLoop::current()->RemoveDestructionObserver(this);
}

// static
void UsbDeviceHandleUsbfs::FileThreadHelper::Start(
    std::unique_ptr<FileThreadHelper> self) {
  base::ThreadRestrictions::AssertIOAllowed();
  self->thread_checker_.DetachFromThread();

  // Linux indicates that URBs are available to reap by marking the file
  // descriptor writable.
  self->watch_controller_ = base::FileDescriptorWatcher::WatchWritable(
      self->fd_, base::Bind(&FileThreadHelper::OnFileCanWriteWithoutBlocking,
                            base::Unretained(self.get())));

  // |self| is now owned by the current message loop.
  base::MessageLoop::current()->AddDestructionObserver(self.release());
}

void UsbDeviceHandleUsbfs::FileThreadHelper::WillDestroyCurrentMessageLoop() {
  DCHECK(thread_checker_.CalledOnValidThread());
  delete this;
}

void UsbDeviceHandleUsbfs::FileThreadHelper::OnFileCanWriteWithoutBlocking() {
  DCHECK(thread_checker_.CalledOnValidThread());

  const size_t MAX_URBS_PER_EVENT = 10;
  std::vector<usbdevfs_urb*> urbs;
  urbs.reserve(MAX_URBS_PER_EVENT);
  for (size_t i = 0; i < MAX_URBS_PER_EVENT; ++i) {
    usbdevfs_urb* urb;
    int rc = HANDLE_EINTR(ioctl(fd_, USBDEVFS_REAPURBNDELAY, &urb));
    if (rc) {
      if (errno == EAGAIN)
        break;
      USB_PLOG(DEBUG) << "Failed to reap urbs";
      if (errno == ENODEV) {
        // Device has disconnected. Stop watching the file descriptor to avoid
        // looping until |device_handle_| is closed.
        watch_controller_.reset();
        break;
      }
    } else {
      urbs.push_back(urb);
    }
  }

  task_runner_->PostTask(
      FROM_HERE,
      base::Bind(&UsbDeviceHandleUsbfs::ReapedUrbs, device_handle_, urbs));
}

struct UsbDeviceHandleUsbfs::Transfer {
  Transfer() = delete;
  Transfer(scoped_refptr<net::IOBuffer> buffer,
           const TransferCallback& callback,
           scoped_refptr<base::SingleThreadTaskRunner> callback_runner);
  Transfer(scoped_refptr<net::IOBuffer> buffer,
           const IsochronousTransferCallback& callback);
  ~Transfer();

  void* operator new(std::size_t size, size_t number_of_iso_packets);
  void RunCallback(UsbTransferStatus status, size_t bytes_transferred);
  void RunIsochronousCallback(const std::vector<IsochronousPacket>& packets);

  scoped_refptr<net::IOBuffer> control_transfer_buffer;
  scoped_refptr<net::IOBuffer> buffer;
  base::CancelableClosure timeout_closure;
  bool cancelled = false;

  // When the URB is |cancelled| these two flags track whether the URB has both
  // been |discarded| and |reaped| since the possiblity of last-minute
  // completion makes these two conditions race.
  bool discarded = false;
  bool reaped = false;

 private:
  TransferCallback callback;
  IsochronousTransferCallback isoc_callback;
  scoped_refptr<base::SingleThreadTaskRunner> callback_runner;

  DISALLOW_COPY_AND_ASSIGN(Transfer);

 public:
  // The |urb| field must be the last in the struct so that the extra space
  // allocated by the overridden new function above extends the length of its
  // |iso_frame_desc| field.
  usbdevfs_urb urb;
};

UsbDeviceHandleUsbfs::Transfer::Transfer(
    scoped_refptr<net::IOBuffer> buffer,
    const TransferCallback& callback,
    scoped_refptr<base::SingleThreadTaskRunner> callback_runner)
    : buffer(buffer), callback(callback), callback_runner(callback_runner) {
  memset(&urb, 0, sizeof(urb));
  urb.usercontext = this;
  urb.buffer = buffer->data();
}

UsbDeviceHandleUsbfs::Transfer::Transfer(
    scoped_refptr<net::IOBuffer> buffer,
    const IsochronousTransferCallback& callback)
    : buffer(buffer), isoc_callback(callback) {
  memset(&urb, 0, sizeof(urb) +
                      sizeof(usbdevfs_iso_packet_desc) * urb.number_of_packets);
  urb.usercontext = this;
  urb.buffer = buffer->data();
}

UsbDeviceHandleUsbfs::Transfer::~Transfer() = default;

void* UsbDeviceHandleUsbfs::Transfer::operator new(
    std::size_t size,
    size_t number_of_iso_packets) {
  void* p = ::operator new(
      size + sizeof(usbdevfs_iso_packet_desc) * number_of_iso_packets);
  Transfer* transfer = static_cast<Transfer*>(p);
  transfer->urb.number_of_packets = number_of_iso_packets;
  return p;
}

void UsbDeviceHandleUsbfs::Transfer::RunCallback(UsbTransferStatus status,
                                                 size_t bytes_transferred) {
  DCHECK_NE(urb.type, USBDEVFS_URB_TYPE_ISO);
  DCHECK(callback);
  if (!callback_runner || callback_runner->BelongsToCurrentThread()) {
    callback.Run(status, buffer, bytes_transferred);
  } else {
    callback_runner->PostTask(
        FROM_HERE, base::Bind(callback, status, buffer, bytes_transferred));
  }
  callback.Reset();
}

void UsbDeviceHandleUsbfs::Transfer::RunIsochronousCallback(
    const std::vector<IsochronousPacket>& packets) {
  DCHECK_EQ(urb.type, USBDEVFS_URB_TYPE_ISO);
  DCHECK(isoc_callback);
  isoc_callback.Run(buffer, packets);
  isoc_callback.Reset();
}

UsbDeviceHandleUsbfs::UsbDeviceHandleUsbfs(
    scoped_refptr<UsbDevice> device,
    base::ScopedFD fd,
    scoped_refptr<base::SequencedTaskRunner> blocking_task_runner)
    : device_(device),
      fd_(std::move(fd)),
      blocking_task_runner_(blocking_task_runner) {
  DCHECK(device_);
  DCHECK(fd_.is_valid());
  DCHECK(blocking_task_runner_);

  task_runner_ = base::ThreadTaskRunnerHandle::Get();

  std::unique_ptr<FileThreadHelper> helper(
      new FileThreadHelper(fd_.get(), this, task_runner_));
  helper_ = helper.get();
  blocking_task_runner_->PostTask(
      FROM_HERE, base::Bind(&FileThreadHelper::Start, base::Passed(&helper)));
}

scoped_refptr<UsbDevice> UsbDeviceHandleUsbfs::GetDevice() const {
  return device_;
}

void UsbDeviceHandleUsbfs::Close() {
  if (!device_)
    return;  // Already closed.

  // On the |task_runner_| thread check |device_| to see if the handle is
  // closed. On the |blocking_task_runner_| thread check |fd_.is_valid()| to
  // see if the handle is closed.
  device_->HandleClosed(this);
  device_ = nullptr;

  for (const auto& transfer : transfers_)
    CancelTransfer(transfer.get(), USB_TRANSFER_CANCELLED);

  blocking_task_runner_->PostTask(
      FROM_HERE, base::Bind(&UsbDeviceHandleUsbfs::CloseBlocking, this));
}

void UsbDeviceHandleUsbfs::SetConfiguration(int configuration_value,
                                            const ResultCallback& callback) {
  // USBDEVFS_SETCONFIGURATION synchronously issues a SET_CONFIGURATION request
  // to the device so it must be performed on a thread where it is okay to
  // block.
  blocking_task_runner_->PostTask(
      FROM_HERE, base::Bind(&UsbDeviceHandleUsbfs::SetConfigurationBlocking,
                            this, configuration_value, callback));
}

void UsbDeviceHandleUsbfs::ClaimInterface(int interface_number,
                                          const ResultCallback& callback) {
  if (!device_) {
    task_runner_->PostTask(FROM_HERE, base::Bind(callback, false));
    return;
  }

  if (base::ContainsKey(interfaces_, interface_number)) {
    USB_LOG(DEBUG) << "Interface " << interface_number << " already claimed.";
    task_runner_->PostTask(FROM_HERE, base::Bind(callback, false));
    return;
  }

  // It appears safe to assume that this ioctl will not block.
  int rc = HANDLE_EINTR(
      ioctl(fd_.get(), USBDEVFS_CLAIMINTERFACE, &interface_number));
  if (rc) {
    USB_PLOG(DEBUG) << "Failed to claim interface " << interface_number;
  } else {
    interfaces_[interface_number].alternate_setting = 0;
    RefreshEndpointInfo();
  }
  task_runner_->PostTask(FROM_HERE, base::Bind(callback, rc == 0));
}

void UsbDeviceHandleUsbfs::ReleaseInterface(int interface_number,
                                            const ResultCallback& callback) {
  // USBDEVFS_RELEASEINTERFACE may issue a SET_INTERFACE request to the
  // device to restore alternate setting 0 so it must be performed on a thread
  // where it is okay to block.
  blocking_task_runner_->PostTask(
      FROM_HERE, base::Bind(&UsbDeviceHandleUsbfs::ReleaseInterfaceBlocking,
                            this, interface_number, callback));
}

void UsbDeviceHandleUsbfs::SetInterfaceAlternateSetting(
    int interface_number,
    int alternate_setting,
    const ResultCallback& callback) {
  // USBDEVFS_SETINTERFACE is synchronous because it issues a SET_INTERFACE
  // request to the device so it must be performed on a thread where it is okay
  // to block.
  blocking_task_runner_->PostTask(
      FROM_HERE, base::Bind(&UsbDeviceHandleUsbfs::SetInterfaceBlocking, this,
                            interface_number, alternate_setting, callback));
}

void UsbDeviceHandleUsbfs::ResetDevice(const ResultCallback& callback) {
  // USBDEVFS_RESET is synchronous because it waits for the port to be reset
  // and the device re-enumerated so it must be performed on a thread where it
  // is okay to block.
  blocking_task_runner_->PostTask(
      FROM_HERE,
      base::Bind(&UsbDeviceHandleUsbfs::ResetDeviceBlocking, this, callback));
}

void UsbDeviceHandleUsbfs::ClearHalt(uint8_t endpoint_address,
                                     const ResultCallback& callback) {
  // USBDEVFS_CLEAR_HALT is synchronous because it issues a CLEAR_FEATURE
  // request to the device so it must be performed on a thread where it is okay
  // to block.
  blocking_task_runner_->PostTask(
      FROM_HERE, base::Bind(&UsbDeviceHandleUsbfs::ClearHaltBlocking, this,
                            endpoint_address, callback));
}

void UsbDeviceHandleUsbfs::ControlTransfer(UsbEndpointDirection direction,
                                           TransferRequestType request_type,
                                           TransferRecipient recipient,
                                           uint8_t request,
                                           uint16_t value,
                                           uint16_t index,
                                           scoped_refptr<net::IOBuffer> buffer,
                                           size_t length,
                                           unsigned int timeout,
                                           const TransferCallback& callback) {
  if (!device_) {
    task_runner_->PostTask(
        FROM_HERE, base::Bind(callback, USB_TRANSFER_DISCONNECT, nullptr, 0));
    return;
  }

  std::unique_ptr<Transfer> transfer(new (0)
                                         Transfer(buffer, callback, nullptr));
  transfer->control_transfer_buffer =
      BuildControlTransferBuffer(direction, request_type, recipient, request,
                                 value, index, buffer, length);
  transfer->urb.type = USBDEVFS_URB_TYPE_CONTROL;
  transfer->urb.endpoint = 0;
  transfer->urb.buffer = transfer->control_transfer_buffer->data();
  transfer->urb.buffer_length = 8 + length;

  // USBDEVFS_SUBMITURB appears to be non-blocking as completion is reported
  // by USBDEVFS_REAPURBNDELAY.
  int rc = HANDLE_EINTR(ioctl(fd_.get(), USBDEVFS_SUBMITURB, &transfer->urb));
  if (rc) {
    rc = logging::GetLastSystemErrorCode();
    USB_PLOG(DEBUG) << "Failed to submit control transfer";
    task_runner_->PostTask(
        FROM_HERE, base::Bind(callback, ConvertTransferResult(rc), nullptr, 0));
  } else {
    SetUpTimeoutCallback(transfer.get(), timeout);
    transfers_.push_back(std::move(transfer));
  }
}

void UsbDeviceHandleUsbfs::IsochronousTransferIn(
    uint8_t endpoint_number,
    const std::vector<uint32_t>& packet_lengths,
    unsigned int timeout,
    const IsochronousTransferCallback& callback) {
  uint8_t endpoint_address = USB_DIR_IN | endpoint_number;
  size_t total_length =
      std::accumulate(packet_lengths.begin(), packet_lengths.end(), 0u);
  scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(total_length));
  IsochronousTransferInternal(endpoint_address, buffer, total_length,
                              packet_lengths, timeout, callback);
}

void UsbDeviceHandleUsbfs::IsochronousTransferOut(
    uint8_t endpoint_number,
    scoped_refptr<net::IOBuffer> buffer,
    const std::vector<uint32_t>& packet_lengths,
    unsigned int timeout,
    const IsochronousTransferCallback& callback) {
  uint8_t endpoint_address = USB_DIR_OUT | endpoint_number;
  size_t total_length =
      std::accumulate(packet_lengths.begin(), packet_lengths.end(), 0u);
  IsochronousTransferInternal(endpoint_address, buffer, total_length,
                              packet_lengths, timeout, callback);
}

void UsbDeviceHandleUsbfs::GenericTransfer(UsbEndpointDirection direction,
                                           uint8_t endpoint_number,
                                           scoped_refptr<net::IOBuffer> buffer,
                                           size_t length,
                                           unsigned int timeout,
                                           const TransferCallback& callback) {
  if (task_runner_->BelongsToCurrentThread()) {
    GenericTransferInternal(direction, endpoint_number, buffer, length, timeout,
                            callback, task_runner_);
  } else {
    task_runner_->PostTask(
        FROM_HERE,
        base::Bind(&UsbDeviceHandleUsbfs::GenericTransferInternal, this,
                   direction, endpoint_number, buffer, length, timeout,
                   callback, base::ThreadTaskRunnerHandle::Get()));
  }
}

const UsbInterfaceDescriptor* UsbDeviceHandleUsbfs::FindInterfaceByEndpoint(
    uint8_t endpoint_address) {
  auto it = endpoints_.find(endpoint_address);
  if (it != endpoints_.end())
    return it->second.interface;
  return nullptr;
}

UsbDeviceHandleUsbfs::~UsbDeviceHandleUsbfs() {
  DCHECK(!device_) << "Handle must be closed before it is destroyed.";
}

void UsbDeviceHandleUsbfs::ReleaseFileDescriptor() {
  ignore_result(fd_.release());
  delete helper_;
}

void UsbDeviceHandleUsbfs::CloseBlocking() {
  fd_.reset(-1);
  delete helper_;
}

void UsbDeviceHandleUsbfs::SetConfigurationBlocking(
    int configuration_value,
    const ResultCallback& callback) {
  if (!fd_.is_valid()) {
    task_runner_->PostTask(FROM_HERE, base::Bind(callback, false));
    return;
  }

  int rc = HANDLE_EINTR(
      ioctl(fd_.get(), USBDEVFS_SETCONFIGURATION, &configuration_value));
  if (rc)
    USB_PLOG(DEBUG) << "Failed to set configuration " << configuration_value;
  task_runner_->PostTask(
      FROM_HERE, base::Bind(&UsbDeviceHandleUsbfs::SetConfigurationComplete,
                            this, configuration_value, rc == 0, callback));
}

void UsbDeviceHandleUsbfs::SetConfigurationComplete(
    int configuration_value,
    bool success,
    const ResultCallback& callback) {
  if (success && device_) {
    device_->ActiveConfigurationChanged(configuration_value);
    // TODO(reillyg): If all interfaces are unclaimed before a new configuration
    // is set then this will do nothing. Investigate.
    RefreshEndpointInfo();
  }
  callback.Run(success);
}

void UsbDeviceHandleUsbfs::ReleaseInterfaceBlocking(
    int interface_number,
    const ResultCallback& callback) {
  if (!fd_.is_valid()) {
    task_runner_->PostTask(FROM_HERE, base::Bind(callback, false));
    return;
  }

  int rc = HANDLE_EINTR(
      ioctl(fd_.get(), USBDEVFS_RELEASEINTERFACE, &interface_number));
  if (rc) {
    USB_PLOG(DEBUG) << "Failed to release interface " << interface_number;
    task_runner_->PostTask(FROM_HERE, base::Bind(callback, false));
  } else {
    task_runner_->PostTask(
        FROM_HERE, base::Bind(&UsbDeviceHandleUsbfs::ReleaseInterfaceComplete,
                              this, interface_number, callback));
  }
}

void UsbDeviceHandleUsbfs::ReleaseInterfaceComplete(
    int interface_number,
    const ResultCallback& callback) {
  auto it = interfaces_.find(interface_number);
  DCHECK(it != interfaces_.end());
  interfaces_.erase(it);
  RefreshEndpointInfo();
  callback.Run(true);
}

void UsbDeviceHandleUsbfs::SetInterfaceBlocking(
    int interface_number,
    int alternate_setting,
    const ResultCallback& callback) {
  if (!fd_.is_valid()) {
    task_runner_->PostTask(FROM_HERE, base::Bind(callback, false));
    return;
  }

  usbdevfs_setinterface cmd = {0};
  cmd.interface = interface_number;
  cmd.altsetting = alternate_setting;
  int rc = HANDLE_EINTR(ioctl(fd_.get(), USBDEVFS_SETINTERFACE, &cmd));
  if (rc) {
    USB_PLOG(DEBUG) << "Failed to set interface " << interface_number
                    << " to alternate setting " << alternate_setting;
  }
  task_runner_->PostTask(FROM_HERE, base::Bind(callback, rc == 0));
}

void UsbDeviceHandleUsbfs::ResetDeviceBlocking(const ResultCallback& callback) {
  if (!fd_.is_valid()) {
    task_runner_->PostTask(FROM_HERE, base::Bind(callback, false));
    return;
  }

  // TODO(reillyg): libusb releases interfaces before and then reclaims
  // interfaces after a reset. We should probably do this too or document that
  // callers have to call ClaimInterface as well.
  int rc = HANDLE_EINTR(ioctl(fd_.get(), USBDEVFS_RESET, nullptr));
  if (rc)
    USB_PLOG(DEBUG) << "Failed to reset the device";
  task_runner_->PostTask(FROM_HERE, base::Bind(callback, rc == 0));
}

void UsbDeviceHandleUsbfs::ClearHaltBlocking(uint8_t endpoint_address,
                                             const ResultCallback& callback) {
  if (!fd_.is_valid()) {
    task_runner_->PostTask(FROM_HERE, base::Bind(callback, false));
    return;
  }

  int tmp_endpoint = endpoint_address;
  int rc = HANDLE_EINTR(ioctl(fd_.get(), USBDEVFS_CLEAR_HALT, &tmp_endpoint));
  if (rc) {
    USB_PLOG(DEBUG) << "Failed to clear the stall condition on endpoint "
                    << static_cast<int>(endpoint_address);
  }
  task_runner_->PostTask(FROM_HERE, base::Bind(callback, rc == 0));
}

void UsbDeviceHandleUsbfs::IsochronousTransferInternal(
    uint8_t endpoint_address,
    scoped_refptr<net::IOBuffer> buffer,
    size_t total_length,
    const std::vector<uint32_t>& packet_lengths,
    unsigned int timeout,
    const IsochronousTransferCallback& callback) {
  if (!device_) {
    ReportIsochronousError(packet_lengths, callback, USB_TRANSFER_DISCONNECT);
    return;
  }

  auto it = endpoints_.find(endpoint_address);
  if (it == endpoints_.end()) {
    USB_LOG(USER) << "Endpoint address " << static_cast<int>(endpoint_address)
                  << " is not part of a claimed interface.";
    ReportIsochronousError(packet_lengths, callback, USB_TRANSFER_ERROR);
    return;
  }

  std::unique_ptr<Transfer> transfer(new (packet_lengths.size())
                                         Transfer(buffer, callback));
  transfer->urb.type = USBDEVFS_URB_TYPE_ISO;
  transfer->urb.endpoint = endpoint_address;
  transfer->urb.buffer_length = total_length;

  for (size_t i = 0; i < packet_lengths.size(); ++i)
    transfer->urb.iso_frame_desc[i].length = packet_lengths[i];

  // USBDEVFS_SUBMITURB appears to be non-blocking as completion is reported
  // by USBDEVFS_REAPURBNDELAY. This code assumes a recent kernel that can
  // accept arbitrarily large transfer requests, hopefully also using a scatter-
  // gather list.
  int rc = HANDLE_EINTR(ioctl(fd_.get(), USBDEVFS_SUBMITURB, &transfer->urb));
  if (rc) {
    rc = logging::GetLastSystemErrorCode();
    USB_PLOG(DEBUG) << "Failed to submit transfer";
    ReportIsochronousError(packet_lengths, callback, ConvertTransferResult(rc));
  } else {
    SetUpTimeoutCallback(transfer.get(), timeout);
    transfers_.push_back(std::move(transfer));
  }
}

void UsbDeviceHandleUsbfs::GenericTransferInternal(
    UsbEndpointDirection direction,
    uint8_t endpoint_number,
    scoped_refptr<net::IOBuffer> buffer,
    size_t length,
    unsigned int timeout,
    const TransferCallback& callback,
    scoped_refptr<base::SingleThreadTaskRunner> callback_runner) {
  if (!device_) {
    callback_runner->PostTask(
        FROM_HERE, base::Bind(callback, USB_TRANSFER_DISCONNECT, nullptr, 0));
    return;
  }

  uint8_t endpoint_address =
      ConvertEndpointDirection(direction) | endpoint_number;
  auto it = endpoints_.find(endpoint_address);
  if (it == endpoints_.end()) {
    USB_LOG(USER) << "Endpoint address " << static_cast<int>(endpoint_address)
                  << " is not part of a claimed interface.";
    callback_runner->PostTask(
        FROM_HERE, base::Bind(callback, USB_TRANSFER_ERROR, nullptr, 0));
    return;
  }

  std::unique_ptr<Transfer> transfer(
      new (0) Transfer(buffer, callback, callback_runner));
  transfer->urb.endpoint = endpoint_address;
  transfer->urb.buffer_length = length;
  transfer->urb.type = ConvertTransferType(it->second.type);

  // USBDEVFS_SUBMITURB appears to be non-blocking as completion is reported
  // by USBDEVFS_REAPURBNDELAY. This code assumes a recent kernel that can
  // accept arbitrarily large transfer requests, hopefully also using a scatter-
  // gather list.
  int rc = HANDLE_EINTR(ioctl(fd_.get(), USBDEVFS_SUBMITURB, &transfer->urb));
  if (rc) {
    rc = logging::GetLastSystemErrorCode();
    USB_PLOG(DEBUG) << "Failed to submit transfer";
    callback_runner->PostTask(
        FROM_HERE, base::Bind(callback, ConvertTransferResult(rc), nullptr, 0));
  } else {
    SetUpTimeoutCallback(transfer.get(), timeout);
    transfers_.push_back(std::move(transfer));
  }
}

void UsbDeviceHandleUsbfs::ReapedUrbs(const std::vector<usbdevfs_urb*>& urbs) {
  for (auto* urb : urbs) {
    Transfer* transfer = static_cast<Transfer*>(urb->usercontext);
    DCHECK_EQ(urb, &transfer->urb);

    if (transfer->cancelled) {
      transfer->reaped = true;
      if (transfer->discarded)
        RemoveFromTransferList(transfer);
    } else {
      TransferComplete(RemoveFromTransferList(transfer));
    }
  }
}

void UsbDeviceHandleUsbfs::TransferComplete(
    std::unique_ptr<Transfer> transfer) {
  if (transfer->cancelled)
    return;

  // The transfer will soon be freed. Cancel the timeout callback so that the
  // raw pointer it holds to |transfer| is not used.
  transfer->timeout_closure.Cancel();

  if (transfer->urb.type == USBDEVFS_URB_TYPE_ISO) {
    std::vector<IsochronousPacket> packets(transfer->urb.number_of_packets);
    for (size_t i = 0; i < packets.size(); ++i) {
      packets[i].length = transfer->urb.iso_frame_desc[i].length;
      packets[i].transferred_length =
          transfer->urb.iso_frame_desc[i].actual_length;
      packets[i].status = ConvertTransferResult(
          transfer->urb.status == 0 ? transfer->urb.iso_frame_desc[i].status
                                    : transfer->urb.status);
    }

    transfer->RunIsochronousCallback(packets);
  } else {
    if (transfer->urb.status == 0 &&
        transfer->urb.type == USBDEVFS_URB_TYPE_CONTROL) {
      // Copy the result of the control transfer back into the original buffer.
      memcpy(transfer->buffer->data(),
             transfer->control_transfer_buffer->data() + 8,
             transfer->urb.actual_length);
    }

    transfer->RunCallback(ConvertTransferResult(-transfer->urb.status),
                          transfer->urb.actual_length);
  }
}

void UsbDeviceHandleUsbfs::RefreshEndpointInfo() {
  endpoints_.clear();

  const UsbConfigDescriptor* config = device_->active_configuration();
  if (!config)
    return;

  for (const auto& entry : interfaces_) {
    auto interface_it = std::find_if(
        config->interfaces.begin(), config->interfaces.end(),
        [entry](const UsbInterfaceDescriptor& interface) {
          uint8_t interface_number = entry.first;
          uint8_t alternate_setting = entry.second.alternate_setting;
          return interface.interface_number == interface_number &&
                 interface.alternate_setting == alternate_setting;
        });
    DCHECK(interface_it != config->interfaces.end());

    for (const auto& endpoint : interface_it->endpoints) {
      EndpointInfo& info = endpoints_[endpoint.address];
      info.type = endpoint.transfer_type;
      info.interface = &*interface_it;
    }
  }
}

void UsbDeviceHandleUsbfs::ReportIsochronousError(
    const std::vector<uint32_t>& packet_lengths,
    const UsbDeviceHandle::IsochronousTransferCallback& callback,
    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;
  }
  task_runner_->PostTask(FROM_HERE, base::Bind(callback, nullptr, packets));
}

void UsbDeviceHandleUsbfs::SetUpTimeoutCallback(Transfer* transfer,
                                                unsigned int timeout) {
  if (timeout > 0) {
    transfer->timeout_closure.Reset(
        base::Bind(&UsbDeviceHandleUsbfs::CancelTransfer, this, transfer,
                   USB_TRANSFER_TIMEOUT));
    task_runner_->PostDelayedTask(FROM_HERE,
                                  transfer->timeout_closure.callback(),
                                  base::TimeDelta::FromMilliseconds(timeout));
  }
}

std::unique_ptr<UsbDeviceHandleUsbfs::Transfer>
UsbDeviceHandleUsbfs::RemoveFromTransferList(Transfer* transfer_ptr) {
  auto it = std::find_if(
      transfers_.begin(), transfers_.end(),
      [transfer_ptr](const std::unique_ptr<Transfer>& transfer) -> bool {
        return transfer.get() == transfer_ptr;
      });
  DCHECK(it != transfers_.end());
  std::unique_ptr<Transfer> transfer = std::move(*it);
  transfers_.erase(it);
  return transfer;
}

void UsbDeviceHandleUsbfs::CancelTransfer(Transfer* transfer,
                                          UsbTransferStatus status) {
  if (transfer->cancelled)
    return;

  // |transfer| must stay in |transfers_| as it is still being processed by the
  // kernel and will be reaped later.
  transfer->cancelled = true;
  transfer->timeout_closure.Cancel();

  if (transfer->urb.type == USBDEVFS_URB_TYPE_ISO) {
    std::vector<IsochronousPacket> packets(transfer->urb.number_of_packets);
    for (size_t i = 0; i < packets.size(); ++i) {
      packets[i].length = transfer->urb.iso_frame_desc[i].length;
      packets[i].transferred_length = 0;
      packets[i].status = status;
    }
    transfer->RunIsochronousCallback(packets);
  } else {
    transfer->RunCallback(status, 0);
  }

  blocking_task_runner_->PostTask(
      FROM_HERE,
      base::Bind(&UsbDeviceHandleUsbfs::DiscardUrbBlocking, this, transfer));
}

void UsbDeviceHandleUsbfs::DiscardUrbBlocking(Transfer* transfer) {
  if (fd_.is_valid())
    HANDLE_EINTR(ioctl(fd_.get(), USBDEVFS_DISCARDURB, &transfer->urb));

  task_runner_->PostTask(
      FROM_HERE,
      base::Bind(&UsbDeviceHandleUsbfs::UrbDiscarded, this, transfer));
}

void UsbDeviceHandleUsbfs::UrbDiscarded(Transfer* transfer) {
  transfer->discarded = true;
  if (transfer->reaped)
    RemoveFromTransferList(transfer);
}

}  // namespace device
