/*
 * BRLTTY - A background process providing access to the console screen (when in
 *          text mode) for a blind person using a refreshable braille display.
 *
 * Copyright (C) 1995-2015 by The BRLTTY Developers.
 *
 * BRLTTY comes with ABSOLUTELY NO WARRANTY.
 *
 * This is free software, placed under the terms of the
 * GNU General Public License, as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any
 * later version. Please see the file LICENSE-GPL for details.
 *
 * Web Page: http://brltty.com/
 *
 * This software is maintained by Dave Mielke <dave@mielke.cc>.
 */

#include "prologue.h"

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>

#ifdef HAVE_REGEX_H
#include <regex.h>
#endif /* HAVE_REGEX_H */

#include "log.h"
#include "strfmt.h"
#include "parameters.h"
#include "parse.h"
#include "file.h"
#include "charset.h"
#include "device.h"
#include "timing.h"
#include "async_wait.h"
#include "io_misc.h"
#include "io_usb.h"
#include "usb_internal.h"
#include "usb_serial.h"

ssize_t
usbControlRead (
  UsbDevice *device,
  uint8_t recipient,
  uint8_t type,
  uint8_t request,
  uint16_t value,
  uint16_t index,
  void *buffer,
  uint16_t length,
  int timeout
) {
  return usbControlTransfer(device, UsbControlDirection_Input, recipient, type,
                            request, value, index, buffer, length, timeout);
}

ssize_t
usbControlWrite (
  UsbDevice *device,
  uint8_t recipient,
  uint8_t type,
  uint8_t request,
  uint16_t value,
  uint16_t index,
  const void *buffer,
  uint16_t length,
  int timeout
) {
  return usbControlTransfer(device, UsbControlDirection_Output, recipient, type,
                            request, value, index, (void *)buffer, length, timeout);
}

ssize_t
usbGetDescriptor (
  UsbDevice *device,
  unsigned char type,
  unsigned char number,
  unsigned int index,
  UsbDescriptor *descriptor,
  int timeout
) {
  return usbControlRead(device, UsbControlRecipient_Device, UsbControlType_Standard,
                        UsbStandardRequest_GetDescriptor, (type << 8) | number, index,
                        descriptor->bytes, sizeof(descriptor->bytes), timeout);
}

int
usbGetDeviceDescriptor (
  UsbDevice *device,
  UsbDeviceDescriptor *descriptor
) {
  UsbDescriptor desc;
  int size = usbGetDescriptor(device, UsbDescriptorType_Device, 0, 0, &desc, 1000);

  if (size != -1) {
    *descriptor = desc.device;
  }

  return size;
}

int
usbGetLanguage (
  UsbDevice *device,
  uint16_t *language,
  int timeout
) {
  UsbDescriptor descriptor;
  ssize_t size = usbGetDescriptor(device, UsbDescriptorType_String,
                              0, 0, &descriptor, timeout);

  if (size != -1) {
    if (size >= 4) {
      *language = getLittleEndian16(descriptor.string.wData[0]);
      logMessage(LOG_CATEGORY(USB_IO), "USB language: %02X", *language);
      return 1;
    } else {
      logMessage(LOG_ERR, "USB language code string too short: %"PRIssize, size);
      errno = EIO;
    }
  } else {
    logMessage(LOG_ERR, "USB language code string read error");
  }

  return 0;
}

char *
usbDecodeString (const UsbStringDescriptor *descriptor) {
  size_t count = (descriptor->bLength - 2) / sizeof(descriptor->wData[0]);
  char buffer[(count * UTF8_LEN_MAX) + 1];

  const uint16_t *source = descriptor->wData;
  const uint16_t *end = source + count;
  char *target = buffer;

  while (source < end) {
    size_t length = convertWcharToUtf8(getLittleEndian16(*source++), target);

    target += length;
  }
  *target = 0;

  {
    char *string = strdup(buffer);

    if (!string) logMallocError();
    return string;
  }
}

char *
usbGetString (
  UsbDevice *device,
  unsigned char number,
  int timeout
) {
  UsbDescriptor descriptor;

  if (!device->language) {
    if (!usbGetLanguage(device, &device->language, timeout)) {
      return NULL;
    }
  }

  if (usbGetDescriptor(device, UsbDescriptorType_String,
                       number, device->language,
                       &descriptor, timeout) == -1) {
    logMessage(LOG_ERR, "USB string read error: %u", number);
    return NULL;
  }

  return usbDecodeString(&descriptor.string);
}

char *
usbGetManufacturer (UsbDevice *device, int timeout) {
  return usbGetString(device, device->descriptor.iManufacturer, timeout);
}

char *
usbGetProduct (UsbDevice *device, int timeout) {
  return usbGetString(device, device->descriptor.iProduct, timeout);
}

char *
usbGetSerialNumber (UsbDevice *device, int timeout) {
  return usbGetString(device, device->descriptor.iSerialNumber, timeout);
}

