usbutils/desc-dump.c

600 lines
17 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* 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");
}
}