diff options
Diffstat (limited to 'desc-dump.c')
-rw-r--r-- | desc-dump.c | 599 |
1 files changed, 599 insertions, 0 deletions
diff --git a/desc-dump.c b/desc-dump.c new file mode 100644 index 0000000..076cb6f --- /dev/null +++ b/desc-dump.c @@ -0,0 +1,599 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * USB descriptor dumping + * + * Copyright (C) 2017-2018 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; +} + +/** + * 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); + +/** + * Read a value from a field of given name. + * + * \param[in] buf Descriptor data. + * \param[in] desc First field in the descriptor definition array. + * \param[in] field The name of the field to get the value for. + * \return The value from the given field. + */ +static unsigned long long get_value_from_field( + const unsigned char *buf, + const struct desc *desc, + const char *field) +{ + size_t offset = 0; + const struct desc *current; + unsigned long long value = 0; + + /* Search descriptor definition array for the field who's value + * gives the value of the entry we're interested in. */ + for (current = desc; current->field != NULL; current++) { + if (strcmp(current->field, field) == 0) { + value = 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); + } + + return value; +} + +/** + * 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 Size of value to render. + * \param[in] buf Byte array containing the descriptor date to dump. + * \param[in] buf_len Byte length of `buf`. + * \param[in] desc First field in the descriptor definition. + * \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 buf_len, + const struct desc *desc, + 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_EXTENSION: { + unsigned int type = get_value_from_field(buf, desc, + current->extension.type_field); + const struct desc *ext_desc; + const struct desc_ext *ext; + + /* Lookup the extention descriptor definitions to use, */ + for (ext = current->extension.d; ext->desc != NULL; ext++) { + if (ext->type == type) { + ext_desc = ext->desc; + break; + } + } + + /* If the type didn't match a known type, use the + * undefined descriptor. */ + if (ext->desc == NULL) { + ext_desc = desc_undefined; + } + + desc_dump(dev, ext_desc, buf + offset, + buf_len - offset, indent); + + 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; + } +} + +/* Documented at forward declaration above. */ +static unsigned int get_entry_size( + const unsigned char *buf, + const struct desc *desc, + const struct desc *entry) +{ + unsigned int size = entry->size; + + if (entry->size_field != NULL) { + /* Variable field length, given by `size_field`'s value. */ + size = get_value_from_field(buf, desc, entry->size_field); + } + + 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. */ + entries = get_value_from_field(buf, desc, + array_entry->array.length_field1); + + if (array_entry->array.length_field2 != NULL) { + /* There's a second field specifying length. The two + * lengths are multiplied. */ + entries *= get_value_from_field(buf, desc, + array_entry->array.length_field2); + } + + /* 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; +} + +/** + * Render a field's name. + * + * \param[in] entry Current entry number (for arrays). + * \param[in] entries Entry count (for arrays). + * \param[in] field_len Character width of field name space for alignment. + * \param[in] current Descriptor definition of field to render. + * \param[in] indent Current indent level. + */ +static void field_render( + unsigned int entry, + unsigned int entries, + unsigned int field_len, + const struct desc *current, + unsigned int indent) +{ + if (current->array.array) { + unsigned int 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); + } +} + +/* 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->type != DESC_EXTENSION) { + field_render(entry, entries, field_len, + current, indent); + } + + /* Dump the value */ + value_renderer(dev, current, current_size, buf, buf_len, + desc, indent, offset); + + if (current->type == DESC_EXTENSION) { + /* A desc extension consumes all remaining + * value buffer. */ + offset = buf_len; + } else { + /* 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"); + } +} |