static size_t
usbFormatLogSetupPacket (char *buffer, size_t size, const void *data) {
  const UsbSetupPacket *setup = data;
  size_t length;
  STR_BEGIN(buffer, size);

  STR_PRINTF("setup packet: Typ:%02X Req:%02X Val:%04X Idx:%04X Len:%04X",
             setup->bRequestType, setup->bRequest,
             getLittleEndian16(setup->wValue),
             getLittleEndian16(setup->wIndex),
             getLittleEndian16(setup->wLength));

  length = STR_LENGTH;
  STR_END;
  return length;
}

void
usbLogSetupPacket (const UsbSetupPacket *setup) {
  logData(LOG_CATEGORY(USB_IO), usbFormatLogSetupPacket, setup);
}

void
usbMakeSetupPacket (
  UsbSetupPacket *setup,
  uint8_t direction,
  uint8_t recipient,
  uint8_t type,
  uint8_t request,
  uint16_t value,
  uint16_t index,
  uint16_t length
) {
  setup->bRequestType = direction | recipient | type;
  setup->bRequest = request;
  putLittleEndian16(&setup->wValue, value);
  putLittleEndian16(&setup->wIndex, index);
  putLittleEndian16(&setup->wLength, length);
  usbLogSetupPacket(setup);
}

void
usbLogEndpointData (
  UsbEndpoint *endpoint, const char *label,
  const void *data, size_t size
) {
  logBytes(LOG_CATEGORY(USB_IO), "endpoint %02X %s", data, size,
           endpoint->descriptor->bEndpointAddress, label);
}

void
usbLogString (
  UsbDevice *device,
  unsigned char number,
  const char *label
) {
  if (number) {
    char *string = usbGetString(device, number, 1000);
    if (string) {
      logMessage(LOG_INFO, "USB: %s: %s", label, string);
      free(string);
    }
  }
}

int
usbStringEquals (const char *reference, const char *value) {
  return strcmp(reference, value) == 0;
}

int
usbStringMatches (const char *reference, const char *value) {
  int ok = 0;

#ifdef HAVE_REGEX_H
  regex_t expression;
  if (regcomp(&expression, value, REG_EXTENDED|REG_NOSUB) == 0) {
    if (regexec(&expression, reference, 0, NULL, 0) == 0) {
      ok = 1;
    }
    regfree(&expression);
  }
#endif /* HAVE_REGEX_H */

  return ok;
}

int
usbVerifyString (
  UsbDevice *device,
  UsbStringVerifier verify,
  unsigned char index,
  const char *value
) {
  int ok = 0;
  if (!(value && *value)) return 1;

  if (index) {
    char *reference = usbGetString(device, index, 1000);
    if (reference) {
      if (verify(reference, value)) ok = 1;
      free(reference);
    }
  }
  return ok;
}

int
usbVerifyManufacturerName (UsbDevice *device, const char *eRegExp) {
  return usbVerifyString(device, usbStringMatches,
                         device->descriptor.iManufacturer, eRegExp);
}

int
usbVerifyProductDescription (UsbDevice *device, const char *eRegExp) {
  return usbVerifyString(device, usbStringMatches,
                         device->descriptor.iProduct, eRegExp);
}

int
usbVerifySerialNumber (UsbDevice *device, const char *string) {
  return usbVerifyString(device, usbStringEquals,
                         device->descriptor.iSerialNumber, string);
}

int
usbParseVendorIdentifier (uint16_t *identifier, const char *string) {
  if (string && *string) {
    unsigned int value;

    if (isUnsignedInteger(&value, string)) {
      if ((value > 0) && (value <= UINT16_MAX)) {
        *identifier = value;
        return 1;
      }
    }

    logMessage(LOG_WARNING, "invalid USB vendor identifier: %s", string);
    return 0;
  }

  *identifier = 0;
  return 1;
}

int
usbVerifyVendorIdentifier (const UsbDeviceDescriptor *descriptor, uint16_t identifier) {
  if (!identifier) return 1;
  return identifier == getLittleEndian16(descriptor->idVendor);
}

int
usbParseProductIdentifier (uint16_t *identifier, const char *string) {
  if (string && *string) {
    unsigned int value;

    if (isUnsignedInteger(&value, string)) {
      if ((value > 0) && (value <= UINT16_MAX)) {
        *identifier = value;
        return 1;
      }
    }

    logMessage(LOG_WARNING, "invalid USB product identifier: %s", string);
    return 0;
  }

  *identifier = 0;
  return 1;
}

int
usbVerifyProductIdentifier (const UsbDeviceDescriptor *descriptor, uint16_t identifier) {
  if (!identifier) return 1;
  return identifier == getLittleEndian16(descriptor->idProduct);
}

const UsbDeviceDescriptor *
usbDeviceDescriptor (UsbDevice *device) {
  return &device->descriptor;
}

int
usbGetConfiguration (
  UsbDevice *device,
  unsigned char *configuration
) {
  ssize_t size = usbControlRead(device, UsbControlRecipient_Device, UsbControlType_Standard,
                                UsbStandardRequest_GetConfiguration, 0, 0,
                                configuration, sizeof(*configuration), 1000);
  if (size != -1) return 1;
  logMessage(LOG_WARNING, "USB standard request not supported: get configuration");
  return 0;
}

static void
usbDeallocateConfigurationDescriptor (UsbDevice *device) {
  if (device->configuration) {
    free(device->configuration);
    device->configuration = NULL;
  }
}

