| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * USB descriptor dumping |
| * |
| * Copyright (C) 2017 Michael Drake <michael.drake@codethink.co.uk> |
| */ |
| |
| #include "config.h" |
| |
| #include <stdbool.h> |
| #include <assert.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| |
| #include <libusb.h> |
| |
| #include "desc-defs.h" |
| #include "desc-dump.h" |
| #include "usbmisc.h" |
| #include "names.h" |
| |
| /** |
| * Print a description of a bmControls field value, using a given string array. |
| * |
| * Handles the DESC_BMCONTROL_1 and DESC_BMCONTROL_2 field types. The former |
| * is one bit per string, and the latter is 2 bits per string, with the |
| * additional bit specifying whether the control is read-only. |
| * |
| * \param[in] bmcontrols The value to dump a human-readable representation of. |
| * \param[in] strings Array of human-readable strings, must be NULL terminated. |
| * \param[in] type The type of the value in bmcontrols. |
| * \param[in] indent The current indent level. |
| */ |
| static void desc_bmcontrol_dump( |
| unsigned long long bmcontrols, |
| const char * const * strings, |
| enum desc_type type, |
| unsigned int indent) |
| { |
| static const char * const setting[] = { |
| "read-only", |
| "ILLEGAL VALUE (0b10)", |
| "read/write" |
| }; |
| unsigned int count = 0; |
| unsigned int control; |
| |
| assert((type == DESC_BMCONTROL_1) || |
| (type == DESC_BMCONTROL_2)); |
| |
| while (strings[count] != NULL) { |
| if (strings[count][0] != '\0') { |
| if (type == DESC_BMCONTROL_1) { |
| if ((bmcontrols >> count) & 0x1) { |
| printf("%*s%s Control\n", |
| indent * 2, "", |
| strings[count]); |
| } |
| } else { |
| control = (bmcontrols >> (count * 2)) & 0x3; |
| if (control) { |
| printf("%*s%s Control (%s)\n", |
| indent * 2, "", |
| strings[count], |
| setting[control-1]); |
| } |
| } |
| } |
| count++; |
| } |
| } |
| |
| /** |
| * Read N bytes from descriptor data buffer into a value. |
| * |
| * Only supports values of up to 8 bytes. |
| * |
| * \param[in] buf Buffer containing the bytes to read. |
| * \param[in] offset Offset in buffer to start reading bytes from. |
| * \param[in] bytes Number of bytes to read. |
| * \return Value contained within the given bytes. |
| */ |
| static unsigned long long get_n_bytes_as_ull( |
| const unsigned char *buf, |
| unsigned int offset, |
| unsigned int bytes) |
| { |
| unsigned long long ret = 0; |
| |
| if (bytes > 8) { |
| fprintf(stderr, "Bad descriptor definition; Field size > 8.\n"); |
| exit(EXIT_FAILURE); |
| } |
| |
| buf += offset; |
| |
| switch (bytes) { |
| case 8: ret |= ((unsigned long long)buf[7]) << 56; /* fall-through */ |
| case 7: ret |= ((unsigned long long)buf[6]) << 48; /* fall-through */ |
| case 6: ret |= ((unsigned long long)buf[5]) << 40; /* fall-through */ |
| case 5: ret |= ((unsigned long long)buf[4]) << 32; /* fall-through */ |
| case 4: ret |= ((unsigned long long)buf[3]) << 24; /* fall-through */ |
| case 3: ret |= ((unsigned long long)buf[2]) << 16; /* fall-through */ |
| case 2: ret |= ((unsigned long long)buf[1]) << 8; /* fall-through */ |
| case 1: ret |= ((unsigned long long)buf[0]); |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * Dump a number as hex to stdout. |
| * |
| * \param[in] buf Descriptor buffer to get values to render from. |
| * \param[in] width Character width to right-align value inside. |
| * \param[in] offset Offset in buffer to start of value to render. |
| * \param[in] bytes Byte length of value to render. |
| */ |
| static void hex_renderer( |
| const unsigned char *buf, |
| unsigned int width, |
| unsigned int offset, |
| unsigned int bytes) |
| { |
| unsigned int align = (width >= bytes * 2) ? width - bytes * 2 : 0; |
| printf(" %*s0x%0*llx", align, "", bytes * 2, |
| get_n_bytes_as_ull(buf, offset, bytes)); |
| } |
| |
| /** |
| * Dump a number to stdout. |
| * |
| * Single-byte numbers a rendered as decimal, otherwise hexadecimal is used. |
| * |
| * \param[in] buf Descriptor buffer to get values to render from. |
| * \param[in] width Character width to right-align value inside. |
| * \param[in] offset Offset in buffer to start of value to render. |
| * \param[in] bytes Byte length of value to render. |
| */ |
| static void number_renderer( |
| const unsigned char *buf, |
| unsigned int width, |
| unsigned int offset, |
| unsigned int bytes) |
| { |
| if (bytes == 1) { |
| /* Render small numbers as decimal */ |
| printf(" %*u", width, buf[offset]); |
| } else { |
| /* Otherwise render as hexadecimal */ |
| hex_renderer(buf, width, offset, bytes); |
| } |
| } |
| |
| /** |
| * Render a field's value to stdout. |
| * |
| * The manner of rendering the value is dependant on the value type. |
| * |
| * \param[in] dev LibUSB device handle. |
| * \param[in] current Descriptor definition field to render. |
| * \param[in] current_size Descriptor definition field to render. |
| * \param[in] buf Byte array containing the descriptor date to dump. |
| * \param[in] indent Current indent level. |
| * \param[in] offset Offset to current value in `buf`. |
| */ |
| static void value_renderer( |
| libusb_device_handle *dev, |
| const struct desc *current, |
| unsigned int current_size, |
| const unsigned char *buf, |
| unsigned int indent, |
| size_t offset) |
| { |
| /** Maximum amount of characters to right align numerical values by. */ |
| const unsigned int size_chars = 4; |
| |
| switch (current->type) { |
| case DESC_NUMBER: /* fall-through */ |
| case DESC_CONSTANT: |
| number_renderer(buf, size_chars, offset, current_size); |
| printf("\n"); |
| break; |
| case DESC_NUMBER_POSTFIX: |
| number_renderer(buf, size_chars, offset, current_size); |
| printf("%s\n", current->number_postfix); |
| break; |
| case DESC_NUMBER_STRINGS: { |
| unsigned int i; |
| unsigned long long value = get_n_bytes_as_ull(buf, offset, current_size); |
| number_renderer(buf, size_chars, offset, current_size); |
| for (i = 0; i <= value; i++) { |
| if (current->number_strings[i] == NULL) { |
| break; |
| } |
| if (value == i) { |
| printf(" %s", current->number_strings[i]); |
| } |
| } |
| printf("\n"); |
| break; |
| } |
| case DESC_BCD: { |
| unsigned int i; |
| printf(" %2x", buf[offset + current_size - 1]); |
| for (i = 1; i < current_size; i++) { |
| printf(".%02x", buf[offset + current_size - 1 - i]); |
| } |
| printf("\n"); |
| break; |
| } |
| case DESC_BITMAP: |
| hex_renderer(buf, size_chars, offset, current_size); |
| printf("\n"); |
| break; |
| case DESC_BMCONTROL_1: /* fall-through */ |
| case DESC_BMCONTROL_2: |
| hex_renderer(buf, size_chars, offset, current_size); |
| printf("\n"); |
| desc_bmcontrol_dump( |
| get_n_bytes_as_ull(buf, offset, current_size), |
| current->bmcontrol, current->type, indent + 1); |
| break; |
| case DESC_BITMAP_STRINGS: { |
| unsigned int i; |
| unsigned long long value = get_n_bytes_as_ull(buf, offset, current_size); |
| hex_renderer(buf, size_chars, offset, current_size); |
| printf("\n"); |
| for (i = 0; i < current->bitmap_strings.count; i++) { |
| if (current->bitmap_strings.strings[i] == NULL) { |
| continue; |
| } |
| if (((value >> i) & 0x1) == 0) { |
| continue; |
| } |
| printf("%*s%s\n", (indent + 1) * 2, "", |
| current->bitmap_strings.strings[i]); |
| } |
| break; |
| } |
| case DESC_STR_DESC_INDEX: { |
| char *string; |
| number_renderer(buf, size_chars, offset, current_size); |
| string = get_dev_string(dev, buf[offset]); |
| if (string) { |
| printf(" %s\n", string); |
| free(string); |
| } else { |
| printf("\n"); |
| } |
| break; |
| } |
| case DESC_CS_STR_DESC_ID: |
| number_renderer(buf, size_chars, offset, current_size); |
| /* TODO: Add support for UAC3 class-specific String descriptor */ |
| printf("\n"); |
| break; |
| case DESC_TERMINAL_STR: |
| number_renderer(buf, size_chars, offset, current_size); |
| printf(" %s\n", names_audioterminal( |
| get_n_bytes_as_ull(buf, offset, current_size))); |
| break; |
| case DESC_SNOWFLAKE: |
| number_renderer(buf, size_chars, offset, current_size); |
| current->snowflake( |
| get_n_bytes_as_ull(buf, offset, current_size), |
| indent + 1); |
| break; |
| } |
| } |
| |
| /** |
| * Get the size of a descriptor field in bytes. |
| * |
| * Normally the size is provided in the entry's size parameter, but some |
| * fields have a variable size, with the actual size being stored in as |
| * the value of another field. |
| * |
| * \param[in] buf Descriptor data. |
| * \param[in] desc First field in the descriptor definition array. |
| * \param[in] entry The descriptor definition field to get size for. |
| * \return Size of the field in bytes. |
| */ |
| static unsigned int get_entry_size( |
| const unsigned char *buf, |
| const struct desc *desc, |
| const struct desc *entry) |
| { |
| const struct desc *current; |
| unsigned int size = entry->size; |
| |
| if (entry->size_field != NULL) { |
| /* Variable field length, given by `size_field`'s value. */ |
| size_t offset = 0; |
| |
| /* Search descriptor definition array for the field who's value |
| * gives the size of the entry we're interested in. */ |
| for (current = desc; current->field != NULL; current++) { |
| if (strcmp(current->field, entry->size_field) == 0) { |
| /* Found the field who's value gives us the |
| * size of, so read that field's value out of |
| * the descriptor data buffer. */ |
| size = get_n_bytes_as_ull(buf, offset, |
| current->size); |
| break; |
| } |
| |
| /* Keep track of our offset in the descriptor data |
| * as we look for the field we want. */ |
| offset += get_entry_size(buf, desc, current); |
| } |
| } |
| |
| if (size == 0) { |
| fprintf(stderr, "Bad descriptor definition; " |
| "'%s' field has zero size.\n", entry->field); |
| exit(EXIT_FAILURE); |
| } |
| |
| return size; |
| } |
| |
| /** |
| * Get the number of entries needed by an descriptor definition array field. |
| * |
| * The number of entries is either calculated from length_field parameters, |
| * which indicate which other field(s) contain values representing the |
| * array length, or the array length is calculated from the buf_len parameter, |
| * which should ultimately have been derived from the bLength field in the raw |
| * descriptor data. |
| * |
| * \param[in] buf Descriptor data. |
| * \param[in] buf_len Byte length of `buf`. |
| * \param[in] desc First field in the descriptor definition. |
| * \param[in] array_entry Array field to get entry count for. |
| * \return Number of entries in array. |
| */ |
| static unsigned int get_array_entry_count( |
| const unsigned char *buf, |
| unsigned int buf_len, |
| const struct desc *desc, |
| const struct desc *array_entry) |
| { |
| const struct desc *current; |
| unsigned int entries = 0; |
| |
| if (array_entry->array.length_field1) { |
| /* We can get the array size from the length_field1. */ |
| size_t offset = 0; |
| for (current = desc; current->field != NULL; current++) { |
| if (strcmp(current->field, array_entry->array.length_field1) == 0) { |
| entries = get_n_bytes_as_ull(buf, offset, current->size); |
| break; |
| } |
| |
| offset += get_entry_size(buf, desc, current); |
| } |
| offset = 0; /* skip first three common 1-byte fields */ |
| if (array_entry->array.length_field2 != NULL) { |
| /* There's a second field specifying length. The two |
| * lengths are multiplied. */ |
| for (current = desc; current->field != NULL; current++) { |
| if (strcmp(current->field, array_entry->array.length_field2) == 0) { |
| entries *= get_n_bytes_as_ull(buf, offset, current->size); |
| break; |
| } |
| |
| offset += get_entry_size(buf, desc, current); |
| } |
| } |
| |
| /* If the bits flag is set, then the entry count so far |
| * was a bit count, and we need to get a byte count. */ |
| if (array_entry->array.bits) { |
| entries = (entries / 8) + (entries & 0x7) ? 1 : 0; |
| } |
| } else { |
| /* Inferred array length. We haven't been given a field to get |
| * length from; start with the descriptor's byte-length, and |
| * subtract the sizes of all the other fields. */ |
| unsigned int size = buf_len; |
| |
| for (current = desc; current->field != NULL; current++) { |
| if (current == array_entry) |
| continue; |
| |
| if (current->array.array) { |
| unsigned int count; |
| /* We can't deal with two inferred-length arrays |
| * in one descriptor definition, because its |
| * an unresolvable ambiguity. If this |
| * happens it's a flaw in the descriptor |
| * definition. */ |
| if (current->array.length_field1 == NULL) { |
| return 0xffffffff; |
| } |
| count = get_array_entry_count(buf, buf_len, |
| desc, current); |
| if (count == 0xffffffff) { |
| fprintf(stderr, "Bad descriptor definition; " |
| "multiple inferred-length arrays.\n"); |
| exit(EXIT_FAILURE); |
| } |
| size -= get_entry_size(buf, desc, current) * |
| count; |
| } else { |
| size -= get_entry_size(buf, desc, current); |
| } |
| } |
| |
| entries = size / get_entry_size(buf, desc, array_entry); |
| } |
| |
| return entries; |
| } |
| |
| /** |
| * Get the number of characters needed to dump an array index |
| * |
| * \param[in] array_entries Number of entries in array. |
| * \return number of characters required to render largest possible index. |
| */ |
| static unsigned int get_char_count_for_array_index(unsigned int array_entries) |
| { |
| /* Arrays are zero-indexed, so largest index is array_entries - 1. */ |
| if (array_entries > 100) { |
| /* [NNN] */ |
| return 5; |
| } else if (array_entries > 10) { |
| /* [NN] */ |
| return 4; |
| } |
| |
| /* [N] */ |
| return 3; |
| } |
| |
| /* Function documented in desc-dump.h */ |
| void desc_dump( |
| libusb_device_handle *dev, |
| const struct desc *desc, |
| const unsigned char *buf, |
| unsigned int buf_len, |
| unsigned int indent) |
| { |
| unsigned int entry; |
| unsigned int entries; |
| unsigned int needed_chars; |
| unsigned int current_size; |
| unsigned int field_len = 18; |
| const struct desc *current; |
| size_t offset = 0; |
| |
| /* Find the buffer length, if we've been instructed to read it from |
| * the first field. */ |
| if ((buf_len == DESC_BUF_LEN_FROM_BUF) && (desc != NULL)) { |
| buf_len = get_n_bytes_as_ull(buf, offset, desc->size); |
| } |
| |
| /* Increase `field_len` to be sufficient for character length of |
| * longest field name for this descriptor. */ |
| for (current = desc; current->field != NULL; current++) { |
| needed_chars = 0; |
| if (current->array.array) { |
| entries = get_array_entry_count(buf, buf_len, |
| desc, current); |
| needed_chars = get_char_count_for_array_index(entries); |
| } |
| if (strlen(current->field) + needed_chars > field_len) { |
| field_len = strlen(current->field) + needed_chars; |
| } |
| } |
| |
| /* Step through each field, and dump it. */ |
| for (current = desc; current->field != NULL; current++) { |
| entries = 1; |
| if (current->array.array) { |
| /* Array type fields may have more than one entry. */ |
| entries = get_array_entry_count(buf, buf_len, |
| desc, current); |
| } |
| |
| current_size = get_entry_size(buf, desc, current); |
| |
| for (entry = 0; entry < entries; entry++) { |
| /* Check there's enough data in buf for this entry. */ |
| if (offset + current_size > buf_len) { |
| unsigned int i; |
| printf("%*sWarning: Length insufficient for " |
| "descriptor type.\n", |
| (indent - 1) * 2, ""); |
| for (i = offset; i < buf_len; i++) { |
| printf("%02x ", buf[i]); |
| } |
| printf("\n"); |
| return; |
| } |
| /* Dump the field name */ |
| if (current->array.array) { |
| needed_chars = field_len - |
| get_char_count_for_array_index( |
| entries) - |
| strlen(current->field); |
| printf("%*s%s(%u)%*s", indent * 2, "", |
| current->field, entry, |
| needed_chars, ""); |
| } else { |
| printf("%*s%-*s", indent * 2, "", |
| field_len, current->field); |
| } |
| /* Dump the value */ |
| value_renderer(dev, current, current_size, buf, |
| indent, offset); |
| /* Advance offset in buffer */ |
| offset += current_size; |
| } |
| } |
| |
| /* Check for junk at end of descriptor. */ |
| if (offset < buf_len) { |
| unsigned int i; |
| printf("%*sWarning: Junk at end of descriptor (%zu bytes):\n", |
| (indent - 1) * 2, "", buf_len - offset); |
| printf("%*s", indent * 2, ""); |
| for (i = offset; i < buf_len; i++) { |
| printf("%02x ", buf[i]); |
| } |
| printf("\n"); |
| } |
| } |