summaryrefslogtreecommitdiff
path: root/src/descriptor.c
diff options
context:
space:
mode:
authorZoRo <dos21h@gmail.com>2022-02-19 12:47:26 +0000
committerZoRo <dos21h@gmail.com>2022-02-19 12:47:26 +0000
commit3d7c48a6e5c5c532cdd66b3ba5a8c5911bcf2383 (patch)
treeb60e21b31eb84cbcbfa97bc10a3cdf1180ce82da /src/descriptor.c
downloadlibrusb-3d7c48a6e5c5c532cdd66b3ba5a8c5911bcf2383.tar.gz
librusb-3d7c48a6e5c5c532cdd66b3ba5a8c5911bcf2383.zip
Initial
Diffstat (limited to 'src/descriptor.c')
-rw-r--r--src/descriptor.c1139
1 files changed, 1139 insertions, 0 deletions
diff --git a/src/descriptor.c b/src/descriptor.c
new file mode 100644
index 0000000..253ef1c
--- /dev/null
+++ b/src/descriptor.c
@@ -0,0 +1,1139 @@
+/* -*- Mode: C; indent-tabs-mode:t ; c-basic-offset:8 -*- */
+/*
+ * USB descriptor handling functions for libusb
+ * Copyright © 2007 Daniel Drake <dsd@gentoo.org>
+ * Copyright © 2001 Johannes Erdfelt <johannes@erdfelt.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "libusbi.h"
+
+#include <string.h>
+
+#define DESC_HEADER_LENGTH 2
+
+/** @defgroup libusb_desc USB descriptors
+ * This page details how to examine the various standard USB descriptors
+ * for detected devices
+ */
+
+#define READ_LE16(p) ((uint16_t) \
+ (((uint16_t)((p)[1]) << 8) | \
+ ((uint16_t)((p)[0]))))
+
+#define READ_LE32(p) ((uint32_t) \
+ (((uint32_t)((p)[3]) << 24) | \
+ ((uint32_t)((p)[2]) << 16) | \
+ ((uint32_t)((p)[1]) << 8) | \
+ ((uint32_t)((p)[0]))))
+
+static void parse_descriptor(const void *source, const char *descriptor, void *dest)
+{
+ const uint8_t *sp = source;
+ uint8_t *dp = dest;
+ char field_type;
+
+ while (*descriptor) {
+ field_type = *descriptor++;
+ switch (field_type) {
+ case 'b': /* 8-bit byte */
+ *dp++ = *sp++;
+ break;
+ case 'w': /* 16-bit word, convert from little endian to CPU */
+ dp += ((uintptr_t)dp & 1); /* Align to 16-bit word boundary */
+
+ *((uint16_t *)dp) = READ_LE16(sp);
+ sp += 2;
+ dp += 2;
+ break;
+ case 'd': /* 32-bit word, convert from little endian to CPU */
+ dp += 4 - ((uintptr_t)dp & 3); /* Align to 32-bit word boundary */
+
+ *((uint32_t *)dp) = READ_LE32(sp);
+ sp += 4;
+ dp += 4;
+ break;
+ case 'u': /* 16 byte UUID */
+ memcpy(dp, sp, 16);
+ sp += 16;
+ dp += 16;
+ break;
+ }
+ }
+}
+
+static void clear_endpoint(struct libusb_endpoint_descriptor *endpoint)
+{
+ free((void *)endpoint->extra);
+}
+
+static int parse_endpoint(struct libusb_context *ctx,
+ struct libusb_endpoint_descriptor *endpoint, const uint8_t *buffer, int size)
+{
+ const struct usbi_descriptor_header *header;
+ const uint8_t *begin;
+ void *extra;
+ int parsed = 0;
+ int len;
+
+ if (size < DESC_HEADER_LENGTH) {
+ usbi_err(ctx, "short endpoint descriptor read %d/%d",
+ size, DESC_HEADER_LENGTH);
+ return LIBUSB_ERROR_IO;
+ }
+
+ header = (const struct usbi_descriptor_header *)buffer;
+ if (header->bDescriptorType != LIBUSB_DT_ENDPOINT) {
+ usbi_err(ctx, "unexpected descriptor 0x%x (expected 0x%x)",
+ header->bDescriptorType, LIBUSB_DT_ENDPOINT);
+ return parsed;
+ } else if (header->bLength < LIBUSB_DT_ENDPOINT_SIZE) {
+ usbi_err(ctx, "invalid endpoint bLength (%u)", header->bLength);
+ return LIBUSB_ERROR_IO;
+ } else if (header->bLength > size) {
+ usbi_warn(ctx, "short endpoint descriptor read %d/%u",
+ size, header->bLength);
+ return parsed;
+ }
+
+ if (header->bLength >= LIBUSB_DT_ENDPOINT_AUDIO_SIZE)
+ parse_descriptor(buffer, "bbbbwbbb", endpoint);
+ else
+ parse_descriptor(buffer, "bbbbwb", endpoint);
+
+ buffer += header->bLength;
+ size -= header->bLength;
+ parsed += header->bLength;
+
+ /* Skip over the rest of the Class Specific or Vendor Specific */
+ /* descriptors */
+ begin = buffer;
+ while (size >= DESC_HEADER_LENGTH) {
+ header = (const struct usbi_descriptor_header *)buffer;
+ if (header->bLength < DESC_HEADER_LENGTH) {
+ usbi_err(ctx, "invalid extra ep desc len (%u)",
+ header->bLength);
+ return LIBUSB_ERROR_IO;
+ } else if (header->bLength > size) {
+ usbi_warn(ctx, "short extra ep desc read %d/%u",
+ size, header->bLength);
+ return parsed;
+ }
+
+ /* If we find another "proper" descriptor then we're done */
+ if (header->bDescriptorType == LIBUSB_DT_ENDPOINT ||
+ header->bDescriptorType == LIBUSB_DT_INTERFACE ||
+ header->bDescriptorType == LIBUSB_DT_CONFIG ||
+ header->bDescriptorType == LIBUSB_DT_DEVICE)
+ break;
+
+ usbi_dbg(ctx, "skipping descriptor 0x%x", header->bDescriptorType);
+ buffer += header->bLength;
+ size -= header->bLength;
+ parsed += header->bLength;
+ }
+
+ /* Copy any unknown descriptors into a storage area for drivers */
+ /* to later parse */
+ len = (int)(buffer - begin);
+ if (len <= 0)
+ return parsed;
+
+ extra = malloc((size_t)len);
+ if (!extra)
+ return LIBUSB_ERROR_NO_MEM;
+
+ memcpy(extra, begin, len);
+ endpoint->extra = extra;
+ endpoint->extra_length = len;
+
+ return parsed;
+}
+
+static void clear_interface(struct libusb_interface *usb_interface)
+{
+ int i;
+
+ if (usb_interface->altsetting) {
+ for (i = 0; i < usb_interface->num_altsetting; i++) {
+ struct libusb_interface_descriptor *ifp =
+ (struct libusb_interface_descriptor *)
+ usb_interface->altsetting + i;
+
+ free((void *)ifp->extra);
+ if (ifp->endpoint) {
+ uint8_t j;
+
+ for (j = 0; j < ifp->bNumEndpoints; j++)
+ clear_endpoint((struct libusb_endpoint_descriptor *)
+ ifp->endpoint + j);
+ }
+ free((void *)ifp->endpoint);
+ }
+ }
+ free((void *)usb_interface->altsetting);
+ usb_interface->altsetting = NULL;
+}
+
+static int parse_interface(libusb_context *ctx,
+ struct libusb_interface *usb_interface, const uint8_t *buffer, int size)
+{
+ int len;
+ int r;
+ int parsed = 0;
+ int interface_number = -1;
+ const struct usbi_descriptor_header *header;
+ const struct usbi_interface_descriptor *if_desc;
+ struct libusb_interface_descriptor *ifp;
+ const uint8_t *begin;
+
+ while (size >= LIBUSB_DT_INTERFACE_SIZE) {
+ struct libusb_interface_descriptor *altsetting;
+
+ altsetting = realloc((void *)usb_interface->altsetting,
+ sizeof(*altsetting) * (size_t)(usb_interface->num_altsetting + 1));
+ if (!altsetting) {
+ r = LIBUSB_ERROR_NO_MEM;
+ goto err;
+ }
+ usb_interface->altsetting = altsetting;
+
+ ifp = altsetting + usb_interface->num_altsetting;
+ parse_descriptor(buffer, "bbbbbbbbb", ifp);
+ if (ifp->bDescriptorType != LIBUSB_DT_INTERFACE) {
+ usbi_err(ctx, "unexpected descriptor 0x%x (expected 0x%x)",
+ ifp->bDescriptorType, LIBUSB_DT_INTERFACE);
+ return parsed;
+ } else if (ifp->bLength < LIBUSB_DT_INTERFACE_SIZE) {
+ usbi_err(ctx, "invalid interface bLength (%u)",
+ ifp->bLength);
+ r = LIBUSB_ERROR_IO;
+ goto err;
+ } else if (ifp->bLength > size) {
+ usbi_warn(ctx, "short intf descriptor read %d/%u",
+ size, ifp->bLength);
+ return parsed;
+ } else if (ifp->bNumEndpoints > USB_MAXENDPOINTS) {
+ usbi_err(ctx, "too many endpoints (%u)", ifp->bNumEndpoints);
+ r = LIBUSB_ERROR_IO;
+ goto err;
+ }
+
+ usb_interface->num_altsetting++;
+ ifp->extra = NULL;
+ ifp->extra_length = 0;
+ ifp->endpoint = NULL;
+
+ if (interface_number == -1)
+ interface_number = ifp->bInterfaceNumber;
+
+ /* Skip over the interface */
+ buffer += ifp->bLength;
+ parsed += ifp->bLength;
+ size -= ifp->bLength;
+
+ begin = buffer;
+
+ /* Skip over any interface, class or vendor descriptors */
+ while (size >= DESC_HEADER_LENGTH) {
+ header = (const struct usbi_descriptor_header *)buffer;
+ if (header->bLength < DESC_HEADER_LENGTH) {
+ usbi_err(ctx,
+ "invalid extra intf desc len (%u)",
+ header->bLength);
+ r = LIBUSB_ERROR_IO;
+ goto err;
+ } else if (header->bLength > size) {
+ usbi_warn(ctx,
+ "short extra intf desc read %d/%u",
+ size, header->bLength);
+ return parsed;
+ }
+
+ /* If we find another "proper" descriptor then we're done */
+ if (header->bDescriptorType == LIBUSB_DT_INTERFACE ||
+ header->bDescriptorType == LIBUSB_DT_ENDPOINT ||
+ header->bDescriptorType == LIBUSB_DT_CONFIG ||
+ header->bDescriptorType == LIBUSB_DT_DEVICE)
+ break;
+
+ buffer += header->bLength;
+ parsed += header->bLength;
+ size -= header->bLength;
+ }
+
+ /* Copy any unknown descriptors into a storage area for */
+ /* drivers to later parse */
+ len = (int)(buffer - begin);
+ if (len > 0) {
+ void *extra = malloc((size_t)len);
+
+ if (!extra) {
+ r = LIBUSB_ERROR_NO_MEM;
+ goto err;
+ }
+
+ memcpy(extra, begin, len);
+ ifp->extra = extra;
+ ifp->extra_length = len;
+ }
+
+ if (ifp->bNumEndpoints > 0) {
+ struct libusb_endpoint_descriptor *endpoint;
+ uint8_t i;
+
+ endpoint = calloc(ifp->bNumEndpoints, sizeof(*endpoint));
+ if (!endpoint) {
+ r = LIBUSB_ERROR_NO_MEM;
+ goto err;
+ }
+
+ ifp->endpoint = endpoint;
+ for (i = 0; i < ifp->bNumEndpoints; i++) {
+ r = parse_endpoint(ctx, endpoint + i, buffer, size);
+ if (r < 0)
+ goto err;
+ if (r == 0) {
+ ifp->bNumEndpoints = i;
+ break;
+ }
+
+ buffer += r;
+ parsed += r;
+ size -= r;
+ }
+ }
+
+ /* We check to see if it's an alternate to this one */
+ if_desc = (const struct usbi_interface_descriptor *)buffer;
+ if (size < LIBUSB_DT_INTERFACE_SIZE ||
+ if_desc->bDescriptorType != LIBUSB_DT_INTERFACE ||
+ if_desc->bInterfaceNumber != interface_number)
+ return parsed;
+ }
+
+ return parsed;
+err:
+ clear_interface(usb_interface);
+ return r;
+}
+
+static void clear_configuration(struct libusb_config_descriptor *config)
+{
+ uint8_t i;
+
+ if (config->interface) {
+ for (i = 0; i < config->bNumInterfaces; i++)
+ clear_interface((struct libusb_interface *)
+ config->interface + i);
+ }
+ free((void *)config->interface);
+ free((void *)config->extra);
+}
+
+static int parse_configuration(struct libusb_context *ctx,
+ struct libusb_config_descriptor *config, const uint8_t *buffer, int size)
+{
+ uint8_t i;
+ int r;
+ const struct usbi_descriptor_header *header;
+ struct libusb_interface *usb_interface;
+
+ if (size < LIBUSB_DT_CONFIG_SIZE) {
+ usbi_err(ctx, "short config descriptor read %d/%d",
+ size, LIBUSB_DT_CONFIG_SIZE);
+ return LIBUSB_ERROR_IO;
+ }
+
+ parse_descriptor(buffer, "bbwbbbbb", config);
+ if (config->bDescriptorType != LIBUSB_DT_CONFIG) {
+ usbi_err(ctx, "unexpected descriptor 0x%x (expected 0x%x)",
+ config->bDescriptorType, LIBUSB_DT_CONFIG);
+ return LIBUSB_ERROR_IO;
+ } else if (config->bLength < LIBUSB_DT_CONFIG_SIZE) {
+ usbi_err(ctx, "invalid config bLength (%u)", config->bLength);
+ return LIBUSB_ERROR_IO;
+ } else if (config->bLength > size) {
+ usbi_err(ctx, "short config descriptor read %d/%u",
+ size, config->bLength);
+ return LIBUSB_ERROR_IO;
+ } else if (config->bNumInterfaces > USB_MAXINTERFACES) {
+ usbi_err(ctx, "too many interfaces (%u)", config->bNumInterfaces);
+ return LIBUSB_ERROR_IO;
+ }
+
+ usb_interface = calloc(config->bNumInterfaces, sizeof(*usb_interface));
+ if (!usb_interface)
+ return LIBUSB_ERROR_NO_MEM;
+
+ config->interface = usb_interface;
+
+ buffer += config->bLength;
+ size -= config->bLength;
+
+ for (i = 0; i < config->bNumInterfaces; i++) {
+ int len;
+ const uint8_t *begin;
+
+ /* Skip over the rest of the Class Specific or Vendor */
+ /* Specific descriptors */
+ begin = buffer;
+ while (size >= DESC_HEADER_LENGTH) {
+ header = (const struct usbi_descriptor_header *)buffer;
+ if (header->bLength < DESC_HEADER_LENGTH) {
+ usbi_err(ctx,
+ "invalid extra config desc len (%u)",
+ header->bLength);
+ r = LIBUSB_ERROR_IO;
+ goto err;
+ } else if (header->bLength > size) {
+ usbi_warn(ctx,
+ "short extra config desc read %d/%u",
+ size, header->bLength);
+ config->bNumInterfaces = i;
+ return size;
+ }
+
+ /* If we find another "proper" descriptor then we're done */
+ if (header->bDescriptorType == LIBUSB_DT_ENDPOINT ||
+ header->bDescriptorType == LIBUSB_DT_INTERFACE ||
+ header->bDescriptorType == LIBUSB_DT_CONFIG ||
+ header->bDescriptorType == LIBUSB_DT_DEVICE)
+ break;
+
+ usbi_dbg(ctx, "skipping descriptor 0x%x", header->bDescriptorType);
+ buffer += header->bLength;
+ size -= header->bLength;
+ }
+
+ /* Copy any unknown descriptors into a storage area for */
+ /* drivers to later parse */
+ len = (int)(buffer - begin);
+ if (len > 0) {
+ uint8_t *extra = realloc((void *)config->extra,
+ (size_t)(config->extra_length + len));
+
+ if (!extra) {
+ r = LIBUSB_ERROR_NO_MEM;
+ goto err;
+ }
+
+ memcpy(extra + config->extra_length, begin, len);
+ config->extra = extra;
+ config->extra_length += len;
+ }
+
+ r = parse_interface(ctx, usb_interface + i, buffer, size);
+ if (r < 0)
+ goto err;
+ if (r == 0) {
+ config->bNumInterfaces = i;
+ break;
+ }
+
+ buffer += r;
+ size -= r;
+ }
+
+ return size;
+
+err:
+ clear_configuration(config);
+ return r;
+}
+
+static int raw_desc_to_config(struct libusb_context *ctx,
+ const uint8_t *buf, int size, struct libusb_config_descriptor **config)
+{
+ struct libusb_config_descriptor *_config = calloc(1, sizeof(*_config));
+ int r;
+
+ if (!_config)
+ return LIBUSB_ERROR_NO_MEM;
+
+ r = parse_configuration(ctx, _config, buf, size);
+ if (r < 0) {
+ usbi_err(ctx, "parse_configuration failed with error %d", r);
+ free(_config);
+ return r;
+ } else if (r > 0) {
+ usbi_warn(ctx, "still %d bytes of descriptor data left", r);
+ }
+
+ *config = _config;
+ return LIBUSB_SUCCESS;
+}
+
+static int get_active_config_descriptor(struct libusb_device *dev,
+ uint8_t *buffer, size_t size)
+{
+ int r = usbi_backend.get_active_config_descriptor(dev, buffer, size);
+
+ if (r < 0)
+ return r;
+
+ if (r < LIBUSB_DT_CONFIG_SIZE) {
+ usbi_err(DEVICE_CTX(dev), "short config descriptor read %d/%d",
+ r, LIBUSB_DT_CONFIG_SIZE);
+ return LIBUSB_ERROR_IO;
+ } else if (r != (int)size) {
+ usbi_warn(DEVICE_CTX(dev), "short config descriptor read %d/%d",
+ r, (int)size);
+ }
+
+ return r;
+}
+
+static int get_config_descriptor(struct libusb_device *dev, uint8_t config_idx,
+ uint8_t *buffer, size_t size)
+{
+ int r = usbi_backend.get_config_descriptor(dev, config_idx, buffer, size);
+
+ if (r < 0)
+ return r;
+ if (r < LIBUSB_DT_CONFIG_SIZE) {
+ usbi_err(DEVICE_CTX(dev), "short config descriptor read %d/%d",
+ r, LIBUSB_DT_CONFIG_SIZE);
+ return LIBUSB_ERROR_IO;
+ } else if (r != (int)size) {
+ usbi_warn(DEVICE_CTX(dev), "short config descriptor read %d/%d",
+ r, (int)size);
+ }
+
+ return r;
+}
+
+/** \ingroup libusb_desc
+ * Get the USB device descriptor for a given device.
+ *
+ * This is a non-blocking function; the device descriptor is cached in memory.
+ *
+ * Note since libusb-1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102, this
+ * function always succeeds.
+ *
+ * \param dev the device
+ * \param desc output location for the descriptor data
+ * \returns 0 on success or a LIBUSB_ERROR code on failure
+ */
+int API_EXPORTED libusb_get_device_descriptor(libusb_device *dev,
+ struct libusb_device_descriptor *desc)
+{
+ usbi_dbg(DEVICE_CTX(dev), " ");
+ static_assert(sizeof(dev->device_descriptor) == LIBUSB_DT_DEVICE_SIZE,
+ "struct libusb_device_descriptor is not expected size");
+ *desc = dev->device_descriptor;
+ return 0;
+}
+
+/** \ingroup libusb_desc
+ * Get the USB configuration descriptor for the currently active configuration.
+ * This is a non-blocking function which does not involve any requests being
+ * sent to the device.
+ *
+ * \param dev a device
+ * \param config output location for the USB configuration descriptor. Only
+ * valid if 0 was returned. Must be freed with libusb_free_config_descriptor()
+ * after use.
+ * \returns 0 on success
+ * \returns LIBUSB_ERROR_NOT_FOUND if the device is in unconfigured state
+ * \returns another LIBUSB_ERROR code on error
+ * \see libusb_get_config_descriptor
+ */
+int API_EXPORTED libusb_get_active_config_descriptor(libusb_device *dev,
+ struct libusb_config_descriptor **config)
+{
+ union usbi_config_desc_buf _config;
+ uint16_t config_len;
+ uint8_t *buf;
+ int r;
+
+ r = get_active_config_descriptor(dev, _config.buf, sizeof(_config.buf));
+ if (r < 0)
+ return r;
+
+ config_len = libusb_le16_to_cpu(_config.desc.wTotalLength);
+ buf = malloc(config_len);
+ if (!buf)
+ return LIBUSB_ERROR_NO_MEM;
+
+ r = get_active_config_descriptor(dev, buf, config_len);
+ if (r >= 0)
+ r = raw_desc_to_config(DEVICE_CTX(dev), buf, r, config);
+
+ free(buf);
+ return r;
+}
+
+/** \ingroup libusb_desc
+ * Get a USB configuration descriptor based on its index.
+ * This is a non-blocking function which does not involve any requests being
+ * sent to the device.
+ *
+ * \param dev a device
+ * \param config_index the index of the configuration you wish to retrieve
+ * \param config output location for the USB configuration descriptor. Only
+ * valid if 0 was returned. Must be freed with libusb_free_config_descriptor()
+ * after use.
+ * \returns 0 on success
+ * \returns LIBUSB_ERROR_NOT_FOUND if the configuration does not exist
+ * \returns another LIBUSB_ERROR code on error
+ * \see libusb_get_active_config_descriptor()
+ * \see libusb_get_config_descriptor_by_value()
+ */
+int API_EXPORTED libusb_get_config_descriptor(libusb_device *dev,
+ uint8_t config_index, struct libusb_config_descriptor **config)
+{
+ union usbi_config_desc_buf _config;
+ uint16_t config_len;
+ uint8_t *buf;
+ int r;
+
+ usbi_dbg(DEVICE_CTX(dev), "index %u", config_index);
+ if (config_index >= dev->device_descriptor.bNumConfigurations)
+ return LIBUSB_ERROR_NOT_FOUND;
+
+ r = get_config_descriptor(dev, config_index, _config.buf, sizeof(_config.buf));
+ if (r < 0)
+ return r;
+
+ config_len = libusb_le16_to_cpu(_config.desc.wTotalLength);
+ buf = malloc(config_len);
+ if (!buf)
+ return LIBUSB_ERROR_NO_MEM;
+
+ r = get_config_descriptor(dev, config_index, buf, config_len);
+ if (r >= 0)
+ r = raw_desc_to_config(DEVICE_CTX(dev), buf, r, config);
+
+ free(buf);
+ return r;
+}
+
+/** \ingroup libusb_desc
+ * Get a USB configuration descriptor with a specific bConfigurationValue.
+ * This is a non-blocking function which does not involve any requests being
+ * sent to the device.
+ *
+ * \param dev a device
+ * \param bConfigurationValue the bConfigurationValue of the configuration you
+ * wish to retrieve
+ * \param config output location for the USB configuration descriptor. Only
+ * valid if 0 was returned. Must be freed with libusb_free_config_descriptor()
+ * after use.
+ * \returns 0 on success
+ * \returns LIBUSB_ERROR_NOT_FOUND if the configuration does not exist
+ * \returns another LIBUSB_ERROR code on error
+ * \see libusb_get_active_config_descriptor()
+ * \see libusb_get_config_descriptor()
+ */
+int API_EXPORTED libusb_get_config_descriptor_by_value(libusb_device *dev,
+ uint8_t bConfigurationValue, struct libusb_config_descriptor **config)
+{
+ uint8_t idx;
+ int r;
+
+ if (usbi_backend.get_config_descriptor_by_value) {
+ void *buf;
+
+ r = usbi_backend.get_config_descriptor_by_value(dev,
+ bConfigurationValue, &buf);
+ if (r < 0)
+ return r;
+
+ return raw_desc_to_config(DEVICE_CTX(dev), buf, r, config);
+ }
+
+ usbi_dbg(DEVICE_CTX(dev), "value %u", bConfigurationValue);
+ for (idx = 0; idx < dev->device_descriptor.bNumConfigurations; idx++) {
+ union usbi_config_desc_buf _config;
+
+ r = get_config_descriptor(dev, idx, _config.buf, sizeof(_config.buf));
+ if (r < 0)
+ return r;
+
+ if (_config.desc.bConfigurationValue == bConfigurationValue)
+ return libusb_get_config_descriptor(dev, idx, config);
+ }
+
+ return LIBUSB_ERROR_NOT_FOUND;
+}
+
+/** \ingroup libusb_desc
+ * Free a configuration descriptor obtained from
+ * libusb_get_active_config_descriptor() or libusb_get_config_descriptor().
+ * It is safe to call this function with a NULL config parameter, in which
+ * case the function simply returns.
+ *
+ * \param config the configuration descriptor to free
+ */
+void API_EXPORTED libusb_free_config_descriptor(
+ struct libusb_config_descriptor *config)
+{
+ if (!config)
+ return;
+
+ clear_configuration(config);
+ free(config);
+}
+
+/** \ingroup libusb_desc
+ * Get an endpoints superspeed endpoint companion descriptor (if any)
+ *
+ * \param ctx the context to operate on, or NULL for the default context
+ * \param endpoint endpoint descriptor from which to get the superspeed
+ * endpoint companion descriptor
+ * \param ep_comp output location for the superspeed endpoint companion
+ * descriptor. Only valid if 0 was returned. Must be freed with
+ * libusb_free_ss_endpoint_companion_descriptor() after use.
+ * \returns 0 on success
+ * \returns LIBUSB_ERROR_NOT_FOUND if the configuration does not exist
+ * \returns another LIBUSB_ERROR code on error
+ */
+int API_EXPORTED libusb_get_ss_endpoint_companion_descriptor(
+ libusb_context *ctx,
+ const struct libusb_endpoint_descriptor *endpoint,
+ struct libusb_ss_endpoint_companion_descriptor **ep_comp)
+{
+ struct usbi_descriptor_header *header;
+ const uint8_t *buffer = endpoint->extra;
+ int size = endpoint->extra_length;
+
+ *ep_comp = NULL;
+
+ while (size >= DESC_HEADER_LENGTH) {
+ header = (struct usbi_descriptor_header *)buffer;
+ if (header->bDescriptorType != LIBUSB_DT_SS_ENDPOINT_COMPANION) {
+ if (header->bLength < DESC_HEADER_LENGTH) {
+ usbi_err(ctx, "invalid descriptor length %u",
+ header->bLength);
+ return LIBUSB_ERROR_IO;
+ }
+ buffer += header->bLength;
+ size -= header->bLength;
+ continue;
+ } else if (header->bLength < LIBUSB_DT_SS_ENDPOINT_COMPANION_SIZE) {
+ usbi_err(ctx, "invalid ss-ep-comp-desc length %u",
+ header->bLength);
+ return LIBUSB_ERROR_IO;
+ } else if (header->bLength > size) {
+ usbi_err(ctx, "short ss-ep-comp-desc read %d/%u",
+ size, header->bLength);
+ return LIBUSB_ERROR_IO;
+ }
+
+ *ep_comp = malloc(sizeof(**ep_comp));
+ if (!*ep_comp)
+ return LIBUSB_ERROR_NO_MEM;
+ parse_descriptor(buffer, "bbbbw", *ep_comp);
+ return LIBUSB_SUCCESS;
+ }
+ return LIBUSB_ERROR_NOT_FOUND;
+}
+
+/** \ingroup libusb_desc
+ * Free a superspeed endpoint companion descriptor obtained from
+ * libusb_get_ss_endpoint_companion_descriptor().
+ * It is safe to call this function with a NULL ep_comp parameter, in which
+ * case the function simply returns.
+ *
+ * \param ep_comp the superspeed endpoint companion descriptor to free
+ */
+void API_EXPORTED libusb_free_ss_endpoint_companion_descriptor(
+ struct libusb_ss_endpoint_companion_descriptor *ep_comp)
+{
+ free(ep_comp);
+}
+
+static int parse_bos(struct libusb_context *ctx,
+ struct libusb_bos_descriptor **bos,
+ const uint8_t *buffer, int size)
+{
+ struct libusb_bos_descriptor *_bos;
+ const struct usbi_bos_descriptor *bos_desc;
+ const struct usbi_descriptor_header *header;
+ uint8_t i;
+
+ if (size < LIBUSB_DT_BOS_SIZE) {
+ usbi_err(ctx, "short bos descriptor read %d/%d",
+ size, LIBUSB_DT_BOS_SIZE);
+ return LIBUSB_ERROR_IO;
+ }
+
+ bos_desc = (const struct usbi_bos_descriptor *)buffer;
+ if (bos_desc->bDescriptorType != LIBUSB_DT_BOS) {
+ usbi_err(ctx, "unexpected descriptor 0x%x (expected 0x%x)",
+ bos_desc->bDescriptorType, LIBUSB_DT_BOS);
+ return LIBUSB_ERROR_IO;
+ } else if (bos_desc->bLength < LIBUSB_DT_BOS_SIZE) {
+ usbi_err(ctx, "invalid bos bLength (%u)", bos_desc->bLength);
+ return LIBUSB_ERROR_IO;
+ } else if (bos_desc->bLength > size) {
+ usbi_err(ctx, "short bos descriptor read %d/%u",
+ size, bos_desc->bLength);
+ return LIBUSB_ERROR_IO;
+ }
+
+ _bos = calloc(1, sizeof(*_bos) + bos_desc->bNumDeviceCaps * sizeof(void *));
+ if (!_bos)
+ return LIBUSB_ERROR_NO_MEM;
+
+ parse_descriptor(buffer, "bbwb", _bos);
+ buffer += _bos->bLength;
+ size -= _bos->bLength;
+
+ /* Get the device capability descriptors */
+ for (i = 0; i < _bos->bNumDeviceCaps; i++) {
+ if (size < LIBUSB_DT_DEVICE_CAPABILITY_SIZE) {
+ usbi_warn(ctx, "short dev-cap descriptor read %d/%d",
+ size, LIBUSB_DT_DEVICE_CAPABILITY_SIZE);
+ break;
+ }
+ header = (const struct usbi_descriptor_header *)buffer;
+ if (header->bDescriptorType != LIBUSB_DT_DEVICE_CAPABILITY) {
+ usbi_warn(ctx, "unexpected descriptor 0x%x (expected 0x%x)",
+ header->bDescriptorType, LIBUSB_DT_DEVICE_CAPABILITY);
+ break;
+ } else if (header->bLength < LIBUSB_DT_DEVICE_CAPABILITY_SIZE) {
+ usbi_err(ctx, "invalid dev-cap bLength (%u)",
+ header->bLength);
+ libusb_free_bos_descriptor(_bos);
+ return LIBUSB_ERROR_IO;
+ } else if (header->bLength > size) {
+ usbi_warn(ctx, "short dev-cap descriptor read %d/%u",
+ size, header->bLength);
+ break;
+ }
+
+ _bos->dev_capability[i] = malloc(header->bLength);
+ if (!_bos->dev_capability[i]) {
+ libusb_free_bos_descriptor(_bos);
+ return LIBUSB_ERROR_NO_MEM;
+ }
+ memcpy(_bos->dev_capability[i], buffer, header->bLength);
+ buffer += header->bLength;
+ size -= header->bLength;
+ }
+ _bos->bNumDeviceCaps = i;
+ *bos = _bos;
+
+ return LIBUSB_SUCCESS;
+}
+
+/** \ingroup libusb_desc
+ * Get a Binary Object Store (BOS) descriptor
+ * This is a BLOCKING function, which will send requests to the device.
+ *
+ * \param dev_handle the handle of an open libusb device
+ * \param bos output location for the BOS descriptor. Only valid if 0 was returned.
+ * Must be freed with \ref libusb_free_bos_descriptor() after use.
+ * \returns 0 on success
+ * \returns LIBUSB_ERROR_NOT_FOUND if the device doesn't have a BOS descriptor
+ * \returns another LIBUSB_ERROR code on error
+ */
+int API_EXPORTED libusb_get_bos_descriptor(libusb_device_handle *dev_handle,
+ struct libusb_bos_descriptor **bos)
+{
+ union usbi_bos_desc_buf _bos;
+ uint16_t bos_len;
+ uint8_t *bos_data;
+ int r;
+ struct libusb_context *ctx = HANDLE_CTX(dev_handle);
+
+ /* Read the BOS. This generates 2 requests on the bus,
+ * one for the header, and one for the full BOS */
+ r = libusb_get_descriptor(dev_handle, LIBUSB_DT_BOS, 0, _bos.buf, sizeof(_bos.buf));
+ if (r < 0) {
+ if (r != LIBUSB_ERROR_PIPE)
+ usbi_err(ctx, "failed to read BOS (%d)", r);
+ return r;
+ }
+ if (r < LIBUSB_DT_BOS_SIZE) {
+ usbi_err(ctx, "short BOS read %d/%d",
+ r, LIBUSB_DT_BOS_SIZE);
+ return LIBUSB_ERROR_IO;
+ }
+
+ bos_len = libusb_le16_to_cpu(_bos.desc.wTotalLength);
+ usbi_dbg(ctx, "found BOS descriptor: size %u bytes, %u capabilities",
+ bos_len, _bos.desc.bNumDeviceCaps);
+ bos_data = calloc(1, bos_len);
+ if (!bos_data)
+ return LIBUSB_ERROR_NO_MEM;
+
+ r = libusb_get_descriptor(dev_handle, LIBUSB_DT_BOS, 0, bos_data, bos_len);
+ if (r >= 0) {
+ if (r != (int)bos_len)
+ usbi_warn(ctx, "short BOS read %d/%u", r, bos_len);
+ r = parse_bos(HANDLE_CTX(dev_handle), bos, bos_data, r);
+ } else {
+ usbi_err(ctx, "failed to read BOS (%d)", r);
+ }
+
+ free(bos_data);
+ return r;
+}
+
+/** \ingroup libusb_desc
+ * Free a BOS descriptor obtained from libusb_get_bos_descriptor().
+ * It is safe to call this function with a NULL bos parameter, in which
+ * case the function simply returns.
+ *
+ * \param bos the BOS descriptor to free
+ */
+void API_EXPORTED libusb_free_bos_descriptor(struct libusb_bos_descriptor *bos)
+{
+ uint8_t i;
+
+ if (!bos)
+ return;
+
+ for (i = 0; i < bos->bNumDeviceCaps; i++)
+ free(bos->dev_capability[i]);
+ free(bos);
+}
+
+/** \ingroup libusb_desc
+ * Get an USB 2.0 Extension descriptor
+ *
+ * \param ctx the context to operate on, or NULL for the default context
+ * \param dev_cap Device Capability descriptor with a bDevCapabilityType of
+ * \ref libusb_capability_type::LIBUSB_BT_USB_2_0_EXTENSION
+ * LIBUSB_BT_USB_2_0_EXTENSION
+ * \param usb_2_0_extension output location for the USB 2.0 Extension
+ * descriptor. Only valid if 0 was returned. Must be freed with
+ * libusb_free_usb_2_0_extension_descriptor() after use.
+ * \returns 0 on success
+ * \returns a LIBUSB_ERROR code on error
+ */
+int API_EXPORTED libusb_get_usb_2_0_extension_descriptor(
+ libusb_context *ctx,
+ struct libusb_bos_dev_capability_descriptor *dev_cap,
+ struct libusb_usb_2_0_extension_descriptor **usb_2_0_extension)
+{
+ struct libusb_usb_2_0_extension_descriptor *_usb_2_0_extension;
+
+ if (dev_cap->bDevCapabilityType != LIBUSB_BT_USB_2_0_EXTENSION) {
+ usbi_err(ctx, "unexpected bDevCapabilityType 0x%x (expected 0x%x)",
+ dev_cap->bDevCapabilityType,
+ LIBUSB_BT_USB_2_0_EXTENSION);
+ return LIBUSB_ERROR_INVALID_PARAM;
+ } else if (dev_cap->bLength < LIBUSB_BT_USB_2_0_EXTENSION_SIZE) {
+ usbi_err(ctx, "short dev-cap descriptor read %u/%d",
+ dev_cap->bLength, LIBUSB_BT_USB_2_0_EXTENSION_SIZE);
+ return LIBUSB_ERROR_IO;
+ }
+
+ _usb_2_0_extension = malloc(sizeof(*_usb_2_0_extension));
+ if (!_usb_2_0_extension)
+ return LIBUSB_ERROR_NO_MEM;
+
+ parse_descriptor(dev_cap, "bbbd", _usb_2_0_extension);
+
+ *usb_2_0_extension = _usb_2_0_extension;
+ return LIBUSB_SUCCESS;
+}
+
+/** \ingroup libusb_desc
+ * Free a USB 2.0 Extension descriptor obtained from
+ * libusb_get_usb_2_0_extension_descriptor().
+ * It is safe to call this function with a NULL usb_2_0_extension parameter,
+ * in which case the function simply returns.
+ *
+ * \param usb_2_0_extension the USB 2.0 Extension descriptor to free
+ */
+void API_EXPORTED libusb_free_usb_2_0_extension_descriptor(
+ struct libusb_usb_2_0_extension_descriptor *usb_2_0_extension)
+{
+ free(usb_2_0_extension);
+}
+
+/** \ingroup libusb_desc
+ * Get a SuperSpeed USB Device Capability descriptor
+ *
+ * \param ctx the context to operate on, or NULL for the default context
+ * \param dev_cap Device Capability descriptor with a bDevCapabilityType of
+ * \ref libusb_capability_type::LIBUSB_BT_SS_USB_DEVICE_CAPABILITY
+ * LIBUSB_BT_SS_USB_DEVICE_CAPABILITY
+ * \param ss_usb_device_cap output location for the SuperSpeed USB Device
+ * Capability descriptor. Only valid if 0 was returned. Must be freed with
+ * libusb_free_ss_usb_device_capability_descriptor() after use.
+ * \returns 0 on success
+ * \returns a LIBUSB_ERROR code on error
+ */
+int API_EXPORTED libusb_get_ss_usb_device_capability_descriptor(
+ libusb_context *ctx,
+ struct libusb_bos_dev_capability_descriptor *dev_cap,
+ struct libusb_ss_usb_device_capability_descriptor **ss_usb_device_cap)
+{
+ struct libusb_ss_usb_device_capability_descriptor *_ss_usb_device_cap;
+
+ if (dev_cap->bDevCapabilityType != LIBUSB_BT_SS_USB_DEVICE_CAPABILITY) {
+ usbi_err(ctx, "unexpected bDevCapabilityType 0x%x (expected 0x%x)",
+ dev_cap->bDevCapabilityType,
+ LIBUSB_BT_SS_USB_DEVICE_CAPABILITY);
+ return LIBUSB_ERROR_INVALID_PARAM;
+ } else if (dev_cap->bLength < LIBUSB_BT_SS_USB_DEVICE_CAPABILITY_SIZE) {
+ usbi_err(ctx, "short dev-cap descriptor read %u/%d",
+ dev_cap->bLength, LIBUSB_BT_SS_USB_DEVICE_CAPABILITY_SIZE);
+ return LIBUSB_ERROR_IO;
+ }
+
+ _ss_usb_device_cap = malloc(sizeof(*_ss_usb_device_cap));
+ if (!_ss_usb_device_cap)
+ return LIBUSB_ERROR_NO_MEM;
+
+ parse_descriptor(dev_cap, "bbbbwbbw", _ss_usb_device_cap);
+
+ *ss_usb_device_cap = _ss_usb_device_cap;
+ return LIBUSB_SUCCESS;
+}
+
+/** \ingroup libusb_desc
+ * Free a SuperSpeed USB Device Capability descriptor obtained from
+ * libusb_get_ss_usb_device_capability_descriptor().
+ * It is safe to call this function with a NULL ss_usb_device_cap
+ * parameter, in which case the function simply returns.
+ *
+ * \param ss_usb_device_cap the SuperSpeed USB Device Capability descriptor
+ * to free
+ */
+void API_EXPORTED libusb_free_ss_usb_device_capability_descriptor(
+ struct libusb_ss_usb_device_capability_descriptor *ss_usb_device_cap)
+{
+ free(ss_usb_device_cap);
+}
+
+/** \ingroup libusb_desc
+ * Get a Container ID descriptor
+ *
+ * \param ctx the context to operate on, or NULL for the default context
+ * \param dev_cap Device Capability descriptor with a bDevCapabilityType of
+ * \ref libusb_capability_type::LIBUSB_BT_CONTAINER_ID
+ * LIBUSB_BT_CONTAINER_ID
+ * \param container_id output location for the Container ID descriptor.
+ * Only valid if 0 was returned. Must be freed with
+ * libusb_free_container_id_descriptor() after use.
+ * \returns 0 on success
+ * \returns a LIBUSB_ERROR code on error
+ */
+int API_EXPORTED libusb_get_container_id_descriptor(libusb_context *ctx,
+ struct libusb_bos_dev_capability_descriptor *dev_cap,
+ struct libusb_container_id_descriptor **container_id)
+{
+ struct libusb_container_id_descriptor *_container_id;
+
+ if (dev_cap->bDevCapabilityType != LIBUSB_BT_CONTAINER_ID) {
+ usbi_err(ctx, "unexpected bDevCapabilityType 0x%x (expected 0x%x)",
+ dev_cap->bDevCapabilityType,
+ LIBUSB_BT_CONTAINER_ID);
+ return LIBUSB_ERROR_INVALID_PARAM;
+ } else if (dev_cap->bLength < LIBUSB_BT_CONTAINER_ID_SIZE) {
+ usbi_err(ctx, "short dev-cap descriptor read %u/%d",
+ dev_cap->bLength, LIBUSB_BT_CONTAINER_ID_SIZE);
+ return LIBUSB_ERROR_IO;
+ }
+
+ _container_id = malloc(sizeof(*_container_id));
+ if (!_container_id)
+ return LIBUSB_ERROR_NO_MEM;
+
+ parse_descriptor(dev_cap, "bbbbu", _container_id);
+
+ *container_id = _container_id;
+ return LIBUSB_SUCCESS;
+}
+
+/** \ingroup libusb_desc
+ * Free a Container ID descriptor obtained from
+ * libusb_get_container_id_descriptor().
+ * It is safe to call this function with a NULL container_id parameter,
+ * in which case the function simply returns.
+ *
+ * \param container_id the Container ID descriptor to free
+ */
+void API_EXPORTED libusb_free_container_id_descriptor(
+ struct libusb_container_id_descriptor *container_id)
+{
+ free(container_id);
+}
+
+/** \ingroup libusb_desc
+ * Retrieve a string descriptor in C style ASCII.
+ *
+ * Wrapper around libusb_get_string_descriptor(). Uses the first language
+ * supported by the device.
+ *
+ * \param dev_handle a device handle
+ * \param desc_index the index of the descriptor to retrieve
+ * \param data output buffer for ASCII string descriptor
+ * \param length size of data buffer
+ * \returns number of bytes returned in data, or LIBUSB_ERROR code on failure
+ */
+int API_EXPORTED libusb_get_string_descriptor_ascii(libusb_device_handle *dev_handle,
+ uint8_t desc_index, unsigned char *data, int length)
+{
+ union usbi_string_desc_buf str;
+ int r, si, di;
+ uint16_t langid, wdata;
+
+ /* Asking for the zero'th index is special - it returns a string
+ * descriptor that contains all the language IDs supported by the
+ * device. Typically there aren't many - often only one. Language
+ * IDs are 16 bit numbers, and they start at the third byte in the
+ * descriptor. There's also no point in trying to read descriptor 0
+ * with this function. See USB 2.0 specification section 9.6.7 for
+ * more information.
+ */
+
+ if (desc_index == 0)
+ return LIBUSB_ERROR_INVALID_PARAM;
+
+ r = libusb_get_string_descriptor(dev_handle, 0, 0, str.buf, 4);
+ if (r < 0)
+ return r;
+ else if (r != 4 || str.desc.bLength < 4)
+ return LIBUSB_ERROR_IO;
+ else if (str.desc.bDescriptorType != LIBUSB_DT_STRING)
+ return LIBUSB_ERROR_IO;
+ else if (str.desc.bLength & 1)
+ usbi_warn(HANDLE_CTX(dev_handle), "suspicious bLength %u for language ID string descriptor", str.desc.bLength);
+
+ langid = libusb_le16_to_cpu(str.desc.wData[0]);
+ r = libusb_get_string_descriptor(dev_handle, desc_index, langid, str.buf, sizeof(str.buf));
+ if (r < 0)
+ return r;
+ else if (r < DESC_HEADER_LENGTH || str.desc.bLength > r)
+ return LIBUSB_ERROR_IO;
+ else if (str.desc.bDescriptorType != LIBUSB_DT_STRING)
+ return LIBUSB_ERROR_IO;
+ else if ((str.desc.bLength & 1) || str.desc.bLength != r)
+ usbi_warn(HANDLE_CTX(dev_handle), "suspicious bLength %u for string descriptor (read %d)", str.desc.bLength, r);
+
+ di = 0;
+ for (si = 2; si < str.desc.bLength; si += 2) {
+ if (di >= (length - 1))
+ break;
+
+ wdata = libusb_le16_to_cpu(str.desc.wData[di]);
+ if (wdata < 0x80)
+ data[di++] = (unsigned char)wdata;
+ else
+ data[di++] = '?'; /* non-ASCII */
+ }
+
+ data[di] = 0;
+ return di;
+}