const UsbConfigurationDescriptor *
usbConfigurationDescriptor (
  UsbDevice *device
) {
  if (!device->configuration) {
    unsigned char current;

    if (device->descriptor.bNumConfigurations < 2) {
      current = 1;
    } else if (!usbGetConfiguration(device, &current)) {
      current = 0;
    }

    if (current) {
      UsbDescriptor descriptor;
      unsigned char number;

      for (number=0; number<device->descriptor.bNumConfigurations; number++) {
        int size = usbGetDescriptor(device, UsbDescriptorType_Configuration,
                                    number, 0, &descriptor, 1000);
        if (size == -1) {
          logMessage(LOG_WARNING, "USB configuration descriptor not readable: %d", number);
        } else if (descriptor.configuration.bConfigurationValue == current) {
          break;
        }
      }

      if (number < device->descriptor.bNumConfigurations) {
        int length = getLittleEndian16(descriptor.configuration.wTotalLength);
        UsbDescriptor *descriptors;

        if ((descriptors = malloc(length))) {
          ssize_t size;

          if (length > sizeof(descriptor)) {
            size = usbControlRead(device, UsbControlRecipient_Device, UsbControlType_Standard,
                                  UsbStandardRequest_GetDescriptor,
                                  (UsbDescriptorType_Configuration << 8) | number,
                                  0, descriptors, length, 1000);
          } else {
            memcpy(descriptors, &descriptor, (size = length));
          }

          if (size != -1) {
            device->configuration = &descriptors->configuration;
          } else {
            free(descriptors);
          }
        } else {
          logSystemError("USB configuration descriptor allocate");
        }
      } else {
        logMessage(LOG_ERR, "USB configuration descriptor not found: %d", current);
      }
    }
  }

  return device->configuration;
}

int
usbConfigureDevice (
  UsbDevice *device,
  unsigned char configuration
) {
  usbCloseInterface(device);

  if (usbSetConfiguration(device, configuration)) {
    usbDeallocateConfigurationDescriptor(device);
    return 1;
  }

  {
    const UsbConfigurationDescriptor *descriptor = usbConfigurationDescriptor(device);

    if (descriptor)
      if (descriptor->bConfigurationValue == configuration)
        return 1;
  }

  return 0;
}

int
usbNextDescriptor (
  UsbDevice *device,
  const UsbDescriptor **descriptor
) {
  if (*descriptor) {
    const UsbDescriptor *next = (UsbDescriptor *)&(*descriptor)->bytes[(*descriptor)->header.bLength];
    const UsbDescriptor *first = (UsbDescriptor *)device->configuration;
    unsigned int length = getLittleEndian16(first->configuration.wTotalLength);
    if ((&next->bytes[0] - &first->bytes[0]) >= length) return 0;
    if ((&next->bytes[next->header.bLength] - &first->bytes[0]) > length) return 0;
    *descriptor = next;
  } else if (usbConfigurationDescriptor(device)) {
    *descriptor = (UsbDescriptor *)device->configuration;
  } else {
    return 0;
  }
  return 1;
}

const UsbInterfaceDescriptor *
usbInterfaceDescriptor (
  UsbDevice *device,
  unsigned char interface,
  unsigned char alternative
) {
  const UsbDescriptor *descriptor = NULL;

  while (usbNextDescriptor(device, &descriptor)) {
    if (descriptor->interface.bDescriptorType == UsbDescriptorType_Interface)
      if (descriptor->interface.bInterfaceNumber == interface)
        if (descriptor->interface.bAlternateSetting == alternative)
          return &descriptor->interface;
  }

  logMessage(LOG_WARNING, "USB: interface descriptor not found: %d.%d", interface, alternative);
  errno = ENOENT;
  return NULL;
}

unsigned int
usbAlternativeCount (
  UsbDevice *device,
  unsigned char interface
) {
  unsigned int count = 0;
  const UsbDescriptor *descriptor = NULL;

  while (usbNextDescriptor(device, &descriptor)) {
    if (descriptor->interface.bDescriptorType == UsbDescriptorType_Interface)
      if (descriptor->interface.bInterfaceNumber == interface)
        count += 1;
  }

  return count;
}

const UsbEndpointDescriptor *
usbEndpointDescriptor (
  UsbDevice *device,
  unsigned char endpointAddress
) {
  const UsbDescriptor *descriptor = NULL;

  while (usbNextDescriptor(device, &descriptor)) {
    if (descriptor->endpoint.bDescriptorType == UsbDescriptorType_Endpoint)
      if (descriptor->endpoint.bEndpointAddress == endpointAddress)
        return &descriptor->endpoint;
  }

  logMessage(LOG_WARNING, "USB: endpoint descriptor not found: %02X", endpointAddress);
  errno = ENOENT;
  return NULL;
}

static void
usbCancelInputMonitor (UsbEndpoint *endpoint) {
  if (endpoint->direction.input.pipe.monitor) {
    asyncCancelRequest(endpoint->direction.input.pipe.monitor);
    endpoint->direction.input.pipe.monitor = NULL;
  }
}

static inline int
usbHaveInputPipe (UsbEndpoint *endpoint) {
  return endpoint->direction.input.pipe.output != INVALID_FILE_DESCRIPTOR;
}

static inline int
usbHaveInputError (UsbEndpoint *endpoint) {
  return endpoint->direction.input.pipe.input == INVALID_FILE_DESCRIPTOR;
}

void
usbSetInputError (UsbEndpoint *endpoint, int error) {
  if (!usbHaveInputError(endpoint)) {
    endpoint->direction.input.pipe.error = error;
    closeFile(&endpoint->direction.input.pipe.input);
  }
}

int
usbEnqueueInput (UsbEndpoint *endpoint, const void *buffer, size_t length) {
  if (usbHaveInputError(endpoint)) {
    errno = EIO;
    return 0;
  }

  return writeFile(endpoint->direction.input.pipe.input, buffer, length) != -1;
}

void
usbDestroyInputPipe (UsbEndpoint *endpoint) {
  usbCancelInputMonitor(endpoint);
  closeFile(&endpoint->direction.input.pipe.input);
  closeFile(&endpoint->direction.input.pipe.output);
}

int
usbMakeInputPipe (UsbEndpoint *endpoint) {
  if (usbHaveInputPipe(endpoint)) return 1;

  if (createAnonymousPipe(&endpoint->direction.input.pipe.input,
                          &endpoint->direction.input.pipe.output)) {
    if (setBlockingIo(endpoint->direction.input.pipe.output, 0)) {
      return 1;
    }
  }

  usbDestroyInputPipe(endpoint);
  return 0;
}

int
usbMonitorInputPipe (
  UsbDevice *device, unsigned char endpointNumber,
  AsyncMonitorCallback *callback, void *data
) {
  UsbEndpoint *endpoint = usbGetInputEndpoint(device, endpointNumber);

  if (endpoint) {
    if (usbHaveInputPipe(endpoint)) {
      usbCancelInputMonitor(endpoint);
      if (!callback) return 1;

      if (asyncMonitorFileInput(&endpoint->direction.input.pipe.monitor,
                                endpoint->direction.input.pipe.output,
                                callback, data)) {
        return 1;
      }
    }
  }

  return 0;
}

static void
usbDeallocateEndpoint (void *item, void *data) {
  UsbEndpoint *endpoint = item;

  switch (USB_ENDPOINT_DIRECTION(endpoint->descriptor)) {
    case UsbEndpointDirection_Input:
      if (endpoint->direction.input.pending) {
        deallocateQueue(endpoint->direction.input.pending);
        endpoint->direction.input.pending = NULL;
      }

      if (endpoint->direction.input.completed.request) {
        free(endpoint->direction.input.completed.request);
        endpoint->direction.input.completed.request = NULL;
      }

      break;

    default:
      break;
  }

  if (endpoint->extension) {
    usbDeallocateEndpointExtension(endpoint->extension);
    endpoint->extension = NULL;
  }

  switch (USB_ENDPOINT_DIRECTION(endpoint->descriptor)) {
    case UsbEndpointDirection_Input:
      usbDestroyInputPipe(endpoint);
      break;

    default:
      break;
  }

  free(endpoint);
}

static int
usbTestEndpoint (const void *item, void *data) {
  const UsbEndpoint *endpoint = item;
  const unsigned char *endpointAddress = data;
  return endpoint->descriptor->bEndpointAddress == *endpointAddress;
}

UsbEndpoint *
usbGetEndpoint (UsbDevice *device, unsigned char endpointAddress) {
  UsbEndpoint *endpoint;
  const UsbEndpointDescriptor *descriptor;

  if ((endpoint = findItem(device->endpoints, usbTestEndpoint, &endpointAddress))) return endpoint;

  if ((descriptor = usbEndpointDescriptor(device, endpointAddress))) {
    {
      const char *direction;
      const char *transfer;

      switch (USB_ENDPOINT_DIRECTION(descriptor)) {
        default:                            direction = "?";   break;
        case UsbEndpointDirection_Input:  direction = "in";  break;
        case UsbEndpointDirection_Output: direction = "out"; break;
      }

      switch (USB_ENDPOINT_TRANSFER(descriptor)) {
        default:                                transfer = "?";   break;
        case UsbEndpointTransfer_Control:     transfer = "ctl"; break;
        case UsbEndpointTransfer_Isochronous: transfer = "iso"; break;
        case UsbEndpointTransfer_Bulk:        transfer = "blk"; break;
        case UsbEndpointTransfer_Interrupt:   transfer = "int"; break;
      }

      logMessage(LOG_CATEGORY(USB_IO), "ept=%02X dir=%s xfr=%s pkt=%d ivl=%dms",
                 descriptor->bEndpointAddress, direction, transfer,
                 getLittleEndian16(descriptor->wMaxPacketSize),
                 descriptor->bInterval);
    }

    if ((endpoint = malloc(sizeof(*endpoint)))) {
      memset(endpoint, 0, sizeof(*endpoint));
      endpoint->device = device;
      endpoint->descriptor = descriptor;
      endpoint->extension = NULL;
      endpoint->prepare = NULL;

      switch (USB_ENDPOINT_DIRECTION(endpoint->descriptor)) {
        case UsbEndpointDirection_Input:
          endpoint->direction.input.pending = NULL;

          endpoint->direction.input.completed.request = NULL;
          endpoint->direction.input.completed.buffer = NULL;
          endpoint->direction.input.completed.length = 0;

          endpoint->direction.input.pipe.input = INVALID_FILE_DESCRIPTOR;
          endpoint->direction.input.pipe.output = INVALID_FILE_DESCRIPTOR;
          endpoint->direction.input.pipe.monitor = NULL;
          endpoint->direction.input.pipe.error = 0;

          break;
      }

      if (usbAllocateEndpointExtension(endpoint)) {
        if (enqueueItem(device->endpoints, endpoint)) {
          if (device->disableEndpointReset) {
            logMessage(LOG_CATEGORY(USB_IO), "endpoint reset disabled");
          } else {
            usbClearHalt(device, endpoint->descriptor->bEndpointAddress);
          }

          if (!endpoint->prepare || endpoint->prepare(endpoint)) return endpoint;
          deleteItem(device->endpoints, endpoint);
        }

        usbDeallocateEndpointExtension(endpoint->extension);
        usbDestroyInputPipe(endpoint);
      }

      free(endpoint);
    }
  }

  return NULL;
}

UsbEndpoint *
usbGetInputEndpoint (UsbDevice *device, unsigned char endpointNumber) {
  return usbGetEndpoint(device, endpointNumber|UsbEndpointDirection_Input);
}

UsbEndpoint *
usbGetOutputEndpoint (UsbDevice *device, unsigned char endpointNumber) {
  return usbGetEndpoint(device, endpointNumber|UsbEndpointDirection_Output);
}

static int
usbFinishEndpoint (void *item, void *data) {
  UsbEndpoint *endpoint = item;

  switch (USB_ENDPOINT_DIRECTION(endpoint->descriptor)) {
    case UsbEndpointDirection_Input:
      if (endpoint->direction.input.pending) deleteElements(endpoint->direction.input.pending);
      break;

    default:
      break;
  }

  return 0;
}

static void
usbRemoveEndpoints (UsbDevice *device, int final) {
  if (device->endpoints) {
    processQueue(device->endpoints, usbFinishEndpoint, NULL);
    deleteElements(device->endpoints);

    if (final) {
      deallocateQueue(device->endpoints);
      device->endpoints = NULL;
    }
  }
}

static void
usbDeallocateInputFilter (void *item, void *data) {
  UsbInputFilterEntry *entry = item;
  free(entry);
}

int
usbAddInputFilter (UsbDevice *device, UsbInputFilter *filter) {
  UsbInputFilterEntry *entry;

  if ((entry = malloc(sizeof(*entry)))) {
    memset(entry, 0, sizeof(*entry));
    entry->filter = filter;

    if (enqueueItem(device->inputFilters, entry)) return 1;

    free(entry);
  }

  return 0;
}

static int
usbApplyInputFilter (void *item, void *data) {
  UsbInputFilterEntry *entry = item;

  return !entry->filter(data);
}

int
usbApplyInputFilters (UsbEndpoint *endpoint, void *buffer, size_t size, ssize_t *length) {
  UsbInputFilterData data = {
    .buffer = buffer,
    .size = size,
    .length = *length
  };

  usbLogEndpointData(endpoint, "unfiltered input", buffer, *length);

  if (processQueue(endpoint->device->inputFilters, usbApplyInputFilter, &data)) {
    errno = EIO;
    return 0;
  }

  *length = data.length;
  usbLogEndpointData(endpoint, "filtered input", buffer, *length);
  return 1;
}

void
usbCloseInterface (
  UsbDevice *device
) {
  usbRemoveEndpoints(device, 0);

  if (device->interface) {
    usbReleaseInterface(device, device->interface->bInterfaceNumber);
    device->interface = NULL;
  }
}

int
usbOpenInterface (
  UsbDevice *device,
  unsigned char interface,
  unsigned char alternative
) {
  const UsbInterfaceDescriptor *descriptor = usbInterfaceDescriptor(device, interface, alternative);
  if (!descriptor) return 0;
  if (descriptor == device->interface) return 1;

  if (device->interface)
    if (device->interface->bInterfaceNumber != interface)
      usbCloseInterface(device);

  if (!device->interface)
    if (!usbClaimInterface(device, interface))
      return 0;

  if (usbAlternativeCount(device, interface) == 1) goto done;

  {
    unsigned char response[1];
    ssize_t size = usbControlRead(device, UsbControlRecipient_Interface, UsbControlType_Standard,
                                  UsbStandardRequest_GetInterface, 0, interface,
                                  response, sizeof(response), 1000);

    if (size != -1) {
      if (response[0] == alternative) goto done;
    } else {
      logMessage(LOG_WARNING, "USB standard request not supported: get interface");
    }
  }

  if (usbSetAlternative(device, interface, alternative)) goto done;
  if (!device->interface) usbReleaseInterface(device, interface);
  return 0;

done:
  device->interface = descriptor;
  return 1;
}

void
usbCloseDevice (UsbDevice *device) {
  usbCloseInterface(device);
  usbRemoveEndpoints(device, 1);

  if (device->inputFilters) {
    deallocateQueue(device->inputFilters);
    device->inputFilters = NULL;
  }

  if (device->serial.data) {
    device->serial.operations->destroyData(device->serial.data);
    device->serial.data = NULL;
  }

  if (device->extension) {
    usbDeallocateDeviceExtension(device->extension);
    device->extension = NULL;
  }

  usbDeallocateConfigurationDescriptor(device);
  free(device);
}

static UsbDevice *
usbOpenDevice (UsbDeviceExtension *extension) {
  UsbDevice *device;
  if ((device = malloc(sizeof(*device)))) {
    memset(device, 0, sizeof(*device));
    device->extension = extension;
    device->serial.operations = NULL;
    device->serial.data = NULL;
    device->disableEndpointReset = 0;

    if ((device->endpoints = newQueue(usbDeallocateEndpoint, NULL))) {
      if ((device->inputFilters = newQueue(usbDeallocateInputFilter, NULL))) {
        if (usbReadDeviceDescriptor(device)) {
          if (device->descriptor.bDescriptorType == UsbDescriptorType_Device) {
            if (device->descriptor.bLength == UsbDescriptorSize_Device) {
              return device;
            }
          }
        }

        deallocateQueue(device->inputFilters);
      }

      usbRemoveEndpoints(device, 1);
    }
    free(device);
  }

  logSystemError("USB device open");
  return NULL;
}

UsbDevice *
usbTestDevice (UsbDeviceExtension *extension, UsbDeviceChooser *chooser, UsbChooseChannelData *data) {
  UsbDevice *device;

  if ((device = usbOpenDevice(extension))) {
    logMessage(LOG_CATEGORY(USB_IO), "testing device: vendor=%04X product=%04X",
               getLittleEndian16(device->descriptor.idVendor),
               getLittleEndian16(device->descriptor.idProduct));

    if (chooser(device, data)) {
      usbLogString(device, device->descriptor.iManufacturer, "Manufacturer Name");
      usbLogString(device, device->descriptor.iProduct, "Product Description");
      usbLogString(device, device->descriptor.iSerialNumber, "Serial Number");
      return device;
    }

    errno = ENOENT;
    device->extension = NULL;
    usbCloseDevice(device);
  }

  return NULL;
}

static void
usbDeallocatePendingInputRequest (void *item, void *data) {
  void *request = item;
  UsbEndpoint *endpoint = data;
  usbCancelRequest(endpoint->device, request);
}

Element *
usbAddPendingInputRequest (
  UsbEndpoint *endpoint
) {
  void *request = usbSubmitRequest(endpoint->device,
                                   endpoint->descriptor->bEndpointAddress,
                                   NULL,
                                   getLittleEndian16(endpoint->descriptor->wMaxPacketSize),
                                   endpoint);

  if (request) {
    Element *element = enqueueItem(endpoint->direction.input.pending, request);

    if (element) return element;
    usbCancelRequest(endpoint->device, request);
  }

  return NULL;
}

void
usbBeginInput (
  UsbDevice *device,
  unsigned char endpointNumber
) {
  UsbEndpoint *endpoint = usbGetInputEndpoint(device, endpointNumber);

  if (endpoint) {
    if (!endpoint->direction.input.pending) {
      if ((endpoint->direction.input.pending = newQueue(usbDeallocatePendingInputRequest, NULL))) {
        setQueueData(endpoint->direction.input.pending, endpoint);
      }
    }

    if (endpoint->direction.input.pending) {
      const int count = USB_INPUT_INTERRUPT_URB_COUNT;

      while (getQueueSize(endpoint->direction.input.pending) < count) {
        if (!usbAddPendingInputRequest(endpoint)) {
          break;
        }
      }
    }
  }
}

int
usbAwaitInput (
  UsbDevice *device,
  unsigned char endpointNumber,
  int timeout
) {
  UsbEndpoint *endpoint;
  int retryInterval;

  if (!(endpoint = usbGetInputEndpoint(device, endpointNumber))) {
    return 0;
  }

  if (usbHaveInputPipe(endpoint)) {
    if (usbHaveInputError(endpoint)) {
      errno = endpoint->direction.input.pipe.error;
      return 0;
    }

    return awaitFileInput(endpoint->direction.input.pipe.output, timeout);
  }

  if (endpoint->direction.input.completed.request) {
    return 1;
  }

  if (!timeout) {
    errno = EAGAIN;
    return 0;
  }

  retryInterval = endpoint->descriptor->bInterval;
  retryInterval = MAX(USB_INPUT_AWAIT_RETRY_INTERVAL_MINIMUM, retryInterval);

  if (!(endpoint->direction.input.pending && getQueueSize(endpoint->direction.input.pending))) {
    int size = getLittleEndian16(endpoint->descriptor->wMaxPacketSize);
    unsigned char *buffer = malloc(size);

    if (buffer) {
      TimePeriod period;
      startTimePeriod(&period, timeout);

      while (1) {
        ssize_t count = usbReadEndpoint(device, endpointNumber, buffer, size, 20);

        if (count != -1) {
          if (count) {
            endpoint->direction.input.completed.request = buffer;
            endpoint->direction.input.completed.buffer = buffer;
            endpoint->direction.input.completed.length = count;
            return 1;
          }

          errno = EAGAIN;
        }

#ifdef ETIMEDOUT
        if (errno == ETIMEDOUT) errno = EAGAIN;
#endif /* ETIMEDOUT */

        if (errno != EAGAIN) break;
        if (afterTimePeriod(&period, NULL)) break;
        asyncWait(retryInterval);
      }

      free(buffer);
    } else {
      logMallocError();
    }

    return 0;
  }

  {
    TimePeriod period;
    startTimePeriod(&period, timeout);

    while (1) {
      UsbResponse response;
      void *request;

      while (!(request = usbReapResponse(device,
                                         endpointNumber | UsbEndpointDirection_Input,
                                         &response, 0))) {
        if (errno != EAGAIN) return 0;
        if (afterTimePeriod(&period, NULL)) return 0;
        asyncWait(retryInterval);
      }

      usbAddPendingInputRequest(endpoint);
      deleteItem(endpoint->direction.input.pending, request);

      if (response.count > 0) {
        endpoint->direction.input.completed.request = request;
        endpoint->direction.input.completed.buffer = response.buffer;
        endpoint->direction.input.completed.length = response.count;
        return 1;
      }

      free(request);
    }
  }
}

ssize_t
usbReadData (
  UsbDevice *device,
  unsigned char endpointNumber,
  void *buffer,
  size_t length,
  int initialTimeout,
  int subsequentTimeout
) {
  UsbEndpoint *endpoint = usbGetInputEndpoint(device, endpointNumber);

  if (endpoint) {
    unsigned char *bytes = buffer;
    unsigned char *target = bytes;

    if (usbHaveInputPipe(endpoint)) {
      if (usbHaveInputError(endpoint)) {
        errno = endpoint->direction.input.pipe.error;
        endpoint->direction.input.pipe.error = EAGAIN;
        return -1;
      }

      return readFile(endpoint->direction.input.pipe.output, buffer, length, initialTimeout, subsequentTimeout);
    }

    while (length > 0) {
      int timeout = (target != bytes)? subsequentTimeout:
                    initialTimeout? initialTimeout:
                    USB_INPUT_READ_INITIAL_TIMEOUT_DEFAULT;

      if (!usbAwaitInput(device, endpointNumber, timeout)) {
        if (errno == EAGAIN) break;
        return -1;
      }

      {
        size_t count = endpoint->direction.input.completed.length;

        if (length < count) count = length;
        memcpy(target, endpoint->direction.input.completed.buffer, count);

        if ((endpoint->direction.input.completed.length -= count)) {
          endpoint->direction.input.completed.buffer += count;
        } else {
          endpoint->direction.input.completed.buffer = NULL;
          free(endpoint->direction.input.completed.request);
          endpoint->direction.input.completed.request = NULL;
        }

        target += count;
        length -= count;
      }
    }

    return target - bytes;
  }

  return -1;
}

ssize_t
usbWriteData (
  UsbDevice *device,
  unsigned char endpointNumber,
  const void *data,
  size_t length,
  int timeout
) {
  UsbEndpoint *endpoint = usbGetOutputEndpoint(device, endpointNumber);

  if (endpoint) {
    const uint16_t size = getLittleEndian16(endpoint->descriptor->wMaxPacketSize);
    const unsigned char *from = data;
    const unsigned char *const end = from + length;

    while (from < end) {
      size_t count = MIN((end - from), size);
      ssize_t result = usbWriteEndpoint(device, endpointNumber, from, count, timeout);

      if (result == -1) return result;
      from += result;
    }

    return length;
  }

  return -1;
}

static int
usbPrepareChannel (UsbChannel *channel) {
  const UsbChannelDefinition *definition = channel->definition;
  UsbDevice *device = channel->device;

  device->disableEndpointReset = definition->disableEndpointReset;

  if (definition->disableAutosuspend) {
    logMessage(LOG_CATEGORY(USB_IO), "disabling autosuspend");
    usbDisableAutosuspend(device);
  }

  if (usbConfigureDevice(device, definition->configuration)) {
    if (usbOpenInterface(device, definition->interface, definition->alternative)) {
      int ok = 1;

      if (ok) {
        if (!usbSetSerialOperations(device)) {
          ok = 0;
        } else if (device->serial.operations) {
          logMessage(LOG_CATEGORY(USB_IO), "serial adapter: %s",
                     device->serial.operations->name);
        }
      }

      if (ok) {
        if (device->serial.operations) {
          if (device->serial.operations->enableAdapter) {
            if (!device->serial.operations->enableAdapter(device)) {
              ok = 0;
            }
          }
        }
      }

      if (ok) {
        if (definition->serial) {
          if (!usbSetSerialParameters(device, definition->serial)) {
            ok = 0;
          }
        }
      }

      if (ok) {
        if (definition->inputEndpoint) {
          UsbEndpoint *endpoint = usbGetInputEndpoint(device, definition->inputEndpoint);

          if (!endpoint) {
            ok = 0;
          } else if ((USB_ENDPOINT_TRANSFER(endpoint->descriptor) == UsbEndpointTransfer_Interrupt) ||
                     usbHaveInputPipe(endpoint)) {
            usbBeginInput(device, definition->inputEndpoint);
          }
        }
      }

      if (ok) {
        if (definition->outputEndpoint) {
          UsbEndpoint *endpoint = usbGetOutputEndpoint(device, definition->outputEndpoint);

          if (!endpoint) {
            ok = 0;
          }
        }
      }

      if (ok) return 1;
      usbCloseInterface(device);
    }
  }

  return 0;
}

struct UsbChooseChannelDataStruct {
  const UsbChannelDefinition *definition;

  const char *serialNumber;
  uint16_t vendorIdentifier;
  uint16_t productIdentifier;
  unsigned genericDevices:1;
};

static int
usbChooseChannel (UsbDevice *device, UsbChooseChannelData *data) {
  const UsbDeviceDescriptor *descriptor = &device->descriptor;
  const UsbChannelDefinition *definition = data->definition;

  if (!(descriptor->iManufacturer ||
        descriptor->iProduct ||
        descriptor->iSerialNumber)) {
    UsbDeviceDescriptor actualDescriptor;
    ssize_t result = usbGetDeviceDescriptor(device, &actualDescriptor);

    if (result == UsbDescriptorSize_Device) {
      logMessage(LOG_CATEGORY(USB_IO), "using actual device descriptor");
      device->descriptor = actualDescriptor;
    }
  }

  while (definition->vendor) {
    if (definition->version && (definition->version != getLittleEndian16(descriptor->bcdUSB))) goto nextDefinition;
    if (!USB_IS_PRODUCT(descriptor, definition->vendor, definition->product)) goto nextDefinition;

    if (!data->genericDevices) {
      const UsbSerialAdapter *adapter = usbFindSerialAdapter(descriptor);

      if (adapter && adapter->generic) goto nextDefinition;
    }

    if (!usbVerifyVendorIdentifier(descriptor, data->vendorIdentifier)) goto nextDefinition;
    if (!usbVerifyProductIdentifier(descriptor, data->productIdentifier)) goto nextDefinition;
    if (!usbVerifySerialNumber(device, data->serialNumber)) goto nextDefinition;

    if (definition->verifyInterface) {
      if (!usbConfigureDevice(device, definition->configuration)) goto nextDefinition;
      if (!usbInterfaceDescriptor(device, definition->interface, definition->alternative)) goto nextDefinition;
    }

    data->definition = definition;
    return 1;

  nextDefinition:
    definition += 1;
  }

  return 0;
}

static UsbChannel *
usbNewChannel (UsbChooseChannelData *data) {
  UsbChannel *channel;

  if ((channel = malloc(sizeof(*channel)))) {
    memset(channel, 0, sizeof(*channel));

    if ((channel->device = usbFindDevice(usbChooseChannel, data))) {
      channel->definition = data->definition;
      return channel;
    }

    free(channel);
  } else {
    logMallocError();
  }

  return NULL;
}

typedef enum {
  USB_CHAN_SERIAL_NUMBER,
  USB_CHAN_VENDOR_IDENTIFIER,
  USB_CHAN_PRODUCT_IDENTIFIER,
  USB_CHAN_GENERIC_DEVICES
} UsbChannelParameter;

static char **
usbGetChannelParameters (const char *identifier) {
  static const char *const names[] = {
    "serialNumber",
    "vendorIdentifier",
    "productIdentifier",
    "genericDevices",
    NULL
  };

  if (!identifier) identifier = "";
  return getDeviceParameters(names, identifier);
}

UsbChannel *
usbOpenChannel (const UsbChannelDefinition *definitions, const char *identifier) {
  UsbChannel *channel = NULL;
  char **parameters = usbGetChannelParameters(identifier);

  if (parameters) {
    int ok = 1;

    UsbChooseChannelData choose = {
      .definition = definitions,
      .serialNumber = parameters[USB_CHAN_SERIAL_NUMBER]
    };

    if (!usbParseVendorIdentifier(&choose.vendorIdentifier, parameters[USB_CHAN_VENDOR_IDENTIFIER])) ok = 0;
    if (!usbParseProductIdentifier(&choose.productIdentifier, parameters[USB_CHAN_PRODUCT_IDENTIFIER])) ok = 0;

    {
      const char *parameter = parameters[USB_CHAN_GENERIC_DEVICES];

      if (!(parameter && *parameter)) {
        choose.genericDevices = 1;
      } else {
        unsigned int flag;

        if (validateYesNo(&flag, parameter)) {
          choose.genericDevices = flag;
        } else {
          logMessage(LOG_WARNING, "invalid generic devices option: %s", parameter);
          ok = 0;
        }
      }
    }

    if (ok) {
      if (!(channel = usbNewChannel(&choose))) {
        logMessage(LOG_CATEGORY(USB_IO), "device not found%s%s",
                   (*identifier? ": ": ""), identifier);
      }
    }

    deallocateStrings(parameters);
  }

  if (channel) {
    if (usbPrepareChannel(channel)) {
      return channel;
    }

    usbCloseChannel(channel);
  }

  return NULL;
}

void
usbCloseChannel (UsbChannel *channel) {
  usbCloseDevice(channel->device);
  free(channel);
}

int
isUsbDevice (const char **identifier) {
  return isQualifiedDevice(identifier, "usb");
}
